如何在 Pandas DataFrame 中将大小不同的基于字符串的列拆分为多个列?

5

I have a pandas DataFrame which is of the form :

A      B       C     D
A1     6       7.5   NaN
A1     4       23.8  <D1 0.0 6.5 12 4, D2 1.0 4 3.5 1>
A2     7       11.9  <D1 2.0 7.5 10 2, D3 7.5 4.2 13.5 4> 
A3    11       0.8   <D2 2.0 7.5 10 2, D3 7.5 4.2 13.5 4, D4 2.0 7.5 10 2, D5 7.5 4.2 13.5 4>
D列是一个原始字符串列,每个条目中包含多个类别。每个类别的条目值是通过将最后两个值相除计算得出的。例如,在第二行中:
D1 = 12/4 = 3
D2 = 3.5/1 = 3.5

我需要将列D根据其类别拆分并加入到我的DataFrame中。问题是该列是动态的,并且可以在单个条目中有近35-40个类别。目前,我所做的就是一个暴力方法,即迭代所有行,这对于大型数据集来说非常慢。请问有人可以帮忙吗?

期望结果

A      B       C     D1  D2  D3  D4  D5
A1     6       7.5   NaN NaN NaN NaN NaN
A1     4       23.8  3.0 3.5 NaN NaN NaN
A2     7       11.9  5.0 NaN 3.4 NaN NaN 
A3    11       0.8   NaN 5.0 3.4 5.0 3.4

如果您正在寻找一个能够根据您的数据创建动态列并执行此操作的函数,恐怕没有这样的函数可供使用。您可以尝试使用df.apply()在列或行上应用您自己创建的函数,并使用df.assign()添加列。 - Loukik
@Loukik 是的,我知道。我尝试过df.apply(),但对于具有大量行条目的大型数据集来说,df.apply()似乎很慢。除法可以通过矢量化操作来实现,但高效地进行拆分是问题所在。 - saran sky
1个回答

3

用途:

d = df['D'].str.extractall(r'(D\d+).*?([\d.]+)\s([\d.]+)(?:,|\>)')
d = d.droplevel(1).set_index(0, append=True).astype(float)
d = df.join(d[1].div(d[2]).round(1).unstack()).drop('D', 1)

细节:

使用 Series.str.extractall 函数,按照正则表达式模式从列 D 中提取所有捕获组。你可以在此处测试正则表达式模式。

print(d)
          0     1  2 # --> capture groups
  match             
1 0      D1    12  4
  1      D2   3.5  1
2 0      D1    10  2
  1      D3  13.5  4
3 0      D2    10  2
  1      D3  13.5  4
  2      D4    10  2
  3      D5  13.5  4

使用DataFrame.droplevel + set_index,结合append=True可删除未使用的级别并将新索引追加到DataFrame中。
print(d)
         1    2
  0            
1 D1  12.0  4.0
  D2   3.5  1.0
2 D1  10.0  2.0
  D3  13.5  4.0
3 D2  10.0  2.0
  D3  13.5  4.0
  D4  10.0  2.0
  D5  13.5  4.0

使用 Series.div 将列 1 除以 2,使用 Series.round 对值进行四舍五入,然后使用 Series.unstack 重新塑造数据帧形状,最后使用 DataFrame.join 将新的数据帧与 df 进行连接。
print(d)
    A   B     C   D1   D2   D3   D4   D5
0  A1   6   7.5  NaN  NaN  NaN  NaN  NaN
1  A1   4  23.8  3.0  3.5  NaN  NaN  NaN
2  A2   7  11.9  5.0  NaN  3.4  NaN  NaN
3  A3  11   0.8  NaN  5.0  3.4  5.0  3.4

非常感谢您的快速回复。除了正则表达式部分,我能够理解一切。正则表达式是如何过滤掉仅最后两个值的?中间的数字是如何被消除的? - saran sky
嗨,Saran,这里我们在正则表达式模式中使用 .*?,它匹配零个或多个字符,但尽可能地匹配“少次”。我已经提供了有关正则表达式的解释链接,请查看。 - Shubham Sharma
谢谢Shubham,我已经理解了正则表达式是如何工作的。但是,还有更多的方法可以进一步优化吗?我在一个包含35万行数据的数据集上运行了这个程序,str.extractall 单独就需要30-32秒。在我的以前的暴力方法中,我对数据进行了拆分并并行迭代,那只需要26-27秒。 - saran sky
1
感谢@Shubham Sharma。实现多进程提高了解决方案的性能。 - saran sky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接