303

pandas dataframeテキスト文字列の 1 つの列にカンマ区切りの値が含まれています。各 CSV フィールドを分割し、エントリごとに新しい行を作成したい (CSV はクリーンで、「,」でのみ分割する必要があると仮定します)。たとえば、次のようにaなりbます。

In [7]: a
Out[7]: 
    var1  var2
0  a,b,c     1
1  d,e,f     2

In [8]: b
Out[8]: 
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

これまで、簡単な関数をいろいろ試してみたのですが.apply、軸で使用した場合、メソッドが戻り値として 1 行しか受け付けないようで、うまくいきません.transform。どんな提案でも大歓迎です!

サンプルデータ:

from pandas import DataFrame
import numpy as np
a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
b = DataFrame([{'var1': 'a', 'var2': 1},
               {'var1': 'b', 'var2': 1},
               {'var1': 'c', 'var2': 1},
               {'var1': 'd', 'var2': 2},
               {'var1': 'e', 'var2': 2},
               {'var1': 'f', 'var2': 2}])

numpy を通過することで DataFrame メタデータが失われるため、これが機能しないことはわかっていますが、私が何をしようとしたかを理解できるはずです。

def fun(row):
    letters = row['var1']
    letters = letters.split(',')
    out = np.array([row] * len(letters))
    out['var1'] = letters
a['idx'] = range(a.shape[0])
z = a.groupby('idx')
z.transform(fun)
4

26 に答える 26

197

更新 3:使用例に示すように、 Series.explode()/DataFrame.explode()メソッド(Pandas 0.25.0 で実装され、Pandas 1.3.0 で複数列の爆発をサポートするために拡張されました)を使用する方が理にかなっています。

単一の列の場合:

In [1]: df = pd.DataFrame({'A': [[0, 1, 2], 'foo', [], [3, 4]],
   ...:                    'B': 1,
   ...:                    'C': [['a', 'b', 'c'], np.nan, [], ['d', 'e']]})

In [2]: df
Out[2]:
           A  B          C
0  [0, 1, 2]  1  [a, b, c]
1        foo  1        NaN
2         []  1         []
3     [3, 4]  1     [d, e]

In [3]: df.explode('A')
Out[3]:
     A  B          C
0    0  1  [a, b, c]
0    1  1  [a, b, c]
0    2  1  [a, b, c]
1  foo  1        NaN
2  NaN  1         []
3    3  1     [d, e]
3    4  1     [d, e]

複数の列の場合 ( Pandas 1.3.0+ の場合):

In [4]: df.explode(['A', 'C'])
Out[4]:
     A  B    C
0    0  1    a
0    1  1    b
0    2  1    c
1  foo  1  NaN
2  NaN  1  NaN
3    3  1    d
3    4  1    e

更新 2:normal複数および複数のlist列に対して機能する、より一般的なベクトル化された関数

def explode(df, lst_cols, fill_value='', preserve_index=False):
    # make sure `lst_cols` is list-alike
    if (lst_cols is not None
        and len(lst_cols) > 0
        and not isinstance(lst_cols, (list, tuple, np.ndarray, pd.Series))):
        lst_cols = [lst_cols]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()
    # preserve original index values    
    idx = np.repeat(df.index.values, lens)
    # create "exploded" DF
    res = (pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols},
                index=idx)
             .assign(**{col:np.concatenate(df.loc[lens>0, col].values)
                            for col in lst_cols}))
    # append those rows that have empty lists
    if (lens == 0).any():
        # at least one list in cells is empty
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # revert the original index order
    res = res.sort_index()
    # reset index if requested
    if not preserve_index:        
        res = res.reset_index(drop=True)
    return res

デモ:

複数listの列 - すべてのlist列は、各行の要素数が同じである必要があります。

In [134]: df
Out[134]:
   aaa  myid        num          text
0   10     1  [1, 2, 3]  [aa, bb, cc]
1   11     2         []            []
2   12     3     [1, 2]      [cc, dd]
3   13     4         []            []

In [135]: explode(df, ['num','text'], fill_value='')
Out[135]:
   aaa  myid num text
0   10     1   1   aa
1   10     1   2   bb
2   10     1   3   cc
3   11     2
4   12     3   1   cc
5   12     3   2   dd
6   13     4

元のインデックス値を保持:

In [136]: explode(df, ['num','text'], fill_value='', preserve_index=True)
Out[136]:
   aaa  myid num text
0   10     1   1   aa
0   10     1   2   bb
0   10     1   3   cc
1   11     2
2   12     3   1   cc
2   12     3   2   dd
3   13     4

設定:

df = pd.DataFrame({
 'aaa': {0: 10, 1: 11, 2: 12, 3: 13},
 'myid': {0: 1, 1: 2, 2: 3, 3: 4},
 'num': {0: [1, 2, 3], 1: [], 2: [1, 2], 3: []},
 'text': {0: ['aa', 'bb', 'cc'], 1: [], 2: ['cc', 'dd'], 3: []}
})

CSV 列:

In [46]: df
Out[46]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [47]: explode(df.assign(var1=df.var1.str.split(',')), 'var1')
Out[47]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

この小さなトリックを使用して、CSV のような列を列に変換できlistます。

In [48]: df.assign(var1=df.var1.str.split(','))
Out[48]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

更新: 一般的なベクトル化されたアプローチ (複数の列に対しても機能します):

元の DF:

In [177]: df
Out[177]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

解決:

まず、CSV 文字列をリストに変換しましょう。

In [178]: lst_col = 'var1' 

In [179]: x = df.assign(**{lst_col:df[lst_col].str.split(',')})

In [180]: x
Out[180]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

これで、次のことができます。

In [181]: pd.DataFrame({
     ...:     col:np.repeat(x[col].values, x[lst_col].str.len())
     ...:     for col in x.columns.difference([lst_col])
     ...: }).assign(**{lst_col:np.concatenate(x[lst_col].values)})[x.columns.tolist()]
     ...:
Out[181]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

古い答え:

@AFinkelstein ソリューションに触発されて、 AFinkelsteinのソリューションと同じくらい高速で、2 つ以上の列を持つ DF に適用できるように、もう少し一般化したいと考えました):

In [2]: df = pd.DataFrame(
   ...:    [{'var1': 'a,b,c', 'var2': 1, 'var3': 'XX'},
   ...:     {'var1': 'd,e,f,x,y', 'var2': 2, 'var3': 'ZZ'}]
   ...: )

In [3]: df
Out[3]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [4]: (df.set_index(df.columns.drop('var1',1).tolist())
   ...:    .var1.str.split(',', expand=True)
   ...:    .stack()
   ...:    .reset_index()
   ...:    .rename(columns={0:'var1'})
   ...:    .loc[:, df.columns]
   ...: )
Out[4]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ
于 2016-11-06T13:12:51.043 に答える
121

パンダ >= 0.25

Series メソッドと DataFrame メソッドは、リストを個別の行に展開する.explode()メソッドを定義します。Exploding a list-like columnのドキュメント セクションを参照してください。

コンマで区切られた文字列のリストがあるため、文字列をコンマで分割して要素のリストを取得し、explodeその列を呼び出します。

df = pd.DataFrame({'var1': ['a,b,c', 'd,e,f'], 'var2': [1, 2]})
df
    var1  var2
0  a,b,c     1
1  d,e,f     2

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

explode(今のところ)単一の列でのみ機能することに注意してください。一度に複数の列を展開するには、以下を参照してください。

NaN と空のリストは、適切な処理を行うために面倒な手順を踏まなくても、適切に処理されます。

df = pd.DataFrame({'var1': ['d,e,f', '', np.nan], 'var2': [1, 2, 3]})
df
    var1  var2
0  d,e,f     1
1            2
2    NaN     3

df['var1'].str.split(',')

0    [d, e, f]
1           []
2          NaN

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    d     1
0    e     1
0    f     1
1          2  # empty list entry becomes empty string after exploding 
2  NaN     3  # NaN left un-touched

ravelこれは、 /repeatベースのソリューション(空のリストを完全に無視し、NaN をチョークする) よりも大きな利点です。


複数の列の分解

explode一度に 1 つの列に対してのみ機能することに注意してください。ただしapply、一度に複数の列を分解するために使用できます。

df = pd.DataFrame({'var1': ['a,b,c', 'd,e,f'], 
                   'var2': ['i,j,k', 'l,m,n'], 
                   'var3': [1, 2]})
df
    var1   var2  var3
0  a,b,c  i,j,k     1
1  d,e,f  l,m,n     2

(df.set_index(['var3']) 
   .apply(lambda col: col.str.split(',').explode())
   .reset_index()
   .reindex(df.columns, axis=1))

df
  var1 var2  var3
0    a    i     1
1    b    j     1
2    c    k     1
3    d    l     2
4    e    m     2
5    f    n     2

アイデアは、展開されるべきではないすべての列をインデックスとして設定し、次に を介して残りの列を展開することapplyです。これは、リストのサイズが等しい場合にうまく機能します。

于 2019-07-20T07:18:40.710 に答える
103

このようなものはどうですか:

In [55]: pd.concat([Series(row['var2'], row['var1'].split(','))              
                    for _, row in a.iterrows()]).reset_index()
Out[55]: 
  index  0
0     a  1
1     b  1
2     c  1
3     d  2
4     e  2
5     f  2

次に、列の名前を変更するだけです

于 2012-10-01T21:15:03.957 に答える
52

この一般的なタスクのために私が書いた関数を次に示します。Series/stackメソッドよりも効率的です。列の順序と名前は保持されます。

def tidy_split(df, column, sep='|', keep=False):
    """
    Split the values of a column and expand so the new DataFrame has one split
    value per row. Filters rows where the column is missing.

    Params
    ------
    df : pandas.DataFrame
        dataframe with the column to split and expand
    column : str
        the column to split and expand
    sep : str
        the string used to split the column's values
    keep : bool
        whether to retain the presplit value as it's own row

    Returns
    -------
    pandas.DataFrame
        Returns a dataframe with the same columns as `df`.
    """
    indexes = list()
    new_values = list()
    df = df.dropna(subset=[column])
    for i, presplit in enumerate(df[column].astype(str)):
        values = presplit.split(sep)
        if keep and len(values) > 1:
            indexes.append(i)
            new_values.append(presplit)
        for value in values:
            indexes.append(i)
            new_values.append(value)
    new_df = df.iloc[indexes, :].copy()
    new_df[column] = new_values
    return new_df

この関数を使用すると、元の質問は次のように単純になります。

tidy_split(a, 'var1', sep=',')
于 2016-10-09T17:57:42.287 に答える
18

同様の質問: pandas: How do I split text in a column to multiple rows?

あなたがすることができます:

>> a=pd.DataFrame({"var1":"a,b,c d,e,f".split(),"var2":[1,2]})
>> s = a.var1.str.split(",").apply(pd.Series, 1).stack()
>> s.index = s.index.droplevel(-1)
>> del a['var1']
>> a.join(s)
   var2 var1
0     1    a
0     1    b
0     1    c
1     2    d
1     2    e
1     2    f
于 2015-06-24T21:01:57.303 に答える
5

任意の数の列を持つデータフレームの解決策を思いつきました (一度に 1 つの列のエントリのみを分離しながら)。

def splitDataFrameList(df,target_column,separator):
    ''' df = dataframe to split,
    target_column = the column containing the values to split
    separator = the symbol used to perform the split

    returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
    The values in the other columns are duplicated across the newly divided rows.
    '''
    def splitListToRows(row,row_accumulator,target_column,separator):
        split_row = row[target_column].split(separator)
        for s in split_row:
            new_row = row.to_dict()
            new_row[target_column] = s
            row_accumulator.append(new_row)
    new_rows = []
    df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
    new_df = pandas.DataFrame(new_rows)
    return new_df
于 2015-04-21T09:02:49.710 に答える
4

リストを爆発させるさまざまな方法を使用してメモリ不足の経験に苦しんでいるため、賛成票を投じる回答を決定するのに役立つベンチマークをいくつか用意しました。リストの長さとリストの数の比率を変えて、5 つのシナリオをテストしました。以下の結果を共有します。

時間: (少ないほど良い、クリックして拡大版を表示)

スピード

ピーク時のメモリ使用量: (少ないほど良い)

ピーク時のメモリ使用量

結論

  • @MaxU の回答(更新 2)、コードネーム連結は、ほとんどすべてのケースで最高の速度を提供しますが、ピークのメモリ使用量を低く保ちます。
  • 比較的小さなリストで多くの行を処理する必要があり、ピークメモリを増やす余裕がある場合は、 @DMulligan の回答(コードネームstack )を参照してください。
  • 受け入れられた@Changの答えは、数行しかないが非常に大きなリストを持つデータフレームに適しています。

完全な詳細 (関数とベンチマーク コード) は、このGitHub gistにあります。ベンチマークの問題は単純化されており、文字列をリストに分割することは含まれていないことに注意してください。これは、ほとんどのソリューションが同様の方法で実行されました。

于 2019-01-22T23:45:09.067 に答える
3

split(___, expand=True)およびlevelとのname引数を使用したワンライナーreset_index():

>>> b = a.var1.str.split(',', expand=True).set_index(a.var2).stack().reset_index(level=0, name='var1')
>>> b
   var2 var1
0     1    a
1     1    b
2     1    c
0     2    d
1     2    e
2     2    f

b質問とまったく同じようにする必要がある場合は、さらに次のことができます。

>>> b = b.reset_index(drop=True)[['var1', 'var2']]
>>> b
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2
于 2019-12-17T08:04:26.887 に答える
1

上記のジルンの優れた回答を使用しましたが、複数の列を分割するために展開する必要がありました。私が共有すると思った。

def splitDataFrameList(df,target_column,separator):
''' df = dataframe to split,
target_column = the column containing the values to split
separator = the symbol used to perform the split

returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
The values in the other columns are duplicated across the newly divided rows.
'''
def splitListToRows(row, row_accumulator, target_columns, separator):
    split_rows = []
    for target_column in target_columns:
        split_rows.append(row[target_column].split(separator))
    # Seperate for multiple columns
    for i in range(len(split_rows[0])):
        new_row = row.to_dict()
        for j in range(len(split_rows)):
            new_row[target_columns[j]] = split_rows[j][i]
        row_accumulator.append(new_row)
new_rows = []
df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
new_df = pd.DataFrame(new_rows)
return new_df
于 2016-06-19T15:42:16.410 に答える
0

同様の問題がありました。私の解決策は、最初にデータフレームを辞書のリストに変換してから、移行を行うことでした。関数は次のとおりです。

import re
import pandas as pd

def separate_row(df, column_name):
    ls = []
    for row_dict in df.to_dict('records'):
        for word in re.split(',', row_dict[column_name]):
            row = row_dict.copy()
            row[column_name]=word
            ls.append(row)
    return pd.DataFrame(ls)

例:

>>> from pandas import DataFrame
>>> import numpy as np
>>> a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
>>> a
    var1  var2
0  a,b,c     1
1  d,e,f     2
>>> separate_row(a, "var1")
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

関数を少し変更して、リスト タイプの行の分離をサポートすることもできます。

于 2020-06-17T11:23:15.397 に答える