使用 pandas.json_normalize 到 "unfold" 字典列表的字典

Using pandas.json_normalize to "unfold" a dictionary of a list of dictionaries

我是 Python 的新手(以及一般的编码),所以我会尽力解释我正在努力应对的挑战。

我正在处理一个从数据库导出为 CSV 文件的大型数据集。但是,此 CSV 导出中有一列包含字典的嵌套列表(据我所知)。我在网上广泛寻找解决方案,包括在 Whosebug 上,但还没有完全找到完整的解决方案。我想我从概念上理解了我想要完成的事情,但不清楚要使用的最佳方法或数据准备过程。

这是一个数据示例(缩减为我感兴趣的两列):

    {
       "app_ID": {
          "0": 1abe23574,
          "1": 4gbn21096
       },
       "locations": {
          "0": "[ {"loc_id" : "abc1",  "lat" : "12.3456",  "long" : "101.9876"  
                  },
                  {"loc_id" : "abc2",  "lat" : "45.7890",  "long" : "102.6543"} 
                ]",
          "1": "[ ]",
         ]"
       }
    }

基本上每个 app_ID 可以有多个位置绑定到一个 ID,或者它可以是空的,如上所示。我尝试使用我在网上找到的一些指南,这些指南使用 Panda 的 json_normalize() 函数来“展开”或将字典列表放入 Panda 数据框中它们自己的行中。

我想以这样的方式结束:

loc_id    lat      long       app_ID
abc1      12.3456  101.9876   1abe23574
abc1      45.7890  102.6543   1abe23574

等...

我正在学习如何使用 json_normalize 的不同功能,例如“record_path”和“meta”,但还无法使用它。

我尝试使用以下方法将 json 文件加载到 Jupyter Notebook 中:

with open('location_json.json', 'r') as f:
          data = json.loads(f.read())
df = pd.json_normalize(data, record_path = ['locations'])

但它只创建一个 1 行和多列长的数据框,我希望从最内层的字典生成多行,这些行与 app_ID 和 loc_ID 字段。

尝试解决方案:

我能够接近我想要使用的数据帧格式:

with open('location_json.json', 'r') as f:
          data = json.loads(f.read())
df = pd.json_normalize(data['locations']['0'])

但这将需要通过列表进行某种迭代以创建数据框,然后我将失去与 app_ID 字段的连接。 (尽我所能理解 json_normalize 函数的工作原理)。

我尝试使用 json_normalize 是否正确,还是应该重新开始并尝试不同的路线?任何建议或指导将不胜感激。

我不能说建议您使用 convtools 库是一件好事,因为您是初学者,因为这个库几乎就像另一个 Python 而不是 Python。它有助于动态定义数据转换(在幕后生成 Python 代码)。

但是无论如何,如果我理解输入数据的话,下面是代码:

import json
from convtools import conversion as c

data = {
    "app_ID": {"0": "1abe23574", "1": "4gbn21096"},
    "locations": {
        "0": """[ {"loc_id" : "abc1",  "lat" : "12.3456",  "long" : "101.9876" },
              {"loc_id" : "abc2",  "lat" : "45.7890",  "long" : "102.6543"} ]""",
        "1": "[ ]",
    },
}

# define it once and use multiple times
converter = (
    c.join(
        # converts "app_ID" data to iterable of dicts
        (
            c.item("app_ID")
            .call_method("items")
            .iter({"id": c.item(0), "app_id": c.item(1)})
        ),
        # converts "locations" data to iterable of dicts,
        # where each id like "0" is zipped to each location.
        # the result is iterable of dicts like {"id": "0", "loc": {"loc_id": ... }}
        (
            c.item("locations")
            .call_method("items")
            .iter(
                c.zip(id=c.repeat(c.item(0)), loc=c.item(1).pipe(json.loads))
            )
            .flatten()
        ),
        # join on "id"
        c.LEFT.item("id") == c.RIGHT.item("id"),
        how="full",
    )
    # process results, where 0 index is LEFT item, 1 index is the RIGHT one
    .iter(
        {
            "loc_id": c.item(1, "loc", "loc_id", default=None),
            "lat": c.item(1, "loc", "lat", default=None),
            "long": c.item(1, "loc", "long", default=None),
            "app_id": c.item(0, "app_id"),
        }
    )
    .as_type(list)
    .gen_converter()
)
result = converter(data)

assert result == [
    {'loc_id': 'abc1', 'lat': '12.3456', 'long': '101.9876', 'app_id': '1abe23574'},
    {'loc_id': 'abc2', 'lat': '45.7890', 'long': '102.6543', 'app_id': '1abe23574'},
    {'loc_id': None, 'lat': None, 'long': None, 'app_id': '4gbn21096'}
]