使用 Pandas 将一列字典拆分/分解为单独的列
- 2024-11-21 08:33:00
- admin 原创
- 6
问题描述:
我将数据保存在postgreSQL
数据库中。我使用 Python2.7 查询此数据并将其转换为 Pandas DataFrame。但是,此数据框的最后一列里面有一个值字典。DataFramedf
如下所示:
Station ID Pollutants
8809 {"a": "46", "b": "3", "c": "12"}
8810 {"a": "36", "b": "5", "c": "8"}
8811 {"b": "2", "c": "7"}
8812 {"c": "11"}
8813 {"a": "82", "c": "15"}
我需要将此列拆分为单独的列,以便 DataFrame`df2 看起来像这样:
Station ID a b c
8809 46 3 12
8810 36 5 8
8811 NaN 2 7
8812 NaN NaN 11
8813 82 NaN 15
我遇到的主要问题是列表的长度不一样。但所有列表都只包含相同的 3 个值: 'a'、'b' 和 'c'。并且它们总是以相同的顺序出现('a' 第一个,'b' 第二个,'c' 第三个)。
以下代码曾经可以工作并且返回我想要的内容(df2)。
objs = [df, pandas.DataFrame(df['Pollutant Levels'].tolist()).iloc[:, :3]]
df2 = pandas.concat(objs, axis=1).drop('Pollutant Levels', axis=1)
print(df2)
上周我还在运行这段代码,它运行良好。但现在我的代码坏了,我从第 [4] 行收到此错误:
IndexError: out-of-bounds on slice (end)
我没有对代码进行任何更改,但现在却收到错误。我觉得这是因为我的方法不够稳健或不恰当。
如果您能就如何将此列列表拆分为单独的列提出任何建议或指导,我们将不胜感激!
编辑:我认为.tolist()
和 .apply 方法不适用于我的代码,因为它是一个 Unicode 字符串,即:
#My data format
u{'a': '1', 'b': '2', 'c': '3'}
#and not
{u'a': '1', u'b': '2', u'c': '3'}
数据postgreSQL
以这种格式从数据库导入。对这个问题有什么帮助或想法吗?有没有办法转换 Unicode?
解决方案 1:
我知道这个问题已经很老了,但我还是来这里寻找答案。实际上,现在有一种更好(更快)的方法来做到这一点json_normalize
:
import pandas as pd
df2 = pd.json_normalize(df['Pollutant Levels'])
这避免了昂贵的应用功能......
解决方案 2:
要将字符串转换为实际的字典,您可以执行。之后,可以使用以下解决方案将字典转换为不同的列。df['Pollutant Levels'].map(ast.literal_eval)
使用一个小例子,你可以使用.apply(pd.Series)
:
In [2]: df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]})
In [3]: df
Out[3]:
a b
0 1 {u'c': 1}
1 2 {u'd': 3}
2 3 {u'c': 5, u'd': 6}
In [4]: df['b'].apply(pd.Series)
Out[4]:
c d
0 1.0 NaN
1 NaN 3.0
2 5.0 6.0
要将其与数据框的其余部分结合起来,您可以concat
使用上述结果的其他列:
In [7]: pd.concat([df.drop(['b'], axis=1), df['b'].apply(pd.Series)], axis=1)
Out[7]:
a c d
0 1 1.0 NaN
1 2 NaN 3.0
2 3 5.0 6.0
使用您的代码,如果我省略以下iloc
部分,这也会有效:
In [15]: pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1)
Out[15]:
a c d
0 1 1.0 NaN
1 2 NaN 3.0
2 3 5.0 6.0
解决方案 3:
dicts
根据Shijith在此答案中执行的时间分析,对平坦的单级列进行规范化的最快方法:df.join(pd.DataFrame(df.pop('Pollutants').values.tolist()))
它不会解决下面解决的带有
list
或 列的其他问题dicts
,例如带有NaN
或 嵌套 的行dicts
。
pd.json_normalize(df.Pollutants)
明显快于df.Pollutants.apply(pd.Series)
参见
%%timeit
下文。对于 1M 行,.json_normalize
比 快 47 倍.apply
。
无论是从文件读取数据,还是从数据库或 API 返回的对象读取数据,可能不清楚该
dict
列是否具有dict
或str
类型。如果列中的字典是类型,则必须使用
str
`dict`ast.literal_eval
或将它们转换回json.loads(…)
类型。
用于
pd.json_normalize
转换dicts
,以keys
作为标题和values
作为行。还有附加参数(例如
record_path
&meta
)用于处理嵌套dicts
。
用于
pandas.DataFrame.join
将原始 DataFramedf
与使用创建的列合并pd.json_normalize
如果索引不是整数(如示例中所示),则首先使用
df.reset_index()
它获取整数索引,然后再进行规范化和连接。pandas.DataFrame.pop
用于从现有数据框中删除指定列。这样就无需稍后使用 删除该列pandas.DataFrame.drop
。
需要注意的是,如果列中
NaN
有dict
df.Pollutants = df.Pollutants.fillna({i: {} for i in df.index})
如果
'Pollutants'
列是字符串,则使用'{}'
。另请参阅如何使用 NaN 对列进行 json_normalize。
import pandas as pd
from ast import literal_eval
import numpy as np
data = {'Station ID': [8809, 8810, 8811, 8812, 8813, 8814],
'Pollutants': ['{"a": "46", "b": "3", "c": "12"}', '{"a": "36", "b": "5", "c": "8"}', '{"b": "2", "c": "7"}', '{"c": "11"}', '{"a": "82", "c": "15"}', np.nan]}
df = pd.DataFrame(data)
# display(df)
Station ID Pollutants
0 8809 {"a": "46", "b": "3", "c": "12"}
1 8810 {"a": "36", "b": "5", "c": "8"}
2 8811 {"b": "2", "c": "7"}
3 8812 {"c": "11"}
4 8813 {"a": "82", "c": "15"}
5 8814 NaN
# check the type of the first value in Pollutants
>>> print(type(df.iloc[0, 1]))
<class 'str'>
# replace NaN with '{}' if the column is strings, otherwise replace with {}
df.Pollutants = df.Pollutants.fillna('{}') # if the NaN is in a column of strings
# df.Pollutants = df.Pollutants.fillna({i: {} for i in df.index}) # if the column is not strings
# Convert the column of stringified dicts to dicts
# skip this line, if the column contains dicts
df.Pollutants = df.Pollutants.apply(literal_eval)
# reset the index if the index is not unique integers from 0 to n-1
# df.reset_index(inplace=True) # uncomment if needed
# remove and normalize the column of dictionaries, and join the result to df
df = df.join(pd.json_normalize(df.pop('Pollutants')))
# display(df)
Station ID a b c
0 8809 46 3 12
1 8810 36 5 8
2 8811 NaN 2 7
3 8812 NaN NaN 11
4 8813 82 NaN 15
5 8814 NaN NaN NaN
%%timeit
# dataframe with 1M rows
dfb = pd.concat([df]*20000).reset_index(drop=True)
%%timeit
dfb.join(pd.json_normalize(dfb.Pollutants))
[out]:
46.9 ms ± 201 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%%timeit
pd.concat([dfb.drop(columns=['Pollutants']), dfb.Pollutants.apply(pd.Series)], axis=1)
[out]:
7.75 s ± 52.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
解决方案 4:
试试这个: 从 SQL 返回的数据必须转换成 Dict。"Pollutant Levels"
或者 现在
可以 Pollutants'
StationID Pollutants
0 8809 {"a":"46","b":"3","c":"12"}
1 8810 {"a":"36","b":"5","c":"8"}
2 8811 {"b":"2","c":"7"}
3 8812 {"c":"11"}
4 8813 {"a":"82","c":"15"}
df2["Pollutants"] = df2["Pollutants"].apply(lambda x : dict(eval(x)) )
df3 = df2["Pollutants"].apply(pd.Series )
a b c
0 46 3 12
1 36 5 8
2 NaN 2 7
3 NaN NaN 11
4 82 NaN 15
result = pd.concat([df, df3], axis=1).drop('Pollutants', axis=1)
result
StationID a b c
0 8809 46 3 12
1 8810 36 5 8
2 8811 NaN 2 7
3 8812 NaN NaN 11
4 8813 82 NaN 15
解决方案 5:
如何使用 Pandas 将一列字典拆分成单独的列?
pd.DataFrame(df['val'].tolist())
是扩展字典列的规范方法
这是使用彩色图表的证明。
供参考的基准代码。
请注意,我只对爆炸进行计时,因为这是回答这个问题最有趣的部分 - 结果构造的其他方面(例如是否使用pop
或drop
)与讨论无关,可以忽略(但应该注意,使用pop
避免了后续drop
调用,因此最终解决方案的性能更高,但我们仍然列出列并将其传递给任pd.DataFrame
一方式)。
此外,pop
还会破坏性地改变输入的 DataFrame,使其更难在基准测试代码中运行,因为基准测试代码假设输入在测试运行期间不会改变。
对其他解决方案的批评
df['val'].apply(pd.Series)
对于较大的 N,速度非常慢,因为 pandas 会为每一行构建 Series 对象,然后继续从它们构建 DataFrame。对于更大的 N,性能会下降到几分钟或几小时的量级。pd.json_normalize(df['val']))
速度较慢的原因很简单,因为json_normalize
它需要处理更复杂的输入数据 - 尤其是具有多个记录路径和元数据的深度嵌套 JSON。我们有一个简单的平面字典就pd.DataFrame
足够了,所以如果你的字典是平面的,就使用它。一些答案建议
df.pop('val').values.tolist()
或df.pop('val').to_numpy().tolist()
。我认为列出系列还是 numpy 数组并没有太大区别。直接列出系列可以减少一个操作,而且实际上并不慢,所以我建议避免在中间步骤中生成 numpy 数组。
解决方案 6:
注意:对于深度=1(一级)的字典
>>> df
Station ID Pollutants
0 8809 {"a": "46", "b": "3", "c": "12"}
1 8810 {"a": "36", "b": "5", "c": "8"}
2 8811 {"b": "2", "c": "7"}
3 8812 {"c": "11"}
4 8813 {"a": "82", "c": "15"}
1000 万行大型数据集的速度比较
>>> df = pd.concat([df]*2000000).reset_index(drop=True)
>>> print(df.shape)
(10000000, 2)
def apply_drop(df):
return df.join(df['Pollutants'].apply(pd.Series)).drop('Pollutants', axis=1)
def json_normalise_drop(df):
return df.join(pd.json_normalize(df.Pollutants)).drop('Pollutants', axis=1)
def tolist_drop(df):
return df.join(pd.DataFrame(df['Pollutants'].tolist())).drop('Pollutants', axis=1)
def vlues_tolist_drop(df):
return df.join(pd.DataFrame(df['Pollutants'].values.tolist())).drop('Pollutants', axis=1)
def pop_tolist(df):
return df.join(pd.DataFrame(df.pop('Pollutants').tolist()))
def pop_values_tolist(df):
return df.join(pd.DataFrame(df.pop('Pollutants').values.tolist()))
>>> %timeit apply_drop(df.copy())
1 loop, best of 3: 53min 20s per loop
>>> %timeit json_normalise_drop(df.copy())
1 loop, best of 3: 54.9 s per loop
>>> %timeit tolist_drop(df.copy())
1 loop, best of 3: 6.62 s per loop
>>> %timeit vlues_tolist_drop(df.copy())
1 loop, best of 3: 6.63 s per loop
>>> %timeit pop_tolist(df.copy())
1 loop, best of 3: 5.99 s per loop
>>> %timeit pop_values_tolist(df.copy())
1 loop, best of 3: 5.94 s per loop
+---------------------+-----------+
| apply_drop | 53min 20s |
| json_normalise_drop | 54.9 s |
| tolist_drop | 6.62 s |
| vlues_tolist_drop | 6.63 s |
| pop_tolist | 5.99 s |
| pop_values_tolist | 5.94 s |
+---------------------+-----------+
df.join(pd.DataFrame(df.pop('Pollutants').values.tolist()))
是最快的
解决方案 7:
我强烈推荐提取“污染物”列的方法:
df_pollutants = pd.DataFrame(df['Pollutants'].values.tolist(), index=df.index)
它比
df_pollutants = df['Pollutants'].apply(pd.Series)
当 df 的尺寸很大时。
解决方案 8:
Merlin 的答案更好,也非常简单,但我们不需要 lambda 函数。可以通过以下两种方式之一安全地忽略字典的求值,如下所示:
方法一:两步
# step 1: convert the `Pollutants` column to Pandas dataframe series
df_pol_ps = data_df['Pollutants'].apply(pd.Series)
df_pol_ps:
a b c
0 46 3 12
1 36 5 8
2 NaN 2 7
3 NaN NaN 11
4 82 NaN 15
# step 2: concat columns `a, b, c` and drop/remove the `Pollutants`
df_final = pd.concat([df, df_pol_ps], axis = 1).drop('Pollutants', axis = 1)
df_final:
StationID a b c
0 8809 46 3 12
1 8810 36 5 8
2 8811 NaN 2 7
3 8812 NaN NaN 11
4 8813 82 NaN 15
方法二:以上两步可以合并成一步:
df_final = pd.concat([df, df['Pollutants'].apply(pd.Series)], axis = 1).drop('Pollutants', axis = 1)
df_final:
StationID a b c
0 8809 46 3 12
1 8810 36 5 8
2 8811 NaN 2 7
3 8812 NaN NaN 11
4 8813 82 NaN 15
解决方案 9:
您可以使用join
with pop
+ 。性能与with +tolist
相当,但有些人可能会发现这种语法更简洁:concat
`drop`tolist
res = df.join(pd.DataFrame(df.pop('b').tolist()))
与其他方法进行基准测试:
df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]})
def joris1(df):
return pd.concat([df.drop('b', axis=1), df['b'].apply(pd.Series)], axis=1)
def joris2(df):
return pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1)
def jpp(df):
return df.join(pd.DataFrame(df.pop('b').tolist()))
df = pd.concat([df]*1000, ignore_index=True)
%timeit joris1(df.copy()) # 1.33 s per loop
%timeit joris2(df.copy()) # 7.42 ms per loop
%timeit jpp(df.copy()) # 7.68 ms per loop
解决方案 10:
一行解决方案如下:
>>> df = pd.concat([df['Station ID'], df['Pollutants'].apply(pd.Series)], axis=1)
>>> print(df)
Station ID a b c
0 8809 46 3 12
1 8810 36 5 8
2 8811 NaN 2 7
3 8812 NaN NaN 11
4 8813 82 NaN 15
解决方案 11:
df = pd.concat([df['a'], df.b.apply(pd.Series)], axis=1)
解决方案 12:
我已经在一种方法中连接了这些步骤,您只需传递数据框和包含要扩展的字典的列:
def expand_dataframe(dw: pd.DataFrame, column_to_expand: str) -> pd.DataFrame:
"""
dw: DataFrame with some column which contain a dict to expand
in columns
column_to_expand: String with column name of dw
"""
import pandas as pd
def convert_to_dict(sequence: str) -> Dict:
import json
s = sequence
json_acceptable_string = s.replace("'", "\"")
d = json.loads(json_acceptable_string)
return d
expanded_dataframe = pd.concat([dw.drop([column_to_expand], axis=1),
dw[column_to_expand]
.apply(convert_to_dict)
.apply(pd.Series)],
axis=1)
return expanded_dataframe
解决方案 13:
my_df = pd.DataFrame.from_dict(my_dict, orient='index', columns=['my_col'])
.. 会正确地解析字典(将每个字典的键放入单独的 df 列,将键值放入 df 行),因此字典首先不会被压缩到单个列中。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件