【问题标题】:Flatten a tripled nested json into a dataframe将三倍嵌套的 json 展平为数据框
【发布时间】:2022-06-21 16:43:49
【问题描述】:

问题

我得到了一个非常大的 json 文件,看起来像这个最小的例子:

json_file = """
{
    "products":
    [

        {
            "id":"0",
            "name": "First",
            "emptylist":[],
            "properties" : 
            {
              "id" : "",
              "name" : ""
            }
        },
        {
            "id":"1",
            "name": "Second",
            "emptylist":[],
            "properties": 
            {
                "id" : "23",
                "name" : "a useful product",
                "features" :
                [
                    {
                        "name":"Features",
                        "id":"18",
                        "features":
                        [
                            {
                                "id":"1001",
                                "name":"Colour",
                                "value":"Black"
                            },
                            {
                                "id":"2093",
                                "name":"Material",
                                "value":"Plastic"
                            }
                        ]
                    },
                    {
                        "name":"Sizes",
                        "id":"34",
                        "features":
                        [
                            {
                                "id":"4736",
                                "name":"Length",
                                "value":"56"
                            },
                            {
                                "id":"8745",
                                "name":"Width",
                                "value":"76"
                            }
                        ]
                    }
                ]
            }
        },
        {
            "id":"2",
            "name": "Third",
            "properties" : 
            {
                "id" : "876",
                "name" : "another one",
                "features" : 
                [
                    {
                        "name":"Box",
                        "id":"937",
                        "features":
                        [
                            {
                                "id":"3758",
                                "name":"Amount",
                                "value":"1"
                            },
                            {
                                "id":"2222",
                                "name":"Packaging",
                                "value":"Blister"
                            }
                        ]
                    },
                    {
                        "name":"Features",
                        "id":"8473",
                        "features":
                        [
                            {
                                "id":"9372",
                                "name":"Colour",
                                "value":"White"
                            },
                            {
                                "id":"9375",
                                "name":"Position",
                                "value":"A"
                            },
                            {
                                "id":"2654",
                                "name":"Amount",
                                "value":"6"
                            }
                        ]
                    }
                ]
            }
        }
    ]
}
"""

我想用它做一张平桌。它应该看起来像这样:

id    name   emptylist  properties.id properties.name    properties.features.name properties.features.id properties.features.features.id properties.features.features.name properties.features.features.value
0     First  []         ""            ""                 NaN                      NaN                    NaN                             NaN                               NaN                               
1     Second []         "23"          "a useful product" Features                 18                     1001                            Colour                            Black                             
1     Second []         "23"          "a useful product" Features                 18                     2093                            Material                          Plastic                           
1     Second []         "23"          "a useful product" Sizes                    34                     4736                            Length                            56                                
1     Second []         "23"          "a useful product" Sizes                    34                     8745                            Width                             76                                
2     Third             "876"         "another one"      Box                      937                    3758                            Amount                            1                                 
2     Third             "876"         "another one"      Box                      937                    2222                            Packaging                         Blister                           
2     Third             "876"         "another one"      Features                 8473                   9372                            Colour                            White                             
2     Third             "876"         "another one"      Features                 8473                   9375                            Position                          A                                 
2     Third             "876"         "another one"      Features                 8473                   2654                            Amount                            6                             

我尝试了什么

我试过了:

import pandas as pd
import json

j = json.loads(json_file)
df = pd.json_normalize(j['products'])
df

  id    name emptylist properties.id   properties.name                                 properties.features  
0  0   First        []                                                                                 NaN  
1  1  Second        []            23  a useful product   [{'name': 'Features', 'id': '18', 'features': ...  
2  2   Third       NaN           876       another one   [{'name': 'Box', 'id': '937', 'features': [{'i...  

   

我尝试使用其他参数进行一些操作,但无济于事。看来这不是正确的方法。

谁能帮帮我?


其他信息

我得到了一个使用 R 的有效解决方案,但我需要能够使用 Python 来完成它。 如果有帮助,这将是我尝试用 Python 翻译的 R 代码。

library(tidyr)
jsonlite::fromJSON(json_file)$products %>% 
  jsonlite::flatten() %>%
  unnest(properties.features         , names_sep = ".", keep_empty = TRUE) %>% 
  unnest(properties.features.features, names_sep = ".", keep_empty = TRUE)

编辑

在@piterbarg 和一些研究的帮助下,我得到了这个解决方案:

j = json.loads(json_file)
df = pd.json_normalize(j['products'])
df1 = df.explode('properties.features')
df2 = pd.concat([df1.reset_index(drop=True).drop('properties.features', axis = 1), 
                df1['properties.features'].apply(pd.Series).reset_index(drop=True).add_prefix("properties.features.").drop("properties.features.0", axis = 1)], axis = 1)
df2 = df2.explode('properties.features.features')
df3 = pd.concat([df2.reset_index(drop=True).drop('properties.features.features', axis = 1), 
                df2['properties.features.features'].apply(pd.Series).reset_index(drop=True).add_prefix("properties.features.features.").drop("properties.features.features.0", axis = 1)], axis = 1)
df3

有了这个,我得到了我正在寻找的解决方案,但代码看起来很乱,我不确定这个解决方案的效率如何。有什么帮助吗?

【问题讨论】:

  • 您可能会发现this post 会有所帮助
  • 无论哪里有列表,都需要explode,然后再json_normalize
  • 嗨,艾玛。你能举个例子解释一下吗?

标签: python json pandas dataframe nested-json


【解决方案1】:

它与您在 Edit 中的类似,但可能语法更短且性能更高。

如果 DataFrame 中有 NaN,旧版本的 Pandas 可能会在 json_normalize 上失败。

此解决方案应该适用于 Pandas 1.3+。

df = pd.json_normalize(products)
df = df.explode('properties.features')
df = pd.concat([df.drop('properties.features', axis=1).reset_index(drop=True),
                pd.json_normalize(df['properties.features']).add_prefix('properties.features.')], axis=1)
df = df.explode('properties.features.features')
df = pd.concat([df.drop('properties.features.features', axis=1).reset_index(drop=True),
                pd.json_normalize(df['properties.features.features']).add_prefix('properties.features.features.')], axis=1)

性能。拥有 1000 种产品。

Code in Edit : 4.85 s ± 218 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
This solution: 58.3 ms ± 10.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

【讨论】:

  • 很高兴听到它的工作,并感谢您的评论。我更新了答案以包含版本信息。
【解决方案2】:

这可以通过重复应用 explode 来扩展列表和 apply(pd.Series) 来扩展字典来完成,虽然有些乏味:

df1 = df.explode('properties.features')
df2 = df1.join(df1['properties.features'].apply(pd.Series), lsuffix = '', rsuffix = '.properties.features').explode('features').drop(columns = 'properties.features')
df3 = df2.join(df2['features'].apply(pd.Series), lsuffix = '', rsuffix='.features').drop(columns = ['features','emptylist']).drop_duplicates()

df3 看起来像这样:

      id  name    properties.id    properties.name      0    id.properties.features  name.properties.features      0.features    id.features  name.features    value
--  ----  ------  ---------------  -----------------  ---  ------------------------  --------------------------  ------------  -------------  ---------------  -------
 0     0  First                                       nan                       nan  nan                                  nan            nan  nan              nan
 1     1  Second  23               a useful product   nan                        18  Features                             nan           1001  Colour           Black
 1     1  Second  23               a useful product   nan                        18  Features                             nan           2093  Material         Plastic
 1     1  Second  23               a useful product   nan                        18  Features                             nan           4736  Length           56
 1     1  Second  23               a useful product   nan                        18  Features                             nan           8745  Width            76
 1     1  Second  23               a useful product   nan                        34  Sizes                                nan           1001  Colour           Black
 1     1  Second  23               a useful product   nan                        34  Sizes                                nan           2093  Material         Plastic
 1     1  Second  23               a useful product   nan                        34  Sizes                                nan           4736  Length           56
 1     1  Second  23               a useful product   nan                        34  Sizes                                nan           8745  Width            76
 2     2  Third   876              another one        nan                       937  Box                                  nan           3758  Amount           1
 2     2  Third   876              another one        nan                       937  Box                                  nan           2222  Packaging        Blister
 2     2  Third   876              another one        nan                       937  Box                                  nan           9372  Colour           White
 2     2  Third   876              another one        nan                       937  Box                                  nan           9375  Position         A
 2     2  Third   876              another one        nan                       937  Box                                  nan           2654  Amount           6
 2     2  Third   876              another one        nan                      8473  Features                             nan           3758  Amount           1
 2     2  Third   876              another one        nan                      8473  Features                             nan           2222  Packaging        Blister
 2     2  Third   876              another one        nan                      8473  Features                             nan           9372  Colour           White
 2     2  Third   876              another one        nan                      8473  Features                             nan           9375  Position         A
 2     2  Third   876              another one        nan                      8473  Features                             nan           2654  Amount           6

名称与您想要的不太一样,如果您愿意,可以使用.rename(columns = {...}) 修复

【讨论】:

  • 我尝试了您的解决方案,但根据我的预期输出,我得到了 165rows X 12cols 数据帧而不是 10x10 数据帧。
  • @Edo 我通过添加 drop_duplicates 编辑了我的答案,所以它减少到 19 行,它们似乎都是不同的
  • 我很抱歉@piterbarg,我知道你想在这里帮助我。我感谢你。但是您的解决方案仍然存在一些问题。使用原始 json,我最终将拥有一个包含 400 万行的数据集:我不能使用 drop_duplicates。您仍然有 11 列而不是 10 列。此外,某些行不应该存在:您将数据中最初不在一起的特征放在一起。这会导致我遇到大量数据质量问题。
  • 嘿@piterbarg。好消息。通过您的代码和一些研究,我得到了我想要的解决方案。该代码看起来很丑陋,我不确定它是一个真正的“pythonian”代码。我会把它添加到问题中。
  • @Edo 很高兴我能帮上忙!
【解决方案3】:
import pandas as pd
tree=     {
    "products":
    [

        {
            "id":"0",
            "name": "First",
            "emptylist":[],
            "properties" : 
            {
              "id" : "",
              "name" : ""
            }
        },
        {
            "id":"1",
            "name": "Second",
            "emptylist":[],
            "properties": 
            {
                "id" : "23",
                "name" : "a useful product",
                "features" :
                [
                    {
                        "name":"Features",
                        "id":"18",
                        "features":
                        [
                            {
                                "id":"1001",
                                "name":"Colour",
                                "value":"Black"
                            },
                            {
                                "id":"2093",
                                "name":"Material",
                                "value":"Plastic"
                            }
                        ]
                    },
                    {
                        "name":"Sizes",
                        "id":"34",
                        "features":
                        [
                            {
                                "id":"4736",
                                "name":"Length",
                                "value":"56"
                            },
                            {
                                "id":"8745",
                                "name":"Width",
                                "value":"76"
                            }
                        ]
                    }
                ]
            }
        },
        {
            "id":"2",
            "name": "Third",
            "properties" : 
            {
                "id" : "876",
                "name" : "another one",
                "features" : 
                [
                    {
                        "name":"Box",
                        "id":"937",
                        "features":
                        [
                            {
                                "id":"3758",
                                "name":"Amount",
                                "value":"1"
                            },
                            {
                                "id":"2222",
                                "name":"Packaging",
                                "value":"Blister"
                            }
                        ]
                    },
                    {
                        "name":"Features",
                        "id":"8473",
                        "features":
                        [
                            {
                                "id":"9372",
                                "name":"Colour",
                                "value":"White"
                            },
                            {
                                "id":"9375",
                                "name":"Position",
                                "value":"A"
                            },
                            {
                                "id":"2654",
                                "name":"Amount",
                                "value":"6"
                            }
                        ]
                    }
                ]
            }
        }
    ]
}


def traverse_parser_dfs(master_tree):
  flatten_tree_node = []
  def _process_leaves(tree:dict,prefix:str = "node", tree_node:dict = dict(), update:bool = True):
      is_nested = False
      if isinstance(tree,dict):
        for k in tree.keys():
            if type(tree[k]) == str:
                colName = prefix + "_" + k
                tree_node[colName] = tree[k]
            elif type(tree[k]) == dict:
                prefix += "_" + k
                leave = tree[k]
                _process_leaves(leave,prefix = prefix, tree_node = tree_node, update = False)
        for k in tree.keys():
            if type(tree[k]) == list:
                is_nested = True
                prefix += "_" + k
                for leave in tree[k]:
                    _process_leaves(leave,prefix = prefix, tree_node = tree_node.copy())
        if not is_nested and update:
            flatten_tree_node.append(tree_node)
        
  _process_leaves(master_tree)
  df = pd.DataFrame(flatten_tree_node)
  df.columns = df.columns.str.replace("@", "_")
  df.columns = df.columns.str.replace("#", "_")
  return df

print(traverse_parser_dfs(tree))

  node_products_id node_products_name  ... node_products_properties_features_features_name node_products_properties_features_features_value
0                1             Second  ...                                          Colour                                            Black
1                1             Second  ...                                        Material                                          Plastic
2                1             Second  ...                                          Length                                               56
3                1             Second  ...                                           Width                                               76
4                2              Third  ...                                          Amount                                                1
5                2              Third  ...                                       Packaging                                          Blister
6                2              Third  ...                                          Colour                                            White
7                2              Third  ...                                        Position                                                A
8                2              Third  ...                                          Amount                                                6
9                2              Third  ...                                             NaN                                              NaN

[10 rows x 9 columns]

【讨论】:

    猜你喜欢
    • 2020-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-14
    • 2020-03-11
    • 2021-04-27
    • 2023-03-07
    • 2023-03-17
    相关资源
    最近更新 更多