結合と変形
田中VPoE「NetShop社のデータは、購買履歴、顧客マスタ、商品マスタが別々のテーブルになっている。分析するには、これらを結合する必要があるんだ。」
あなた「SQLのJOINみたいなことをPandasでやるんですね。」
田中VPoE「その通り。さらに、分析目的に応じてデータの形を変える(変形する)こともよくある。横持ち・縦持ちの変換は特に頻出だよ。」
データの結合
merge(SQLのJOINに相当)
import pandas as pd
# 内部結合(両方にあるキーのみ)
result = pd.merge(orders, customers, on='customer_id', how='inner')
# 左外部結合(左テーブルの全行を保持)
result = pd.merge(orders, customers, on='customer_id', how='left')
# 右外部結合
result = pd.merge(orders, customers, on='customer_id', how='right')
# 完全外部結合
result = pd.merge(orders, customers, on='customer_id', how='outer')
結合の種類
| 結合タイプ | SQL相当 | 説明 | 主な用途 |
|---|---|---|---|
| inner | INNER JOIN | 両方に存在するキーのみ | 完全なデータのみ分析 |
| left | LEFT JOIN | 左テーブルの全行を保持 | マスタに情報を付加 |
| right | RIGHT JOIN | 右テーブルの全行を保持 | あまり使わない |
| outer | FULL OUTER JOIN | 両方の全行を保持 | 結合漏れの確認 |
結合キーが異なる場合
# キー名が異なるテーブルの結合
result = pd.merge(
orders, products,
left_on='product_code',
right_on='item_id',
how='left'
)
複数キーでの結合
# 複数キーで結合
result = pd.merge(
daily_sales, targets,
on=['year', 'month', 'category'],
how='left'
)
結合の検証
結合後は必ず結果を確認しましょう:
print(f"結合前 - orders: {len(orders)}, customers: {len(customers)}")
result = pd.merge(orders, customers, on='customer_id', how='left')
print(f"結合後: {len(result)}")
# キーの重複による行数増加を確認
assert len(result) == len(orders), "結合でレコードが増加しています!"
# 結合できなかったレコードの確認
unmatched = result[result['customer_name'].isna()]
print(f"結合できなかったレコード: {len(unmatched)}")
concat(縦・横の連結)
# 縦方向の連結(同じカラム構造のDataFrame)
all_months = pd.concat([jan_df, feb_df, mar_df], ignore_index=True)
# 横方向の連結
combined = pd.concat([df1, df2], axis=1)
データの変形
melt(横持ち → 縦持ち)
横に広がったデータを縦に変換します:
# 横持ちデータ
# customer_id jan_sales feb_sales mar_sales
# C001 10000 15000 12000
# 縦持ちに変換
melted = pd.melt(
wide_df,
id_vars=['customer_id'],
value_vars=['jan_sales', 'feb_sales', 'mar_sales'],
var_name='month',
value_name='sales'
)
# 結果:
# customer_id month sales
# C001 jan_sales 10000
# C001 feb_sales 15000
# C001 mar_sales 12000
pivot(縦持ち → 横持ち)
縦に並んだデータを横に展開します:
# 縦持ちデータを横持ちに変換
pivoted = melted.pivot(
index='customer_id',
columns='month',
values='sales'
)
使い分けの指針
| 形式 | メリット | 適する場面 |
|---|---|---|
| 縦持ち(tidy data) | groupbyしやすい、可視化しやすい | 分析・集計・グラフ作成 |
| 横持ち | 一覧性が高い、比較しやすい | レポート表示・クロス集計 |
マルチインデックス
groupbyの結果などで、複数レベルのインデックスが生成されることがあります:
# マルチインデックスの例
summary = df.groupby(['category', 'month'])['amount'].sum()
# マルチインデックスの操作
summary.loc['家電'] # 第1レベルでフィルタ
summary.loc[('家電', '1月')] # 両レベルで指定
# マルチインデックスの解除
summary_flat = summary.reset_index()
実践的なデータ結合パイプライン
NetShop社のデータを結合する実例です:
# Step 1: 各データの読み込み
orders = pd.read_csv('orders.csv', parse_dates=['order_date'])
customers = pd.read_csv('customers.csv')
products = pd.read_csv('products.csv')
# Step 2: 購買履歴に顧客情報を結合
df = pd.merge(orders, customers, on='customer_id', how='left')
# Step 3: 商品情報を結合
df = pd.merge(df, products, on='product_id', how='left')
# Step 4: 結合結果の検証
print(f"最終レコード数: {len(df)}")
print(f"欠損値:\n{df.isnull().sum()}")
# Step 5: 年月カラムの追加
df['year_month'] = df['order_date'].dt.to_period('M')
# Step 6: カテゴリ別・月別の売上集計
monthly_category_sales = df.pivot_table(
values='amount',
index='year_month',
columns='category',
aggfunc='sum',
fill_value=0
)
まとめ
| 項目 | ポイント |
|---|---|
| merge | SQLのJOINに相当。inner/left/right/outerの4種類 |
| concat | 同じ構造のDataFrameの縦・横連結 |
| melt | 横持ち→縦持ちの変換(分析向け) |
| pivot | 縦持ち→横持ちの変換(レポート向け) |
| 結合検証 | 行数の変化と欠損値を必ず確認する |
チェックリスト
- merge の4つの結合タイプを使い分けられる
- 結合後のデータ検証ができる
- meltとpivotで縦持ち・横持ちを変換できる
- 複数テーブルを結合するパイプラインを構築できる
次のステップへ
データの結合と変形を学びました。次は演習で、NetShop社の購買データを実際に前処理してみましょう。
推定読了時間:30分