【问题标题】:Split cell into multiple rows in pandas dataframe在熊猫数据框中将单元格拆分为多行
【发布时间】:2018-11-16 18:41:16
【问题描述】:

我有一个包含订单数据的数据框,每个订单都有多个包存储为逗号分隔的字符串 [package & package_code] 列

我想拆分包裹数据并为每个包裹创建一行,包括其订单详情

这是一个示例输入数据框:

import pandas as pd
df = pd.DataFrame({"order_id":[1,3,7],"order_date":["20/5/2018","22/5/2018","23/5/2018"], "package":["p1,p2,p3","p4","p5,p6"],"package_code":["#111,#222,#333","#444","#555,#666"]})

这就是我想要实现的输出:

我怎样才能用熊猫做到这一点?

【问题讨论】:

标签: python pandas dataframe


【解决方案1】:

熊猫 >= 0.25

假设所有可拆分列具有相同数量的逗号分隔项,您可以按逗号拆分,然后在每列上使用Series.explode

(df.set_index(['order_id', 'order_date'])
   .apply(lambda x: x.str.split(',').explode())
   .reset_index())                                                   

   order_id order_date package package_code
0         1  20/5/2018      p1         #111
1         1  20/5/2018      p2         #222
2         1  20/5/2018      p3         #333
3         3  22/5/2018      p4         #444
4         7  23/5/2018      p5         #555
5         7  23/5/2018      p6         #666

详情

将不被触摸的列设置为索引,

df.set_index(['order_id', 'order_date'])

                      package    package_code
order_id order_date                          
1        20/5/2018   p1,p2,p3  #111,#222,#333
3        22/5/2018         p4            #444
7        23/5/2018      p5,p6       #555,#666

下一步是一个两步过程:用逗号拆分以获得一列列表,然后调用 explode 将列表值分解为各自的行。

_.apply(lambda x: x.str.split(',').explode())

                    package package_code
order_id order_date                     
1        20/5/2018       p1         #111
         20/5/2018       p2         #222
         20/5/2018       p3         #333
3        22/5/2018       p4         #444
7        23/5/2018       p5         #555
         23/5/2018       p6         #666

最后,重置索引。

_.reset_index()

   order_id order_date package package_code
0         1  20/5/2018      p1         #111
1         1  20/5/2018      p2         #222
2         1  20/5/2018      p3         #333
3         3  22/5/2018      p4         #444
4         7  23/5/2018      p5         #555
5         7  23/5/2018      p6         #666

熊猫

这应该适用于任何数量的这样的列。本质是str.split 的一点堆栈解栈魔法。

(df.set_index(['order_date', 'order_id'])
   .stack()
   .str.split(',', expand=True)
   .stack()
   .unstack(-2)
   .reset_index(-1, drop=True)
   .reset_index()
)

  order_date  order_id package package_code
0  20/5/2018         1      p1         #111
1  20/5/2018         1      p2         #222
2  20/5/2018         1      p3         #333
3  22/5/2018         3      p4         #444
4  23/5/2018         7      p5         #555
5  23/5/2018         7      p6         #666

还有另一个涉及chain 的高性能替代方案,但您需要显式链接并重复每一列(很多列有点问题)。选择最符合您的问题描述的任何内容,因为没有单一的答案。

详情

首先,将不被触摸的列设置为索引。

df.set_index(['order_date', 'order_id'])
 
                      package    package_code
order_date order_id                          
20/5/2018  1         p1,p2,p3  #111,#222,#333
22/5/2018  3               p4            #444
23/5/2018  7            p5,p6       #555,#666

接下来,stack 行。

_.stack()

order_date  order_id              
20/5/2018   1         package               p1,p2,p3
                      package_code    #111,#222,#333
22/5/2018   3         package                     p4
                      package_code              #444
23/5/2018   7         package                  p5,p6
                      package_code         #555,#666
dtype: object

我们现在有一个系列。所以请用逗号拨打str.split

_.str.split(',', expand=True)

                                     0     1     2
order_date order_id                               
20/5/2018  1        package         p1    p2    p3
                    package_code  #111  #222  #333
22/5/2018  3        package         p4  None  None
                    package_code  #444  None  None
23/5/2018  7        package         p5    p6  None
                    package_code  #555  #666  None

我们需要去掉 NULL 值,所以再次调用stack

_.stack()

order_date  order_id                 
20/5/2018   1         package       0      p1
                                    1      p2
                                    2      p3
                      package_code  0    #111
                                    1    #222
                                    2    #333
22/5/2018   3         package       0      p4
                      package_code  0    #444
23/5/2018   7         package       0      p5
                                    1      p6
                      package_code  0    #555
                                    1    #666
dtype: object

我们快到了。现在我们希望索引的倒数第二级成为我们的列,因此使用unstack(-2)(倒数第二级上的unstack)取消堆叠

_.unstack(-2)

                      package package_code
order_date order_id                       
20/5/2018  1        0      p1         #111
                    1      p2         #222
                    2      p3         #333
22/5/2018  3        0      p4         #444
23/5/2018  7        0      p5         #555
                    1      p6         #666

使用reset_index去除多余的最后一层:

_.reset_index(-1, drop=True)

                    package package_code
order_date order_id                     
20/5/2018  1             p1         #111
           1             p2         #222
           1             p3         #333
22/5/2018  3             p4         #444
23/5/2018  7             p5         #555
           7             p6         #666

最后,

_.reset_index()

  order_date  order_id package package_code
0  20/5/2018         1      p1         #111
1  20/5/2018         1      p2         #222
2  20/5/2018         1      p3         #333
3  22/5/2018         3      p4         #444
4  23/5/2018         7      p5         #555
5  23/5/2018         7      p6         #666

【讨论】:

  • 可以补充一下吗
  • @pyd 您对此有什么特别想了解的?
  • 使用-2 和reset_index 使用-1 进行unstacking 时发生了什么
  • @AdarshRavi 你可以这样做:keep_columns = list(set(df.columns) - set([reshape_columns]))
  • 我更新了 pandas(版本 1.0.3),最新的表格似乎不再起作用:ValueError: cannot handle a non-unique multi-index!
【解决方案2】:

这是使用numpy.repeatitertools.chain 的一种方法。从概念上讲,这正是您想要做的:重复一些价值观,链接其他价值观。推荐用于少量列,否则基于 stack 的方法可能会更好。

import numpy as np
from itertools import chain

# return list from series of comma-separated strings
def chainer(s):
    return list(chain.from_iterable(s.str.split(',')))

# calculate lengths of splits
lens = df['package'].str.split(',').map(len)

# create new dataframe, repeating or chaining as appropriate
res = pd.DataFrame({'order_id': np.repeat(df['order_id'], lens),
                    'order_date': np.repeat(df['order_date'], lens),
                    'package': chainer(df['package']),
                    'package_code': chainer(df['package_code'])})

print(res)

   order_id order_date package package_code
0         1  20/5/2018      p1         #111
0         1  20/5/2018      p2         #222
0         1  20/5/2018      p3         #333
1         3  22/5/2018      p4         #444
2         7  23/5/2018      p5         #555
2         7  23/5/2018      p6         #666

【讨论】:

  • 如果有两列需要扩展并且有不同的镜头怎么办?
  • @Moj,这个问题没有很好的定义。例如,您不能通过 1 对 1 映射将 3 个值与 5 个值对齐。如果您的问题没有在其他地方得到回答,我建议您提出一个新问题,具体说明您想要的输出。
【解决方案3】:

接近冷的方法:-)

df.set_index(['order_date','order_id']).apply(lambda x : x.str.split(',')).stack().apply(pd.Series).stack().unstack(level=2).reset_index(level=[0,1])
Out[538]: 
  order_date  order_id package package_code
0  20/5/2018         1      p1         #111
1  20/5/2018         1      p2         #222
2  20/5/2018         1      p3         #333
0  22/5/2018         3      p4         #444
0  23/5/2018         7      p5         #555
1  23/5/2018         7      p6         #666

【讨论】:

  • @coldspeed 是的,我会在脑海中再次强调它(应用不好!:-()
  • 如果apply(pd.Series) 不好,stack 不是更糟吗?我经常看到它的性能不如简单迭代!
  • @jpp 是的,这是对 Wen 的一般建议(他明白了),因为他使用它的次数超出了应有的程度,并且您会期望拥有金牌徽章的 60k 用户想要传播良好在可能的情况下进行实践 ;-) 肯定堆栈很烂,但它的灵活性在这里很有用。
【解决方案4】:

看看今天的 pandas 版本 0.25: https://pandas.pydata.org/pandas-docs/stable/whatsnew/v0.25.0.html#series-explode-to-split-list-like-values-to-rows

df = pd.DataFrame([{'var1': 'a,b,c', 'var2': 1}, {'var1': 'd,e,f', 'var2': 2}])
df.assign(var1=df.var1.str.split(',')).explode('var1').reset_index(drop=True)

【讨论】:

  • 这很整洁。很高兴知道 (a) Pandas 如何在内部实现这个功能,(b) 这个解决方案在性能方面与其他解决方案相比如何。
  • 这种方式只能展开一列
  • @cs95 不,通过这样的调整可以在多个列上爆炸:df = pd.DataFrame([{'var1': 'a,b,c','var3': 'x1,x2,x3', 'var2': 1}, {'var1': 'd,e,f','var3': 'x1,x2,x4', 'var2': 2}]) df.assign(var1=df.var1.str.split(','), var3=df.var3.str.split(',')).explode('var1').explode('var3').reset_index(drop=True)
  • 结果不正确,每一列都是单独展开而不是相互串联,结果中的行数比预期的多。
  • 我不明白,你能举一个数据和想要的结果的例子吗?
【解决方案5】:

鉴于explode 无论如何只会影响列表列,一个简单的解决方案是:

# Convert columns of interest to list columns
d["package"]      = d["package"].str.split(",")
d["package_code"] = d["package_code"].str.split(",")

# Explode the entire data frame
d = d.apply( pandas.Series.explode )

优势

  • 避免必须将核心数据移动到索引以“将其挡在外面”,因此当数据包含重复时不会因“重复索引”错误而失败。

缺点

  • 仅当数据中没有列表列时才有效(尽管几乎总是如此)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-03
    • 1970-01-01
    • 2018-12-04
    • 1970-01-01
    • 2021-12-04
    • 1970-01-01
    • 1970-01-01
    • 2020-02-19
    相关资源
    最近更新 更多