熊猫:如何将一列中的文本分成多行?


135

我正在处理一个较大的csv文件,并且最后一列的旁边是一串文本,我想用一个特定的分隔符来分割它。我想知道是否有使用pandas或python的简单方法?

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。

最终,我想记录约翰·列侬的记录并创建多行,并将每组座位的信息放在单独的行上。


这个大问题与熊猫中的FlatMap有关,目前尚不存在
cdarlint

Answers:


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的内存使用(+ swap ...)。
David Nemeskey

1
尽管我不确定是由于引起的split(),因为单单reduce()通过专栏的工作就像一种魅力。那么问题可能出在stack()……
David Nemeskey

4
我得到这个错误NameError: name 'Series' is not defined。其中Series从何而来?编辑:没关系,应该是pandas.Series因为它是指从pandas
user5359531 '16

2
是的,@ user5359531。我from pandas import Series为方便/简洁起见。
Dan Allan

52

与Dan不同的是,我认为他的回答相当优雅……但是不幸的是,它的效率也非常低下。因此,由于问题提到“大的csv文件”,因此我建议尝试使用Shell 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()"

第二个简单地避免了分配10万个序列,这足以使它快10倍左右。但是,第三种解决方案有点讽刺地浪费了对str.split()的调用(每行每列调用一次,因此比其他两种解决方案多三倍),它比第一种解决方案快40倍,因为它甚至避免实例化100000个列表。是的,这确实有点丑陋...

编辑: 此答案建议如何使用“ to_list()”并避免使用lambda。结果是像

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()"

这比第三个解决方案更有效,而且肯定更优雅。

编辑:更简单

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()"

我在使用此方法消耗的内存量方面遇到了一些麻烦,我想知道您是否可以给我一些建议。我有一个DataFrame,其中包含大约8000行,每行包含一个字符串,其中包含9216个空格定界的8位整数。这大约是75MB,但是当我逐字应用最后一个解决方案时,Python占用了2GB的内存。您能否指出一些信息的方向,告诉我这是为什么,以及如何解决这个问题?谢谢。
布拉沃城堡

1
您有很多列表和非常小的字符串,这或多或少是python中内存使用情况的最坏情况(中间步骤“ .split()。tolist()”会生成纯python对象)。我可能会在您的位置执行的操作是将DataFrame转储到文件中,然后使用read_csv(...,sep ='')将其作为csv打开。但是,请继续关注:第一个解决方案(与第三个解决方案结合在一起,但是应该非常慢)可能是在4种方法中为您提供最低内存使用率的解决方案,因为您只有相对少量的相对较长的行。
Pietro Battiston,2014年

嘿,Pietro,我尝试了您保存到文件并重新加载的建议,效果很好。当我尝试在StringIO对象中执行此操作时遇到了一些麻烦,并且已在此处发布针对我的问题的不错的解决方案。
布拉沃城堡

3
您的最后一个建议tolist()是完美的。就我而言,我只想要列表中的数据片段之一,并且能够使用.ix直接将单个列添加到我现有的df中:df['newCol'] = pd.DataFrame(df.col.str.split().tolist()).ix[:,2]
fantabolous

啊,一开始我很难让它起作用- obect of type 'float' has no len()对此有些莫名其妙,直到我意识到其中有些行包含NaN在其中,而不是str
dwanderson '16

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_indexrename

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

如果in列中不是NOT 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.splitexpand=True返回的参数一起使用DataFrame值为(document)的,那么它解释了为什么它比较慢:

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

@holzkohlengrill-谢谢您的评论,我将其添加为答案。
jezrael

@jezrael,执行该代码花了我很长时间,这是预期的。我究竟该如何使其更快?如果我将它放在一个for循环中,例如:for df [Seablocks] [:100]中的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

也可以使用groupby()而不需要加入和stack()。

使用上面的示例数据:

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

提前致谢。我如何通过相应地拆分两列来使用以上代码。例如:0 31316 Lennon,John 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,我尝试理解这个问题。您是说拆分后两列的成员数必须相同吗?您对0 31316 Lennon,John 25 F01 300 1:13:36:1,12 1:13:37:1,13 A,B,C的预期结果是什么?
Ben2018

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.