パンダ:列内のテキストを複数の行に分割するにはどうすればよいですか?


135

大きなcsvファイルを使用していて、最後から2番目の列に、特定の区切り文字で分割するテキストの文字列があります。パンダやパイソンを使ってこれを行う簡単な方法があるかどうか疑問に思っていましたか?

CustNum  CustomerName     ItemQty  Item   Seatblocks                 ItemExt
32363    McCartney, Paul      3     F04    2:218:10:4,6                   60
31316    Lennon, John        25     F01    1:13:36:1,12 1:13:37:1,13     300

スペース(' ')で区切っ(':')てからSeatblocks列のコロンで区切りたいのですが、セルごとに異なる列数になります。Seatblocks列がシートの最後になるように列を再配置する機能がありますが、そこから何をするかわかりません。組み込みtext-to-columns関数とクイックマクロを使用してExcelで実行できますが、データセットにExcelで処理するにはレコードが多すぎます。

最終的には、John Lennonのようなレコードを取り、複数の行を作成し、座席の各セットからの情報を別々の行に入れたいと思います。


このすばらしい質問はパンダのFlatMapに関連しますが、現在は存在しません
cdarlint 2017年

回答:


203

これにより、シートブロックがスペースで分割され、それぞれに独自の行が割り当てられます。

In [43]: df
Out[43]: 
   CustNum     CustomerName  ItemQty Item                 Seatblocks  ItemExt
0    32363  McCartney, Paul        3  F04               2:218:10:4,6       60
1    31316     Lennon, John       25  F01  1:13:36:1,12 1:13:37:1,13      300

In [44]: s = df['Seatblocks'].str.split(' ').apply(Series, 1).stack()

In [45]: s.index = s.index.droplevel(-1) # to line up with df's index

In [46]: s.name = 'Seatblocks' # needs a name to join

In [47]: s
Out[47]: 
0    2:218:10:4,6
1    1:13:36:1,12
1    1:13:37:1,13
Name: Seatblocks, dtype: object

In [48]: del df['Seatblocks']

In [49]: df.join(s)
Out[49]: 
   CustNum     CustomerName  ItemQty Item  ItemExt    Seatblocks
0    32363  McCartney, Paul        3  F04       60  2:218:10:4,6
1    31316     Lennon, John       25  F01      300  1:13:36:1,12
1    31316     Lennon, John       25  F01      300  1:13:37:1,13

または、コロンで区切られた各文字列を独自の列に含めるには:

In [50]: df.join(s.apply(lambda x: Series(x.split(':'))))
Out[50]: 
   CustNum     CustomerName  ItemQty Item  ItemExt  0    1   2     3
0    32363  McCartney, Paul        3  F04       60  2  218  10   4,6
1    31316     Lennon, John       25  F01      300  1   13  36  1,12
1    31316     Lennon, John       25  F01      300  1   13  37  1,13

これは少し見苦しいですが、誰かがもっときれいなソリューションを利用するかもしれません。


7
@DanAllanは、適用時にシリーズにインデックスを付けます。列名になります
Jeff

4
これは質問に答えますが、(おそらく)split()が各行のリストを作成するため、DataFrame非常に速くサイズが膨らむことに言及する価値があります。私の場合、約200Mのテーブルでコードを実行すると、約10Gのメモリ(+スワップ...)が使用されました。
David Nemeskey 2016年

1
なぜかはわかりませんがsplit()、単にreduce()列を通過することは魅力のように機能するためです。その場合、問題はstack()...にある可能性があります
David Nemeskey

4
NameError: name 'Series' is not definedこのエラーが発生しています。どこSeriesから来るのですか?編集:気にしないでください。pandas.Seriesそれは次のアイテムを参照しているためですpandas
user5359531

2
はい、@ user5359531。from pandas import Series便宜上/簡潔にするため。
Dan Allan

52

ダンとは異なり、私は彼の答えを非常にエレガントだと思います...しかし、残念ながらそれは非常に非効率的です。だから、言及された質問以来「大きなcsvファイル」と Danの解決策をシェルで試すことを提案させてください:

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print df['col'].apply(lambda x : pd.Series(x.split(' '))).head()"

...この代替と比較して:

time python -c "import pandas as pd;
from scipy import array, concatenate;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(concatenate(df['col'].apply( lambda x : [x.split(' ')]))).head()"

... この:

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(dict(zip(range(3), [df['col'].apply(lambda x : x.split(' ')[i]) for i in range(3)]))).head()"

2つ目は単に100 000シリーズの割り当てを控えたものであり、これで約10倍速くなります。しかし、皮肉なことに、str.split()への呼び出しの多くを浪費する3番目のソリューション(行ごとに列ごとに1回呼び出されるため、他の2つのソリューションの3倍)は、最初のソリューションよりも約40倍高速です。 100 000個のリストをインスタンス化することも回避しているためです。そして、はい、確かに少し醜いです...

編集: この回答は、「to_list()」の使用方法とラムダの必要性を回避する方法を提案しています。結果は次のようなものです

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(df.col.str.split().tolist()).head()"

これは3番目のソリューションよりも効率的で、確かにはるかにエレガントです。

編集:さらにシンプルに

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(list(df.col.str.split())).head()"

も動作し、ほぼ同じくらい効率的です。

編集: さらにシンプルに!また、NaNを処理します(ただし、効率は低下します)。

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print df.col.str.split(expand=True).head()"

このメソッドが消費するメモリの量に少し問題があります。少しアドバイスをいただけないでしょうか。約8000行を含むDataFrameがあり、それぞれに9216スペースで区切られた8ビット整数を含む文字列があります。これは約75MBですが、最後のソリューションをそのまま適用すると、Pythonは2GBのメモリを消費します。これがなぜか、そしてそれを回避するために何ができるかを教えてくれるいくつかの情報源の方向に私を向けることができますか?ありがとう。
城ブラボー14

1
多くのリストと非常に小さな文字列があり、それは多かれ少なかれPythonでのメモリ使用の最悪のケースです(そして中間ステップ ".split()。tolist()"は純粋なPythonオブジェクトを生成します)。私がおそらくあなたの代わりに行うことは、DataFrameをファイルにダンプし、それをcsvとしてread_csv(...、sep = '')で開くことです。しかし、トピックに留まるには、最初のソリューション(3番目のソリューションと一緒に、ただし非常に遅いはずです)は、比較的長い行が比較的少ないため、4つの中で最も低いメモリ使用量を提供するソリューションである可能性があります。
Pietro Battiston 14

ちょっとピエトロ、私はあなたの提案をファイルに保存して再ロードすることを試みましたが、それは非常にうまくいきました。StringIOオブジェクトでこれを実行しようとしたときにいくつかの問題に遭遇し、私の問題に対する素晴らしい解決策がここに投稿されました。。
城ブラボー14

3
あなたの最後の提案tolist()は完璧です。私の場合は、リスト内のデータの1を望んでいたし、直接.IXを使用して、私の既存のDFへの単一の列を追加することができた:df['newCol'] = pd.DataFrame(df.col.str.split().tolist()).ix[:,2]
fantabolous

ああ、私はこれを最初に機能させるのに苦労していました-とは対照的に、いくつかの行がそれらobect of type 'float' has no len()にあることに気づくまで、それは不可解でした。NaNstr
dwanderson 2016年

14
import pandas as pd
import numpy as np

df = pd.DataFrame({'ItemQty': {0: 3, 1: 25}, 
                   'Seatblocks': {0: '2:218:10:4,6', 1: '1:13:36:1,12 1:13:37:1,13'}, 
                   'ItemExt': {0: 60, 1: 300}, 
                   'CustomerName': {0: 'McCartney, Paul', 1: 'Lennon, John'}, 
                   'CustNum': {0: 32363, 1: 31316}, 
                   'Item': {0: 'F04', 1: 'F01'}}, 
                    columns=['CustNum','CustomerName','ItemQty','Item','Seatblocks','ItemExt'])

print (df)
   CustNum     CustomerName  ItemQty Item                 Seatblocks  ItemExt
0    32363  McCartney, Paul        3  F04               2:218:10:4,6       60
1    31316     Lennon, John       25  F01  1:13:36:1,12 1:13:37:1,13      300

チェーンを使用した別の同様のソリューションは、use reset_indexand renameです。

print (df.drop('Seatblocks', axis=1)
             .join
             (
             df.Seatblocks
             .str
             .split(expand=True)
             .stack()
             .reset_index(drop=True, level=1)
             .rename('Seatblocks')           
             ))

   CustNum     CustomerName  ItemQty Item  ItemExt    Seatblocks
0    32363  McCartney, Paul        3  F04       60  2:218:10:4,6
1    31316     Lennon, John       25  F01      300  1:13:36:1,12
1    31316     Lennon, John       25  F01      300  1:13:37:1,13

列内が値でない NaN場合、最も速い解決策はlistDataFrameコンストラクターで理解を使用することです。

df = pd.DataFrame(['a b c']*100000, columns=['col'])

In [141]: %timeit (pd.DataFrame(dict(zip(range(3), [df['col'].apply(lambda x : x.split(' ')[i]) for i in range(3)]))))
1 loop, best of 3: 211 ms per loop

In [142]: %timeit (pd.DataFrame(df.col.str.split().tolist()))
10 loops, best of 3: 87.8 ms per loop

In [143]: %timeit (pd.DataFrame(list(df.col.str.split())))
10 loops, best of 3: 86.1 ms per loop

In [144]: %timeit (df.col.str.split(expand=True))
10 loops, best of 3: 156 ms per loop

In [145]: %timeit (pd.DataFrame([ x.split() for x in df['col'].tolist()]))
10 loops, best of 3: 54.1 ms per loop

しかし、列に含まれているパラメータでNaNのみ機能str.splitする場合(documentation)、それが遅い理由を説明します:expand=TrueDataFrame

df = pd.DataFrame(['a b c']*10, columns=['col'])
df.loc[0] = np.nan
print (df.head())
     col
0    NaN
1  a b c
2  a b c
3  a b c
4  a b c

print (df.col.str.split(expand=True))
     0     1     2
0  NaN  None  None
1    a     b     c
2    a     b     c
3    a     b     c
4    a     b     c
5    a     b     c
6    a     b     c
7    a     b     c
8    a     b     c
9    a     b     c

たとえば、使用中にexpand=Trueオプションpandas.DataFramesを使用する必要があることは言うまでもありません.str.split()
holzkohlengrill 2016

@holzkohlengrill-コメントありがとうございます。回答に追加します。
jezrael 2016

@jezrael、このコードを実行するのに非常に時間がかかりますが、それは予想どおりです。どのように正確に速くするのですか?私がそれをforループに入れた場合:df [Seablocks] [:100]のfor xをサブセットでのみ実行し、これらのサブセットで連結する場合、それは機能しますか?
bernando_vialli

2

別のアプローチは次のようになります:

temp = df['Seatblocks'].str.split(' ')
data = data.reindex(data.index.repeat(temp.apply(len)))
data['new_Seatblocks'] = np.hstack(temp)

1

また、joinおよびstack()を使用せずにgroupby()を使用できます。

上記のサンプルデータを使用します。

import pandas as pd
import numpy as np


df = pd.DataFrame({'ItemQty': {0: 3, 1: 25}, 
                   'Seatblocks': {0: '2:218:10:4,6', 1: '1:13:36:1,12 1:13:37:1,13'}, 
                   'ItemExt': {0: 60, 1: 300}, 
                   'CustomerName': {0: 'McCartney, Paul', 1: 'Lennon, John'}, 
                   'CustNum': {0: 32363, 1: 31316}, 
                   'Item': {0: 'F04', 1: 'F01'}}, 
                    columns=['CustNum','CustomerName','ItemQty','Item','Seatblocks','ItemExt']) 
print(df)

   CustNum     CustomerName  ItemQty Item                 Seatblocks  ItemExt
0  32363    McCartney, Paul  3        F04  2:218:10:4,6               60     
1  31316    Lennon, John     25       F01  1:13:36:1,12 1:13:37:1,13  300  


#first define a function: given a Series of string, split each element into a new series
def split_series(ser,sep):
    return pd.Series(ser.str.cat(sep=sep).split(sep=sep)) 
#test the function, 
split_series(pd.Series(['a b','c']),sep=' ')
0    a
1    b
2    c
dtype: object

df2=(df.groupby(df.columns.drop('Seatblocks').tolist()) #group by all but one column
          ['Seatblocks'] #select the column to be split
          .apply(split_series,sep=' ') # split 'Seatblocks' in each group
         .reset_index(drop=True,level=-1).reset_index()) #remove extra index created

print(df2)
   CustNum     CustomerName  ItemQty Item  ItemExt    Seatblocks
0    31316     Lennon, John       25  F01      300  1:13:36:1,12
1    31316     Lennon, John       25  F01      300  1:13:37:1,13
2    32363  McCartney, Paul        3  F04       60  2:218:10:4,6

前もって感謝します。2つの列を誤って分割して上記のコードを使用する方法。例:0 31316レノン、ジョン25 F01 300 1:13:36:1,12 1:13:37:1,13 A、B ..結果は0 31316 Lennon, John 25 F01 300 1:13:36:1,12 A次のようになります。次の行 0 31316 Lennon, John 25 F01 300 1:13:37:1,13 B
Krithi.S

@ Krithi.S、私は質問を理解しようとします。分割後、2つの列のメンバー数は同じでなければならないのですか?0 31316 Lennon、John 25 F01 300 1:13:36:1,12 1:13:37:1,13 A、B、Cの予想結果は何ですか?
Ben2018

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.