如何将列中的文本分割为多行

我正在处理一个大的 csv 文件,在最后一列的旁边有一个文本字符串,我想用一个特定的分隔符来分隔它。我想知道是否有一个简单的方法来做到这一点使用熊猫或蟒蛇?

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列就在工作表的末尾,但是我不确定从那里开始做什么。我可以做它在 Excel 与内置的 text-to-columns函数和一个快速宏,但我的数据集有太多的记录 Excel 处理。

最后,我想采取记录约翰列侬的和创建多行,与信息从每一套座位在一个单独的行。

186567 次浏览

这将按空间分割 Seatblock,并给出每个 Seatblock 各自的行。

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

This is a little ugly, but maybe someone will chime in with a prettier solution.

与丹不同的是,我认为他的回答相当优雅... ... 但不幸的是,这也是非常非常低效的。因此,既然问题提到了 “一个大的 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()"

第二种方法只是避免分配100000系列,这足以使它的速度提高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()"

同样有效,而且 差不多也同样有效。

编辑: 更简单! 并处理 NaNs (但效率较低) :

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

链接的另一个类似解决方案是使用 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

如果列中是 没有 NaN值,最快的解决方案是使用 list理解和 DataFrame构造函数:

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与返回 DataFrame(文件)的参数 expand=True一起工作,并且它解释了为什么它比较慢:

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

也可以使用 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

另一种方法是这样的:

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

这种方法似乎比本帖其他地方建议的方法要简单得多。

在熊猫数据框中拆分行

现在回答这个问题可能有点晚了,但我希望能记录下熊猫的两个优秀特性: 具有正则表达式的 pandas.Series.str.split()pandas.Series.explode()

import pandas as pd
import numpy as np


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


print(df)
print('-'*80+'\n')


df['Seatblocks'] = df['Seatblocks'].str.split('[ :]')
df = df.explode('Seatblocks').reset_index(drop=True)
cols = list(df.columns)
cols.append(cols.pop(cols.index('CustomerName')))
df = df[cols]




print(df)
print('='*80+'\n')
print(df[df['CustomerName'] == 'Lennon, John'])

输出结果是:

   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      360
--------------------------------------------------------------------------------


CustNum  ItemQty Item Seatblocks  ItemExt     CustomerName
0     32363        3  F04          2       60  McCartney, Paul
1     32363        3  F04        218       60  McCartney, Paul
2     32363        3  F04         10       60  McCartney, Paul
3     32363        3  F04        4,6       60  McCartney, Paul
4     31316       25  F01          1      360     Lennon, John
5     31316       25  F01         13      360     Lennon, John
6     31316       25  F01         36      360     Lennon, John
7     31316       25  F01       1,12      360     Lennon, John
8     31316       25  F01          1      360     Lennon, John
9     31316       25  F01         13      360     Lennon, John
10    31316       25  F01         37      360     Lennon, John
11    31316       25  F01       1,13      360     Lennon, John
================================================================================


CustNum  ItemQty Item Seatblocks  ItemExt  CustomerName
4     31316       25  F01          1      360  Lennon, John
5     31316       25  F01         13      360  Lennon, John
6     31316       25  F01         36      360  Lennon, John
7     31316       25  F01       1,12      360  Lennon, John
8     31316       25  F01          1      360  Lennon, John
9     31316       25  F01         13      360  Lennon, John
10    31316       25  F01         37      360  Lennon, John
11    31316       25  F01       1,13      360  Lennon, John