【问题标题】:flattening nested Json in pandas data frame在熊猫数据框中展平嵌套的 Json
【发布时间】: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"

你能告诉我如何展平位置和标签(那些有嵌套数据的)。

谢谢, 泽普

【问题讨论】:

    标签: python json pandas flatten json-normalize


    【解决方案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)
    

    positionspd.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']
    

    tagspd.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 代码不是我的,我在herehere 中看到了它,但无法确定原始来源。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-29
      • 2020-12-28
      • 2017-11-28
      • 2017-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-17
      相关资源
      最近更新 更多