LESSON

結合と変形

田中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相当説明主な用途
innerINNER JOIN両方に存在するキーのみ完全なデータのみ分析
leftLEFT JOIN左テーブルの全行を保持マスタに情報を付加
rightRIGHT JOIN右テーブルの全行を保持あまり使わない
outerFULL 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
)

まとめ

項目ポイント
mergeSQLのJOINに相当。inner/left/right/outerの4種類
concat同じ構造のDataFrameの縦・横連結
melt横持ち→縦持ちの変換(分析向け)
pivot縦持ち→横持ちの変換(レポート向け)
結合検証行数の変化と欠損値を必ず確認する

チェックリスト

  • merge の4つの結合タイプを使い分けられる
  • 結合後のデータ検証ができる
  • meltとpivotで縦持ち・横持ちを変換できる
  • 複数テーブルを結合するパイプラインを構築できる

次のステップへ

データの結合と変形を学びました。次は演習で、NetShop社の購買データを実際に前処理してみましょう。


推定読了時間:30分