我有以下 DataFrame,其中一列是对象(列表类型单元格):
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[1,2]]}) df Out[458]: A B 0 1 [1, 2] 1 2 [1, 2]
我的预期输出是:
A B 0 1 1 1 1 2 3 2 1 4 2 2
我应该怎么做才能实现这一目标?
我知道objectdtype 列使数据难以使用 pandas 函数进行转换。当我收到这样的数据时,首先想到的是“展平”或取消嵌套列。
object
我正在使用 pandas 和 Python 函数来解决这类问题。如果您担心上述解决方案的速度,请查看user3483203 的答案,因为它使用的是 numpy 并且大多数时候 numpy 更快。如果速度很重要,我推荐Cython或numba 。
*选项1*
如果另一列中的所有子列表长度相同,numpy则此处可能是一个有效的选择:
numpy
vals = np.array(df.B.values.tolist()) a = np.repeat(df.A, vals.shape[1]) pd.DataFrame(np.column_stack((a, vals.ravel())), columns=df.columns)
A B 0 1 1 1 1 2 2 2 1 3 2 2
*选项 2*
如果子列表的长度不同,则需要额外的步骤:
vals = df.B.values.tolist() rs = [len(r) for r in vals] a = np.repeat(df.A, rs) pd.DataFrame(np.column_stack((a, np.concatenate(vals))), columns=df.columns)
*选项 3*
我尝试将其推广到展N平列和平铺M列,稍后我将努力提高效率:
N
M
df = pd.DataFrame({'A': [1,2,3], 'B': [[1,2], [1,2,3], [1]], 'C': [[1,2,3], [1,2], [1,2]], 'D': ['A', 'B', 'C']})
A B C D 0 1 [1, 2] [1, 2, 3] A 1 2 [1, 2, 3] [1, 2] B 2 3 [1] [1, 2] C
def unnest(df, tile, explode): vals = df[explode].sum(1) rs = [len(r) for r in vals] a = np.repeat(df[tile].values, rs, axis=0) b = np.concatenate(vals.values) d = np.column_stack((a, b)) return pd.DataFrame(d, columns = tile + ['_'.join(explode)]) unnest(df, ['A', 'D'], ['B', 'C'])
A D B_C 0 1 A 1 1 1 A 2 2 1 A 1 3 1 A 2 4 1 A 3 5 2 B 1 6 2 B 2 7 2 B 3 8 2 B 1 9 2 B 2 10 3 C 1 11 3 C 1 12 3 C 2
*功能*
def wen1(df): return df.set_index('A').B.apply(pd.Series).stack().reset_index(level=0).rename(columns={0: 'B'}) def wen2(df): return pd.DataFrame({'A':df.A.repeat(df.B.str.len()),'B':np.concatenate(df.B.values)}) def wen3(df): s = pd.DataFrame({'B': np.concatenate(df.B.values)}, index=df.index.repeat(df.B.str.len())) return s.join(df.drop('B', 1), how='left') def wen4(df): return pd.DataFrame([[x] + [z] for x, y in df.values for z in y],columns=df.columns) def chris1(df): vals = np.array(df.B.values.tolist()) a = np.repeat(df.A, vals.shape[1]) return pd.DataFrame(np.column_stack((a, vals.ravel())), columns=df.columns) def chris2(df): vals = df.B.values.tolist() rs = [len(r) for r in vals] a = np.repeat(df.A.values, rs) return pd.DataFrame(np.column_stack((a, np.concatenate(vals))), columns=df.columns)
*计时*
import pandas as pd import matplotlib.pyplot as plt import numpy as np from timeit import timeit res = pd.DataFrame( index=['wen1', 'wen2', 'wen3', 'wen4', 'chris1', 'chris2'], columns=[10, 50, 100, 500, 1000, 5000, 10000], dtype=float ) for f in res.index: for c in res.columns: df = pd.DataFrame({'A': [1, 2], 'B': [[1, 2], [1, 2]]}) df = pd.concat([df]*c) stmt = '{}(df)'.format(f) setp = 'from __main__ import df, {}'.format(f) res.at[f, c] = timeit(stmt, setp, number=50) ax = res.div(res.min()).T.plot(loglog=True) ax.set_xlabel("N") ax.set_ylabel("time (relative)")
*表现*
方法0 [pandas >= 0.25] 从pandas 0.25开始,如果只需要爆炸 一 列,可以使用pandas.DataFrame.explode函数:
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
list
NaN
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 (简单易懂但在性能方面不推荐。)
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 使用repeatwith DataFrameconstructor ,重新创建你的数据框(性能好,不擅长多列)
repeat
DataFrame
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”之后的单列
join
merge
index
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请在末尾添加。
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
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解决方案只是为了好玩
itertools
cycle
chain
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构造函数检查
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')