【问题标题】:How to flatten a pandas dataframe with some columns as json?如何用一些列作为 json 展平熊猫数据框?
【发布时间】:2017-02-15 09:02:13
【问题描述】:

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

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"}]
...

如您所见,并非所有行在列的 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 

我试过像这样使用json_normalize

from pandas.io.json import json_normalize
json_normalize(df)

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

【问题讨论】:

  • B 列中的值呢?你也想扁平化字典吗?
  • 是的。它们也需要展平。原始问题中有一个错字,我将 columnA 用于所有展平的列,但现在更正了。

标签: python json pandas dataframe flatten


【解决方案1】:

最快的似乎是:

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

【讨论】:

  • 这绝对是最简单的方法,也是对我有用的方法。唯一需要注意的是您的嵌套对象将以长名称结束(data.level1.level2.level3 ...等)
  • 这绝对是我选择的答案 - 完美且非常简洁的解决方案。谢谢!
【解决方案2】:

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

它采用可能在其列中包含嵌套列表和/或字典的数据框,并递归地展开/展平这些列。

它使用 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)

这不是地球上最有效的事情,它具有重置数据框索引的副作用,但它可以完成工作。随意调整它。

【讨论】:

  • 这是迄今为止我见过的最好的解决方案!干得好!
  • 您好,这很有帮助,但似乎没有保存新的数据框
  • @CameronStewart 保存在哪里?
【解决方案3】:

这是再次使用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.') 

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

df[['id', 'name']].join([A, B])


编辑:- 根据@MartijnPieters 的评论,解码 json 字符串的推荐方法是使用 json.loads(),与如果您知道数据源是 JSON,请使用 ast.literal_eval()

【讨论】:

  • 非常感谢您的回答!但有一件事是 list_of_dicts (list(d.values())[0], list(d.values())[1]) 上的返回列表,而不是相反?否则这对我来说是完美的。
  • 如您所知,dictionaries 在执行迭代时不会保留顺序,dict 中的值出现的顺序与您的顺序相反,因此需要使用切片符号与您的不同。如果它的出现顺序与您提到的相同,请继续使用它,或者您甚至可以使用Ordered Dict 来保留顺序。
  • 为什么在您应该使用json.loads() 时调用(慢!)ast.literal_eval()?后者处理正确的 JSON 数据,前者仅处理 Python 语法,当涉及 B​​MP 之外的布尔值、空值和 unicode 数据时,它有很大不同
  • @MartijnPieters:感谢您的评论。我已经更新了我的帖子。
  • 不仅速度更快,而且当涉及truefalsenull 值时,它还将避免ValueError 异常。 JSON 不是 Python。
【解决方案4】:

创建一个自定义函数来展平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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-13
    • 1970-01-01
    • 1970-01-01
    • 2021-07-17
    • 1970-01-01
    • 2020-04-30
    • 1970-01-01
    • 2019-03-18
    相关资源
    最近更新 更多