使用pandas将一列字典拆分/分解为单独的列

ehxuflar  于 2021-09-08  发布在  Java
关注(0)|答案(13)|浏览(793)

我把数据保存在一个文件夹中 postgreSQL 数据库我正在使用python2.7查询这些数据,并将其转换为一个 Dataframe 。但是,此 Dataframe 的最后一列中有一个值字典。 Dataframe df 看起来像这样:

  1. Station ID Pollutants
  2. 8809 {"a": "46", "b": "3", "c": "12"}
  3. 8810 {"a": "36", "b": "5", "c": "8"}
  4. 8811 {"b": "2", "c": "7"}
  5. 8812 {"c": "11"}
  6. 8813 {"a": "82", "c": "15"}

我需要将此列拆分为单独的列,以便dataframe`df2如下所示:

  1. Station ID a b c
  2. 8809 46 3 12
  3. 8810 36 5 8
  4. 8811 NaN 2 7
  5. 8812 NaN NaN 11
  6. 8813 82 NaN 15

我遇到的主要问题是列表的长度不同。但所有列表最多只包含相同的3个值:“a”、“b”和“c”。它们总是以相同的顺序出现(“a'第一,'b'第二,'c'第三”)。
下面的代码用于工作并返回我想要的内容(df2)。

  1. objs = [df, pandas.DataFrame(df['Pollutant Levels'].tolist()).iloc[:, :3]]
  2. df2 = pandas.concat(objs, axis=1).drop('Pollutant Levels', axis=1)
  3. print(df2)

我上周刚刚运行了这段代码,它运行得很好。但是现在我的代码被破坏了,我从第[4]行得到了这个错误:

  1. IndexError: out-of-bounds on slice (end)

我没有对代码做任何更改,但现在得到了错误。我觉得这是因为我的方法不健全或不恰当。
如果您对如何将此列表列拆分为单独的列有任何建议或指导,我们将不胜感激!
编辑:我认为 .tolist() 和.apply方法不适用于我的代码,因为它是一个unicode字符串,即:

  1. # My data format
  2. u{'a': '1', 'b': '2', 'c': '3'}
  3. # and not
  4. {u'a': '1', u'b': '2', u'c': '3'}

数据是从数据库导入的 postgreSQL 此格式的数据库。在这个问题上有什么帮助或想法吗?有没有转换unicode的方法?

bksxznpy

bksxznpy1#

梅林的答案更好,也非常简单,但我们不需要lambda函数。字典的评估可以通过以下两种方式之一安全地忽略,如下所示:
方法1:两步

  1. # step 1: convert the `Pollutants` column to Pandas dataframe series
  2. df_pol_ps = data_df['Pollutants'].apply(pd.Series)
  3. df_pol_ps:
  4. a b c
  5. 0 46 3 12
  6. 1 36 5 8
  7. 2 NaN 2 7
  8. 3 NaN NaN 11
  9. 4 82 NaN 15
  10. # step 2: concat columns `a, b, c` and drop/remove the `Pollutants`
  11. df_final = pd.concat([df, df_pol_ps], axis = 1).drop('Pollutants', axis = 1)
  12. df_final:
  13. StationID a b c
  14. 0 8809 46 3 12
  15. 1 8810 36 5 8
  16. 2 8811 NaN 2 7
  17. 3 8812 NaN NaN 11
  18. 4 8813 82 NaN 15

方式2:以上两个步骤可以一次完成:

  1. df_final = pd.concat([df, df['Pollutants'].apply(pd.Series)], axis = 1).drop('Pollutants', axis = 1)
  2. df_final:
  3. StationID a b c
  4. 0 8809 46 3 12
  5. 1 8810 36 5 8
  6. 2 8811 NaN 2 7
  7. 3 8812 NaN NaN 11
  8. 4 8813 82 NaN 15
展开查看全部
ykejflvf

ykejflvf2#

你可以用 join 具有 pop + tolist . 性能可与 concat 具有 drop + tolist ,但有些人可能会发现这种语法更简洁:

  1. res = df.join(pd.DataFrame(df.pop('b').tolist()))

采用其他方法进行基准测试:

  1. df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]})
  2. def joris1(df):
  3. return pd.concat([df.drop('b', axis=1), df['b'].apply(pd.Series)], axis=1)
  4. def joris2(df):
  5. return pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1)
  6. def jpp(df):
  7. return df.join(pd.DataFrame(df.pop('b').tolist()))
  8. df = pd.concat([df]*1000, ignore_index=True)
  9. %timeit joris1(df.copy()) # 1.33 s per loop
  10. %timeit joris2(df.copy()) # 7.42 ms per loop
  11. %timeit jpp(df.copy()) # 7.68 ms per loop
展开查看全部
vsdwdz23

vsdwdz233#

  1. >>> df
  2. Station ID Pollutants
  3. 0 8809 {"a": "46", "b": "3", "c": "12"}
  4. 1 8810 {"a": "36", "b": "5", "c": "8"}
  5. 2 8811 {"b": "2", "c": "7"}
  6. 3 8812 {"c": "11"}
  7. 4 8813 {"a": "82", "c": "15"}

1000万行的大型数据集的速度比较

  1. >>> df = pd.concat([df]*100000).reset_index(drop=True)
  2. >>> df = pd.concat([df]*20).reset_index(drop=True)
  3. >>> print(df.shape)
  4. (10000000, 2)
  1. def apply_drop(df):
  2. return df.join(df['Pollutants'].apply(pd.Series)).drop('Pollutants', axis=1)
  3. def json_normalise_drop(df):
  4. return df.join(pd.json_normalize(df.Pollutants)).drop('Pollutants', axis=1)
  5. def tolist_drop(df):
  6. return df.join(pd.DataFrame(df['Pollutants'].tolist())).drop('Pollutants', axis=1)
  7. def vlues_tolist_drop(df):
  8. return df.join(pd.DataFrame(df['Pollutants'].values.tolist())).drop('Pollutants', axis=1)
  9. def pop_tolist(df):
  10. return df.join(pd.DataFrame(df.pop('Pollutants').tolist()))
  11. def pop_values_tolist(df):
  12. return df.join(pd.DataFrame(df.pop('Pollutants').values.tolist()))
  1. >>> %timeit apply_drop(df.copy())
  2. 1 loop, best of 3: 53min 20s per loop
  3. >>> %timeit json_normalise_drop(df.copy())
  4. 1 loop, best of 3: 54.9 s per loop
  5. >>> %timeit tolist_drop(df.copy())
  6. 1 loop, best of 3: 6.62 s per loop
  7. >>> %timeit vlues_tolist_drop(df.copy())
  8. 1 loop, best of 3: 6.63 s per loop
  9. >>> %timeit pop_tolist(df.copy())
  10. 1 loop, best of 3: 5.99 s per loop
  11. >>> %timeit pop_values_tolist(df.copy())
  12. 1 loop, best of 3: 5.94 s per loop
  1. +---------------------+-----------+
  2. | apply_drop | 53min 20s |
  3. | json_normalise_drop | 54.9 s |
  4. | tolist_drop | 6.62 s |
  5. | vlues_tolist_drop | 6.63 s |
  6. | pop_tolist | 5.99 s |
  7. | pop_values_tolist | 5.94 s |
  8. +---------------------+-----------+
  9. ``` `df.join(pd.DataFrame(df.pop('Pollutants').values.tolist()))` 是最快的
展开查看全部
pieyvz9o

pieyvz9o4#

如何使用pandas将一列词典拆分为单独的列?

dataframe(df['val'].tolist())是用于分解字典列的规范方法

这是你用彩色图表做的证明。

基准测试代码供参考。
请注意,我只是为爆炸计时,因为这是回答这个问题最有趣的部分——结果构造的其他方面(例如是否使用 popdrop )与讨论无关,可以忽略(但应注意,使用 pop 避免后续行动 drop 调用,因此最终的解决方案的性能会更好一些,但我们仍在列出该列并将其传递给 pd.DataFrame 无论如何)。
另外,, pop 破坏性地改变输入 Dataframe ,使其更难在基准测试代码中运行,而基准测试代码假定输入在测试运行期间没有改变。

对其他解决方案的批评 df['val'].apply(pd.Series) 对于大n来说非常慢,因为pandas为每一行构造系列对象,然后继续从它们构造 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数组。

展开查看全部
dpiehjr4

dpiehjr45#

单线解决方案如下:

  1. >>> df = pd.concat([df['Station ID'], df['Pollutants'].apply(pd.Series)], axis=1)
  2. >>> print(df)
  3. Station ID a b c
  4. 0 8809 46 3 12
  5. 1 8810 36 5 8
  6. 2 8811 NaN 2 7
  7. 3 8812 NaN NaN 11
  8. 4 8813 82 NaN 15
6tdlim6h

6tdlim6h6#

  1. df = pd.concat([df['a'], df.b.apply(pd.Series)], axis=1)
wfsdck30

wfsdck307#

my_df = pd.DataFrame.from_dict(my_dict, orient='index', columns=['my_col']) .. 将正确解析dict(将每个dict键放入单独的df列中,将键值放入df行中),这样dict就不会首先被压缩到单个列中。

arknldoa

arknldoa8#

我已将这些步骤连接到一个方法中,您只需传递dataframe和包含要展开的dict的列:

  1. def expand_dataframe(dw: pd.DataFrame, column_to_expand: str) -> pd.DataFrame:
  2. """
  3. dw: DataFrame with some column which contain a dict to expand
  4. in columns
  5. column_to_expand: String with column name of dw
  6. """
  7. import pandas as pd
  8. def convert_to_dict(sequence: str) -> Dict:
  9. import json
  10. s = sequence
  11. json_acceptable_string = s.replace("'", "\"")
  12. d = json.loads(json_acceptable_string)
  13. return d
  14. expanded_dataframe = pd.concat([dw.drop([column_to_expand], axis=1),
  15. dw[column_to_expand]
  16. .apply(convert_to_dict)
  17. .apply(pd.Series)],
  18. axis=1)
  19. return expanded_dataframe
展开查看全部
eoxn13cs

eoxn13cs9#

要将字符串转换为实际的dict,可以执行以下操作 df['Pollutant Levels'].map(eval) . 然后,可以使用下面的解决方案将dict转换为不同的列。
使用一个小示例,您可以使用 .apply(pd.Series) :

  1. In [2]: df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]})
  2. In [3]: df
  3. Out[3]:
  4. a b
  5. 0 1 {u'c': 1}
  6. 1 2 {u'd': 3}
  7. 2 3 {u'c': 5, u'd': 6}
  8. In [4]: df['b'].apply(pd.Series)
  9. Out[4]:
  10. c d
  11. 0 1.0 NaN
  12. 1 NaN 3.0
  13. 2 5.0 6.0

要将其与 Dataframe 的其余部分结合起来,您可以 concat 具有上述结果的其他列:

  1. In [7]: pd.concat([df.drop(['b'], axis=1), df['b'].apply(pd.Series)], axis=1)
  2. Out[7]:
  3. a c d
  4. 0 1 1.0 NaN
  5. 1 2 NaN 3.0
  6. 2 3 5.0 6.0

使用您的代码,如果我省略 iloc 第部分:

  1. In [15]: pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1)
  2. Out[15]:
  3. a c d
  4. 0 1 1.0 NaN
  5. 1 2 NaN 3.0
  6. 2 3 5.0 6.0
展开查看全部
czq61nw1

czq61nw110#

我知道这个问题很老了,但我来这里是为了寻找答案。实际上,现在有一种更好(更快)的方法使用 json_normalize :

  1. import pandas as pd
  2. df2 = pd.json_normalize(df['Pollutant Levels'])

这避免了昂贵的应用函数。。。

xmjla07d

xmjla07d11#

规范化一列平面、一个标高的最快方法 dicts ,根据shijith在本回答中进行的时间分析: df.join(pd.DataFrame(df.pop('Pollutants').values.tolist())) 它不会解决列的其他问题 listdicts 下面列出的,例如带有 NaN ,或嵌套 dicts . pd.json_normalize(df.Pollutants) 明显快于 df.Pollutants.apply(pd.Series)%%timeit 在下面对于100万行, .json_normalize 它的速度是它的47倍 .apply .
无论是从文件中读取数据,还是从数据库或api返回的对象中读取数据,都可能不清楚 dict 专栏有 dictstr 类型。
如果列中的词典是 str 类型,则必须将其转换回 dict 类型,使用 ast.literal_eval .
使用 pd.json_normalize 转换 dicts 具有 keys 作为标题和 values 行。
还有其他参数(例如。 record_path & meta )用于处理嵌套 dicts .
使用 pandas.DataFrame.join 要组合原始 Dataframe , df ,使用 pd.json_normalize 如果索引不是整数(如示例中所示),请首先使用 df.reset_index() 在执行规格化和联接之前,获取整数索引。
最后,使用 pandas.DataFrame.drop ,以删除不需要的列 dicts 请注意,如果该列有 NaN ,必须用空的
dict df.Pollutants = df.Pollutants.fillna({i: {} for i in df.index}) 如果 'Pollutants' 列是字符串,请使用 '{}' .
另请参见如何使用nans?规范化列?。

  1. import pandas as pd
  2. from ast import literal_eval
  3. import numpy as np
  4. data = {'Station ID': [8809, 8810, 8811, 8812, 8813, 8814],
  5. '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]}
  6. df = pd.DataFrame(data)
  7. # display(df)
  8. Station ID Pollutants
  9. 0 8809 {"a": "46", "b": "3", "c": "12"}
  10. 1 8810 {"a": "36", "b": "5", "c": "8"}
  11. 2 8811 {"b": "2", "c": "7"}
  12. 3 8812 {"c": "11"}
  13. 4 8813 {"a": "82", "c": "15"}
  14. 5 8814 NaN
  15. # replace NaN with '{}' if the column is strings, otherwise replace with {}
  16. # df.Pollutants = df.Pollutants.fillna('{}') # if the NaN is in a column of strings
  17. df.Pollutants = df.Pollutants.fillna({i: {} for i in df.index}) # if the column is not strings
  18. # Convert the column of stringified dicts to dicts
  19. # skip this line, if the column contains dicts
  20. df.Pollutants = df.Pollutants.apply(literal_eval)
  21. # reset the index if the index is not unique integers from 0 to n-1
  22. # df.reset_index(inplace=True) # uncomment if needed
  23. # normalize the column of dictionaries and join it to df
  24. df = df.join(pd.json_normalize(df.Pollutants))
  25. # drop Pollutants
  26. df.drop(columns=['Pollutants'], inplace=True)
  27. # display(df)
  28. Station ID a b c
  29. 0 8809 46 3 12
  30. 1 8810 36 5 8
  31. 2 8811 NaN 2 7
  32. 3 8812 NaN NaN 11
  33. 4 8813 82 NaN 15
  34. 5 8814 NaN NaN NaN

%%时间

  1. # dataframe with 1M rows
  2. dfb = pd.concat([df]*200000).reset_index(drop=True)
  3. %%timeit
  4. dfb.join(pd.json_normalize(dfb.Pollutants))
  5. [out]:
  6. 5.44 s ± 32.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
  7. %%timeit
  8. pd.concat([dfb.drop(columns=['Pollutants']), dfb.Pollutants.apply(pd.Series)], axis=1)
  9. [out]:
  10. 4min 17s ± 2.44 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
展开查看全部
ohfgkhjo

ohfgkhjo12#

试试这个:从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 )

  1. 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

展开查看全部
eivnm1vs

eivnm1vs13#

我强烈推荐提取“污染物”列的方法: df_pollutants = pd.DataFrame(df['Pollutants'].values.tolist(), index=df.index) 比以前快多了 df_pollutants = df['Pollutants'].apply(pd.Series) 当df的大小是巨大的。

相关问题