Python 用JSON对象展开DataFrame列

Python 用JSON对象展开DataFrame列,python,json,pandas,dataframe,Python,Json,Pandas,Dataframe,我正在寻找一种干净、快速的方法来扩展包含json对象(本质上是嵌套dict的dict)的pandas dataframe列,这样json列中的每个元素都可以有一个json规范化形式的列;但是,这也需要保留所有原始数据帧列。在某些情况下,这个dict可能有一个公共标识符,我可以用来与原始数据帧合并,但并不总是这样。例如: import pandas as pd import numpy as np df = pd.DataFrame([ { 'col1': 'a',

我正在寻找一种干净、快速的方法来扩展包含json对象(本质上是嵌套dict的dict)的pandas dataframe列,这样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都是一个dict,其中包含另一个嵌套dict,或者可以是空值,包含我希望能够访问的嵌套元素。(对于空值,我希望能够在任何级别处理它们——数据帧中的整个元素,或者只是行中的特定元素。)在这种情况下,它们没有可以链接到原始数据帧的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)。所以我想我可能会寻找两种方法——为dict中的每个值获取一列,或者为一些特定的值获取一列。前一种选择我还没有找到一个很好的解决办法;我看了一些以前的答案,但发现它们相当混乱,而且大多数都有错误。当列中嵌套有dict时,这似乎特别困难。为了尝试第二种解决方案,我尝试了以下代码:

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)
对于这个例子来说,这似乎是可行的,我对输出非常满意,但是对于我的实际数据帧,它包含了更多的数据,这似乎有点慢——我可以想象,这不是一个很好的或可扩展的解决方案。有没有人能提供一种替代方法来解决我面临的问题

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


不要在有字典的列上使用
apply
pd.json\u normalize
,而是将整个数据帧转换为字典并在其上使用
pd.json\u 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\u normalize
应该比您的尝试快,但它并不比在纯python中进行扁平化快,因此,如果您编写一个自定义的
transform
函数并按如下方式构造数据帧,您可能会获得更高的性能

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

您的实际数据有多大?现在我有1000行数据,每行大约有100列,然后我要展开的列中有大约50个嵌套的键/值对。我希望在未来一年左右的时间里,数据能够以相同的列数扩展到10万行,因此我希望有一个可扩展的过程准备就绪。嵌套数据的深度和任意性如何。嵌套的树结构在一开始就知道了,允许编写一个非递归的
flatten
函数吗?嵌套的树结构从一开始就知道了——但我希望尽可能地将其概括(因为有时我处理的数据帧可能有两个或更多不同的json列)。