在熊猫数据框中展平嵌套的 Json
Posted
技术标签:
【中文标题】在熊猫数据框中展平嵌套的 Json【英文标题】:flattening nested Json in pandas data frame 【发布时间】:2019-03-18 15:09:29 【问题描述】:我正在尝试将 json 文件加载到 pandas 数据框。 我发现有一些嵌套的json。 以下是示例 json:
'events': ['id': 142896214,
'playerId': 37831,
'teamId': 3157,
'matchId': 2214569,
'matchPeriod': '1H',
'eventSec': 0.8935539999999946,
'eventId': 8,
'eventName': 'Pass',
'subEventId': 85,
'subEventName': 'Simple pass',
'positions': ['x': 51, 'y': 49, 'x': 40, 'y': 53],
'tags': ['id': 1801, 'tag': 'label': 'accurate']
我使用以下代码将 json 加载到数据帧中:
with open('EVENTS.json') as f:
jsonstr = json.load(f)
df = pd.io.json.json_normalize(jsonstr['events'])
下面是df.head()的输出
但是我发现了两个嵌套列,比如位置和标签。
我尝试使用以下代码将其展平:
Position_data = json_normalize(data =jsonstr['events'], record_path='positions', meta = ['x','y','x','y'] )
它向我显示如下错误:
KeyError: "Try running with errors='ignore' as key 'x' is not always present"
你能告诉我如何展平位置和标签(那些有嵌套数据的)。
谢谢, 泽普
【问题讨论】:
【参考方案1】: 如已接受的答案中所述,flatten_json
可能是一个不错的选择,具体取决于 JSON 的结构以及应如何展平结构。
在这种情况下,OP 希望 1 个事件的所有值都位于一行,因此 flatten_json
有效
如果想要的结果是positions
中的每个位置都有单独的行,那么pandas.json_normalize
是更好的选择。
flatten_json
的一个问题是,如果positions
有很多,那么events
中每个事件的列数可能会非常大。
如果使用flatten_json
,请参阅How to flatten a nested JSON recursively, with flatten_json? 以获得更详尽的说明。
为events
中的每个dict
创建1 行
data = 'events': ['id': 142896214,
'playerId': 37831,
'teamId': 3157,
'matchId': 2214569,
'matchPeriod': '1H',
'eventSec': 0.8935539999999946,
'eventId': 8,
'eventName': 'Pass',
'subEventId': 85,
'subEventName': 'Simple pass',
'positions': ['x': 51, 'y': 49, 'x': 40, 'y': 53],
'tags': ['id': 1801, 'tag': 'label': 'accurate']]
创建数据框
df = pd.DataFrame.from_dict(data)
df = df['events'].apply(pd.Series)
将positions
与pd.Series
展平
df_p = df['positions'].apply(pd.Series)
df_p_0 = df_p[0].apply(pd.Series)
df_p_1 = df_p[1].apply(pd.Series)
重命名positions[0]
& positions[1]
:
df_p_0.columns = ['pos_0_x', 'pos_0_y']
df_p_1.columns = ['pos_1_x', 'pos_1_y']
将tags
与pd.Series
展平:
df_t = df.tags.apply(pd.Series)
df_t = df_t[0].apply(pd.Series)
df_t_t = df_t.tag.apply(pd.Series)
重命名id
& label
:
df_t = df_t.rename(columns='id': 'tags_id')
df_t_t.columns = ['tags_tag_label']
将它们全部与pd.concat
结合起来:
df_new = pd.concat([df, df_p_0, df_p_1, df_t.tags_id, df_t_t], axis=1)
删除旧列:
df_new = df_new.drop(['positions', 'tags'], axis=1)
为positions
中的每个位置创建一个单独的行
# normalize events
df = pd.json_normalize(data, 'events')
# explode all columns with lists of dicts
df = df.apply(lambda x: x.explode()).reset_index(drop=True)
# list of columns with dicts
cols_to_normalize = ['positions', 'tags']
# if there are keys, which will become column names, overlap with excising column names
# add the current column name as a prefix
normalized = list()
for col in cols_to_normalize:
d = pd.json_normalize(df[col], sep='_')
d.columns = [f'col_v' for v in d.columns]
normalized.append(d.copy())
# combine df with the normalized columns
df = pd.concat([df] + normalized, axis=1).drop(columns=cols_to_normalize)
# display(df)
id playerId teamId matchId matchPeriod eventSec eventId eventName subEventId subEventName positions_x positions_y tags_id tags_tag_label
0 142896214 37831 3157 2214569 1H 0.893554 8 Pass 85 Simple pass 51 49 1801 accurate
1 142896214 37831 3157 2214569 1H 0.893554 8 Pass 85 Simple pass 40 53 1801 accurate
【讨论】:
亲爱的特伦顿,非常感谢您的建议。它奏效了。【参考方案2】:如果您正在寻找一种更通用的方法来从 json 展开多个层次结构,您可以使用 recursion
和列表推导来重塑您的数据。下面介绍了一种替代方法:
def flatten_json(nested_json, exclude=['']):
"""Flatten json object with nested keys into a single level.
Args:
nested_json: A nested json object.
exclude: Keys to exclude from output.
Returns:
The flattened json object if successful, None otherwise.
"""
out =
def flatten(x, name='', exclude=exclude):
if type(x) is dict:
for a in x:
if a not in exclude: flatten(x[a], name + a + '_')
elif type(x) is list:
i = 0
for a in x:
flatten(a, name + str(i) + '_')
i += 1
else:
out[name[:-1]] = x
flatten(nested_json)
return out
然后您可以应用到您的数据,独立于嵌套级别:
新样本数据
this_dict = 'events': [
'id': 142896214,
'playerId': 37831,
'teamId': 3157,
'matchId': 2214569,
'matchPeriod': '1H',
'eventSec': 0.8935539999999946,
'eventId': 8,
'eventName': 'Pass',
'subEventId': 85,
'subEventName': 'Simple pass',
'positions': ['x': 51, 'y': 49, 'x': 40, 'y': 53],
'tags': ['id': 1801, 'tag': 'label': 'accurate'],
'id': 142896214,
'playerId': 37831,
'teamId': 3157,
'matchId': 2214569,
'matchPeriod': '1H',
'eventSec': 0.8935539999999946,
'eventId': 8,
'eventName': 'Pass',
'subEventId': 85,
'subEventName': 'Simple pass',
'positions': ['x': 51, 'y': 49, 'x': 40, 'y': 53,'x': 51, 'y': 49],
'tags': ['id': 1801, 'tag': 'label': 'accurate']
]
用法
pd.DataFrame([flatten_json(x) for x in this_dict['events']])
Out[1]:
id playerId teamId matchId matchPeriod eventSec eventId \
0 142896214 37831 3157 2214569 1H 0.893554 8
1 142896214 37831 3157 2214569 1H 0.893554 8
eventName subEventId subEventName positions_0_x positions_0_y \
0 Pass 85 Simple pass 51 49
1 Pass 85 Simple pass 51 49
positions_1_x positions_1_y tags_0_id tags_0_tag_label positions_2_x \
0 40 53 1801 accurate NaN
1 40 53 1801 accurate 51.0
positions_2_y
0 NaN
1 49.0
请注意,这个flatten_json
代码不是我的,我在here 和here 中看到了它,但无法确定原始来源。
【讨论】:
以上是关于在熊猫数据框中展平嵌套的 Json的主要内容,如果未能解决你的问题,请参考以下文章