【问题标题】:pandas: How do I split text in a column into multiple rows?pandas:如何将列中的文本拆分为多行?
【发布时间】:2013-06-11 14:15:59
【问题描述】:

我正在处理一个大的 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 中完成,但我的数据集有太多记录,无法处理。

最终,我想记录约翰列侬的记录并创建多条线路,每组座位的信息在单独的线路上。

【问题讨论】:

  • 这个好问题与 Pandas 中的 FlatMap 相关,目前不存在

标签: python pandas dataframe


【解决方案1】:

这会按空间划分座位区,并为每个座位区分配自己的行。

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

这有点难看,但也许有人会提出更漂亮的解决方案。

【讨论】:

  • @DanAllan 在您申请时为该系列提供索引;它们将成为列名
  • 虽然这回答了问题,但值得一提的是(可能) split() 会为每一行创建一个列表,这会很快增加 DataFrame 的大小。就我而言,在 ~200M 表上运行代码会导致 ~10G 内存(+swap...)使用。
  • 虽然我不确定是不是因为split(),因为仅仅reduce()'ing 通过该列就像一个魅力。那么问题可能出在stack()...
  • 我收到了错误NameError: name 'Series' is not defined。 Series 应该来自哪里?编辑:没关系,应该是pandas.Series,因为它指的是来自pandas的项目
  • 是的,@user5359531。为了方便/简洁,我from pandas import Series。
【解决方案2】:

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

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

编辑:this answer 建议如何使用“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()"

也有效,并且几乎同样有效。

编辑: even simpler!并处理 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 的内存。你能指出一些可以告诉我为什么会这样的消息来源的方向,以及我能做些什么来解决它吗?谢谢。
  • 你有很多列表和非常小的字符串,这或多或少是python中内存使用的最坏情况(中间步骤“.split().tolist()”产生纯python对象)。我可能会做的是将DataFrame转储到文件中,然后使用read_csv(...,sep ='')将其作为csv打开。但要保持话题:第一个解决方案(与第三个一起,但应该非常慢)可能是 4 个中为您提供最低内存使用的解决方案,因为您的相对较长的行数相对较少。跨度>
  • 嘿 Pietro,我尝试了你的保存到文件并重新加载的建议,效果很好。当我尝试在 StringIO 对象中执行此操作时遇到了一些麻烦,我的问题的一个很好的解决方案已发布here。
  • 您对tolist() 的最后建议是完美的。在我的情况下,我只想要列表中的一条数据,并且能够使用 .ix 直接向我现有的 df 添加一列:df['newCol'] = pd.DataFrame(df.col.str.split().tolist()).ix[:,2]
  • 啊,一开始我很难让它工作 - 关于 obect of type 'float' has no len() 的事情令人困惑,直到我意识到我的 一些 行中有 NaN ,而不是str。
【解决方案3】:
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_index 和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

如果列中是NOT 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 与参数 expand=True 返回 DataFrame (documentation),它解释了为什么它更慢:

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

【讨论】:

  • 也许值得一提的是,例如在使用 .str.split() 时,您必须需要 expand=True 选项与 pandas.DataFrames 一起使用。
  • @holzkohlengrill - 感谢您的评论,我将其添加到答案中。
  • @jezrael,执行此代码需要很长时间,这是预期的吗。我究竟如何让它更快?如果我把它放在一个 for 循环中,例如:for x in df[Seablocks][:100] 只在一个子集上执行它,然后在这些子集上连接,那会起作用吗?
【解决方案4】:

也可以使用 groupby() 而无需 join 和 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,我试着理解这个问题。您的意思是拆分后两列必须具有相同数量的成员吗?您对 0 31316 Lennon, John 25 F01 300 1:13:36:1,12 1:13:37:1,13 A,B,C 的预期结果是什么?
【解决方案5】:

另一种方法是这样的:

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

【讨论】:

    【解决方案6】:

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

    split rows in pandas dataframe

    【讨论】:

      【解决方案7】:

      回答这个问题可能已经晚了,但我希望记录下 Pandas 的 2 个优秀特性: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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-04-06
        • 1970-01-01
        • 1970-01-01
        • 2021-01-24
        • 1970-01-01
        • 2018-02-25
        • 1970-01-01
        • 2020-01-22
        相关资源
        最近更新 更多