コンテンツにスキップ

3.3.8 データ結合

  • merge(SQL 風の結合)を身につける
  • join(インデックスベースの結合)を理解する
  • concat(連結操作)を身につける
  • さまざまな結合方法の使い分けを理解する

データ結合は、「共通キーがあるかどうか」で考えると分かりやすいです。

Pandas 結合・連結・接続の図

この節で本当に解決したいのは、次の2点です。

  • どんなときに merge を最初に思い浮かべるべきか
  • どんなときに単純な連結でよいのか

実際のデータは、たいてい複数の表に分かれています。たとえば EC サイトなら、次のような表があります。

  • ユーザー表:ユーザーID、名前、登録時間
  • 注文表:注文ID、ユーザーID、商品、金額
  • 商品表:商品ID、名前、カテゴリ、価格

「各ユーザーが何を買ったか」を分析するには、これらの表を結合する必要があります。

flowchart LR
A["ユーザー表"] --> D["結合後の完全なデータ"]
B["注文表"] --> D
C["商品表"] --> D
style D fill:#4caf50,color:#fff

初心者向けの分かりやすいたとえ

Section titled “初心者向けの分かりやすいたとえ”

データ結合は、次のように考えると理解しやすいです。

  • さまざまな表にある情報を、同じ人や同じ記録にひも付ける

つまり、

  • merge は、身分証番号で2つの台帳をそろえるイメージ
  • concat は、2つの表を上下または左右に並べてつなぐイメージ

この違いはとても大切です。なぜなら、ここで次の2つを分けて考えられるようになるからです。

  • 「そろえる」
  • 「つなぐ」

この2つは、実は同じではありません。


merge は、最も強力な結合方法で、SQL の JOIN に似ています。

import pandas as pd
# ユーザー表
users = pd.DataFrame({
"ユーザーID": [1, 2, 3, 4],
"名前": ["張三", "李四", "王五", "趙六"],
"都市": ["北京", "上海", "広州", "深圳"]
})
# 注文表
orders = pd.DataFrame({
"注文ID": [101, 102, 103, 104, 105],
"ユーザーID": [1, 2, 1, 3, 5], # 注意:ユーザー5はユーザー表にいない
"商品": ["スマホ", "パソコン", "イヤホン", "タブレット", "キーボード"],
"金額": [5999, 8999, 299, 3999, 199]
})

両方にあるものだけを残します。

result = pd.merge(users, orders, on="ユーザーID", how="inner")
print(result)
# ユーザーID 名前 都市 注文ID 商品 金額
# 0 1 張三 北京 101 スマホ 5999
# 1 1 張三 北京 103 イヤホン 299
# 2 2 李四 上海 102 パソコン 8999
# 3 3 王五 広州 104 タブレット 3999
# ユーザー4(趙六)には注文がない → 表示されない
# ユーザー5 はユーザー表にない → 表示されない

左側の表の行をすべて残します。

result = pd.merge(users, orders, on="ユーザーID", how="left")
print(result)
# ユーザーID 名前 都市 注文ID 商品 金額
# 0 1 張三 北京 101.0 スマホ 5999.0
# 1 1 張三 北京 103.0 イヤホン 299.0
# 2 2 李四 上海 102.0 パソコン 8999.0
# 3 3 王五 広州 104.0 タブレット 3999.0
# 4 4 趙六 深圳 NaN NaN NaN ← 趙六には注文がないので NaN になる

右側の表の行をすべて残します。

result = pd.merge(users, orders, on="ユーザーID", how="right")
print(result)
# ユーザー5 が表示される(名前と都市は NaN)

両方の表の行をすべて残します。

result = pd.merge(users, orders, on="ユーザーID", how="outer")
print(result)
# すべてのユーザーとすべての注文が表示され、足りない部分は NaN で埋められる
結合残る ID意味
inner{1,2,3}両方にある行
left{1,2,3,4}左表の全行と、右表の一致情報
right{1,2,3,5}右表の全行と、左表の一致情報
outer{1,2,3,4,5}すべて残す

初心者がまず覚えやすい早見表

Section titled “初心者がまず覚えやすい早見表”
目的まず思い浮かべる方法
両方に一致する行だけ残したいinner merge
左表を基準にして、右表の情報を補いたいleft merge
両方を残して、足りない部分は NaN にしたいouter merge
複数の表を上下にくっつけたいconcat(axis=0)
複数の列を左右に並べたいconcat(axis=1)

この表は初心者にとても役立ちます。なぜなら、「結合方法がたくさんある」という状態を、よくある業務目的に整理し直してくれるからです。

# 2つの表で結合列の名前が違う場合
df1 = pd.DataFrame({"user_id": [1, 2], "name": ["A", "B"]})
df2 = pd.DataFrame({"uid": [1, 2], "score": [90, 85]})
result = pd.merge(df1, df2, left_on="user_id", right_on="uid")
print(result)
# 複数の列で一致させる
result = pd.merge(df1, df2, on=["col1", "col2"])

concat は、複数の DataFrame を縦または横に連結するときに使います(共通キーは不要です)。

まずいちばん大事なのはこれです。

concat は「キーをそろえる」のではなく、「表をつなぐ」操作です。

なので、頭の中で考えていることが

  • ユーザーID が一致するか

なら、まずは merge を考えるのが自然です。

# 1月と2月の売上データ
jan = pd.DataFrame({
"商品": ["リンゴ", "牛乳"],
"売上": [100, 80],
"": ["1月", "1月"]
})
feb = pd.DataFrame({
"商品": ["リンゴ", "パン"],
"売上": [120, 90],
"": ["2月", "2月"]
})
# 上下に連結
all_sales = pd.concat([jan, feb], ignore_index=True)
print(all_sales)
# 商品 売上 月
# 0 リンゴ 100 1月
# 1 牛乳 80 1月
# 2 リンゴ 120 2月
# 3 パン 90 2月
info = pd.DataFrame({"名前": ["張三", "李四"], "年齢": [22, 25]})
scores = pd.DataFrame({"数学": [90, 85], "英語": [88, 92]})
# 左右に連結
combined = pd.concat([info, scores], axis=1)
print(combined)
# 名前 年齢 数学 英語
# 0 張三 22 90 88
# 1 李四 25 85 92

方法適用場面たとえ
merge共通列で2つの表を結合するSQL JOIN
concat単純に上下または左右に連結するのりで貼り合わせる
joinインデックスで結合する特別な merge
flowchart TD
A["データを結合したい"] --> B{"共通の key 列はある?"}
B -->|"ある"| C["merge を使う"]
B -->|"ない、ただ並べたい"| D{"上下に並べる?左右?"}
D -->|"上下"| E["concat(axis=0)"]
D -->|"左右"| F["concat(axis=1)"]

初心者がそのまま使えるデータ結合チェックリスト

Section titled “初心者がそのまま使えるデータ結合チェックリスト”

初めて複数表の問題に取り組むときは、次の順番で確認すると安全です。

  1. 共通キーはあるか?
  2. キーの型と値の範囲は一致しているか?
  3. 結合後に行数が増えたり減ったりするのはなぜか?
  4. 今やっているのは「そろえる」ことか、それとも「つなぐ」ことか?

この4つを先に考えるだけで、merge / concat の問題は、魔法のように見えなくなります。


import pandas as pd
# 3つの表を作成
# タスク表
tasks = pd.DataFrame({
"タスク ID": [1, 2, 3, 4, 5],
"機能": ["ログイン API", "RAG デモ", "グラフ画面", "デプロイスクリプト", "評価レポート"],
"モジュール": ["バックエンド", "AI", "フロントエンド", "運用", "AI"]
})
# 作業ログ表(タスクによっては複数の作業記録がある)
work_logs = pd.DataFrame({
"タスク ID": [1, 1, 2, 2, 3, 3, 4, 4, 5, 5],
"段階": ["設計", "実装", "設計", "実装", "設計", "実装", "実装", "検証", "設計", "検証"],
"時間": [2.0, 5.0, 3.0, 6.5, 1.5, 4.0, 3.5, 1.0, 2.5, 2.0]
})
# モジュール担当表
modules = pd.DataFrame({
"モジュール": ["バックエンド", "AI", "フロントエンド", "運用"],
"担当者": ["Mina", "Kai", "Riley", "Noah"],
"スプリント目標": ["安定した API", "根拠のある回答", "読みやすい UI", "再現できるリリース"]
})
# 結合1:タスク + 作業ログ
task_logs = pd.merge(tasks, work_logs, on="タスク ID")
print(task_logs.head())
# 結合2:さらにモジュール担当を追加
full = pd.merge(task_logs, modules, on="モジュール")
print(full.head())
# 分析:各モジュールの平均作業時間
print(full.groupby(["モジュール", "担当者"])["時間"].mean())
# 分析:各タスクの合計作業時間ランキング
total_hours = full.groupby(["タスク ID", "機能"])["時間"].sum().reset_index()
total_hours["順位"] = total_hours["時間"].rank(ascending=False, method="dense")
print(total_hours.sort_values("順位"))

このページを終えたら、この evidence card を残します。

データフレーム状態
列、dtype、行数、欠損値、サンプル行
操作
read/write、select/filter、clean、transform、groupby、merge、または時系列処理
出力
resulting table、保存ファイル、aggregation、join結果、または時系列インデックスビュー
失敗確認
dtype 不一致、欠損データ、重複キー、チェーン代入、または誤った時間頻度
期待される成果
前後の表サンプルと、変換理由
操作関数重要な引数
SQL 風の結合pd.merge()on, how (inner/left/right/outer)
縦方向の連結pd.concat(axis=0)ignore_index=True
横方向の連結pd.concat(axis=1)
インデックスでの結合df.join()how

この節で必ず持ち帰りたいこと

Section titled “この節で必ず持ち帰りたいこと”
  • merge は共通キーでそろえる操作、concat は表をつなぐ操作
  • 「共通キーがあるか?」を先に考えると、どの方法を使うか判断しやすい
  • 複数表の分析では、後の集計ミスよりも、最初の結合ミスのほうがよく起こる

# 2つの表がある:社員表と部署表
# 1. inner join で結合する
# 2. left join で部署が未割り当ての社員を見つける
# 3. outer join で社員がいない部署を見つける
# 商品表、注文表、顧客表を作成する
# 1. 3つの表を結合して1つの完全な表にする
# 2. 各顧客がどのカテゴリの商品を買ったかを分析する
# 3. 購入金額が最も高い上位3人の顧客を見つける
# 四半期ごとの売上データ(4つの独立した DataFrame)がある
# 1. 縦方向に連結して年間データにする
# 2. "四半期" 列を追加してデータの出所を示す
# 3. 年間の各四半期の売上傾向を集計する
参考実装と解説
  • 一致したキーだけが必要なら inner join、左表を正とするなら left join、両側の不一致を調べたいなら outer join を使います。
  • 結合前に重複キーを確認し、関係が 1 対 1、1 対多、多対多のどれかを決めます。可能なら validate= を使い、想定外の重複を Pandas に検出させます。
  • 各 merge の後は、行数を比較し、結合列の null を確認し、未一致キーをサンプル表示します。これらを書き残して初めて結合完了です。