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

2024-11-21 08:33:00
admin
原创
6
摘要:问题描述:我将数据保存在postgreSQL数据库中。我使用 Python2.7 查询此数据并将其转换为 Pandas DataFrame。但是,此数据框的最后一列里面有一个值字典。DataFramedf如下所示:Station ID Pollutants 8809 {"...

问题描述:

我将数据保存在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

  1. pd.json_normalize(df.Pollutants)明显快于df.Pollutants.apply(pd.Series)

    • 参见%%timeit下文。对于 1M 行,.json_normalize比 快 47 倍.apply

  2. 无论是从文件读取数据,还是从数据库或 API 返回的对象读取数据,可能不清楚该dict列是否具有dictstr类型。

    • 如果列中的字典是类型,则必须使用str`dict`ast.literal_eval或将它们转换回json.loads(…)类型。

  3. 用于pd.json_normalize转换dicts,以keys作为标题和values作为行。

    • 还有附加参数(例如record_path& meta)用于处理嵌套dicts

  4. 用于pandas.DataFrame.join将原始 DataFramedf与使用创建的列合并pd.json_normalize

    • 如果索引不是整数(如示例中所示),则首先使用df.reset_index()它获取整数索引,然后再进行规范化和连接。

    • pandas.DataFrame.pop用于从现有数据框中删除指定列。这样就无需稍后使用 删除该列pandas.DataFrame.drop

  • 需要注意的是,如果列中NaNdict

    • 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())是扩展字典列的规范方法

这是使用彩色图表的证明。

在此处输入图片描述

供参考的基准代码。

请注意,我只对爆炸进行计时,因为这是回答这个问题最有趣的部分 - 结果构造的其他方面(例如是否使用popdrop)与讨论无关,可以忽略(但应该注意,使用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:

您可以使用joinwith 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 行),因此字典首先不会被压缩到单个列中。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用