pandasのjson_normalizeで辞書のリストをDataFrameに変換
pandas.json_normalize()
を使うと共通のキーをもつ辞書のリストをpandas.DataFrame
に変換できる。
Web APIなどで取得できるJSONによく使われる形式なので、それをpandas.DataFrame
に変換できるのは非常に便利。
ここでは以下の内容について説明する。
pandas.DataFrame()
による変換pandas.json_normalize()
の基本的な使い方- より複雑な場合: 引数
record_path
,meta
- JSON文字列・ファイルの一部を読み込み
辞書やリストからなるオブジェクトではなくJSON形式の文字列やファイルを直接pandas.DataFrame
として読み込むにはpandas.read_json()
を使う。
なお、pandas.json_normalize()
となったのはpandas1.0.0
からで、それ以前のバージョンではpandas.io.json.json_normalize()
として提供されていた。1.2.2
時点ではpandas.io.json.json_normalize()
もまだ使えるが、Deprecated(非推奨)となっており、警告(FutureWarning
)が出る。
- What’s new in 1.0.0 (January 29, 2020) - Deprecations — pandas 1.0.0 documentation
- Move json_normalize to pd namespace · Issue #27586 · pandas-dev/pandas
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.json_normalize()
でも同じ結果。
print(pd.json_normalize(l_simple))
# name age
# 0 Alice 25.0
# 1 Bob NaN
pandas.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.json_normalize()
を使うと、ネストした辞書もキーごとに個別の列として変換される。
print(pd.json_normalize(l_nested))
# name age id.x id.y
# 0 Alice 25.0 2 8
# 1 Bob NaN 10 4
ネストしている部分はデフォルトでは<親のキー>.<子のキー>
が列名となる。このセパレータ.
は引数sep
で変更できる。
print(pd.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.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.json_normalize(l_complex, record_path='data'))
# a b
# 0 1 2
# 1 3 4
# 2 10 20
# 3 30 40
print(pd.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.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.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.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.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.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.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.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.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.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_path
やmeta
を設定すると、pandas.json_normalize()
でpandas.DataFrame
に変換できる。
print(pd.json_normalize(d))
# OTHER DATA
# 0 x [{'name': 'Alice', 'age': 25}, {'name': 'Bob',...
print(pd.json_normalize(d, record_path='DATA'))
# name age
# 0 Alice 25
# 1 Bob 42
print(pd.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.json_normalize(d['DATA']))
# name age
# 0 Alice 25
# 1 Bob 42