如何将一些列作为json展平pandas数据帧?

sfa*_*tor 23 python json flatten dataframe pandas

我有一个df从数据库加载数据的数据框.大多数列都是json字符串,而有些列甚至是jsons列表.例如:

id     name     columnA                               columnB
1     John     {"dist": "600", "time": "0:12.10"}    [{"pos": "1st", "value": "500"},{"pos": "2nd", "value": "300"},{"pos": "3rd", "value": "200"}, {"pos": "total", "value": "1000"}]
2     Mike     {"dist": "600"}                       [{"pos": "1st", "value": "500"},{"pos": "2nd", "value": "300"},{"pos": "total", "value": "800"}]
...
Run Code Online (Sandbox Code Playgroud)

如您所见,并非所有行在列的json字符串中具有相同数量的元素.

我需要做的是保持正常的列像它一样,idname像这样平整json列:

id    name   columnA.dist   columnA.time   columnB.pos.1st   columnB.pos.2nd   columnB.pos.3rd     columnB.pos.total
1     John   600            0:12.10        500               300               200                 1000 
2     Mark   600            NaN            500               300               Nan                 800 
Run Code Online (Sandbox Code Playgroud)

我试过这样使用json_normalize:

from pandas.io.json import json_normalize
json_normalize(df)
Run Code Online (Sandbox Code Playgroud)

但似乎存在一些问题keyerror.这样做的正确方法是什么?

小智 26

最快的似乎是:

import pandas as pd
import json

json_struct = json.loads(df.to_json(orient="records"))    
df_flat = pd.io.json.json_normalize(json_struct) #use pd.io.json
Run Code Online (Sandbox Code Playgroud)

  • 这绝对是最简单的方法,也是对我有用的方法。唯一需要注意的是,您的嵌套对象将以长名称结束(data.level1.level2.level3 ...等) (2认同)
  • 得给我一些 `orient="records` 和 `pd.io.json.json_normalize` 。现在把这些东西粘到长期记忆脑细胞中 (2认同)

Nic*_*eli 24

这是一个使用json_normalize()自定义函数再次使用的解决方案,以便以json_normalize函数理解的正确格式获取数据.

import ast
from pandas.io.json import json_normalize

def only_dict(d):
    '''
    Convert json string representation of dictionary to a python dict
    '''
    return ast.literal_eval(d)

def list_of_dicts(ld):
    '''
    Create a mapping of the tuples formed after 
    converting json strings of list to a python list   
    '''
    return dict([(list(d.values())[1], list(d.values())[0]) for d in ast.literal_eval(ld)])

A = json_normalize(df['columnA'].apply(only_dict).tolist()).add_prefix('columnA.')
B = json_normalize(df['columnB'].apply(list_of_dicts).tolist()).add_prefix('columnB.pos.') 
Run Code Online (Sandbox Code Playgroud)

最后,加入DFs公共索引得到:

df[['id', 'name']].join([A, B])
Run Code Online (Sandbox Code Playgroud)

图片


编辑: -根据@MartijnPieters的评论,解释json字符串的推荐方法是使用json.loads(),与使用相比,ast.literal_eval()如果您知道数据源是JSON,则速度要快得多.

  • 为什么在你应该使用 `json.loads()` 时调用(慢!)`ast.literal_eval()`?后者处理正确的 JSON 数据,前者只有 *Python* 语法,当涉及 B​​MP 之外的布尔值、空值和 unicode 数据时,它*有很大不同*。 (2认同)

Mic*_*ini 10

TL;DR复制粘贴以下函数并像这样使用它:flatten_nested_json_df(df)

这是我能想到的最通用的函数:

def flatten_nested_json_df(df):

    df = df.reset_index()

    print(f"original shape: {df.shape}")
    print(f"original columns: {df.columns}")


    # search for columns to explode/flatten
    s = (df.applymap(type) == list).all()
    list_columns = s[s].index.tolist()

    s = (df.applymap(type) == dict).all()
    dict_columns = s[s].index.tolist()

    print(f"lists: {list_columns}, dicts: {dict_columns}")
    while len(list_columns) > 0 or len(dict_columns) > 0:
        new_columns = []

        for col in dict_columns:
            print(f"flattening: {col}")
            # explode dictionaries horizontally, adding new columns
            horiz_exploded = pd.json_normalize(df[col]).add_prefix(f'{col}.')
            horiz_exploded.index = df.index
            df = pd.concat([df, horiz_exploded], axis=1).drop(columns=[col])
            new_columns.extend(horiz_exploded.columns) # inplace

        for col in list_columns:
            print(f"exploding: {col}")
            # explode lists vertically, adding new columns
            df = df.drop(columns=[col]).join(df[col].explode().to_frame())
            new_columns.append(col)

        # check if there are still dict o list fields to flatten
        s = (df[new_columns].applymap(type) == list).all()
        list_columns = s[s].index.tolist()

        s = (df[new_columns].applymap(type) == dict).all()
        dict_columns = s[s].index.tolist()

        print(f"lists: {list_columns}, dicts: {dict_columns}")

    print(f"final shape: {df.shape}")
    print(f"final columns: {df.columns}")
    return df
Run Code Online (Sandbox Code Playgroud)

它需要一个可能在其列中嵌套列表和/或字典的数据框,并递归地分解/展平这些列。

它使用pandas'pd.json_normalize来分解字典(创建新列),使用pandas'explode来分解列表(创建新行)。

使用简单:

# Test
df = pd.DataFrame(
    columns=['id','name','columnA','columnB'],
    data=[
        [1,'John',{"dist": "600", "time": "0:12.10"},[{"pos": "1st", "value": "500"},{"pos": "2nd", "value": "300"},{"pos": "3rd", "value": "200"}, {"pos": "total", "value": "1000"}]],
        [2,'Mike',{"dist": "600"},[{"pos": "1st", "value": "500"},{"pos": "2nd", "value": "300"},{"pos": "total", "value": "800"}]]
    ])

flatten_nested_json_df(df)
Run Code Online (Sandbox Code Playgroud)

它不是地球上最有效的东西,它具有重置数据框索引的副作用,但它完成了工作。随意调整它。

  • 这是迄今为止我长期以来见过的最好的解决方案!好工作! (3认同)

piR*_*red 9

创建一个自定义函数来展平columnB然后使用pd.concat

def flatten(js):
    return pd.DataFrame(js).set_index('pos').squeeze()

pd.concat([df.drop(['columnA', 'columnB'], axis=1),
           df.columnA.apply(pd.Series),
           df.columnB.apply(flatten)], axis=1)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述