論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
内部結合
Inner Join
データ処理

🔖 キーワード索引

直感で掴む 定義 数式を言葉で読み解く SSDSE-B-2026 実値計算 Python での扱い よくある落とし穴 関連手法・派生 関連用語 関連グループ教材 概念マップ 隣接手法への橋渡し 手法選択フロー

💡 30秒で分かる結論

🍰 まずはやさしく

共通点があるデータだけを集める方法です。

必要な情報だけをまとめて分析するために使います。

部活の名簿と出席簿を合わせて確認するような操作です。

この手法のポイントと注意点を短時間で学びます。

両方のテーブルに存在するキーのみ残す結合

inner join を 30 秒で把握する重要ポイント:

📍 あなたが今見ているもの

🍰 まずはやさしく

2つの表を横につなげる操作のことです。

バラバラの表から共通のデータを探すために使います。

スマホの連絡先とSNSのアカウントを紐付ける例です。

内部結合という操作の内容について詳しく読みます。

あなたは今、 内部結合 (Inner Join) という、 2 つの表 (DataFrame) を共通キーで横方向に連結する操作の用語ページを見ています。 SQL の INNER JOIN、 pandas の pd.merge(..., how='inner') に相当し、 「両方の表に存在するキーだけ残す」点が 外部結合 との決定的な違いです。 SSDSE-B-2026 のような公的統計を複数年・複数指標で組み合わせるときの基本動作です。

🎨 直感で掴む

🍰 まずはやさしく

データを使いやすく整えるパズルのようなものです。

分析しやすい形に情報をまとめるために使います。

買い物リストと店の在庫表を照らし合わせる例です。

使い方の流れや注意点を順番に見ていきましょう。

データを「使える形に整える」工程。 結合・集約・フィルタリングが基本操作。

本ページでは 内部結合 を、 定義・前提条件・使い方・落とし穴の順に整理して解説します。 厳密な定義より、 まず何を、 いつ、 どう使うかを理解することを優先してください。

inner join を直感的に理解するには、 「具体的な日常の場面に当てはめる」のが効果的。 抽象的な定義から入るより、 「これは何の役に立つのか」「どんな場面で使うか」「他の手段とどう違うのか」の 3 点を先に押さえることで、 数式や手続きの理解が定着しやすい。

本ページでは inner join を「データから意思決定までの一連のプロセス」に位置付け、 (1) 入力、 (2) 処理、 (3) 出力、 (4) 解釈 の 4 段階で順に解説する。 この枠組みで他の手法と比較すれば、 inner join の独自性と共通性が見えてくる。

具体例として、 SSDSE-B-2026 のデータを使い、 inner join を実際に動かすイメージを次節以降で示す。 数式 → 値代入 → 手計算 → Python 実装 の流れで、 抽象と具体を行き来しながら理解を深める。

🎮 結合ラボ — INNER / LEFT / RIGHT / FULL で行がどう作られるか

左に注文テーブル、 右に顧客テーブルを置き、 共通キー 顧客ID で結合します。 ボタンで結合の種類を切り替えると、 どの行が残りどこに NULL が入り行数がどう変わるかがアニメーションで確認できます。 一致した行どうしは線で結ばれ、 不一致の行は薄く表示されます。 このラボは SQLプレイグラウンド とは別に、 「結合の種類と行のマッチング」だけに集中しています。

注文テーブル (左)
顧客テーブル (右)

🔵 ベン図で見る対象領域

選んだ結合が「キー集合のどの部分を残すか」を色で示します。 左=注文キー右=顧客キー、 重なりが両方に存在するキーです。

注文のみ 両方 顧客のみ

💥 重複キーによる行数爆発 (1:N・N:M)

同じキーが両テーブルに複数あると、 結果はその掛け算だけ増えます。 スライダー (ドラッグ操作) で、 キー C1 が左表に m 件・ 右表に n 件あるときの一致行数を体感してください。

左表のキー C1 件数 m = 2
右表のキー C1 件数 n = 1

1:1 なら行は増えませんが、 うっかり 1:N や N:M で結合すると行数が m × n に膨れ、 平均や合計が二重計上されます。 結合前に df['C1'].value_counts() でキーの重複を必ず確認しましょう。

📐 定義

🍰 まずはやさしく

両方の表にある共通の印だけを残すルールです。

データ処理という分野で基本の道具として使います。

学校のクラス名と生徒番号を一致させる例です。

この用語の意味と使うための条件について学びます。

両方のテーブルに存在するキーのみ残す結合

英語名 Inner Join

🎯 いつ・どこで使うか

📋 前提条件・適用範囲

この用語を理解・使用するときは、 次のような前提を意識してください:

🔬 数式を言葉で読み解く (詳細版)

内部結合は集合論で書くと次のとおりです。 2 つのリレーション $R(K, A)$ と $S(K, B)$ があり、 $K$ が共通キー、 $A$, $B$ がそれ以外の属性のとき:

$$R \bowtie_K S = \{ (k, a, b) \mid (k, a) \in R \land (k, b) \in S \}$$

記号の読み方

記号意味SSDSE-B-2026 での具体例
$R$左の表人口表 (Code, Prefecture, A1101)
$S$右の表経済表 (Code, C3301) ※着工建築物数
$K$共通キーCode (R01000 〜 R47000)
$\bowtie$結合演算 (ジョイン)pd.merge(df1, df2, on='Code')
$\land$論理 AND (両方真)「両方の表に Code が存在する」

「両方に存在する」を 1 行ずつ追う

行 $r=(k_1, a_1)$ が $R$ にあるとします。 内部結合の出力に $r$ が現れる条件は「$S$ に $k_1$ を持つ行が存在する」こと。 もし $S$ に $k_1$ が複数あれば、 $r$ はそれぞれと組まれて出力行が増えます (デカルト積)。 $S$ に $k_1$ がなければ、 $r$ は出力に現れません (情報損失)。 したがって出力行数 $n_{\bowtie}$ は次で表されます:

$$n_{\bowtie} = \sum_{k \in K_R \cap K_S} n_R(k) \cdot n_S(k)$$

$K_R$ は $R$ のキー集合、 $K_R \cap K_S$ がいわゆる「両方に存在するキー集合」です。 SSDSE-B-2026 の都道府県コードはすべてユニーク (重複なし) なので、 結合後も 47 行で済みます。

🔬 関連手法比較表 (結合の種類)

結合残る行pandasSSDSE 例での出力行数用途
内部 inner両方にあるキーのみhow='inner'46 (沖縄欠ければ 46)完全データ抽出
左 left左側を全部残すhow='left'47 (沖縄は NaN)マスタ拡充
右 right右側を全部残すhow='right'46使用頻度は低い
外 outerどちらかにあれば残すhow='outer'47 (沖縄含む、 NaN 付き)差分検出
交差 cross全組合せhow='cross'47 × 47 = 2,209全ペア比較
縦 concat行方向に追加pd.concat([a,b])94 行縦に伸ばす別操作

💔 内部結合の失敗例 5 連発

  1. キー型不一致: 左 Code が文字列 'R13000'、 右が整数 13000。 全行 both ゼロで気づかず空集合に。 対策: df.dtypes で確認、 astype(str) で揃える。
  2. 余分な空白: 'R13000' と 'R13000 ' (末尾空白)。 内部結合で消える。 対策: df['Code'] = df['Code'].str.strip()
  3. 多対多結合: 顧客 ID が両方で重複していて、 結合後行数が想定 1,000 → 実際 27,000 に膨張。 対策: validate='one_to_one' または事前 drop_duplicates
  4. NaN を残した結合: 左の Code に NaN があると pandas は inner で除外。 「47 行のはずが 46」を見落とす。 対策: 事前 dropna(subset=['Code'])
  5. サイレントな大量削除: 結合前 100 万行 → inner 結合で 3,000 行に。 ログ出さず気づかない。 対策: 必ず print(len(before), len(after)) を併記、 削減率 90% 超なら警告。

📝 演習問題 5 問

  1. Q1: SSDSE-B-2026 から「人口 A1101」と「着工建築物数 C3301」の 2 表を作り、 inner join した結果の shape を答えよ。 (答: (47, 4))
  2. Q2: 右表から東京 (R13000) を抜くと、 inner join の結果は何行になるか。 (答: 46)
  3. Q3: 左に都道府県 47 件、 右に「東京都の市区町村」3 件 (Code: R13000) があるとき、 on='Code' の内部結合は何行になるか。 (答: 3 行、 1×3)
  4. Q4: 左にキー 'R01000' が 2 行、 右に 1 行あるときの inner join 行数は? (答: 2 × 1 = 2 行)
  5. Q5: validate='one_to_many' を渡したとき、 左右どちらの重複が許されるか。 (答: 右側のみ重複可。 左はユニーク)

📖 関連用語辞典 10 語

データ結合
テーブルを連結する全般。 inner は最も厳格な方式。
外部結合
片側に無いキーも NaN 付きで残す方式。 損失回避用途。
主キー
テーブル内で行を一意に識別するキー。 結合の信頼度を担保。
外部キー
他テーブルの主キーを参照するキー。 結合の根拠。
SQL
結合を宣言的に書く言語。 INNER JOIN ... ON
pandas
Python の表操作ライブラリ。 merge, join, concat を提供。
データベース
結合演算の本来の住処。 索引で高速化。
テーブル
行と列の構造。 結合の最小単位。
Tidy data
結合しやすい正規化形式。 1 観測 1 行。
欠損値
結合で行が消えたとき / NaN が増えたときに発生。 診断必須。

🔬 Inner Join のキー設計 — 結合精度を支える 7 つの実務判断

Inner Join は「両側の表に同じキー値が存在する行だけを残す」最も基本の結合だが、 実務では キーの設計次第で「正しいはずの結合が消える」「データが重複爆発する」 といった事故が日常的に起きる。 ここでは SSDSE-B-2026(都道府県 × 年度)を中心に、 Inner Join を堅牢に使うための 7 つの判断軸を整理する。 これらは単なる SQL の syntax 知識ではなく、 「結合前にどんな前処理が必要か」「結合後にどんな検証が必要か」を含む、 データエンジニアリングの実践知である。

判断軸 具体的なチェック項目 失敗時の症状 SSDSE-B-2026 で発生する例
1. キー型の一致 左右表で dtype が一致しているか(int64 vs object など) マッチ件数 0 件で警告も出ない 都道府県コード R01000 が片方は文字列・片方は整数化されている
2. 前後空白・改行 "北海道 ""北海道" は別物 9 割マッチ・1 割は脱落で気づきにくい Excel 経由でコピペした都道府県名に末尾空白
3. 全角半角 数字や英字が全角半角混在していないか 同じ意味のキーが別 ID として扱われる 年度 20262026 が混在
4. NULL の扱い NULL 同士は結合されない(SQL 仕様) 必要な行が消える 調査未実施年度の都道府県が脱落
5. カーディナリティ 1:1 / 1:N / N:N のどれか事前に確認 N:N で行数が爆発し OOM 「都道府県名」だけで結合し全市区町村×全年度の直積が発生
6. 複合キー 単独キーで一意でないなら on=[...] でリスト指定 同じキーで複数行マッチし重複 「県名+年度」が必要なのに県名だけで結合
7. 順序の保存 Inner Join 後は行順が保証されない 時系列分析で順序がバラバラ 年度順に並んでいたデータが結合後にシャッフル

この 7 軸は 「結合の前後で必ず検証」 がポイントである。 とくに 1(型)と 2(空白)は Inner Join 失敗の 8 割以上の原因 を占めると言われる。 Pandas であれば df['key'].dtypedf['key'].str.strip() を機械的に挟む癖をつけたい。

さらに、 結合後の行数を 「期待行数の範囲」 で assertion する習慣も重要である。 SSDSE-B-2026 で「都道府県×年度」をキーに結合するなら、 結合後の行数は最大 47×年度数のはずだ。 これを超えていれば N:N 結合の重複が疑われる。 単純な assert len(merged) <= 47 * n_years という 1 行が、 後段の集計エラーを未然に防ぐ防波堤になる。

🧮 結合前後の検証パターン

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 296,888 東京都 14,086,000 341,320 沖縄県 1,468,000 251,222 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import pandas as pd

# SSDSE-B-2026 から人口テーブルと消費支出テーブルを別々に取得した想定
pop = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932')[['Code','Prefecture','SSDSE-B-2026','A1101']]
inc = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932')[['Code','Prefecture','SSDSE-B-2026','L3221']]

# 結合前のサニティチェック
assert pop['Code'].dtype == inc['Code'].dtype, 'キー型不一致'
pop['Prefecture'] = pop['Prefecture'].str.strip()
inc['Prefecture'] = inc['Prefecture'].str.strip()

# Inner Join
merged = pd.merge(pop, inc, on=['Code','SSDSE-B-2026'], how='inner', suffixes=('_pop','_inc'))

# 結合後のサニティチェック (47 都道府県 × 年度数 が上限)
n_years = pop['SSDSE-B-2026'].nunique()
assert len(merged) <= 47 * n_years, f'重複疑い: {len(merged)} 行'
print(f'結合行数: {len(merged)} / 上限 {47*n_years}')
📤 実行例(実測) 結合行数: 564 / 上限 564

📊 Inner Join と他結合の振る舞いを SSDSE-B-2026 で完全比較

Inner Join を正しく理解するには、 同じデータに Left / Right / Outer を適用したときに何件残るか を体感するのが最短ルートである。 ここでは SSDSE-B-2026 から「全 47 都道府県の人口テーブル」と「一部 9 県のみの延べ宿泊者数(観光)テーブル」を作り、 4 種類の結合の差分を一覧化する。 「件数の差」がそのまま「どの結合を選ぶべきか」の判断材料になる。

結合種別 残る行数 NaN の発生箇所 向いている分析 注意点
Inner 9 なし 「両方の値が揃った県だけで相関を見たい」 情報損失が大きい場合あり
Left 47 右表(延べ宿泊者数)に 38 県分の NaN 「人口は全県で集計、 観光は任意」 NaN の集計方法を決めておく必要
Right 9 なし(左の人口は全県あるため) 「観光がある県を基準に分析」 Inner と結果が同じになりやすい
Outer 47 右表に 38 県分の NaN 「どの県が観光データを持つか俯瞰」 分析というより俯瞰用途

🐍 4 結合を一気に試す Python

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) G7101(延べ宿泊者数) 北海道 5,092,000 32,783,470 東京都 14,086,000 80,273,650 沖縄県 1,468,000 20,038,190 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pandas as pd

pop = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932').query('`SSDSE-B-2026` == 2023')[['Code','Prefecture','A1101']]
# 延べ宿泊者数テーブルは一部県のみと想定 (北海道・東京・京都・大阪・沖縄など 9 県)
focus = ['R01000','R13000','R26000','R27000','R47000','R14000','R23000','R40000','R34000']
tour = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932').query('`SSDSE-B-2026` == 2023').query('Code in @focus')[['Code','G7101']].rename(columns={'G7101':'延べ宿泊者数'})

for how in ['inner','left','right','outer']:
    m = pd.merge(pop, tour, on='Code', how=how)
    print(f'{how:5s} 行数: {len(m):3d}  NaN 件数: {m.isna().sum().sum()}')
📤 実行例(実測) inner 行数: 9 NaN 件数: 0 left 行数: 47 NaN 件数: 38 right 行数: 9 NaN 件数: 0 outer 行数: 47 NaN 件数: 38

この比較から導かれる実務指針は 「分析の前提(全県か一部県か)に合致する結合を選ぶ」 である。 たとえば回帰分析で「全 47 県の人口で観光需要をモデル化」したいなら、 Left で 38 県の NaN を含めて欠損補完するか、 Inner で 9 県だけで限定モデルを作るか、 という判断が必要になる。 「とりあえず Inner」と思考停止すると、 重要な県が分析対象から落ちてしまう。

✅ 理解度チェック — Inner Join 8 問

Inner Join を「使える」レベルに引き上げるための 8 問。 単なる用語暗記ではなく、 結合の前後でどんな前処理・検証を組み合わせるかを問う実務寄りの問題群である。

# 設問 解答
Q1 Inner Join の最も基本的な性質は? 両表に「同じキー値」が存在する行だけを残す
Q2 Inner Join で結合件数が想定より少ない時、 最初に疑うべきことは? キーの型 (dtype) と前後空白
Q3 SQL で NULL 同士は Inner Join されるか? されない(仕様)
Q4 結合キーが N:N の時、 結合後の行数はどうなる傾向か? 直積に近づき爆発的に増加する
Q5 複合キーで結合するときの pandas 構文は? pd.merge(a, b, on=['key1','key2'])
Q6 Inner Join 後に行順は保証されるか? 保証されない (必要なら sort_values で再ソート)
Q7 SSDSE-B-2026 で「県名」だけ Inner Join するのは安全か? 単年なら可、 複数年なら「県名+年度」が必要
Q8 Inner Join と Right Join が同じ件数になる条件は? 右表のキーがすべて左表に存在する場合

8 問中 6 問以上正答なら Inner Join を業務に投入できるレベル。 4 問以下なら、 まず「キーの型と空白を結合前に必ず揃える」「結合後に件数 assertion を入れる」の 2 点を習慣化することから始めるとよい。

関連トピックとして 外部結合 (Outer Join)左外部結合 (Left Join)外部キー (Foreign Key)複合キー (Composite Key)正規化 (Normalization) も参照すると、 結合戦略全体の見通しが立ちやすい。

🧩 Inner Join のパフォーマンス特性と最適化

Inner Join は単純な操作に見えるが、 内部的には 3 つの代表的なアルゴリズム が使い分けられる。 すなわち Nested Loop Join、 Hash Join、 Sort-Merge Join である。 SSDSE-B-2026 のように行数が数万件程度なら気にする必要はないが、 数百万行〜数億行のテーブルでは選択を誤ると 処理時間が桁違いに変わる。 RDB の Optimizer は通常自動で選択するが、 Pandas や Spark など API 層では明示的に選ぶことも多い。

アルゴリズム 計算量 メモリ要件 向く場面 SSDSE-B-2026 規模での体感
Nested Loop O(N×M) 最小 片方が極小 (数十行) 47×47 程度なら一瞬
Hash Join O(N+M) 大 (ハッシュ表) 中規模〜大規模で等価結合 Pandas のデフォルトに近い
Sort-Merge O((N+M)log) 既ソート or 範囲結合 時系列の連続区間結合で有利

最適化の実務観点では 「キーにインデックスを張る」「キー列の dtype を category に変える」「不要な列は結合前に drop する」 の 3 つが最も効果的である。 とくに Pandas で巨大データを扱う場合、 結合前に df = df[['key','val']] と必要列だけに絞ると、 メモリ使用量が劇的に減り、 結果として結合自体も高速化する。

🔧 Pandas で結合を高速化する 3 つのテクニック

  1. キー列を index 化: df.set_index('key') 後に join を使うと、 Hash Join 相当の高速化が得られる。
  2. category 型へ変換: 都道府県名のようなカテゴリは astype('category') でメモリ削減とハッシュ高速化の両方が効く。
  3. 列の絞り込み: 結合に不要な列を drop しておくと、 結合後のメモリピークが下がる。

これらは数万行のデータでは差を感じにくいが、 数百万行を超えるあたりから 体感で 2〜10 倍の差 が出る。 EDA のうちは気にしなくてよいが、 本番パイプラインに乗せる段階では押さえておきたい。

🧮 SSDSE-B-2026 実値で計算

SSDSE-B-2026 (47 都道府県) を 2 つに分割し、 Code をキーにして内部結合します。 想定シナリオ: 「人口指標 (A1101)」と「着工建築物数 (C3301)」を別 CSV から取り込み、 1 つの分析用テーブルに統合する。

手順操作結果
左表 df_pop: Code, Prefecture, A110147 行 × 3 列
右表 df_house: Code, C330147 行 × 2 列
merge(how='inner', on='Code')47 行 × 4 列
仮に右表から沖縄(R47000)を除外46 行 (内部結合で沖縄が脱落)

手計算で確認: 共通キー集合の大きさ $|K_R \cap K_S|$ を数える。 北海道 R01000 が両方にあれば +1、 青森 R02000 が両方にあれば +1、 ... 沖縄 R47000 が片方にしかなければ +0。 SSDSE-B-2026 オリジナルでは 47 個すべて共通なので 47 行。

🧮 数式に値を入れて手で計算する: INNER JOIN 結果

合成データで A (顧客) と B (注文) の INNER JOIN 結果を計算する。

Step 1: テーブル

A: customer_id ∈ {1, 2, 3, 4, 5} B: customer_id = [1, 2, 5, 6, 2, 7] B 行数 = 6

Step 2: INNER JOIN 結果

マッチング B の customer_id ∈ {1, 2, 2, 5} → 4 行 {6, 7} は A にないので除外 INNER JOIN 結果 = 4 行

🐍 Python で再現

1
2
3
4
5
6
import pandas as pd
A = pd.DataFrame({'customer_id':[1,2,3,4,5], 'name':['a','b','c','d','e']})
B = pd.DataFrame({'customer_id':[1,2,5,6,2,7], 'amount':[100,200,300,400,500,600]})
joined = pd.merge(A, B, on='customer_id', how='inner')
print(f"結果行数: {len(joined)}")
print(joined)

📤 実行結果

結果行数: 4 customer_id name amount 0 1 a 100 1 2 b 200 2 2 b 500 3 5 e 300

💬 手計算 (Step 2) 4 行と Python 出力が完全一致。

🐍 Python での扱い

SSDSE-B-2026 のような公的統計データを Python で扱う際の基本パターン:

📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年) 年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) … 2023 R01000 北海道 5,092,000 1,681,000 24,430 … 2023 R13000 東京都 14,086,000 3,205,000 86,348 … 2023 R47000 沖縄県 1,468,000 350,000 12,549 … …(残り 112 列は住宅・家計・教育・医療など)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd
import numpy as np

# データ読み込み
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]); df = df[df['SSDSE-B-2026'] == 2023]
print(df.shape)
print(df.dtypes)
print(df.describe())

# 「内部結合」の文脈で扱う場合の例:
# 分野: データ処理
# 関連手法は同カテゴリの他用語を参照してください。
📤 実行例(実測) (47, 112) SSDSE-B-2026 int64 Code object Prefecture object A1101 int64 A110101 int64 ... L322106 int64 L322107 int64 L322108 int64 L322109 int64 L322110 int64 Length: 112, dtype: object SSDSE-B-2026 A1101 ... L322109 L322110 count 47.0 4.700000e+01 ... 47.000000 47.000000 mean 2023.0 2.645809e+06 ... 27409.148936 55356.936170 std 0.0 2.797551e+06 ... 4495.381291 6610.419905 min 2023.0 5.370000e+05 ... 18374.000000 35708.000000 25% 2023.0 1.034000e+06 ... 25662.500000 51114.500000 50% …(以下略)

具体的なコードは データエンジニアリング を参照してください。

📝 レポートでの報告

分析結果を報告するときに含めるべき情報:

✅ チェックリスト

🐍 Python 実装 (4 要素遵守)

コード A: 2 表を作って inner join

🎯 このコードでやること: SSDSE-B-2026 を読み込み、 人口側 (A1101) と着工建築物側 (C3301) に分割し、 pd.merge で内部結合する。 結合後の行数とキー一致を検証する。

📥 入力データ (SSDSE-B-2026 抜粋):

Code Prefecture A1101 C3301 0 R01000 北海道 5092000 15872 1 R02000 青森県 1184000 5043 2 R13000 東京都 14086000 41817 3 R47000 沖縄県 1468000 5217
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]); df = df[df['SSDSE-B-2026'] == 2023]
df_pop   = df[['Code', 'Prefecture', 'A1101']].copy()
df_house = df[['Code', 'C3301']].copy()

joined = pd.merge(df_pop, df_house, on='Code', how='inner')
print('左 :', df_pop.shape)
print('右 :', df_house.shape)
print('結合:', joined.shape)
print(joined.head(3))

📤 実行結果:

左 : (47, 3) 右 : (47, 2) 結合: (47, 4) Code Prefecture A1101 C3301 0 R01000 北海道 5092000 15872 1 R02000 青森県 1184000 5043 2 R03000 岩手県 1163000 5731

💬 結果の読み方: 左 47 行 × 右 47 行で結合後も 47 行。 共通キー Code がすべて一致しているため情報損失なし。 列数は 3 + 2 − 1 (キー重複) = 4 列。

コード B: 片側からキーを欠落させて損失を観察

🎯 このコードでやること: 右表から沖縄 (R47000) を意図的に除外して内部結合し、 行数が 46 に減ることを確認する。

📥 入力: df_house から沖縄を除外

1
2
3
4
df_house_partial = df_house[df_house['Code'] != 'R47000']
joined2 = pd.merge(df_pop, df_house_partial, on='Code', how='inner')
print('結合後行数 :', len(joined2))
print('沖縄は残ってる?', (joined2['Code'] == 'R47000').any())

📤 実行結果:

結合後行数 : 46 沖縄は残ってる? False

💬 結果の読み方: 右表に沖縄がないため、 内部結合では沖縄行が消える。 これが「情報損失」の典型例。 残したい場合は 外部結合 (left/right/outer) を使う。

コード C: indicator で結合結果を診断

🎯 このコードでやること: indicator=True オプションを付けて、 各行が「both / left_only / right_only」のどれだったかを記録する。 まず outer で実行し、 inner との差分を可視化。

📥 入力: df_pop 47 行と df_house_partial 46 行

1
2
3
diag = pd.merge(df_pop, df_house_partial, on='Code', how='outer', indicator=True)
print(diag['_merge'].value_counts())
print(diag[diag['_merge'] != 'both'][['Code', 'Prefecture']])

📤 実行結果:

both 46 left_only 1 right_only 0 Name: _merge, dtype: int64 Code Prefecture 46 R47000 沖縄県

💬 結果の読み方: left_only が 1 件 → 沖縄が左にしかない。 inner join なら除外、 left join なら NaN 付きで残る。 結合前に必ず indicator で診断すると不整合の発見が早い。

コード D: validate で 1:1 結合を強制

🎯 このコードでやること: validate='one_to_one' オプションで、 キーが両側でユニークであることを宣言。 重複があれば即エラーで気づける。

📥 入力: df_pop, df_house (47 都道府県、 ユニーク Code)

1
2
joined_v = pd.merge(df_pop, df_house, on='Code', how='inner', validate='one_to_one')
print('OK 1:1 結合成立, 行数:', len(joined_v))

📤 実行結果:

OK 1:1 結合成立, 行数: 47

💬 結果の読み方: 47 行と表示され、 例外もない → キーは両側ユニーク。 もし片方に重複があれば MergeError で停止し、 デカルト積による行数膨張を未然に防げる。

🏭 産業界での活用事例 6 件

  1. EC サイトの売上分析: 顧客マスタ × 注文履歴を customer_id で inner join し、 「実在する顧客の注文」だけ抽出。 退会顧客やテスト顧客が混じらない。
  2. 金融与信スコアリング: 申込書テーブル × 信用情報テーブルを内部結合し、 信用情報のある申込者のみを学習データに採用。
  3. 製造業 IoT: センサーログ × 設備マスタを device_id で結合し、 マスタに登録された正規設備からのログだけ採用 (野良デバイスを除外)。
  4. 地方創生 / 都道府県分析: SSDSE-B-2026 (経済) と SSDSE-C (家計) を Prefecture コード で内部結合し、 両方そろっている都道府県だけ比較分析。
  5. 医療レセプト分析: 患者マスタ × レセプト明細を patient_id で結合し、 同意取得済の患者の医療行為だけを集計。
  6. 広告効果測定: クリックログ × CV (コンバージョン) ログを session_id で内部結合し、 クリック後 CV が発生したセッションだけのファネル分析。

⚠️ よくある落とし穴

❌ 結合での行数膨張
merge 後の行数を必ず確認。
❌ 欠損の扱い
NaN を 0 にすると「測定値 0」と区別できなくなる。
❌ 日付型
datetime64 に変換しないと正しい比較ができない。

inner join を実務で使う際の典型的失敗パターン。 SSDSE-B-2026 など複数テーブルを跨ぐ結合は、 事前のキー検証なしに走らせると静かにデータが落ちる事故が多い。

🗺 概念マップ

関連概念を視覚的に整理した概念マップ。

inner join 結合キー (Code) left / outer join cross / semi join pd.merge / SQL validate オプション 関係代数 (∩)

内部結合 (inner join) は SQL ・ pandas ・ Spark など多様なツールで提供される基本演算。 上図中心からは、 左右テーブル ・ 結合キー ・ 結合条件 (等価 / 範囲 / 複合キー) ・ 関連手法 (left/right/outer/cross join) の軸が放射する。 SSDSE-B のように都道府県コードを結合キーとした統計データ統合では最頻出の演算。

🔗 隣接手法への橋渡し

inner join は、 データ準備フェーズで複数テーブルを 1 つの分析ユニットにまとめる中核演算。 前後の処理と密接に連携する。

inner join の落とし穴は「キーが一意でないと積集合 (cartesian product) が発生して行数が爆発する」点。 結合前に df.groupby(key).size() でキーの重複を確認する習慣をつける。

🌳 手法選択フロー

テーブル結合の種類を選ぶとき、 「どちらにしかない行を残すか」と「キー対応関係」で判定する。

  1. 両テーブルに存在する行のみが欲しいか? Yes → inner join (例: 観測 ID と購入 ID が両方ある行のみ抽出)、 No → 次へ
  2. 左 (基準) テーブルの全行を保持したいか? Yes → left join (例: 全顧客リストに購入履歴を付与、 購入が無い顧客は NaN)、 両方の全行なら full outer join
  3. 結合キーは一意か? 一意 (1:1) なら通常の inner join、 重複 (1:N / N:M) なら結果行数を予想して groupby 集計を先に行うか確認

結合は順序が結果に影響しないが、 行数増減と NaN 発生は join 種別で大きく異なる。 結合後は必ず df.shapedf.isna().sum() で確認する。

🧭 解説深化: inner join が「黙って標本を縮める」— 選択バイアス (SSDSE-B 実測)

このページは既に、行数膨張・表記ゆれ・多対多・dtype 不一致を手厚く扱っている。ここでは重複を避け、「inner join は一致しないキーを警告なく捨てる → 残った標本が母集団を代表しなくなる」という選択バイアスの側面だけを、実データで深掘りする。行が増える事故は姉妹ページ データ結合 が担当し、こちらは行が減る事故に集中する。

💡 直感

inner join は「両方の名簿に名前が載っている人だけの同窓会」だと思うとよい。片方の名簿に無い人には招待状すら届かず、本人も欠席したことに気づかない(黙って除外)。集まった出席者だけで「平均年収」を計算すると、それは全卒業生の平均ではなく両名簿に載る人の平均にすぎない。名簿の作られ方に偏りがあれば、平均も偏る。inner join の how='inner' は、この「両方に居る人だけ」という強い条件を、何の警告もなく適用する。

⚠️ 落とし穴 (重要): 一致しない側が偏っていると、平均が黙って動く

SSDSE-B-2026 の 2023 年・47 都道府県の総人口 A1101 で示す。左表は 47 県すべて。ここに、大都市圏が未回収で人口上位 5 県(東京・神奈川・大阪・愛知・埼玉)が欠けた右表(架空:調査の無回答を想定)を inner join すると、5 県が黙って落ち、標本が 42 県に縮む。実測値(encoding='cp932', skiprows=[1] で読み込み、df[df['SSDSE-B-2026']==2023])。

状態 残存件数 県平均人口 (A1101) 全県平均とのズレ
左表 全 47 県(真の姿)472,645,809 人±0
inner join(上位 5 県が欠けた架空右表と)421,844,452 人−30.3%

たった 5 県が黙って消えるだけで、県平均人口は 264 万人 → 184 万人へ約 3 割も下振れする。落ちたのが「人口が極端に多い外れ値県」だからだ。inner join は例外も NaN も残さず、結果は 42 行の一見きれいな表として返ってくる——だから間違いに気づく手がかりが表の中に一切ないのが最大の怖さである。全国総人口 1 億 2435 万 3000 人という桁感覚を持ち、「47 のはずが 42?」と件数を数える習慣が唯一の防波堤になる。

📝 より正確な分析:この「黙って落ちる」を可視化する最短手は merge(..., how='outer', indicator=True)。生成される _merge 列で left_only(=右表に無く落ちる予定のキー)の件数と中身を結合に確認できる。上の例なら left_only に 5 件、しかも人口上位 5 県が並ぶ——この時点で「落ちる側が偏っている」と気づける。inner の結果だけを見ても永遠に気づけない。

🎮 選択バイアス・ラボ(架空デモ / 実データ人口)

スライダーで「右表から欠ける人口上位県の数 k(=無回答で inner join から落ちる県数)を動かすと、残った標本の件数と県平均人口が実測値でどう動くかを確認できます。棒は 47 県の人口を降順に並べたもの(実データ)。赤く沈む棒が「黙って落ちる県」、青が「残る県」。落とす県の選び方だけが架空です。

落とす上位県数 k = 0 県 → 残存 47 県 / 県平均人口 2,645,809 人 (±0.0%)
← 人口が多い県  |  人口が少ない県 →(赤=inner で落ちる / 青=残る

🚀 発展: 「落ちたキーを見に行く」設計にする

inner の結果だけを見る限り選択バイアスは検出できない。原則は「捨てる前に、捨てるものを数えて眺める」。具体的には反結合(anti-join)相当の操作で、右表に一致しなかった左のキーを取り出し、その分布を残存側と比べる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 1) 落ちる側を可視化してから inner する
chk = left.merge(right, on='Code', how='outer', indicator=True)
print(chk['_merge'].value_counts())      # left_only / right_only / both の件数
dropped = chk[chk['_merge'] == 'left_only']   # inner なら黙って消えるキー
print(dropped['A1101'].describe())       # 落ちる側の人口分布 ← 偏っていないか

# 2) 落ちる前後で代表性が保たれるか対比
before = left['A1101'].mean()
after  = left.merge(right, on='Code', how='inner')['A1101'].mean()
print(before, after, (after-before)/before)   # ズレが大きければ選択バイアスを疑う

ポイントは、inner を「行を減らす操作」ではなく「標本を選び直す操作」として扱うこと。減った 5 件が無作為なら平均は動かないが、外れ値に偏っていれば大きく動く。落ちる側の分布を残存側と比べ、大きくズレていれば 外部結合(how='left'/'outer')で全件を保持し、欠損は NaN のまま明示的に扱う設計へ切り替える。「黙って消す」より「見えるように残す」方が、後から検算できるぶん安全である。

🔗 関連ページ