使用 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】:
不要在有字典的列上使用apply
或pd.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 列的主要内容,如果未能解决你的问题,请参考以下文章
Pandas 分析错误 AttributeError:“DataFrame”对象没有属性“profile_report”
将 pandas 数据帧转换为 json 对象 - pandas