Pandas json_normalizeでネストされたJSONをDataFrameに展開!データ分析を加速 🚀


 

Web APIから取得したデータや、一部のNoSQLデータベースの出力は、ネストされた(階層構造を持つ)JSON形式で提供されることがよくあります。このような複雑なJSONデータをPandasのDataFrameで効率的に扱うためには、そのままでは困難です。そこで活躍するのが、Pandasの**json_normalize()関数**です。

json_normalize()は、ネストされた辞書のリストをフラットなDataFrameに変換し、階層構造を列として展開してくれる強力なツールです。この記事では、json_normalize()の基本的な使い方から、実践的なオプションまでを詳しく解説します。


 

ネストされたJSONとは? 🤔

 

通常のJSONはキーと値のペアで構成されますが、ネストされたJSONでは、値の中にさらに辞書やリストが含まれています。

例: ユーザー情報の中に、住所(address)が別の辞書として、スキル(skills)がリストとしてネストされている場合。

JSON
 
[
  {
    "id": 1,
    "name": "Alice",
    "address": {
      "street": "123 Main St",
      "city": "Tokyo"
    },
    "skills": ["Python", "SQL"]
  },
  {
    "id": 2,
    "name": "Bob",
    "address": {
      "street": "456 Oak Ave",
      "city": "Osaka"
    },
    "skills": ["Java"]
  }
]

このような構造のJSONを直接pd.DataFrame()で読み込もうとすると、addressskillsの列には辞書やリストがそのまま格納されてしまい、分析しにくい形になります。


 

json_normalize()の基本:ネストをフラット化 ✨

 

json_normalize()は、Pandas 1.0以降ではトップレベルのpandasモジュールに直接あります。辞書や辞書のリストを引数に取ります。

 

最もシンプルな使い方

 

ネストされた辞書のリストをjson_normalize()に渡すだけで、自動的にフラットなDataFrameに展開されます。

Python
 
import pandas as pd

# ネストされたJSONデータ (辞書のリスト)
data = [
  {
    "id": 1,
    "name": "Alice",
    "address": {
      "street": "123 Main St",
      "city": "Tokyo"
    },
    "skills": ["Python", "SQL"]
  },
  {
    "id": 2,
    "name": "Bob",
    "address": {
      "street": "456 Oak Ave",
      "city": "Osaka"
    },
    "skills": ["Java"]
  }
]

# json_normalizeでDataFrameに変換
df_flat = pd.json_normalize(data)
print("--- フラット化されたDataFrame ---")
print(df_flat)
# 出力例:
#    id   name     address.street address.city      skills
# 0   1  Alice        123 Main St        Tokyo  [Python, SQL]
# 1   2    Bob        456 Oak Ave        Osaka         [Java]

ご覧の通り、addressのネストされたキー(street, city)が、元のキー名とドット(.)で連結された新しい列名として展開されています。


 

json_normalize()の便利なオプション 🛠️

 

json_normalize()は、より複雑なJSON構造に対応するための柔軟なオプションを提供します。

 

1. record_path:展開するリストを指定

 

JSONの最上位が辞書で、その中に展開したいレコードのリストが含まれている場合、record_pathでそのリストのパスを指定します。

Python
 
import pandas as pd

# 例: 注文情報の中に商品リストがネストされている
order_data = [
  {
    "order_id": "A001",
    "customer": "Alice",
    "items": [
      {"item_id": "P001", "price": 100},
      {"item_id": "P002", "price": 200}
    ]
  },
  {
    "order_id": "A002",
    "customer": "Bob",
    "items": [
      {"item_id": "P003", "price": 300}
    ]
  }
]

# 'items'リストを展開
df_items = pd.json_normalize(order_data, record_path='items')
print("--- 'items'リストを展開 ---")
print(df_items)
# 出力例:
#   item_id  price
# 0    P001    100
# 1    P002    200
# 2    P003    300

 

2. meta:元の階層の情報を保持する

 

record_pathでリストを展開した場合、元の親要素の情報を新しいDataFrameに含めたいことがあります。その際にmetaオプションを使います。

Python
 
import pandas as pd

order_data = [
  {
    "order_id": "A001",
    "customer": "Alice",
    "items": [
      {"item_id": "P001", "price": 100},
      {"item_id": "P002", "price": 200}
    ]
  },
  {
    "order_id": "A002",
    "customer": "Bob",
    "items": [
      {"item_id": "P003", "price": 300}
    ]
  }
]

# 'items'を展開し、'order_id'と'customer'をメタデータとして含める
df_items_with_meta = pd.json_normalize(order_data, record_path='items',
                                       meta=['order_id', 'customer'])
print("\n--- 'items'展開 + 親要素のメタデータ ---")
print(df_items_with_meta)
# 出力例:
#   item_id  price order_id customer
# 0    P001    100     A001    Alice
# 1    P002    200     A001    Alice
# 2    P003    300     A002      Bob

 

3. errors='ignore' / 'raise':エラー処理

 

存在しないキーを指定した場合の挙動を制御します。

  • errors='raise' (デフォルト): キーが存在しない場合、エラーを発生させます。

  • errors='ignore': キーが存在しない場合、その列をNaNで埋めます。

Python
 
import pandas as pd

incomplete_data = [
  {"id": 1, "name": "Alice"},
  {"id": 2, "name": "Bob", "email": "bob@example.com"} # emailがないレコードがある
]

# 'email'列が存在しない場合でもエラーにしない
df_ignore_errors = pd.json_normalize(incomplete_data, errors='ignore')
print("\n--- 存在しないキーを無視 ---")
print(df_ignore_errors)
# 出力例:
#    id  name          email
# 0   1  Alice            NaN
# 1   2    Bob  bob@example.com

 

4. sep:展開された列名の区切り文字

 

デフォルトではドット(.)が使われますが、sepオプションで別の区切り文字を指定できます。

Python
 
import pandas as pd

data_sep = [
  {"user": {"name": "Charlie"}}
]

# 区切り文字をアンダースコアに変更
df_sep = pd.json_normalize(data_sep, sep='_')
print("\n--- 区切り文字を'_'に変更 ---")
print(df_sep)
# 出力例:
#    user_name
# 0    Charlie

 

まとめ

 

Pandasのjson_normalize()関数は、ネストされたJSONデータをフラットなDataFrameに効率的に変換するための不可欠なツールです。

  • 基本的なフラット化: pd.json_normalize(data)

  • リストの展開: record_pathオプション

  • 親要素情報の保持: metaオプション

  • エラー処理: errors='ignore'

  • 展開された列名の区切り文字: sepオプション

これらのオプションを使いこなすことで、複雑なJSON構造を持つデータも、Pandasを使ってスムーズに分析プロセスに乗せることができるでしょう。Webデータ分析を行う際には、ぜひjson_normalize()を積極的に活用してください!