python - How to unnest (explode) a column in a pandas DataFrame, into multiple rows
问题描述
I have the following DataFrame where one of the columns is an object (list type cell):
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[1,2]]})
df
Out[458]:
A B
0 1 [1, 2]
1 2 [1, 2]
My expected output is:
A B
0 1 1
1 1 2
3 2 1
4 2 2
What should I do to achieve this?
Related question
pandas: When cell contents are lists, create a row for each element in the list
Good question and answer but only handle one column with list(In my answer the self-def function will work for multiple columns, also the accepted answer is use the most time consuming apply
, which is not recommended, check more info When should I ever want to use pandas apply() in my code?)
解决方案
我知道object
dtype 列使数据难以使用 pandas 函数进行转换。当我收到这样的数据时,首先想到的是“展平”或取消嵌套列。
我正在使用 pandas 和 Python 函数来解决这类问题。如果您担心上述解决方案的速度,请查看user3483203 的答案,因为它使用 numpy 并且大多数时候 numpy 更快。如果速度很重要,我推荐Cython或numba 。
方法0 [pandas >= 0.25]
从pandas 0.25开始,如果只需要爆炸一列,可以使用pandas.DataFrame.explode
函数:
df.explode('B')
A B
0 1 1
1 1 2
0 2 1
1 2 2
给定一个数据框,列中为空list
或 a NaN
。空列表不会引起问题,但NaN
需要用 a 填充list
df = pd.DataFrame({'A': [1, 2, 3, 4],'B': [[1, 2], [1, 2], [], np.nan]})
df.B = df.B.fillna({i: [] for i in df.index}) # replace NaN with []
df.explode('B')
A B
0 1 1
0 1 2
1 2 1
1 2 2
2 3 NaN
3 4 NaN
方法一
apply + pd.Series
(简单易懂但在性能方面不推荐。)
df.set_index('A').B.apply(pd.Series).stack().reset_index(level=0).rename(columns={0:'B'})
Out[463]:
A B
0 1 1
1 1 2
0 2 1
1 2 2
方法 2
使用repeat
with DataFrame
constructor ,重新创建你的数据框(性能好,不擅长多列)
df=pd.DataFrame({'A':df.A.repeat(df.B.str.len()),'B':np.concatenate(df.B.values)})
df
Out[465]:
A B
0 1 1
0 1 2
1 2 1
1 2 2
例如方法 2.1 除了 A 我们还有 A.1 .....An 如果我们仍然使用上面的方法(方法 2),我们很难一一重新创建列。
解决方案:join
或者merge
使用index
“unnest”之后的单列
s=pd.DataFrame({'B':np.concatenate(df.B.values)},index=df.index.repeat(df.B.str.len()))
s.join(df.drop('B',1),how='left')
Out[477]:
B A
0 1 1
0 2 1
1 1 2
1 2 2
如果您需要与以前完全相同的列顺序,reindex
请在末尾添加。
s.join(df.drop('B',1),how='left').reindex(columns=df.columns)
方法 3
重新创建list
pd.DataFrame([[x] + [z] for x, y in df.values for z in y],columns=df.columns)
Out[488]:
A B
0 1 1
1 1 2
2 2 1
3 2 2
如果多于两列,请使用
s=pd.DataFrame([[x] + [z] for x, y in zip(df.index,df.B) for z in y])
s.merge(df,left_on=0,right_index=True)
Out[491]:
0 1 A B
0 0 1 1 [1, 2]
1 0 2 1 [1, 2]
2 1 1 2 [1, 2]
3 1 2 2 [1, 2]
方法 4
使用reindex
或loc
df.reindex(df.index.repeat(df.B.str.len())).assign(B=np.concatenate(df.B.values))
Out[554]:
A B
0 1 1
0 1 2
1 2 1
1 2 2
#df.loc[df.index.repeat(df.B.str.len())].assign(B=np.concatenate(df.B.values))
列表仅包含唯一值时的方法5 :
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[3,4]]})
from collections import ChainMap
d = dict(ChainMap(*map(dict.fromkeys, df['B'], df['A'])))
pd.DataFrame(list(d.items()),columns=df.columns[::-1])
Out[574]:
B A
0 1 1
1 2 1
2 3 2
3 4 2
方法 6
用于numpy
高性能:
newvalues=np.dstack((np.repeat(df.A.values,list(map(len,df.B.values))),np.concatenate(df.B.values)))
pd.DataFrame(data=newvalues[0],columns=df.columns)
A B
0 1 1
1 1 2
2 2 1
3 2 2
方法7
使用基函数itertools
cycle
和chain
:纯python解决方案只是为了好玩
from itertools import cycle,chain
l=df.values.tolist()
l1=[list(zip([x[0]], cycle(x[1])) if len([x[0]]) > len(x[1]) else list(zip(cycle([x[0]]), x[1]))) for x in l]
pd.DataFrame(list(chain.from_iterable(l1)),columns=df.columns)
A B
0 1 1
1 1 2
2 2 1
3 2 2
泛化到多列
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[3,4]],'C':[[1,2],[3,4]]})
df
Out[592]:
A B C
0 1 [1, 2] [1, 2]
1 2 [3, 4] [3, 4]
自定义功能:
def unnesting(df, explode):
idx = df.index.repeat(df[explode[0]].str.len())
df1 = pd.concat([
pd.DataFrame({x: np.concatenate(df[x].values)}) for x in explode], axis=1)
df1.index = idx
return df1.join(df.drop(explode, 1), how='left')
unnesting(df,['B','C'])
Out[609]:
B C A
0 1 1 1
0 2 2 1
1 3 3 2
1 4 4 2
逐列取消嵌套
以上所有方法都是关于垂直取消嵌套和分解,如果您确实需要水平扩展列表,请与pd.DataFrame
构造函数检查
df.join(pd.DataFrame(df.B.tolist(),index=df.index).add_prefix('B_'))
Out[33]:
A B C B_0 B_1
0 1 [1, 2] [1, 2] 1 2
1 2 [3, 4] [3, 4] 3 4
更新功能
def unnesting(df, explode, axis):
if axis==1:
idx = df.index.repeat(df[explode[0]].str.len())
df1 = pd.concat([
pd.DataFrame({x: np.concatenate(df[x].values)}) for x in explode], axis=1)
df1.index = idx
return df1.join(df.drop(explode, 1), how='left')
else :
df1 = pd.concat([
pd.DataFrame(df[x].tolist(), index=df.index).add_prefix(x) for x in explode], axis=1)
return df1.join(df.drop(explode, 1), how='left')
测试输出
unnesting(df, ['B','C'], axis=0)
Out[36]:
B0 B1 C0 C1 A
0 1 2 1 2 1
1 3 4 3 4 2
2021-02-17 更新原始爆炸功能
def unnesting(df, explode, axis):
if axis==1:
df1 = pd.concat([df[x].explode() for x in explode], axis=1)
return df1.join(df.drop(explode, 1), how='left')
else :
df1 = pd.concat([
pd.DataFrame(df[x].tolist(), index=df.index).add_prefix(x) for x in explode], axis=1)
return df1.join(df.drop(explode, 1), how='left')
推荐阅读
- vba - 将功能键分配给 excel 宏
- swift - Swift ImageCropper 返回指定“窗口”之外的图像
- swift - 强制展开的可选选项不会崩溃。为什么不?
- r - 错误:包“BiocGenerics”的延迟加载失败
- json - 我希望我的 Swift 程序中“self.q”中的值也打印到 xcode 控制台
- c# - 从列表c#中替换其描述中的值
- r - 如何在 R 中实现相当于 Refresh All(在 Excel 中)?
- .net - 如何在 .NET 中监视现有进程
- input - 在输入占位符文本中使用 Font Awesome (5) 图标
- php - PHAR 编译后不起作用