【Python】【pandas】json_normalize()がよくわからない年頃
世界がおかしいのか、私がおかしいのか。
この関数の挙動がおかしいのか、私の理解がおかしいのか。
1.インターフェース概要
pandas.json_normalize(data, record_path=None, meta=None, meta_prefix=None, record_prefix=None, errors='raise', sep='.', max_level=None)
凖構造化JSONデータをフラットテーブルに正規化する関数。JSONデータとあるが、配列や辞書が入れ子構造となっている木構造データをテーブル形式に変換してくれる。
1-1. パラメータ
パラメーター | 型 | デフォルト | 説明(Google翻訳にぶち込んだだけ) |
---|---|---|---|
data | dict or list of dict | - | シリアル化されていないJSONオブジェクト。 |
record_path | str or list of str | None | 各オブジェクト内のレコードリストへのパス。渡されない場合、データはレコード配列であると見なされます。 |
meta | list of paths(str or list of str) | None | 結果テーブルの各レコードのメタデータとして使用するフィールド。 |
meta_prefix | str | None | Trueの場合、レコードの前にドット(?)パスが付きます。 たとえば、metaが["foo", "bar"]の場合は、foo.bar.fieldになります。 |
record_prefix | str | None | True の場合、レコードの先頭にドット (?) パスが付きます。 たとえば、レコードへのパスが [‘foo’, ‘bar’] の場合は foo.bar.field になります。 |
errors | {"raise", "ignore"} | "raise" | エラー処理を設定します。 ignore’ : メタにリストされているキーが常に存在するとは限らない場合、KeyError を無視します。 ‘raise’ : メタにリストされているキーが常に存在するとは限らない場合、KeyError を発生させます。 |
sep | str | "." | ネストされたレコードは、sep で区切られた名前を生成します。例: sep=’.’, {‘foo’: {‘bar’: 0}} -> foo.bar。 |
max_level | int | None | 正規化するレベルの最大数 (辞書の深さ)。 None の場合は、すべてのレベルを正規化します。 |
2. 動かせ!!!!
ごちゃごちゃと細かい事が書いてあるが、実際にドキュメントのサンプルコードを動かしてみれば一発で処理のイメージがつく。
import pandas as pd
data = [
{
"id": 1,
"name": "Cole Volk",
"fitness": {"height": 130, "weight": 60},
},
{
"name": "Mark Reg",
"fitness": {"height": 130, "weight": 60}
},
{
"id": 2,
"name": "Faye Raker",
"fitness": {"height": 130, "weight": 60},
},
]
pd.json_normalize(data, max_level=0)
id | name | fitness | |
---|---|---|---|
0 | 1.0 | Cole Volk | {'height': 130, 'weight': 60} |
1 | NaN | Mark Reg | {'height': 130, 'weight': 60} |
2 | 2.0 | Faye Raker | {'height': 130, 'weight': 60} |
続いて、max_level=1を指定するともう一段深い階層で表を作成してくれる。
pd.json_normalize(data, max_level=1)
id | name | fitness.height | fitness.weight | |
---|---|---|---|---|
0 | 1.0 | Cole Volk | 130 | 60 |
1 | NaN | Mark Reg | 130 | 60 |
2 | 2.0 | Faye Raker | 130 | 60 |
続いて、もう少し複雑なJSONデータを扱う。
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},
],
},
]
pd.json_normalize(
data,
record_path="counties",
meta=["state", "shortname", ["info", "governor"]]
)
record_path
は表にしたいデータを選択する。文字列、または上位からの文字列のリストを指定する。
meta
はrecord_path外にあるデータで、表に追加したいものを指定する。ここでは、"state", "shortname", 及び "info" -> "governor"を表に追加する。
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 |
3. 問題の挙動
次のようなデータ構造を持つJSONデータを扱う。
{
"GET_STATS": {
"RESULT": { HTTPのResult Codeなど },
"PARAMETER": { API問い合わせパラメータ },
"STATISTICAL_DATA": {
"RESULT_INF": { "TOTAL_NUMBER": 全件数 },
"TABLE_INF": { "STAT_NAME": 出典 },
"DATA_INF": {
"DATA_OBJ": [
{ "VALUE": { データ1 }},
{ "VALUE": { データ2 }},
{ "VALUE": { データ3 }},
:
]
}
}
}
}
統計ダッシュボードのAPIで取得できる。
import requests
api_url = "https://dashboard.e-stat.go.jp/api/1.0/Json/getData?Lang=JP&IndicatorCode=1402020000000010010&RegionalRank=3"
response = requests.get(api_url)
dict_json = response.json()
JSONデータはDictionary形式なので、次のようにアクセスしていくことで各データへアクセスすることができる。
dict_json["GET_STATS"]["STATISTICAL_DATA"]["DATA_INF"]["DATA_OBJ"][0]["VALUE"]
{'@indicator': '1402020000000010010',
'@unit': '055',
'@stat': '00200502',
'@regionCode': '01000',
'@time': '1975CY00',
'@cycle': '3',
'@regionRank': '3',
'@isSeasonal': '1',
'@isProvisional': '0',
'$': '78502'}
ここで、もしデータ構造が次のようにアクセス可能であれば、コンストラクタから比較的楽にDataFrameを作成できる。
dict_json["GET_STATS"]["STATISTICAL_DATA"]["DATA_INF"]["DATA_OBJ"]["VALUE"][0]
今回はコンストラクタから楽にDataFrameインスタンスを生成できないので、pd.json_normalize()
を使ってDataFrameを作成する。
pd.json_normalize(
dict_json["GET_STATS"]["STATISTICAL_DATA"],
record_path=["DATA_INF", "DATA_OBJ"],
max_level=2,
meta=[
["RESULT_INF"],
["TABLE_INF"],
],
)
VALUE.@indicator | VALUE.@unit | VALUE.@stat | VALUE.@regionCode | VALUE.@time | VALUE.@cycle | VALUE.@regionRank | VALUE.@isSeasonal | VALUE.@isProvisional | VALUE.$ | RESULT_INF | TABLE_INF | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1402020000000010010 | 055 | 00200502 | 01000 | 1975CY00 | 3 | 3 | 1 | 0 | 78502 | {'TOTAL_NUMBER': '2209'} | {'STAT_NAME': [{'@code': '00200502', '$': '社会・人口統計体系'}]} |
1 | 1402020000000010010 | 055 | 00200502 | 02000 | 1975CY00 | 3 | 3 | 1 | 0 | 11876 | {'TOTAL_NUMBER': '2209'} | {'STAT_NAME': [{'@code': '00200502', '$': '社会・人口統計体系'}]} |
2 | 1402020000000010010 | 055 | 00200502 | 03000 | 1975CY00 | 3 | 3 | 1 | 0 | 9472 | {'TOTAL_NUMBER': '2209'} | {'STAT_NAME': [{'@code': '00200502', '$': '社会・人口統計体系'}]} |
(略)
ここまではいい。
metaで追加した列がDictionaryオブジェクトになっているので、まずはTOTAL_NUMBERの値をそのまま出してみよう。
※注:TOTAL_NUMBERを表に出力する目的はjson_normalize()
を使って色々試すことであり、全件数が必要というわけではありません。
pd.json_normalize(
dict_json["GET_STATS"]["STATISTICAL_DATA"],
record_path=["DATA_INF", "DATA_OBJ"],
meta=[
["RESULT_INF", "TOTAL_NUMBER"],
["TABLE_INF"],
],
)
KeyError: "Key 'TOTAL_NUMBER' not found. To replace missing values of 'TOTAL_NUMBER' with np.nan, pass in errors='ignore'"
なぜぇぇぇぇぇぇーーーーー!!!
パラメーターにerrors="ignore"
を指定すれば、このエラーは回避されるが、どうやらTOTAL_NUMBERの値が認識されないみたいでNaN
となってしまう。
pd.json_normalize(
dict_json["GET_STATS"]["STATISTICAL_DATA"],
record_path=["DATA_INF", "DATA_OBJ"],
meta=[
["RESULT_INF", "TOTAL_NUMBER"],
["TABLE_INF"],
],
errors="ignore",
)
VALUE.@indicator | VALUE.@unit | VALUE.@stat | VALUE.@regionCode | VALUE.@time | VALUE.@cycle | VALUE.@regionRank | VALUE.@isSeasonal | VALUE.@isProvisional | VALUE.$ | RESULT_INF.TOTAL_NUMBER | TABLE_INF | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1402020000000010010 | 055 | 00200502 | 01000 | 1975CY00 | 3 | 3 | 1 | 0 | 78502 | nan | {'STAT_NAME': [{'@code': '00200502', '$': '社会・人口統計体系'}]} |
1 | 1402020000000010010 | 055 | 00200502 | 02000 | 1975CY00 | 3 | 3 | 1 | 0 | 11876 | nan | {'STAT_NAME': [{'@code': '00200502', '$': '社会・人口統計体系'}]} |
2 | 1402020000000010010 | 055 | 00200502 | 03000 | 1975CY00 | 3 | 3 | 1 | 0 | 9472 | nan | {'STAT_NAME': [{'@code': '00200502', '$': '社会・人口統計体系'}]} |
json_normalize()
のsourceをコピペしてデバッグ実行して確認してみたところ、GET_STATS.STATISTICAL_DATA.RESULT_INF.TOTAL_NUMBERというKeyで、値の問い合わせをGET_STATS.STATISTICAL_DATA.TABLE_INFに対して行っているため、値が見つからずにNaNになっているように見受けられるが・・・
これ以上時間を使って調べても、な感じなので、
「なんか使えない場合もある」ぐらいの認識で私の旅はここで終わろうと思います。
4. 結論
この記事ってなんか意味あるの?
Discussion