将嵌套的 JSON 读入 Pandas DataFrame

Posted

技术标签:

【中文标题】将嵌套的 JSON 读入 Pandas DataFrame【英文标题】:Read nested JSON into Pandas DataFrame 【发布时间】:2022-01-07 16:11:42 【问题描述】:

背景信息 -我有来自 API 调用的 JSON 响应,我试图将其保存在 pandas DataFrame 中,同时保持与我在系统中查看时相同的结构我已经调用了数据。

调用 JSON Response 的函数 -def api_call(): 调用 API(注意: url_list 目前只包含 1x url)并将响应保存在api_response 变量,使用 json.loads(response.text)

def api_call():
    url_list = url_constructor()
    for url in url_list:
        response = requests.get(url_list[0], auth = HTTPBasicAuth(key, secret), headers="Firm":"583")
    api_response = json.loads(response.text)
    return api_response

将响应保存到文件并返回的功能: def response_writer():api_response 保存为 JSON 文件。它还返回api_response

def response_writer():
    api_response = api_call()
    timestr = datetime.datetime.now().strftime("%Y-%m-%d-%H:%M")
    filename = 'api_response_'+timestr+'.json'
    with open(filename, 'w') as output_data:
        json.dump(api_response, output_data)
        print("-------------------------------------------------------\n", 
              "API RESPONSE SAVED:", filename, "\n-------------------------------------------------------")
    return api_response

JSON 响应 -


  "meta": 
    "columns": [
      
        "key": "node_id",
        "display_name": "Entity ID",
        "output_type": "Word"
      ,
      
        "key": "bottom_level_holding_account_number",
        "display_name": "Holding Account Number",
        "output_type": "Word"
      ,
      
        "key": "value",
        "display_name": "Adjusted Value (USD)",
        "output_type": "Number",
        "currency": "USD"
      ,
      
        "key": "node_ownership",
        "display_name": "% Ownership",
        "output_type": "Percent"
      ,
      
        "key": "model_type",
        "display_name": "Model Type",
        "output_type": "Word"
      ,
      
        "key": "valuation",
        "display_name": "Valuation (USD)",
        "output_type": "Number",
        "currency": "USD"
      ,
      
        "key": "_custom_jb_custodian_305769",
        "display_name": "JB Custodian",
        "output_type": "Word"
      ,
      
        "key": "top_level_owner",
        "display_name": "Top Level Owner",
        "output_type": "Word"
      ,
      
        "key": "top_level_legal_entity",
        "display_name": "Top Level Legal Entity",
        "output_type": "Word"
      ,
      
        "key": "direct_owner",
        "display_name": "Direct Owner",
        "output_type": "Word"
      ,
      
        "key": "online_status",
        "display_name": "Online Status",
        "output_type": "Word"
      ,
      
        "key": "financial_service",
        "display_name": "Financial Service",
        "output_type": "Word"
      ,
      
        "key": "_custom_placeholder_461415",
        "display_name": "Placeholder or Fee Basis",
        "output_type": "Boolean"
      ,
      
        "key": "_custom_close_date_411160",
        "display_name": "Account Close Date",
        "output_type": "Date"
      ,
      
        "key": "_custom_ownership_audit_note_425843",
        "display_name": "Ownership Audit Note",
        "output_type": "Word"
      
    ],
    "groupings": [
      
        "key": "holding_account",
        "display_name": "Holding Account"
      
    ]
  ,
  "data": 
    "type": "portfolio_views",
    "attributes": 
      "total": 
        "name": "Total",
        "columns": 
          "direct_owner": null,
          "node_ownership": null,
          "online_status": null,
          "_custom_ownership_audit_note_425843": null,
          "model_type": null,
          "_custom_placeholder_461415": null,
          "top_level_owner": null,
          "_custom_close_date_411160": null,
          "valuation": null,
          "bottom_level_holding_account_number": null,
          "_custom_jb_custodian_305769": null,
          "financial_service": null,
          "top_level_legal_entity": null,
          "value": null,
          "node_id": null
        ,
        "children": [
          
            "entity_id": 4754837,
            "name": "Apple Holdings Adv (748374923)",
            "grouping": "holding_account",
            "columns": 
              "direct_owner": "Apple Holdings LLC",
              "node_ownership": 1,
              "online_status": "Online",
              "_custom_ownership_audit_note_425843": null,
              "model_type": "Holding Account",
              "_custom_placeholder_461415": false,
              "top_level_owner": "Forsyth Family",
              "_custom_close_date_411160": null,
              "valuation": 10423695.609450001,
              "bottom_level_holding_account_number": "748374923",
              "_custom_jb_custodian_305769": "Laverockbank",
              "financial_service": "laverockbankcustodianservice",
              "top_level_legal_entity": "Apple Holdings LLC",
              "value": 10423695.609450001,
              "node_id": "4754837"
            ,
          
        ]
      
    
  ,
  "included": []

Pandas DataFrame 中 JSON 的预期结构 -这是我试图在我的 pandas DataFrame 中传达的结构 -

| Holding Account                 | Entity ID | Holding Account Number | Adjusted Value (USD) | % Ownership | Model Type      | Valuation (USD) | JB Custodian | Top Level Owner | Top Level Legal Entity          | Direct Owner                    | Online Status | Financial Service   | Placeholder or Fee Basis | Account Close Date | Ownership Audit Note |
|---------------------------------|-----------|------------------------|----------------------|-------------|-----------------|-----------------|--------------|-----------------|---------------------------------|---------------------------------|---------------|---------------------|--------------------------|--------------------|----------------------|
| Apple Holdings Adv (748374923)  | 4754837   | 748374923              | $10,423,695.06       | 100.00%     | Holding Account | $10,423,695.06  | BRF          | Forsyth Family  | Apple Holdings Partners LLC     | Apple Holdings Partners LLC     | Online        | custodianservice    | No                       | -                  | -                    |

我对 JSON 结构的解释 -看来我需要专注于 'columns:(具有列标题)和 'children'(代表数据行,在我的情况下,只有'data': 的 1x 行)。我可以忽略'groupings': ['key': 'holding_account', 'display_name': 'Holding Account'],,因为这最终是系统中数据的排序方式。

有人对我如何获取 JSON 并加载到具有演示结构的 DataFrame 有建议吗?

我的解释是我需要将display_names [columns] 设置为标题,然后在每个相应的display_names / 标题下映射相应的children 值。 注意: 通常情况下,children 会更多(代表我的 DataFrame 的每一行数据),但是我已经删除了除了 1x 之外的所有内容,以便于解释。

【问题讨论】:

查看 Glom (pypi.org/project/glom),它可以帮助您将数据格式化为所需的格式,然后使用 Pandas 从中创建数据框。 【参考方案1】:

我建议使用pd.json_normalize() (https://pandas.pydata.org/pandas-docs/version/1.2.0/reference/api/pandas.json_normalize.html),它有助于将 JSON 数据转换为 pandas DataFrame。

注意 1: 下面我假设数据在名为 data 的 python 字典中可用。出于测试目的,我使用了

import json
json_data = '''

  "meta": 
      # ....
  ,
  #...
  "included": []

'''
data = json.loads(json_data)

json_data 是您的 JSON 响应。由于json.loads() 不接受最后的逗号,所以我在 children 对象后省略了逗号。

pd.json_normalize() 提供不同的选项。一种可能性是简单地读取所有“子”数据,然后删除不需要的列。此外,在规范化某些列之后,会有一个前缀“列”。需要删除。

import pandas as pd
df = pd.json_normalize(data['data']['attributes']['total']['children'])
df.drop(columns=['grouping', 'entity_id'], inplace=True)
df.columns = df.columns.str.replace(r'columns.', '')

最后,需要将列名替换为“列”数据中的列名:

column_name_mapper = column['key']: column['display_name'] for column in data['meta']['columns']
df.rename(columns=column_name_mapper, inplace=True)

注意 2: 与您描述的预期结构略有不同。最值得注意的是,数据框标题中的“名称”一词(行值为“Apple Holdings Adv (748374923)”)未更改为“Holding Account”,因为在列列表中未找到这两个术语。描述的 JSON 响应和预期结构之间的一些其他值只是不同。

【讨论】:

感谢您的贡献。我创建了一个返回 api 响应并存储在“api_response”中的函数,然后尝试了“data = json.loads(api_response)”。但是,我收到“TypeError:JSON 对象必须是 str、bytes 或 bytearray,而不是 dict”消息。任何想法为什么? json.loads() 需要一个 str、bytes 或 bytearray... 所以我实际上将 JSON 响应作为字符串传递给函数(使用三引号,因为它跨越多行)。我在答案中澄清了那部分。这适用于测试。不过,在您的特定上下文中,其他一些解决方案可能更合适,具体取决于您如何从 API 调用接收 json。 谢谢罗莎——我怕我看错了。感谢您的精彩解释;我学到了很多!【参考方案2】:

我不确定这是解压字典的最佳方法,但它确实有效: (它用于保持孩子的“元数据”,如 id(重复),并持有帐户全名)

def unpack_dict(item, out):
    for k, v in item.items():
        if type(v) == dict:
            unpack_dict(v, out)
        else:
            out[k] = v
    return out

现在我们需要对每个孩子使用它来获取数据

从您的示例中,您似乎想保留控股帐户(来自孩子),但您不想要 entity_id,因为它在 node_id 中重复?

不确定,所以我将只包含所有列及其“原始”名称

columns = unpack_dict(res["data"]["attributes"]["total"]["children"][0]
children = res["data"]["attributes"]["total"]["children"]
data = []

for i in children:
    data.append(list(unpack_dict(i, ).values()))

并从中创建一个数据框:

>>> pd.DataFrame(data=data, columns = columns)
   entity_id                            name  ...         value  node_id
0    4754837  Apple Holdings Adv (748374923)  ...  1.042370e+07  4754837

[1 rows x 18 columns]

现在可以将其更改为显示名称而不是这些原始名称。不过,您可能需要删除一些列,正如我上面提到的,id 是重复的,您提到了分组等。

如果您正在处理大量数据(数以千计的条目)并且解析它需要很长时间,可以在插入data 之前删除多余的列,以便以后节省一些时间。

使用dict 重命名列:

df.rename(columns='oldName1': 'newName1', 'oldName2': 'newName2')

【讨论】:

非常感谢您的贡献。您的解释是有道理的,但是您能否提供有关如何将此功能集成到我的代码中的上下文? API 响应存储在一个名为“api_response”的变量中 注意:我有 inc。在我的问题中调用和存储 API 响应的函数。 @William 对,不要使用res,而是使用您的api_response,它应该可以工作,因为它是dict 感谢您的确认。此外,这应该如何构建,即“def unpack_dict(item,out):”函数的位置和您提到的继续代码?我只是想了解如何拼凑,所以我可以学习和理解它是如何工作的,当然还有测试! @William 是的,你可以一个接一个地放置,然后用最后的pd.DataFrame(data=data, columns = columns) 你会得到数据框表

以上是关于将嵌套的 JSON 读入 Pandas DataFrame的主要内容,如果未能解决你的问题,请参考以下文章

使用 python/pandas 从特定文件夹中读取几个嵌套的 .json 文件到 excel 中

嵌套字典到 MultiIndex pandas DataFrame(3 级)

用正确的 Dtype 将带有无意义键的 json 读入 pandas data.frame

将带有嵌套标签的 XML 读入 Spark RDD,并转换为 JSON

将 Pandas 数据框转换为嵌套 JSON

将 pandas 嵌套的 JSON 结构转换为数据框