JSON - python的扁平化处理(透视表)

1. 提出问题

在面对嵌套的JSON对象时,我们通常使用Python尝试将嵌套结构中的键转换为列。但是当数据加载到pandas中往往会得到如下结果:

df = pd.DataFrame.from_records(results [“ issues”],columns = [“ key”,“ fields”]

json

  • 说明:这里results是一个大的字典,issues是results其中的一个键,issues的值为一个嵌套JSON对象字典的列表,后面会看到JSON嵌套结构。

上述结果的问题在于API返回了嵌套的JSON结构,而我们关心的键在对象中确处于不同级别。

嵌套的JSON结构如下:
json

而我们想要的是下面这样的结果:
json2

下面以一个API返回的数据为例,API通常包含有关字段的元数据(metadata)。假设下面这些是我们想要的字段。


key:JSON密钥,在第一级的位置。

summary:第二级的“字段”对象。

status name:第三级位置。

statusCategory name:位于第4个嵌套级别。

如上,我们选择要提取的字段在issues列表内的JSON结构中分别处于4个不同的嵌套级别,一环扣一环。

{
  "expand": "schema,names",
  "issues": [
    {
      "fields": {
        "issuetype": {
          "avatarId": 10300,
          "description": "",
          "id": "10005",
          "name": "New Feature",
          "subtask": False
        },
        "status": {
          "description": "A resolution has been taken, and it is awaiting verification by reporter. From here issues are either reopened, or are closed.",
          "id": "5",
          "name": "Resolved",
          "statusCategory": {
            "colorName": "green",
            "id": 3,
            "key": "done",
            "name": "Done",
          }
        },
        "summary": "Recovered data collection Defraglar $MFT problem"
      },
      "id": "11861",
      "key": "CAE-160",
    },
    {
      "fields": { 
... more issues],
  "maxResults": 5,
  "startAt": 0,
  "total": 160
}

2. 解决方案

2.1 直接撸码
  • 写一个查找特定字段的函数,但问题是必须对每个嵌套字段调用此函数,然后再调用.apply到DataFrame中的新列。

为获取我们想要的几个字段,首先我们提取fields键内的对象至列:

df = (
    df["fields"]
    .apply(pd.Series)
    .merge(df, left_index=True, right_index = True)
)

r3

从上表看出,只有summary是可用的,issuetype、status等仍然埋在嵌套对象中。

下面是提取issuetype中的name的一种方法。

# 提取issue type的name到一个新列叫"issue_type"
df_issue_type = (
    df["issuetype"]
    .apply(pd.Series)
    .rename(columns={"name": "issue_type_name"})["issue_type_name"]
)
df = df.assign(issue_type_name = df_issue_type)

r6

像上面这样,如果嵌套层级特别多,就需要自己手撸一个递归来实现了,因为每层嵌套都需要调用一个像上面解析并添加到新列的方法。

对于编程基础薄弱的朋友,手撸一个其实还挺麻烦的,尤其是对于数据分析师,着急想用数据的时候,希望可以快速拿到结构化的数据进行分析。

2.2 内置的解决方案

pandas提供的内置解决方法:pandas.json_noramlize
pandas.json_normalize(data, record_path=None, meta=None, meta_prefix=None, record_prefix=None, errors='raise', sep='.', max_level=None)

Parameters
data : dict or list of dicts
# 未被序列化的json对象

record_path: str or list of str, default None
# 每个对象中record(或多个record形成的列表)的路径。实际上我们是获取这些records(JSON中的键)下的内容,指定了获取内容的路径(或者键)

meta: list of paths (str or list of str), default None
# 在结果列变中,每个record的元数据字段

meta_prefix: str, default None
If True, prefix records with dotted (?) path, e.g. foo.bar.field if meta is [‘foo’, ‘bar’].

record_prefix: str, default None
If True, prefix records with dotted (?) path, e.g. foo.bar.field if path to records is [‘foo’, ‘bar’].

errors: {raise, ‘ignore’}, default ‘raise’
Configures error handling.
‘ignore’ : will ignore KeyError if keys listed in meta are not always present.raise: will raise KeyError if keys listed in meta are not always present.

sep: str, default ‘.’
Nested records will generate names separated by sep. e.g., for sep=., {‘foo’: {‘bar’: 0}} -> foo.bar.

max_level: int, default None
Max number of levels(depth of dict) to normalize. if None, normalizes all levels.

Returns
frame: DataFrame
Normalize semi-structured JSON data into a flat table.
  • 作用:将半结构化JSON数据规范化为透视表。

前面方案的所有代码,用这个内置功能仅需要3行就可搞定。步骤很简单,懂了下面几个用法即可。

确定我们要想的字段,使用 . 符号连接嵌套对象。

将想要处理的嵌套列表(这里是results[“issues”])作为参数放进 .json_normalize 中。

过滤我们定义的FIELDS列表。

FIELDS = ["key", "fields.summary", "fields.issuetype.name", "fields.status.name", "fields.status.statusCategory.name"]
df = pd.json_normalize(results["issues"])
df[FIELDS]

r8

2.2.1 其它操作
  1. 记录路径
    除了像上面那样传递results[“issues”]列表之外,我们还使用record_path参数在JSON对象中指定列表的路径。
# 使用路径而不是直接用results["issues"]
pd.json_normalize(results, record_path="issues")[FIELDS]
  1. 自定义分隔符

还可以使用sep参数自定义嵌套结构连接的分隔符,比如下面将默认的“.”替换“-”。

# 用 "-" 替换默认的 "."
FIELDS = ["key", "fields-summary", "fields-issuetype-name", "fields-status-name", "fields-status-statusCategory-name"]
pd.json_normalize(results["issues"], sep = "-")[FIELDS]
  1. 控制递归

如果不想递归到每个子对象,可以使用max_level参数控制深度。在这种情况下,由于statusCategory.name字段位于JSON对象的第4级,因此不会包含在结果DataFrame中。

# 只深入到嵌套第二级
pd.json_normalize(results, record_path="issues", max_level = 2)
下面是.json_normalize的pandas官方文档说明,如有不明白可自行学习,本次东哥就介绍到这里。
2.3 实例
data = [{'id': 1, 'name': {'first': 'Coleen', 'last': 'Volk'}},
        {'name': {'given': 'Mose', 'family': 'Regner'}},
        {'id': 2, 'name': 'Faye Raker'}]
pandas.json_normalize(data)
Out:
    id        name name.family name.first name.given name.last
0  1.0         NaN         NaN     Coleen        NaN      Volk
1  NaN         NaN      Regner        NaN       Mose       NaN
2  2.0  Faye Raker         NaN        NaN        NaN       NaN
# -------------------------------------------------------- #
data = [{'id': 1,
         'name': "Cole Volk",
         'fitness': {'height': 130, 'weight': 60}},
        {'name': "Mose Reg",
         'fitness': {'height': 130, 'weight': 60}},
        {'id': 2, 'name': 'Faye Raker',
         'fitness': {'height': 130, 'weight': 60}}]
json_normalize(data, max_level=0)
Out:
            fitness                 id        name
0   {'height': 130, 'weight': 60}  1.0   Cole Volk
1   {'height': 130, 'weight': 60}  NaN    Mose Reg
2   {'height': 130, 'weight': 60}  2.0  Faye Raker
Normalizes nested data up to level 1.
# ------------------------------------------------------ #
data = [{'id': 1,
         'name': "Cole Volk",
         'fitness': {'height': 130, 'weight': 60}},
        {'name': "Mose Reg",
         'fitness': {'height': 130, 'weight': 60}},
        {'id': 2, 'name': 'Faye Raker',
         'fitness': {'height': 130, 'weight': 60}}]
json_normalize(data, max_level=1)
Out:
  fitness.height  fitness.weight   id    name
0   130              60          1.0    Cole Volk
1   130              60          NaN    Mose Reg
2   130              60          2.0    Faye Raker
# ------------------------------------------------------ #
data = [{'state': 'Florida',
         'shortname': 'FL',
         'info': {'governor': 'Rick Scott'},
         'counties': [{'name': 'Dade', 'population': 12345},
                      {'name': 'Broward', 'population': 40000},
                      {'name': 'Palm Beach', 'population': 60000}]},
        {'state': 'Ohio',
         'shortname': 'OH',
         'info': {'governor': 'John Kasich'},
         'counties': [{'name': 'Summit', 'population': 1234},
                      {'name': 'Cuyahoga', 'population': 1337}]}]
result = json_normalize(data, 'counties', ['state', 'shortname',
                                           ['info', 'governor']])
result
Out:
         name  population    state shortname info.governor
0        Dade       12345   Florida    FL    Rick Scott
1     Broward       40000   Florida    FL    Rick Scott
2  Palm Beach       60000   Florida    FL    Rick Scott
3      Summit        1234   Ohio       OH    John Kasich
4    Cuyahoga        1337   Ohio       OH    John Kasich
# ------------------------------------------------------------- #
data = {'A': [1, 2]}
json_normalize(data, 'A', record_prefix='Prefix.')
Out:
    Prefix.0
0          1
1          2
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值