有 Pandas 列包含列表,如何将唯一列表元素转换为列?
Have Pandas column containing lists, how to pivot unique list elements to columns?
我写了一个网络抓取工具来从 table 的产品中提取信息并构建一个数据框。数据 table 有一个描述列,其中包含描述产品的逗号分隔的属性字符串。我想在数据框中为每个唯一属性创建一个列,并用该属性的子字符串填充该列中的行。下面的示例 df。
PRODUCTS DATE DESCRIPTION
Product A 2016-9-12 Steel, Red, High Hardness
Product B 2016-9-11 Blue, Lightweight, Steel
Product C 2016-9-12 Red
我想第一步是将描述拆分成一个列表。
In: df2 = df['DESCRIPTION'].str.split(',')
Out:
DESCRIPTION
['Steel', 'Red', 'High Hardness']
['Blue', 'Lightweight', 'Steel']
['Red']
我想要的输出类似于下面的 table。列名不是特别重要。
PRODUCTS DATE STEEL_COL RED_COL HIGH HARDNESS_COL BLUE COL LIGHTWEIGHT_COL
Product A 2016-9-12 Steel Red High Hardness
Product B 2016-9-11 Steel Blue Lightweight
Product C 2016-9-12 Red
我相信可以使用 Pivot 设置列,但我不确定在建立列后填充列的最 Pythonic 方式。感谢任何帮助。
更新
非常感谢您的回答。我选择@MaxU 的响应是正确的,因为它看起来稍微灵活一些,但@piRSquared 的结果非常相似,甚至可以被认为是更 Pythonic 的方法。我测试了两个版本,都做了我需要的。谢谢!
如果产品具有该功能,在功能列中放置 'X' 怎么样?
下面创建了一个独特的特征列表('Steel'、'Red' 等),然后为原始 df 中的每个特征创建一列。然后我们遍历每一行,对于每个产品特征,我们在单元格中放置一个 'X'。
ml = []
a = [ml.append(item) for l in df.DESCRIPTION for item in l]
unique_list_of_attributes = list(set(ml)) # unique features list
# place empty columns in original df for each feature
df = pd.concat([df,pd.DataFrame(columns=unique_list_of_attributes)]).fillna(value='')
# add 'X' in column if product has feature
for row in df.iterrows():
for attribute in row[1]['DESCRIPTION']:
df.loc[row[0],attribute] = 'X'
更新了示例输出:
PRODUCTS DATE DESCRIPTION Blue HighHardness \
0 Product A 2016-9-12 [Steel, Red, HighHardness] X
1 Product B 2016-9-11 [Blue, Lightweight, Steel] X
2 Product C 2016-9-12 [Red]
Lightweight Red Steel
0 X X
1 X X
2 X
你可以建立一个稀疏矩阵:
In [27]: df
Out[27]:
PRODUCTS DATE DESCRIPTION
0 Product A 2016-9-12 Steel, Red, High Hardness
1 Product B 2016-9-11 Blue, Lightweight, Steel
2 Product C 2016-9-12 Red
In [28]: (df.set_index(['PRODUCTS','DATE'])
....: .DESCRIPTION.str.split(',\s*', expand=True)
....: .stack()
....: .reset_index()
....: .pivot_table(index=['PRODUCTS','DATE'], columns=0, fill_value=0, aggfunc='size')
....: )
Out[28]:
0 Blue High Hardness Lightweight Red Steel
PRODUCTS DATE
Product A 2016-9-12 0 1 0 1 1
Product B 2016-9-11 1 0 1 0 1
Product C 2016-9-12 0 0 0 1 0
In [29]: (df.set_index(['PRODUCTS','DATE'])
....: .DESCRIPTION.str.split(',\s*', expand=True)
....: .stack()
....: .reset_index()
....: .pivot_table(index=['PRODUCTS','DATE'], columns=0, fill_value='', aggfunc='size')
....: )
Out[29]:
0 Blue High Hardness Lightweight Red Steel
PRODUCTS DATE
Product A 2016-9-12 1 1 1
Product B 2016-9-11 1 1 1
Product C 2016-9-12 1
cols = ['PRODUCTS', 'DATE']
pd.get_dummies(
df.set_index(cols).DESCRIPTION \
.str.split(',\s*', expand=True).stack()
).groupby(level=cols).sum().astype(int)
@piRSquared 和@MaxU 发布的答案非常有效。
但是, 仅当数据没有任何 NaN
值时。我使用的数据非常稀少。它有大约 100 万行,在应用上述方法后减少到只有大约 100 行,因为它删除了任何列中带有 NaN
的所有行。我花了一天多的时间来找出修复方法。分享稍微修改过的代码,以节省其他人的时间。
假设你有 df
如上所述的 DataFrame,
首先将所有出现的 NaN
替换为任何其他列中不期望的内容,因为稍后您必须将其替换回 NaN
。
cols = ['PRODUCTS', 'DATE']
col = "DESCRIPTION"
df.loc[:, cols] = df.loc[:, cols].fillna("SOME_UNIQ_NAN_REPLACEMENT")
这是必需的,因为 groupby 会删除所有具有 NaN 值的行。 :/
然后我们 运行 对其他答案中的建议稍作修改 stack(dropna=False)
。默认情况下,dropna=True
。
df = pd.get_dummies(df.set_index(index_columns[col]\
.str.split(",\s*", expand=True).stack(dropna=False), prefix=col)\
.groupby(index_columns, sort=False).sum().astype(int).reset_index()
然后将 NaN
放回 df
以不更改其他列的数据。
df.replace("SOME_UNIQ_NAN_REPLACEMENT", np.nan, inplace=True)
希望这可以为某人节省数小时的挫败感。
这是我从我已经在处理的问题扩展而来的解决方案。
def group_agg_pivot_df(df, group_cols, agg_func='count', agg_col=None):
if agg_col is None:
agg_col = group_cols[0]
grouped = df.groupby(group_cols).agg({agg_col: agg_func}) \
.unstack().fillna(0)
# drop aggregation column name from hierarchical column names
grouped.columns = grouped.columns.droplevel()
# promote index to column (the first element of group_cols)
pivot_df = grouped.reset_index()
pivot_df.columns = [s.replace(' ', '_').lower() for s in pivot_df.columns]
return pivot_df
def split_stack_df(df, id_cols, split_col, new_col_name):
# id_cols are the columns we want to pair with the values
# from the split column
stacked = df.set_index(id_cols)[split_col].str.split(',', expand=True) \
.stack().reset_index(level=id_cols)
stacked.columns = id_cols + [new_col_name]
return stacked
stacked = split_stack_df(df, ['PRODUCTS', 'DATE'], 'DESCRIPTION', 'desc')
final_df = group_agg_pivot_df(stacked, ['PRODUCTS', 'DATE', 'desc'])
我还在具有 11592 行的 pandas 数据框和包含具有 2681 个唯一值的列表的列上对 @MaxU、@piRSquared 和我的解决方案进行了基准测试。显然,测试数据框中的列名不同,但我将它们与问题中的列名保持一致。
以下是每种方法的基准
In [277]: %timeit pd.get_dummies(df.set_index(['PRODUCTS', 'DATE']) \
...: .DESCRIPTION.str.split(',', expand=True) \
...: .stack()) \
...: .groupby(['PRODUCTS', 'DATE']).sum()
...:
1 个循环,3 个循环中的最佳:每个循环 1.14 秒
In [278]: %timeit df.set_index(['PRODUCTS', 'DATE']) \
...: .DESCRIPTION.str.split(',', expand=True) \
...: .stack() \
...: .reset_index() \
...: .pivot_table(index=['PRODUCTS', 'DATE'], columns=0, fill_value=0, aggfunc='size')
1 个循环,3 个循环中的最佳:每个循环 612 毫秒
In [286]: %timeit stacked = split_stack_df(df, ['PRODUCTS', 'DATE'], 'DESCRIPTION', 'desc'); \
...: final_df = group_agg_pivot_df(stacked, ['PRODUCTS', 'DATE', 'desc'])
1 个循环,3 个循环中的最佳:每个循环 62.7 毫秒
我的猜测是聚合和拆分比 pivot_table() 或 pd.get_dummies() 更快。
我写了一个网络抓取工具来从 table 的产品中提取信息并构建一个数据框。数据 table 有一个描述列,其中包含描述产品的逗号分隔的属性字符串。我想在数据框中为每个唯一属性创建一个列,并用该属性的子字符串填充该列中的行。下面的示例 df。
PRODUCTS DATE DESCRIPTION
Product A 2016-9-12 Steel, Red, High Hardness
Product B 2016-9-11 Blue, Lightweight, Steel
Product C 2016-9-12 Red
我想第一步是将描述拆分成一个列表。
In: df2 = df['DESCRIPTION'].str.split(',')
Out:
DESCRIPTION
['Steel', 'Red', 'High Hardness']
['Blue', 'Lightweight', 'Steel']
['Red']
我想要的输出类似于下面的 table。列名不是特别重要。
PRODUCTS DATE STEEL_COL RED_COL HIGH HARDNESS_COL BLUE COL LIGHTWEIGHT_COL
Product A 2016-9-12 Steel Red High Hardness
Product B 2016-9-11 Steel Blue Lightweight
Product C 2016-9-12 Red
我相信可以使用 Pivot 设置列,但我不确定在建立列后填充列的最 Pythonic 方式。感谢任何帮助。
更新
非常感谢您的回答。我选择@MaxU 的响应是正确的,因为它看起来稍微灵活一些,但@piRSquared 的结果非常相似,甚至可以被认为是更 Pythonic 的方法。我测试了两个版本,都做了我需要的。谢谢!
如果产品具有该功能,在功能列中放置 'X' 怎么样?
下面创建了一个独特的特征列表('Steel'、'Red' 等),然后为原始 df 中的每个特征创建一列。然后我们遍历每一行,对于每个产品特征,我们在单元格中放置一个 'X'。
ml = []
a = [ml.append(item) for l in df.DESCRIPTION for item in l]
unique_list_of_attributes = list(set(ml)) # unique features list
# place empty columns in original df for each feature
df = pd.concat([df,pd.DataFrame(columns=unique_list_of_attributes)]).fillna(value='')
# add 'X' in column if product has feature
for row in df.iterrows():
for attribute in row[1]['DESCRIPTION']:
df.loc[row[0],attribute] = 'X'
更新了示例输出:
PRODUCTS DATE DESCRIPTION Blue HighHardness \
0 Product A 2016-9-12 [Steel, Red, HighHardness] X
1 Product B 2016-9-11 [Blue, Lightweight, Steel] X
2 Product C 2016-9-12 [Red]
Lightweight Red Steel
0 X X
1 X X
2 X
你可以建立一个稀疏矩阵:
In [27]: df
Out[27]:
PRODUCTS DATE DESCRIPTION
0 Product A 2016-9-12 Steel, Red, High Hardness
1 Product B 2016-9-11 Blue, Lightweight, Steel
2 Product C 2016-9-12 Red
In [28]: (df.set_index(['PRODUCTS','DATE'])
....: .DESCRIPTION.str.split(',\s*', expand=True)
....: .stack()
....: .reset_index()
....: .pivot_table(index=['PRODUCTS','DATE'], columns=0, fill_value=0, aggfunc='size')
....: )
Out[28]:
0 Blue High Hardness Lightweight Red Steel
PRODUCTS DATE
Product A 2016-9-12 0 1 0 1 1
Product B 2016-9-11 1 0 1 0 1
Product C 2016-9-12 0 0 0 1 0
In [29]: (df.set_index(['PRODUCTS','DATE'])
....: .DESCRIPTION.str.split(',\s*', expand=True)
....: .stack()
....: .reset_index()
....: .pivot_table(index=['PRODUCTS','DATE'], columns=0, fill_value='', aggfunc='size')
....: )
Out[29]:
0 Blue High Hardness Lightweight Red Steel
PRODUCTS DATE
Product A 2016-9-12 1 1 1
Product B 2016-9-11 1 1 1
Product C 2016-9-12 1
cols = ['PRODUCTS', 'DATE']
pd.get_dummies(
df.set_index(cols).DESCRIPTION \
.str.split(',\s*', expand=True).stack()
).groupby(level=cols).sum().astype(int)
@piRSquared 和@MaxU 发布的答案非常有效。
但是, 仅当数据没有任何 NaN
值时。我使用的数据非常稀少。它有大约 100 万行,在应用上述方法后减少到只有大约 100 行,因为它删除了任何列中带有 NaN
的所有行。我花了一天多的时间来找出修复方法。分享稍微修改过的代码,以节省其他人的时间。
假设你有 df
如上所述的 DataFrame,
首先将所有出现的
NaN
替换为任何其他列中不期望的内容,因为稍后您必须将其替换回NaN
。cols = ['PRODUCTS', 'DATE'] col = "DESCRIPTION" df.loc[:, cols] = df.loc[:, cols].fillna("SOME_UNIQ_NAN_REPLACEMENT")
这是必需的,因为 groupby 会删除所有具有 NaN 值的行。 :/
然后我们 运行 对其他答案中的建议稍作修改
stack(dropna=False)
。默认情况下,dropna=True
。df = pd.get_dummies(df.set_index(index_columns[col]\ .str.split(",\s*", expand=True).stack(dropna=False), prefix=col)\ .groupby(index_columns, sort=False).sum().astype(int).reset_index()
然后将
NaN
放回df
以不更改其他列的数据。df.replace("SOME_UNIQ_NAN_REPLACEMENT", np.nan, inplace=True)
希望这可以为某人节省数小时的挫败感。
这是我从我已经在处理的问题扩展而来的解决方案。
def group_agg_pivot_df(df, group_cols, agg_func='count', agg_col=None):
if agg_col is None:
agg_col = group_cols[0]
grouped = df.groupby(group_cols).agg({agg_col: agg_func}) \
.unstack().fillna(0)
# drop aggregation column name from hierarchical column names
grouped.columns = grouped.columns.droplevel()
# promote index to column (the first element of group_cols)
pivot_df = grouped.reset_index()
pivot_df.columns = [s.replace(' ', '_').lower() for s in pivot_df.columns]
return pivot_df
def split_stack_df(df, id_cols, split_col, new_col_name):
# id_cols are the columns we want to pair with the values
# from the split column
stacked = df.set_index(id_cols)[split_col].str.split(',', expand=True) \
.stack().reset_index(level=id_cols)
stacked.columns = id_cols + [new_col_name]
return stacked
stacked = split_stack_df(df, ['PRODUCTS', 'DATE'], 'DESCRIPTION', 'desc')
final_df = group_agg_pivot_df(stacked, ['PRODUCTS', 'DATE', 'desc'])
我还在具有 11592 行的 pandas 数据框和包含具有 2681 个唯一值的列表的列上对 @MaxU、@piRSquared 和我的解决方案进行了基准测试。显然,测试数据框中的列名不同,但我将它们与问题中的列名保持一致。
以下是每种方法的基准
In [277]: %timeit pd.get_dummies(df.set_index(['PRODUCTS', 'DATE']) \
...: .DESCRIPTION.str.split(',', expand=True) \
...: .stack()) \
...: .groupby(['PRODUCTS', 'DATE']).sum()
...:
1 个循环,3 个循环中的最佳:每个循环 1.14 秒
In [278]: %timeit df.set_index(['PRODUCTS', 'DATE']) \
...: .DESCRIPTION.str.split(',', expand=True) \
...: .stack() \
...: .reset_index() \
...: .pivot_table(index=['PRODUCTS', 'DATE'], columns=0, fill_value=0, aggfunc='size')
1 个循环,3 个循环中的最佳:每个循环 612 毫秒
In [286]: %timeit stacked = split_stack_df(df, ['PRODUCTS', 'DATE'], 'DESCRIPTION', 'desc'); \
...: final_df = group_agg_pivot_df(stacked, ['PRODUCTS', 'DATE', 'desc'])
1 个循环,3 个循环中的最佳:每个循环 62.7 毫秒
我的猜测是聚合和拆分比 pivot_table() 或 pd.get_dummies() 更快。