【Python】Pandasを利用して、グループ化されたDataFrameに対して集約関数を適用する

# 概要
Pandasライブラリを使用して、JSONファイルからデータを読み込み、とある条件が一致したものをグループ化し加工（=DataFrameに対して集約関数を適用）したデータを、最終的にExcelファイルに保存する処理を行いました。

## サンプルコード解説

以下のような JSONがあるとします。


```json
{
	"tokyo": [
		{
			"country": "日本",
			"country_code": "JP",
			"area": "アジア",
			"tokyo_memo": "東京で有名なものといえば、東京タワーですね!"
		}
	],
    "osaka": [
		{
			"country": "日本",
			"country_code": "JP",
			"area": "アジア",
			"osaka_memo": "大阪といえば！やっぱりたこ焼きとお好み焼きです。"
		}
	]
}
```

上記のコードから、国、国コード、エリアが一致したものをグループ化し、それぞれのメモとその文字数を表示したものをエクセルに変換します。

```py
import pandas as pd
import json

# JSONファイルを読み込む
json_file = 'sample.json'
with open(json_file, 'r', encoding='utf-8') as f:
    data = json.load(f)

# "tokyo"データをDataFrameに変換
tokyo_df = pd.DataFrame(data['tokyo'])

# "osaka"データをDataFrameに変換
osaka_df = pd.DataFrame(data['osaka'])

# 同じ組み合わせのデータをグループ化してまとめる
grouped_tokyo = tokyo_df.groupby(['country', 'country_code', 'area']).agg({
    'tokyo_memo': 'first'
}).reset_index()

grouped_osaka = osaka_df.groupby(['country', 'country_code', 'area']).agg({
    'osaka_memo': 'first'
}).reset_index()

# 文字数を計算して新しいカラムとして追加
grouped_tokyo['tokyo_length'] = grouped_tokyo['tokyo_memo'].apply(len)
grouped_osaka['osaka_length'] = grouped_osaka['osaka_memo'].apply(len)

# 両方のDataFrameをマージ
merged_df = pd.merge(grouped_tokyo, grouped_osaka, on=['country', 'country_code', 'area'], how='left')

# カラムの順番を調整
final_columns = ['country', 'country_code', 'area', 'tokyo_memo', 'tokyo_length', 'osaka_memo', 'osaka_length']
merged_df = merged_df[final_columns]

# Excelファイルに保存
excel_file = 'output_sample.xlsx'
merged_df.to_excel(excel_file, index=False)

print(f"Excelファイル '{excel_file}' にデータが保存されました。")

```

結果、以下のように集約されたエクセルファイルが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/d1a77f1d-9ef7-57b6-b8ac-9c094fe98bf5.png)


### グループ化と集約について

まず、Pandasを利用して`"tokyo_df"`と`"osaka_df"`それぞれのDataFrameを、`'country'`、`'country_code'`、`'area'`の組み合わせでグループ化し、`'XXX_memo'`列の最初の値を集約します。

・`groupby`メソッドは、指定したカラムまたはカラムのリストに基づいて、データをグループ化するために使用されます。ここでは、`'country'`、`'country_code'`、および`'area'`の3つのカラムに基づいて、先ほど作ったDataFrameをグループ化します。

・`agg`メソッドは、グループごとに集約関数を適用してデータを集約するために使用されます。ここでは、`'XXX_memo'`カラムに対して、`'first'`という集約関数を適用しています。これにより、各グループごとに最初の行の`'XXX_memo'`の値が抽出されます。

※`agg`の集約関数には、`first`のほかにも`sum`や`count`などもあります。
以下はカテゴリーのグループごとに集約結果（最初の値と合計値）を表示した例です。

```py
data = {
    'category': ['A', 'B', 'A', 'B', 'A', 'B'],
    'value': [10, 20, 15, 25, 30, 40],
    'note': ['note1', 'note2', 'note3', 'note4', 'note5', 'note6']
}
df = pd.DataFrame(data)

# グループごとに値を取得する
agg_dict = {
    'value': 'sum',  # 'value'カラムの合計の値を取得
    'note': 'first'    # 'note'カラムの最初の値を取得
}
grouped = df.groupby('category').agg(agg_dict)

print(grouped)

# 出力結果
#           value   note
# category              
# A            55  note1
# B            85  note2
```

・`reset_index`メソッドは、インデックスをリセットして新しいデフォルトの整数インデックスを持つDataFrameを作成します。グループ化と集約の後、元のDataFrameである`tokyo_df`からインデックスを外して新しいDataFrameを生成します。これにより、元のグループ化されたカラム（`'country'`、`'country_code'`、`'area'`）は通常のカラムになります。

このリセット処理は、出力後のエクセルにこれらのカラムが必要な場合のみ実施すればOKです。
必要である（`final_columns`に記載している）のにこのメソッドを記載しない場合、以下のエラーになります。

```
    raise KeyError(f"{not_found} not in index")
KeyError: "['country', 'country_code', 'area'] not in index"
```


### 左結合について
最後に、`"tokyo"`と`"osaka"`のデータをそれぞれ集約したDataFrameを、`'country'`、`'country_code`'、`'area'`の値をキーとして左結合して1つのDataFrameにマージしています。

`pd.merge()`は、Pandasのデータフレームを結合するための関数。`on=`でマージの際の結合キー（列）を指定しています。
この場合、`'country'`、`'country_code`'、`'area'`の3つのカラムの値が一致する行同士がマージされます。

`how='left':`は、マージの方法を指定しています。
`'left'`は左側のDataFrame（ここでは`grouped_tokyo`）のすべての行を保持し、右側のDataFrame（ここでは`grouped_osaka`）の対応する行がある場合は結合します。右側のDataFrameに対応する行がない場合は、欠損値（`NaN`）が埋められます。