使用 JSON 对象展开 Pandas DataFrame 列

Posted

技术标签:

【中文标题】使用 JSON 对象展开 Pandas DataFrame 列【英文标题】:Expand Pandas DataFrame Column with JSON Object 【发布时间】:2021-06-06 00:42:28 【问题描述】:

我正在寻找一种干净、快速的方法来扩展包含 json 对象(本质上是嵌套字典的字典)的 pandas 数据框列,因此我可以在 json 规范化形式的 json 列中为每个元素设置一个列;但是,这也需要保留所有原始数据框列。在某些情况下,这个 dict 可能有一个通用标识符,我可以用它来与原始数据框合并,但并非总是如此。例如:

import pandas as pd
import numpy as np
df = pd.DataFrame([
    
        'col1': 'a',
        'col2': 'col2.1': 'a1', 'col2.2': 'col2.2.1': 'a2.1', 'col2.2.2': 'a2.2',
        'col3': '3a'
    ,
    
        'col1': 'b',
        'col2': np.nan,
        'col3': '3b'
    ,
    
        'col1': 'c',
        'col2': 'col2.1': 'c1', 'col2.2': 'col2.2.1': np.nan, 'col2.2.2': 'c2.2',
        'col3': '3c'
    
])

这是一个示例数据框。如您所见, col2 在所有这些情况下都是一个字典,其中包含另一个嵌套字典,或者可能是一个空值,包含我希望能够访问的嵌套元素。 (对于空值,我希望能够在任何级别处理它们——数据框中的整个元素,或者只是行中的特定元素。)在这种情况下,它们没有可以链接到原始数据框的 ID .我的最终目标基本上是拥有这个:

final = pd.DataFrame([
    
        'col1': 'a',
        'col2.1': 'a1',
        'col2.2.col2.2.1': 'a2.1',
        'col2.2.col2.2.2': 'a2.2',
        'col3': '3a'
    ,
    
        'col1': 'b',
        'col2.1': np.nan,
        'col2.2.col2.2.1': np.nan,
        'col2.2.col2.2.2': np.nan,
        'col3': '3b'
    ,
    
        'col1': 'c',
        'col2.1': 'c1',
        'col2.2.col2.2.1': np.nan,
        'col2.2.col2.2.2': 'c2.2',
        'col3': '3c'
    
])

在我的例子中,dict 最多可以有 50 个嵌套的键值对,我可能只需要访问其中的几个。此外,我还有大约 50 - 100 个其他数据列需要与这些新列一起保存(因此最终目标约为 100 - 150)。所以我想我可能会寻找两种方法——为字典中的每个值获取一列,或者为少数几个获取一列。前一个选项我还没有找到一个很好的解决方法;我查看了一些先前的答案,但发现它们相当混乱,并且大多数都抛出了错误。当列内嵌套有字典时,这似乎特别困难。为了尝试第二种解决方案,我尝试了以下代码:

def get_val_from_dict(row, col, label):
    if pd.isnull(row[col]):
        return np.nan
    
    norm = pd.json_normalize(row[col])
    
    try:
        return norm[label]
    except:
        return np.nan


needed_cols = ['col2.1', 'col2.2.col2.2.1', 'col2.2.col2.2.2']


for label in needed_cols:
    df[label] = df.apply(get_val_from_dict, args = ('col2', label), axis = 1)

这似乎适用于这个例子,我对输出非常满意,但对于我的实际数据帧,它有更多的数据,这似乎有点慢 - 而且,我想,这不是一个很好的或可扩展的解决方案。谁能提供一种替代这种缓慢方法来解决我遇到的问题的方法?

(另外,对于我在这里命名中的大量嵌套表示歉意。如果有帮助,我将在下面添加几张数据帧的图像——原始的,然后是目标的,然后是当前的输出。)

【问题讨论】:

您的实际数据有多大 现在我有 1000 行数据,每行大约有 100 列,然后我要扩展的列中有大约 50 个嵌套的键/值对。我希望在接下来的一年左右,数据可以扩展到具有相同列数的 10 万行,因此我希望届时可以准备好一个可扩展的流程。 嵌套数据的深度和任意性。一开始就知道嵌套的树结构,允许编写非递归的flatten函数吗? 嵌套的树结构从一开始就为人所知——但我想让它尽可能通用(因为有时我正在使用可能具有 2 个或更多这些不同 json 列的数据帧)。 【参考方案1】:

不要在有字典的列上使用applypd.json_normalize,而是将整个数据框转换为字典并在其上使用pd.json_normalize,最后选择您希望保留的字段。这是可行的,因为虽然任何给定行的单个列可能为空,但整行不会。

示例:

# note that this method also prefixes an extra `col2.` 
# at the start of the names of the denested data, 
# which is not present in the example output
# the column renaming conforms to your desired name.
import re
final_cols = ['col1', 'col2.col2.1', 'col2.col2.2.col2.2.1', 'col2.col2.2.col2.2.2', 'col3']
out = pd.json_normalize(df.to_dict(orient='records'))[final_cols]
out.rename(columns=lambda x: re.sub(r'^col2\.', '', x), inplace=True)
out
# out:
  col1 col2.1 col2.2.col2.2.1 col2.2.col2.2.2 col3
0    a     a1            a2.1            a2.2   3a
1    b    NaN             NaN             NaN   3b
2    c     c1             NaN            c2.2   3c

但是对于我的实际数据帧,它有更多的数据,这很慢

现在我有 1000 行数据,每行大约有 100 列,然后我要扩展的列中有大约 50 个嵌套的键/值对。我希望在接下来的一年左右,数据可以扩展到具有相同列数的 10 万行,因此我希望届时可以准备好一个可扩展的流程

pd.json_normalize 应该比您的尝试更快,但它并不比在纯 python 中进行展平更快,因此如果您编写自定义transform 函数并构造如下数据框,您可能会获得更高的性能。

out = pd.DataFrame(transform(x) for x in df.to_dict(orient='records'))

【讨论】:

以上是关于使用 JSON 对象展开 Pandas DataFrame 列的主要内容,如果未能解决你的问题,请参考以下文章

将包含 JSON 对象的数据框展开为更大的数据框

Pandas 分析错误 AttributeError:“DataFrame”对象没有属性“profile_report”

认识pandas

将 pandas 数据帧转换为 json 对象 - pandas

在 Pandas 中为 DataFrame 中的每一行返回多行

如何访问 Pandas DataFrame 中嵌入的 json 对象?