note.nkmk.me

pandasのjson_normalizeで辞書のリストをDataFrameに変換

Date: 2018-05-14 / Modified: 2019-11-24 / tags: Python, pandas, JSON

pandas.io.json.json_normalize()関数を使うと共通のキーをもつ辞書のリストをpandas.DataFrameに変換できる。

Web APIなどで取得できるJSONによく使われる形式なので、それをpandas.DataFrameに変換できるのは非常に便利。

ここでは以下の内容について説明する。

  • pandas.DataFrame()による変換
  • pandas.io.json.json_normalize()の基本的な使い方
  • より複雑な場合: 引数record_path, meta
  • JSON文字列・ファイルの一部を読み込み
スポンサーリンク

pandas.DataFrame()による変換

以下のような辞書のリストを例とする。

import pandas as pd

l_simple = [{'name': 'Alice', 'age': 25},
            {'name': 'Bob'}]

このようなシンプルな辞書のリストはpandas.DataFrame()で変換可能。

辞書のキーkeyが列ラベル(列名)になり、キーが存在しない場合の要素は欠損値NaNとなる。

print(pd.DataFrame(l_simple))
#     name   age
# 0  Alice  25.0
# 1    Bob   NaN

pandas.io.json.json_normalize()でも同じ結果。

print(pd.io.json.json_normalize(l_simple))
#     name   age
# 0  Alice  25.0
# 1    Bob   NaN

pandas.io.json.json_normalizeの基本的な使い方

辞書の値valueとして辞書を持つ、ネストした(入れ子になった)辞書のリストを例とする。

l_nested = [{'name': 'Alice', 'age': 25, 'id': {'x': 2, 'y': 8}},
            {'name': 'Bob', 'id': {'x': 10, 'y': 4}}]

pandas.DataFrame()を使うと、値の辞書がそのまま要素として変換されてしまう。

print(pd.DataFrame(l_nested))
#     name   age                 id
# 0  Alice  25.0   {'x': 2, 'y': 8}
# 1    Bob   NaN  {'x': 10, 'y': 4}

pandas.io.json.json_normalize()を使うと、ネストした辞書もキーごとに個別の列として変換される。

print(pd.io.json.json_normalize(l_nested))
#     name   age  id.x  id.y
# 0  Alice  25.0     2     8
# 1    Bob   NaN    10     4

ネストしている部分はデフォルトでは<親のキー>.<子のキー>が列名となる。このセパレータ.は引数sepで変更できる。

print(pd.io.json.json_normalize(l_nested, sep='_'))
#     name   age  id_x  id_y
# 0  Alice  25.0     2     8
# 1    Bob   NaN    10     4

より複雑な場合: 引数record_path, meta

以下のように辞書の値が辞書のリストになっている場合。

l_complex = [{'label': 'X',
              'info' : {'n': 'nx', 'm': 'mx'},
              'data': [{'a': 1, 'b': 2},
                       {'a': 3, 'b': 4}]},
             {'label': 'Y',
              'info' : {'n': 'ny', 'm': 'my'},
              'data': [{'a': 10, 'b': 20},
                       {'a': 30, 'b': 40}]}]

デフォルトだと、辞書のリストがそのまま要素となる。

print(pd.io.json.json_normalize(l_complex))
#   label                                      data info.n info.m
# 0     X      [{'a': 1, 'b': 2}, {'a': 3, 'b': 4}]     nx     mx
# 1     Y  [{'a': 10, 'b': 20}, {'a': 30, 'b': 40}]     ny     my

引数record_pathでキーを指定すると、そのキーに対応する値を対象として変換する。引数record_prefixで列名にプレフィックスを追加可能。

print(pd.io.json.json_normalize(l_complex, record_path='data'))
#     a   b
# 0   1   2
# 1   3   4
# 2  10  20
# 3  30  40

print(pd.io.json.json_normalize(l_complex, record_path='data', record_prefix='data_'))
#    data_a  data_b
# 0       1       2
# 1       3       4
# 2      10      20
# 3      30      40

他のキーの値も変換したい場合は引数metaで指定する。引数meta_prefixで列名にプレフィックスを追加可能。

print(pd.io.json.json_normalize(l_complex, record_path='data',
                                meta='label'))
#     a   b label
# 0   1   2     X
# 1   3   4     X
# 2  10  20     Y
# 3  30  40     Y

print(pd.io.json.json_normalize(l_complex, record_path='data',
                                meta='label', meta_prefix='meta_'))
#     a   b meta_label
# 0   1   2          X
# 1   3   4          X
# 2  10  20          Y
# 3  30  40          Y

metaで指定するキーのペアとなる値が辞書の場合はリスト[[<親のキー>, <子のキー>], ...]で子のキーを指定できる。デフォルトでは<親のキー>.<子のキー>が列名となるが、ここでも引数sepでセパレータを変更できる。

print(pd.io.json.json_normalize(l_complex, record_path='data',
                                meta='info'))
#     a   b                    info
# 0   1   2  {'n': 'nx', 'm': 'mx'}
# 1   3   4  {'n': 'nx', 'm': 'mx'}
# 2  10  20  {'n': 'ny', 'm': 'my'}
# 3  30  40  {'n': 'ny', 'm': 'my'}

print(pd.io.json.json_normalize(l_complex, record_path='data',
                                meta=[['info', 'n'], ['info', 'm']]))
#     a   b info.n info.m
# 0   1   2     nx     mx
# 1   3   4     nx     mx
# 2  10  20     ny     my
# 3  30  40     ny     my

print(pd.io.json.json_normalize(l_complex, record_path='data',
                                meta=[['info', 'n'], ['info', 'm']],
                                sep='_'))
#     a   b info_n info_m
# 0   1   2     nx     mx
# 1   3   4     nx     mx
# 2  10  20     ny     my
# 3  30  40     ny     my

この例のすべての要素をpandas.DataFrameに変換するには、以下のように設定する。

print(pd.io.json.json_normalize(l_complex, record_path='data',
                                meta=['label', ['info', 'n'], ['info', 'm']],
                                sep='_'))
#     a   b label info_n info_m
# 0   1   2     X     nx     mx
# 1   3   4     X     nx     mx
# 2  10  20     Y     ny     my
# 3  30  40     Y     ny     my

子のキーを単独で指定する場合でもmeta=[[<親のキー>, <子のキー>]]のようにリストのリストにする必要があるので注意。meta=[<親のキー>, <子のキー>]だとエラー。

print(pd.io.json.json_normalize(l_complex, record_path='data',
                                meta=[['info', 'n']]))
#     a   b info.n
# 0   1   2     nx
# 1   3   4     nx
# 2  10  20     ny
# 3  30  40     ny

# print(pd.io.json.json_normalize(l_complex, record_path='data',
#                                 meta=['info', 'n']))
# KeyError: "Try running with errors='ignore' as key 'n' is not always present"

JSON文字列・ファイルの一部を読み込み

辞書のリストはWeb APIなどで取得できるJSONによくある形式だと最初に書いたが、このままの形で取得できることは少なく、JSON文字列・ファイルの一部(要素)を抽出する必要がある場合が多い。

以下のJSON形式の文字列を例に説明する。

import pandas as pd
import json

s = '{"OTHER": "x", "DATA": [{"name":"Alice","age":25},{"name":"Bob","age":42}]}'

JSON文字列やファイルをpandas.DataFrameに変換する関数としてpandas.read_json()があるが、辞書が入れ子になっていると以下のようにそのまま要素となる、

print(pd.read_json(s))
#   OTHER                          DATA
# 0     x  {'name': 'Alice', 'age': 25}
# 1     x    {'name': 'Bob', 'age': 42}

pandas.io.json.json_normalize()を利用するために、まず、標準ライブラリのjsonモジュールのjson.loads()で文字列を辞書やリストからなるオブジェクトに変換する。JSONファイルを読み込む場合はjson.load()を使う。

d = json.loads(s)
print(d)
# {'OTHER': 'x', 'DATA': [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 42}]}

print(type(d))
# <class 'dict'>

引数record_pathmetaを設定すると、pandas.io.json.json_normalize()pandas.DataFrameに変換できる。

print(pd.io.json.json_normalize(d))
#   OTHER                                               DATA
# 0     x  [{'name': 'Alice', 'age': 25}, {'name': 'Bob',...

print(pd.io.json.json_normalize(d, record_path='DATA'))
#     name  age
# 0  Alice   25
# 1    Bob   42

print(pd.io.json.json_normalize(d, record_path='DATA', meta='OTHER'))
#     name  age OTHER
# 0  Alice   25     x
# 1    Bob   42     x

一部分のみをpandas.DataFrameに変換したいのであれば、オブジェクトから読み込みたい部分のみを抽出してもよい。ネストが深い場合は[キー名][キー名]のように繰り返す。

上述のように、入れ子になっていない辞書のリストはpandas.DataFrame()でも変換可能。

print(d['DATA'])
# [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 42}]

print(type(d['DATA']))
# <class 'list'>

print(pd.DataFrame(d['DATA']))
#     name  age
# 0  Alice   25
# 1    Bob   42

print(pd.io.json.json_normalize(d['DATA']))
#     name  age
# 0  Alice   25
# 1    Bob   42
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事