我有以下数据帧,其中一列是一个对象(列表类型单元格):
df = pd.DataFrame({'A': [1, 2], 'B': [[1, 2], [1, 2]]})
输出:
A B
0 1 [1, 2]
1 2 [1, 2]
我的期望输出是:
A B
0 1 1
1 1 2
3 2 1
4 2 2
我该怎么做才能做到这一点呢?
相关的问题
Pandas列的列表,为每个列表元素创建一行
很好的问题和答案,但只处理一个列与列表(在我的回答自定义函数将工作于多个列,也接受的答案是使用最耗时的应用,这是不建议的,检查更多信息当我(不)想要使用熊猫应用()在我的代码?)
在pandas 0.25中,由于增加了explosion()方法,爆炸一个类似列表的列被大大简化了:
df = pd.DataFrame({'A': [1, 2], 'B': [[1, 2], [1, 2]]})
df.explode('B')
Out:
A B
0 1 1
0 1 2
1 2 1
1 2 2
下面是一个简单的水平爆炸函数,基于@BEN_YO的答案。
import typing
import pandas as pd
def horizontal_explode(df: pd.DataFrame, col_name: str, new_columns: typing.Union[list, None]=None) -> pd.DataFrame:
t = pd.DataFrame(df[col_name].tolist(), columns=new_columns, index=df.index)
return pd.concat([df, t], axis=1)
运行示例:
items = [
["1", ["a", "b", "c"]],
["2", ["d", "e", "f"]]
]
df = pd.DataFrame(items, columns = ["col1", "col2"])
print(df)
t = horizontal_explode(df=df, col_name="col2")
del t["col2"]
print(t)
t = horizontal_explode(df=df, col_name="col2", new_columns=["new_col1", "new_col2", "new_col3"])
del t["col2"]
print(t)
这是相关的输出:
col1 col2
0 1 [a, b, c]
1 2 [d, e, f]
col1 0 1 2
0 1 a b c
1 2 d e f
col1 new_col1 new_col2 new_col3
0 1 a b c
1 2 d e f
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[1,2]]})
out = pd.concat([df.loc[:,'A'],(df.B.apply(pd.Series))], axis=1, sort=False)
out = out.set_index('A').stack().droplevel(level=1).reset_index().rename(columns={0:"B"})
A B
0 1 1
1 1 2
2 2 1
3 2 2
如果您不希望创建中间对象,可以将其实现为一行
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[1,2]]})
pd.concat([df['A'], pd.DataFrame(df['B'].values.tolist())], axis = 1)\
.melt(id_vars = 'A', value_name = 'B')\
.dropna()\
.drop('variable', axis = 1)
A B
0 1 1
1 2 1
2 1 2
3 2 2
对我想到的这个方法有什么意见吗?或者同时做concat和melt被认为太“昂贵”?
我有另一个好方法来解决这个问题当你有不止一列要爆炸的时候。
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[1,2]], 'C':[[1,2,3],[1,2,3]]})
print(df)
A B C
0 1 [1, 2] [1, 2, 3]
1 2 [1, 2] [1, 2, 3]
我想爆炸B和C列,首先爆炸B,第二爆炸C,然后从原来的df中去掉B和C。之后,我将在3个dfs上做一个索引连接。
explode_b = df.explode('B')['B']
explode_c = df.explode('C')['C']
df = df.drop(['B', 'C'], axis=1)
df = df.join([explode_b, explode_c])
因为通常子列表的长度是不同的,join/merge的计算成本要高得多。我对不同长度的子列表和更多正常列重新测试了该方法。
MultiIndex也应该是一种更简单的编写方法,并且具有与numpy方法几乎相同的性能。
令人惊讶的是,在我的实现理解方式有最好的表现。
def stack(df):
return df.set_index(['A', 'C']).B.apply(pd.Series).stack()
def comprehension(df):
return pd.DataFrame([x + [z] for x, y in zip(df[['A', 'C']].values.tolist(), df.B) for z in y])
def multiindex(df):
return pd.DataFrame(np.concatenate(df.B.values), index=df.set_index(['A', 'C']).index.repeat(df.B.str.len()))
def array(df):
return pd.DataFrame(
np.column_stack((
np.repeat(df[['A', 'C']].values, df.B.str.len(), axis=0),
np.concatenate(df.B.values)
))
)
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from timeit import timeit
res = pd.DataFrame(
index=[
'stack',
'comprehension',
'multiindex',
'array',
],
columns=[1000, 2000, 5000, 10000, 20000, 50000],
dtype=float
)
for f in res.index:
for c in res.columns:
df = pd.DataFrame({'A': list('abc'), 'C': list('def'), 'B': [['g', 'h', 'i'], ['j', 'k'], ['l']]})
df = pd.concat([df] * c)
stmt = '{}(df)'.format(f)
setp = 'from __main__ import df, {}'.format(f)
res.at[f, c] = timeit(stmt, setp, number=20)
ax = res.div(res.min()).T.plot(loglog=True)
ax.set_xlabel("N")
ax.set_ylabel("time (relative)")
性能
每种方法的相对时间