404 Not Found

404 Not Found


nginx

【Pandas マージ】merge()とjoin()の結合方法|inner・left・right・outer完全ガイド

現実世界のデータが1つのテーブルにまとまっていることはまずありません。ユーザー情報はあるテーブルに、注文履歴は別のテーブルに、商品詳細はさらに別のテーブルに、という具合です。分析ではこれらを互いに結びつける必要があり、それを実現するのがまさにmergeです。Pandasのmergeは、SQLのJOINタイプをすべて実装しています。本节では、Mermaid図を使って4種類すべての結合タイプの効果を図解し、「左テーブルから何が残り、右テーブルから何が残るのか」を明確に理解できるようにします。

⚠️ 注意: 以下のコードはローカルのPython環境で実行する必要があります。

1. 学習内容



2. ボブのユーザー・注文関連付け

(1) 問題点:2つの別々のテーブルでは分析できない

ボブにはusersテーブルとordersテーブルがあります。彼は「各ユーザーが何を買ったか」を確認したいと考えています。

PYTHON
import pandas as pd

users = pd.DataFrame({
    'user_id': [1, 2, 3, 4],
    'name': ['アリス', 'ボブ', 'チャーリー', 'キャロル']
})

orders = pd.DataFrame({
    'order_id': ['O001', 'O002', 'O003'],
    'user_id': [1, 2, 2],
    'amount': [120, 85, 200]
})

# どうやって組み合わせる?user_idで関連付ける必要がある
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

(2) 解決策:1行のmerge

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

: 基本的なマージ(難易度 ⭐)

PYTHON
import pandas as pd

users = pd.DataFrame({
    'user_id': [1, 2, 3, 4],
    'name': ['アリス', 'ボブ', 'チャーリー', 'キャロル']
})

orders = pd.DataFrame({
    'order_id': ['O001', 'O002', 'O003'],
    'user_id': [1, 2, 2],
    'amount': [120, 85, 200]
})

# 内部結合(デフォルト)— 一致するuser_idのみ
result = pd.merge(users, orders, on='user_id')
print(result)
#    user_id    name order_id  amount
# 0        1   アリス     O001     120
# 1        2     ボブ     O002      85
# 2        2     ボブ     O003     200
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。


3. 4つの結合タイプ

(1) Mermaid図

100%
graph TB
    subgraph Inner["inner — 一致する行のみ残す"]
        I1["user_id 1: アリス+O001"]
        I2["user_id 2: ボブ+O002"]
        I3["user_id 2: ボブ+O003"]
    end
    subgraph Left["left — 左テーブルの全行を残す"]
        L1["user_id 1: アリス+O001"]
        L2["user_id 2: ボブ+O002"]
        L3["user_id 2: ボブ+O003"]
        L4["user_id 3: チャーリー+NaN"]
        L5["user_id 4: キャロル+NaN"]
    end
    subgraph Right["right — 右テーブルの全行を残す"]
        R1["user_id 1: アリス+O001"]
        R2["user_id 2: ボブ+O002"]
        R3["user_id 2: ボブ+O003"]
    end
    subgraph Outer["outer — 両側の全行を残す"]
        O1["user_id 1: アリス+O001"]
        O2["user_id 2: ボブ+O002"]
        O3["user_id 2: ボブ+O003"]
        O4["user_id 3: チャーリー+NaN"]
        O5["user_id 4: キャロル+NaN"]
    end
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

(2) 4つの結合タイプの比較

タイプ how 左テーブルで不一致 右テーブルで不一致 行数
inner 'inner' 削除 削除 ≤min(left, right)
left 'left' 保持(NaNで埋める) 削除 = 左行数 × 一致数
right 'right' 削除 保持(NaNで埋める) = 右行数 × 一致数
outer 'outer' 保持(NaNで埋める) 保持(NaNで埋める) ≥max(left, right)

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

: 4つの結合タイプの比較(難易度 ⭐⭐)

PYTHON
import pandas as pd

left = pd.DataFrame({
    'id': [1, 2, 3],
    'name': ['アリス', 'ボブ', 'チャーリー']
})

right = pd.DataFrame({
    'id': [2, 3, 4],
    'score': [85, 92, 78]
})

# 内部結合 — id 2、3のみ(両テーブルに存在)
print("内部結合(INNER):")
print(pd.merge(left, right, on='id', how='inner'))
#    id    name  score
# 0   2     ボブ     85
# 1   3 チャーリー     92

# 左結合 — 左テーブルの全行、右は不一致のところをNaNで埋める
print("\n左結合(LEFT):")
print(pd.merge(left, right, on='id', how='left'))
#    id     name  score
# 0   1    アリス    NaN  ← 右テーブルに一致なし
# 1   2      ボブ   85.0
# 2   3  チャーリー   92.0

# 右結合 — 右テーブルの全行
print("\n右結合(RIGHT):")
print(pd.merge(left, right, on='id', how='right'))
#    id    name  score
# 0   2     ボブ     85
# 1   3 チャーリー     92
# 2   4    NaN     78  ← 左テーブルに一致なし

# 外部結合 — 両側の全行
print("\n外部結合(OUTER):")
print(pd.merge(left, right, on='id', how='outer'))
#    id     name  score
# 0   1    アリス    NaN
# 1   2      ボブ   85.0
# 2   3  チャーリー   92.0
# 3   4     NaN    78.0
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。


4. 複数キー結合と異なる列名

(1) 複数キー結合

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

: 複数キー結合(難易度 ⭐⭐)

PYTHON
import pandas as pd

sales = pd.DataFrame({
    'region': ['北', '北', '南', '南'],
    'category': ['家電', '衣料品', '家電', '衣料品'],
    'revenue': [5000, 800, 2000, 600]
})

targets = pd.DataFrame({
    'region': ['北', '北', '南', '南'],
    'category': ['家電', '衣料品', '家電', '衣料品'],
    'target': [4500, 1000, 2500, 500]
})

# 複数のキーで結合
result = pd.merge(sales, targets, on=['region', 'category'])
print(result)
#   region    category  revenue  target
# 0     北        家電     5000    4500
# 1     北       衣料品      800    1000
# 2     南        家電     2000    2500
# 3     南       衣料品      600     500

# 達成率を計算
result['achievement'] = (result['revenue'] / result['target'] * 100).round(1)
print(result[['region', 'category', 'achievement']])
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

(2) 異なる列名での結合

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

: left_on/right_on(難易度 ⭐⭐)

PYTHON
import pandas as pd

employees = pd.DataFrame({
    'emp_id': [1, 2, 3],
    'name': ['アリス', 'ボブ', 'チャーリー']
})

salaries = pd.DataFrame({
    'employee_id': [1, 2, 3],
    'salary': [75000, 92000, 68000]
})

# 列名が異なる:emp_id と employee_id
result = pd.merge(
    employees, salaries,
    left_on='emp_id', right_on='employee_id',
    how='inner'
)
print(result)
#    emp_id     name  employee_id  salary
# 0       1    アリス            1   75000
# 1       2      ボブ            2   92000
# 2       3  チャーリー            3   68000

# 冗長な列を削除
result = result.drop(columns='employee_id')
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。


5. suffixesとindicator

(1) 列名の競合を処理する

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

: 重複する列名に対するsuffixes(難易度 ⭐)

PYTHON
import pandas as pd

df1 = pd.DataFrame({
    'id': [1, 2],
    'value': [100, 200]
})

df2 = pd.DataFrame({
    'id': [1, 2],
    'value': [300, 400]
})

# デフォルトのsuffixes:_x と _y
result = pd.merge(df1, df2, on='id')
print(result)
#    id  value_x  value_y
# 0   1      100      300
# 1   2      200      400

# カスタムsuffixes
result2 = pd.merge(df1, df2, on='id', suffixes=('_left', '_right'))
print(result2)
#    id  value_left  value_right
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

(2) マージ元を診断するindicator

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

: 行の出所を追跡するindicator(難易度 ⭐⭐)

PYTHON
import pandas as pd

left = pd.DataFrame({'id': [1, 2, 3], 'name': ['アリス', 'ボブ', 'チャーリー']})
right = pd.DataFrame({'id': [2, 3, 4], 'score': [85, 92, 78]})

# _merge列を追加して各行の出所を確認する
result = pd.merge(left, right, on='id', how='outer', indicator=True)
print(result)
#    id     name  score      _merge
# 0   1    アリス    NaN   left_only
# 1   2      ボブ   85.0        both
# 2   3  チャーリー   92.0        both
# 3   4     NaN    78.0  right_only

# マージ元でフィルタリング
only_left = result[result['_merge'] == 'left_only']
print(f"左テーブルのみに存在:{len(only_left)} 行")  # 1(アリス)

# データの整合性を検証 — 一致しないレコードを見つける
unmatched = result[result['_merge'] != 'both']
print(f"一致しないレコード:{len(unmatched)}")  # 2
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。


6. validateとクロス結合

(1) 結合関係を検証するvalidate

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

: validateによる検証(難易度 ⭐⭐)

PYTHON
import pandas as pd

users = pd.DataFrame({'user_id': [1, 2, 3], 'name': ['アリス', 'ボブ', 'チャーリー']})
orders = pd.DataFrame({'order_id': ['O1', 'O2', 'O3'], 'user_id': [1, 2, 2]})

# 検証:各ユーザーの注文は最大1件であるべき(1:1)
# ユーザー2は2件の注文があるため、これは失敗します
try:
    result = pd.merge(users, orders, on='user_id', validate='1:1')
except Exception as e:
    print(f"検証に失敗しました:{e}")

# 検証:各ユーザーは複数の注文を持てる(1:m)— 成功
result = pd.merge(users, orders, on='user_id', validate='1:m')
print(result)  # OK

# 検証タイプ:'1:1'、'1:m'、'm:1'、'm:m'
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

(2) クロス結合

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

: クロス結合(難易度 ⭐)

PYTHON
import pandas as pd

colors = pd.DataFrame({'color': ['赤', '青']})
sizes = pd.DataFrame({'size': ['S', 'M', 'L']})

# すべての組み合わせ(直積)
result = pd.merge(colors, sizes, how='cross')
print(result)
#   color size
# 0    赤    S
# 1    赤    M
# 2    赤    L
# 3    青    S
# 4    青    M
# 5    青    L
# 6行 = 2色 × 3サイズ
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。


7. mergeとjoinの違い

機能 merge join
呼び出し形式 pd.merge(df1, df2) df1.join(df2)
デフォルトの結合キー onで指定した列 インデックス
複数列の結合 ✅ on=['a','b'] ❌ インデックスのみ
結合タイプ inner/left/right/outer left(デフォルト)
柔軟性 高い 低い
最適な用途 汎用的な結合 インデックスの位置合わせ

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

: joinのショートカット(難易度 ⭐)

PYTHON
import pandas as pd

df1 = pd.DataFrame({'A': [1, 2, 3]}, index=['x', 'y', 'z'])
df2 = pd.DataFrame({'B': [4, 5]}, index=['x', 'y'])

# joinはデフォルトでインデックスを使用
result = df1.join(df2)  # how='left'がデフォルト
print(result)
#    A    B
# x  1  4.0
# y  2  5.0
# z  3  NaN  ← 左結合は左テーブルの全行を保持

# howパラメータを指定したjoin
result2 = df1.join(df2, how='inner')
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。


8. 総合例:3テーブル結合分析

▶ サンプル

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

: 3テーブルマージのワークフロー(難易度 ⭐⭐⭐)

PYTHON
import pandas as pd

# ============================================
# 総合例:3テーブルのマージ
# users + orders + products → 完全な分析
# ============================================

# 1. ユーザーテーブル
users = pd.DataFrame({
    'user_id': [1, 2, 3, 4, 5],
    'name': ['アリス', 'ボブ', 'チャーリー', 'キャロル', 'デビッド'],
    'region': ['北', '南', '東', '北', '西']
})

# 2. 注文テーブル
orders = pd.DataFrame({
    'order_id': ['O001', 'O002', 'O003', 'O004', 'O005', 'O006'],
    'user_id': [1, 2, 2, 3, 1, 5],
    'product_id': ['P01', 'P02', 'P03', 'P01', 'P04', 'P02'],
    'quantity': [2, 1, 3, 1, 5, 2]
})

# 3. 商品テーブル
products = pd.DataFrame({
    'product_id': ['P01', 'P02', 'P03', 'P04', 'P05'],
    'product_name': ['ノートPC', 'スマホ', 'タブレット', 'マウス', 'キーボード'],
    'price': [999, 699, 349, 29, 79]
})

# ステップ1:orders + products → 価格付きの注文明細
order_details = pd.merge(orders, products, on='product_id', how='left')
order_details['total'] = order_details['quantity'] * order_details['price']

# ステップ2:order_details + users → 全体像
full = pd.merge(order_details, users, on='user_id', how='left')

# ステップ3:地域別に分析
region_sales = full.groupby('region')['total'].sum().sort_values(ascending=False)
print("=== 地域別売上 ===")
print(region_sales)

# ステップ4:上位顧客
top_customers = full.groupby('name')['total'].sum().sort_values(ascending=False)
print("\n=== 上位顧客 ===")
print(top_customers)

# ステップ5:一致しない商品(productsにはあるが一度も注文されていない)
unmatched = pd.merge(
    products, orders[['product_id']].drop_duplicates(),
    on='product_id', how='left', indicator=True
)
never_ordered = unmatched[unmatched['_merge'] == 'left_only']
print(f"\n=== 一度も注文されていない商品 ===")
print(never_ordered[['product_id', 'product_name']])
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンにより若干異なる場合があります。

❓ よくある質問

Q innerとleftはどちらを使うべきですか?
A デフォルトはinner(一致する行のみを保持し、クリーンなデータになる)にします。左テーブルの全行が必要な場合(例:「注文の有無にかかわらず全ユーザー」)はleftを使います。rightはほとんど使いません(テーブルを入れ替えてleftを使えば済みます)。outerは差分を見つける場合に使います(「注文のないユーザー+ユーザーのない注文」)。日常的な場面では80%の場合、innerが正しい選択です。
Q テーブル間でキーの列名が異なる場合は?
A left_onとright_onを使って、各テーブルのキー列を個別に指定します。例えば pd.merge(a, b, left_on='emp_id', right_on='employee_id') のようにします。マージ後は両方の列が残るので、冗長な方はdropで削除します。片方のキーがインデックスの場合は、left_index=True / right_index=Trueを使います。
Q suffixesの競合はどう処理しますか?
A 両テーブルに同名の列(キー以外)がある場合、mergeは自動的に_xと_yのsuffixを付与します。suffixes=('_left','_right')でカスタマイズできます。ベストプラクティスは、マージ前に列名を変更して曖昧さを避けることです:df.rename(columns={'value': 'value_a'})
Q mergeとjoinの違いは何ですか?
A mergeはより多用途で、列ベースの結合、複数キー結合、4種類すべての結合タイプをサポートします。joinはより簡潔で、デフォルトでインデックスに基づいて結合し、デフォルトは左結合です。複数列の結合が必要ならmergeを、単純なインデックスの位置合わせならjoinを使います。一般的には、完全性のためmergeを優先し、joinはショートカットとして扱います。
Q マージ結果が正しいことをどう検証しますか?
A 3段階でチェックします。①マージ前後の行数を比較(innerは≤min、leftは左行数×一致数になるはず)。②indicator=Trueで各行の出所を確認(both/left_only/right_only)。③validate='1:1'や'1:m'で期待する関係を表明し、違反すればエラーが発生します。
Q mergeで行数が爆発的に増えたら?
A 行数の爆発はm:m(多対多)マージを示しています。右テーブルの各キーがN行に一致すると、結果は左行数×Nになります。修正方法:マージ前に右テーブルを重複排除または集計(groupby + agg)して、一致する行を減らします。validate='m:1'を使えば早期に検出できます。
Q クロス結合はいつ使うべきですか?
A クロス結合は直積(全行×全行)を生成し、行数=左行数×右行数になります。組み合わせの生成(例:色×サイズ=全SKUバリエーション)にのみ使います。大きなテーブルにクロス結合は絶対に使わないでください。1000×1000=100万行になります!

📖 まとめ


📝 練習問題

  1. 基礎(難易度 ⭐):studentsテーブルとgradesテーブルを作成し(student_idを共有)、inner/left/outerマージを実行して、行数がどう変化するか観察してください。
  2. 応用(難易度 ⭐⭐):departmentsテーブル(dept_id/dept_name)とemployeesテーブル(emp_id/dept_id/salary)を作成して結合し、部門ごとの平均給与を計算し、indicatorを使って従業員がいない部門を見つけてください。
  3. 挑戦(難易度 ⭐⭐⭐):3つのテーブル(users/orders/products)をシミュレートし、以下を完成させてください:innerマージで結合 → ユーザーごとの総支出を計算 → leftマージで支出ゼロのユーザーを抽出 → validateで関係を検証 → indicatorで出所を診断。

← 前へ:GroupByと集計 · 次へ:ConcatとAppend →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%