将 Pandas 列表中的一列拆分为多列
- 2024-11-21 08:33:00
- admin 原创
- 6
问题描述:
我有一个包含一列的 Pandas DataFrame:
import pandas as pd
df = pd.DataFrame({"teams": [["SF", "NYG"] for _ in range(7)]})
teams
0 [SF, NYG]
1 [SF, NYG]
2 [SF, NYG]
3 [SF, NYG]
4 [SF, NYG]
5 [SF, NYG]
6 [SF, NYG]
如何将这一列列表分成两列?
期望结果:
team1 team2
0 SF NYG
1 SF NYG
2 SF NYG
3 SF NYG
4 SF NYG
5 SF NYG
6 SF NYG
解决方案 1:
您可以使用以下方式创建的DataFrame
构造函数:lists
`to_list`
import pandas as pd
d1 = {'teams': [['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],
['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG']]}
df2 = pd.DataFrame(d1)
print (df2)
teams
0 [SF, NYG]
1 [SF, NYG]
2 [SF, NYG]
3 [SF, NYG]
4 [SF, NYG]
5 [SF, NYG]
6 [SF, NYG]
df2[['team1','team2']] = pd.DataFrame(df2.teams.tolist(), index= df2.index)
print (df2)
teams team1 team2
0 [SF, NYG] SF NYG
1 [SF, NYG] SF NYG
2 [SF, NYG] SF NYG
3 [SF, NYG] SF NYG
4 [SF, NYG] SF NYG
5 [SF, NYG] SF NYG
6 [SF, NYG] SF NYG
对于新的DataFrame
:
df3 = pd.DataFrame(df2['teams'].to_list(), columns=['team1','team2'])
print (df3)
team1 team2
0 SF NYG
1 SF NYG
2 SF NYG
3 SF NYG
4 SF NYG
5 SF NYG
6 SF NYG
解决方案apply(pd.Series)
非常慢:
#7k rows
df2 = pd.concat([df2]*1000).reset_index(drop=True)
In [121]: %timeit df2['teams'].apply(pd.Series)
1.79 s ± 52.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [122]: %timeit pd.DataFrame(df2['teams'].to_list(), columns=['team1','team2'])
1.63 ms ± 54.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
解决方案 2:
更简单的解决方案:
pd.DataFrame(df2["teams"].to_list(), columns=['team1', 'team2'])
产量,
team1 team2
-------------
0 SF NYG
1 SF NYG
2 SF NYG
3 SF NYG
4 SF NYG
5 SF NYG
6 SF NYG
7 SF NYG
如果您想要拆分一列分隔字符串而不是列表,您可以类似地这样做:
pd.DataFrame(df["teams"].str.split('<delim>', expand=True).values,
columns=['team1', 'team2'])
解决方案 3:
此解决方案保留了 DataFrame 的索引df2
,与使用以下方法的任何解决方案不同tolist()
:
df3 = df2.teams.apply(pd.Series)
df3.columns = ['team1', 'team2']
结果如下:
team1 team2
0 SF NYG
1 SF NYG
2 SF NYG
3 SF NYG
4 SF NYG
5 SF NYG
6 SF NYG
解决方案 4:
与建议的解决方案相比,似乎有一种语法上更简单的方法,因此更容易记住。我假设该列在数据框 df 中称为“meta”:
df2 = pd.DataFrame(df['meta'].str.split().values.tolist())
解决方案 5:
我想推荐一种更高效、更 Pythonic 的方式。
首先定义 DataFrame 为原始帖子:
df = pd.DataFrame({"teams": [["SF", "NYG"] for _ in range(7)]})
我的解决方案:
%%timeit
df['team1'], df['team2'] = zip(*list(df['teams'].values))
>> 761 µs ± 8.35 µs per loop
相比之下,获得最多支持的方案是:
%%timeit
df[['team1','team2']] = pd.DataFrame(df.teams.tolist(), index=df.index)
df = pd.DataFrame(df['teams'].to_list(), columns=['team1','team2'])
>> 1.31 ms ± 11.2 µs per loop
我的解决方案节省了 40% 的时间,而且更短。你唯一需要记住的是如何使用 解包和重塑二维列表zip(*list)
。
解决方案 6:
列表推导
使用列表推导的简单实现(我最喜欢的)
df = pd.DataFrame([pd.Series(x) for x in df.teams])
df.columns = ['team_{}'.format(x+1) for x in df.columns]
输出时序:
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 2.71 ms
输出:
team_1 team_2
0 SF NYG
1 SF NYG
2 SF NYG
3 SF NYG
4 SF NYG
5 SF NYG
6 SF NYG
解决方案 7:
以前的解决方案对我不起作用,因为我有nan
观察结果dataframe
。 在我的例子中,df2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)
结果是:
object of type 'float' has no len()
我使用列表推导来解决这个问题。以下是可复制的示例:
import pandas as pd
import numpy as np
d1 = {'teams': [['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],
['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG']]}
df2 = pd.DataFrame(d1)
df2.loc[2,'teams'] = np.nan
df2.loc[4,'teams'] = np.nan
df2
输出:
teams
0 [SF, NYG]
1 [SF, NYG]
2 NaN
3 [SF, NYG]
4 NaN
5 [SF, NYG]
6 [SF, NYG]
df2['team1']=np.nan
df2['team2']=np.nan
使用列表推导式解决,
for i in [0,1]:
df2['team{}'.format(str(i+1))]=[k[i] if isinstance(k,list) else k for k in df2['teams']]
df2
产量:
teams team1 team2
0 [SF, NYG] SF NYG
1 [SF, NYG] SF NYG
2 NaN NaN NaN
3 [SF, NYG] SF NYG
4 NaN NaN NaN
5 [SF, NYG] SF NYG
6 [SF, NYG] SF NYG
解决方案 8:
这是使用df.transform
和 的另一种解决方案df.set_index
:
>>> from operator import itemgetter
>>> df['teams'].transform({'item1': itemgetter(0), 'item2': itemgetter(1)})
team1 team2
0 SF NYG
1 SF NYG
2 SF NYG
3 SF NYG
4 SF NYG
5 SF NYG
6 SF NYG
当然可以概括为:
>>> indices = range(len(df['teams'][0]))
>>> df['teams'].transform({f'team{i+1}': itemgetter(i) for i in indices})
team1 team2
0 SF NYG
1 SF NYG
2 SF NYG
3 SF NYG
4 SF NYG
5 SF NYG
6 SF NYG
这种方法还有提取所需索引的额外好处:
>>> df
teams
0 [SF, NYG, XYZ, ABC]
1 [SF, NYG, XYZ, ABC]
2 [SF, NYG, XYZ, ABC]
3 [SF, NYG, XYZ, ABC]
4 [SF, NYG, XYZ, ABC]
5 [SF, NYG, XYZ, ABC]
6 [SF, NYG, XYZ, ABC]
>>> indices = [0, 2]
>>> df['teams'].transform({f'team{i+1}': itemgetter(i) for i in indices})
team1 team3
0 SF XYZ
1 SF XYZ
2 SF XYZ
3 SF XYZ
4 SF XYZ
5 SF XYZ
6 SF XYZ
解决方案 9:
根据前面的答案,这里是另一个解决方案,它返回与 df2.teams.apply(pd.Series) 相同的结果,但运行时间更快:
pd.DataFrame([{x: y for x, y in enumerate(item)} for item in df2['teams'].values.tolist()], index=df2.index)
时间安排:
In [1]:
import pandas as pd
d1 = {'teams': [['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],
['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG']]}
df2 = pd.DataFrame(d1)
df2 = pd.concat([df2]*1000).reset_index(drop=True)
In [2]: %timeit df2['teams'].apply(pd.Series)
8.27 s ± 2.73 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [3]: %timeit pd.DataFrame([{x: y for x, y in enumerate(item)} for item in df2['teams'].values.tolist()], index=df2.index)
35.4 ms ± 5.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
解决方案 10:
如果有人来这里寻找现成的函数,我写了一个。
columns
如果没有指定,它会找到所有带有列表的列并展开它们;添加的列命名为
column_name_0
、column_name_1
等;列的顺序保留在最终的数据框中;
如果是
strict=True
,它会检查给定列中的列表是否大小相等。
欢迎提出改进和意见。
def unfold_columns(df, columns=[], strict=False):
assert isinstance(columns, list), "Columns should be a list of column names"
if len(columns) == 0:
columns = [
column for column in df.columns
if df.applymap(lambda x: isinstance(x, list)).all()[column]
]
else:
assert(all([(column in df.columns) for column in columns])), \n "Not all given columns are found in df"
columns_order = df.columns
for column_name in columns:
if df[column_name].apply(lambda x: isinstance(x, list)).all():
if strict:
assert len(set(df[column_name].apply(lambda x: len(x)))) == 1, \n f"Lists in df['{column_name}'] are not of equal length"
unfolded = pd.DataFrame(df[column_name].tolist())
unfolded.columns = [f'{column_name}_{x}' for x in unfolded.columns]
columns_order = [
*columns_order[:list(columns_order).index(column_name)],
*unfolded.columns,
*columns_order[list(columns_order).index(column_name)+1:]
]
df = df.join(unfolded).drop([column_name], axis=1)
return df[columns_order]
解决方案 11:
要将两个新列附加到现有的 DataFrame:
df[['team1', 'team2']] = df["teams"].to_list()
解决方案 12:
总结所有答案。如果需要,只需创建具有 2 列的新 DataFrame
pd.DataFrame(df['teams'].tolist(), columns=['team1', 'team2'], index=df.index)
如果要分配给同一个 df,则有几种选择。
最短
df[['团队1', '团队2']] = df['团队'].tolist()
最慢(不推荐,它可能慢 10 倍甚至更多,而没有任何优势)
df[['团队1', '团队2']] = df['团队'].apply(pd.Series)
并且由于某种原因也是最快的(几乎比第一个快 2 倍)。
df['team1'], df['team2'] = zip(*df['teams'].tolist())
所以我推荐 1。如果您真的需要速度,您可以尝试 3,但这看起来很奇怪,并且优势可能会在未来的版本中消失。
解决方案 13:
您可以尝试使用两次应用在您的 df 中创建新列“team1”和“team2”
df = pd.DataFrame({"teams": [["SF", "NYG"] for _ in range(7)]})
df["team1"]=df['teams'].apply(lambda x: x[0] )
df["team2"]=df['teams'].apply(lambda x: x[1] )
df
在此处输入图片描述
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件