論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
データ統合
Data Integration
データ前処理 / データエンジニアリング

🔖 キーワード索引

このページで扱う主要キーワード(クリックで該当セクションへ):

JOIN(結合) マージ(merge) 内部結合 外部結合 縦結合(concat) 名寄せ 表記ゆれ スキーマ統合 マスターデータ 主キー 外部キー pandas.merge

💡 30秒で分かる結論

🍰 まずはやさしく

バラバラの表を1つにまとめるパズルのような操作です。

データを分析しやすい形にするために使います。

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

この章ではデータのまとめ方について読みます。

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

🍰 まずはやさしく

バラバラに保存されたデータを1つにする方法です。

正しい分析を始めるための準備として使います。

学校の出席簿とテストの結果をまとめる時に似ています。

ここでは統合の流れと関連する言葉について読みます。

データ統合」 (Data Integration) は、 SSDSE-B-2026 などの公的統計データを使った教材・分析で頻出するキーワードです。 現実のデータは「人口の表」「死亡の表」「所得の表」のように別々のファイル・別々のシステムに分かれて存在します。 分析の第一歩は、 それらを共通のキー(年・都道府県など)で 1 枚の表に束ねること。 本ページでは、 まず直感、 次に結合の種類の定義、 そして 47 都道府県の実データで確かめる、 という流れで整理します。

関連用語(前提・並列・発展)も末尾にまとめてあるので、 用語の地図として活用してください。

🎨 直感で掴む

🍰 まずはやさしく

2枚の名簿を突き合わせるイメージです。

共通の目印を使って情報を付け足すために使います。

部活の名簿に、個人の連絡先を書き足すようなものです。

ここでは横に貼る方法と縦に積む方法について読みます。

データ統合のいちばん素朴なイメージは「2 枚の名簿を突き合わせる」こと。 片方に「都道府県ごとの人口」、 もう片方に「都道府県ごとの死亡数」があるとき、 都道府県名という共通の目印(キー)で 2 枚を横に貼り合わせれば、 「人口と死亡数が同じ行に並んだ 1 枚の表」ができます。 あとは割り算するだけで死亡率が出ます。

直感 1: キーで突き合わせる = JOIN(横結合)

SSDSE では都道府県ごとに人口 A1101 と死亡数 A4200 が並んでいます。 これを別ソース由来の 2 表だと考えると、 「都道府県」というキーで突き合わせて列を増やす操作が JOIN です。 表計算ソフトの VLOOKUP と同じ発想で、 「キーが一致した行だけ、 相手の列を持ってくる」わけです。

直感 2: 横に貼る(JOIN)と 縦に積む(concat)

統合には 2 方向あります。 横結合(JOIN/merge)は「同じ対象について列を増やす」操作。 縦結合(concat/UNION)は「同じ列構成の表を行方向に積む」操作で、 2012 年ファイルと 2023 年ファイルを積んでパネルデータを作るときに使います。 列を増やすのか、 行を増やすのか — ここを取り違えないことが第一歩です。

直感 3: キーが命 — 表記ゆれで突き合わせは壊れる

JOIN はキーの完全一致で結びつきます。 だから「東京都」と「東京」、 「株式会社ABC」と「(株)ABC」のように表記がぶれると、 見た目は同じでも機械的には別物と判定され、 突き合わせが 1 件も成立しないことがあります。 この表記を揃える作業が名寄せで、 データ統合で最も手間のかかる部分です。

直感 4: 相手にない行をどう扱うか = 内部結合 / 外部結合

「両方の表にキーがある行だけ残す」のが内部結合(inner)、 「片方(基準表)の行は全部残し、 相手にない部分は欠損にする」のが外部結合(left/right/full outer)。 内部結合は静かに行を落とすので、 「なぜか件数が減った」の多くはこれが原因です。

📐 定義・結合の種類

🍰 まずはやさしく

共通の項目を基準に表をくっつける操作のことです。

どの行を残してどの行を捨てるかを決めるために使います。

買い物リストと商品の値段表を合わせるようなものです。

ここでは結合の詳しい種類について読みます。

データ統合とは、 異なるソースの複数の表 $R, S$ を、 共通の属性(キー)$k$ の一致に基づいて 1 つの表に結合する操作の総称です。 関係代数では横結合を結合演算 $\bowtie$ で表します。

【内部結合(INNER JOIN)】
$$R \bowtie_{k} S = \{\, (r, s) \mid r \in R,\; s \in S,\; r.k = s.k \,\}$$

両表でキー $k$ が一致する組だけを残す。 一致しない行は捨てられる。

【左外部結合(LEFT OUTER JOIN)】
$$R \;⟕_{k}\; S = (R \bowtie_{k} S)\;\cup\;\{\,(r,\varnothing)\mid r\in R,\; \nexists s\in S:\, r.k=s.k \,\}$$

左表 $R$ の行はすべて残し、 右に相手がなければ欠損(NULL / NaN)で埋める。

結合(JOIN)の 4 タイプ

種類pandas の how残る行典型的な用途
内部結合 INNER"inner"両表にキーがある行のみ両方そろって初めて意味を持つ分析(人口×死亡→死亡率)
左外部結合 LEFT"left"左表は全件、 右は一致分のみマスター(47都道府県)を軸に、 実績を貼り付ける
右外部結合 RIGHT"right"右表は全件、 左は一致分のみleft の左右を入れ替えただけ
完全外部結合 FULL"outer"どちらかにあれば全部2 ソースの差分・欠けを洗い出す突合

横結合(JOIN / merge)と 縦結合(concat / UNION)

観点横結合(JOIN / merge)縦結合(concat / UNION)
増える方向列(属性を足す)行(レコードを積む)
前提共通キーがある列構成(スキーマ)が揃っている
人口表 + 死亡表 → 死亡率表2012年 + … + 2023年 → パネル
pandaspd.merge(a, b, on=...)pd.concat([a, b])

統合にまつわる基本用語(一言定義)

用語一言定義
キー(結合キー)表と表を突き合わせるための共通の目印となる列(年・都道府県など)。
主キー1 つの表の中で行を一意に識別する列(またはその組)。 重複・欠損があってはいけない。
外部キー別の表の主キーを参照する列。 表と表の関係を張る。
マスターデータ都道府県一覧・商品台帳のように、 分析の基準となる「正」の参照表。 これを軸に外部結合することが多い。
名寄せ「東京」「東京都」のように表記の異なる同一実体を同じキーに揃える作業。
スキーマ統合ソースごとに違う列名・単位・型・粒度を、 共通の定義に合わせて揃えること。
レコードリンケージ共通 ID がないデータ同士を、 氏名・住所などの類似度で確率的に突き合わせる名寄せの発展形。

🔬 深掘り — 結合キーの「一意性」が行数を決める

ここまでの①〜④とシミュレータで「結合タイプで行が消える」「表記ゆれで突合が全滅する」を見ました。 最後に、 データ統合で起きる行数の変化(脱落と増殖)を一本の式で串刺しにし、 実務で刺さりやすい残りの落とし穴(複合キーの取り違え・時点整合・同名異義列・検算)まで踏み込みます。

🎨 直感: 結合後の行数は「キーの重複数のかけ算」で決まる

横結合(JOIN)の後の行数は、 難しく考えなくても次の一行で説明できます。 あるキー値 $k$ について、 左表でそのキーを持つ行が $a_k$ 個、 右表で $b_k$ 個あるとき、 内部結合で生まれる行数は

【内部結合の行数(キー別のかけ算の総和)】
$$\lvert R \bowtie_{k} S \rvert \;=\; \sum_{k}\, a_k \times b_k$$

左でも右でもキーが一意(各 $a_k, b_k \in \{0,1\}$)なら積は 0 か 1 で、 行は増えも減りもしない(一致すれば 1、 相手が無ければ 0=脱落)。 どちらかでキーが重複($a_k$ や $b_k$ が 2 以上)すると、 その分だけ行が掛け算で増殖する。

この式ひとつで、 これまでの現象が全部つながります。 内部結合の脱落(③の 47→10 行)は「相手にキーが無い= $b_k=0$ で積が 0」。 多対多結合の行増殖(誤解 2)は「片方で $a_k\ge2$ や $b_k\ge2$」。 名寄せ(②)は「そもそも $k$ が揃っていないと全部 $a_k\times b_k=0$ になる」問題でした。 だから統合の設計は、 まず「結合キーは各表で一意か($a_k, b_k$ は 1 以下か)」を確かめることに尽きます。

⚠️ 落とし穴(重要): 「年」を落とした複合キーで行が 12 倍に膨らむ

誤解 2 の行増殖を、 SSDSE-B-2026 の実データで再現します。 このデータは 564 行 = 47 都道府県 × 12 年(2012–2023)で、 1 行を一意に決めるキーは「年 + 都道府県」の複合キーです。 うっかり「年」を落として Prefecture だけで結合すると、 各都道府県が 12 回重複する非一意キーになり、 掛け算で行が膨らみます。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) 北海道 5,092,000 東京都 14,086,000 沖縄県 1,468,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import pandas as pd

df = pd.read_csv("data/raw/SSDSE-B-2026.csv", encoding="cp932", skiprows=[1])
hok = df[df["Prefecture"] == "北海道"][["SSDSE-B-2026", "Prefecture", "A1101"]]
print("北海道の行数:", len(hok))                 # 12年分 = 12行(Prefectureは12回重複)

# キーから「年」を落とすと Prefecture は非一意キー → 掛け算で増殖
bad  = pd.merge(hok, hok, on="Prefecture", how="inner")
print("年を忘れて Prefecture だけで結合:", len(bad), "行")   # 12×12 = 144

# 正しくは 年+都道府県 の複合キーで結合
good = pd.merge(hok, hok, on=["SSDSE-B-2026", "Prefecture"], how="inner")
print("年+都道府県で結合:", len(good), "行")               # 12

# validate で関係を宣言し、意図しない多対多を例外で検知
try:
    pd.merge(hok, hok, on="Prefecture", validate="one_to_one")
except Exception as e:
    print("validate=one_to_one で例外:", type(e).__name__)

📤 実行結果:

北海道の行数: 12 年を忘れて Prefecture だけで結合: 144 行 年+都道府県で結合: 12 行 validate=one_to_one で例外: MergeError

💬 読み取り:北海道だけで 12 行(各年 1 行)。 結合キーから「年」を落とすと Prefecture が 12 回重複する非一意キーになり、 $12\times12=144$ 行に膨れ上がりました(脱落と真逆の事故)。 複合キーの一部を書き忘れるのが、 実務で最も多い増殖原因です。 正しく「年 + 都道府県」で結合すれば 12 行のまま。 そして validate="one_to_one" を付けておけば、 非一意キーでの結合を MergeError実行前に叩き落とせます。 「結合したら行数が想定と違う」を検知する最も安いガードです。

⚠️ さらに刺さりやすい 3 つの落とし穴

落とし穴 A:時点整合 — 「同じ年」に見えて実は違う基準時点
人口は 10 月 1 日時点、 経済統計は年度末(翌 3 月)、 別ソースは年平均… と基準時点がずれたまま同じ「年」キーで結合すると、 意味の異なる数字を横に並べてしまいます。 SSDSE のように「年」で揃っているデータでも、 外部ソースを足すときは時点定義を必ず確認し、 必要なら時点を明記した列名(例: pop_oct1)にしてから統合します。
落とし穴 B:同名異義列 — 列名が衝突して _x / _y が生える
両表に同名だが意味の違う列(例: 一方は「総人口」、 他方は「推計人口」なのにどちらも 人口)があると、 pd.merge は自動で 人口_x / 人口_y にリネームします。 これを見落とすと、 後段でどちらを使っているか分からなくなります。 結合前に suffixes=("_住基", "_推計") で由来を明示するか、 事前に列名を一意化しておきましょう。
落とし穴 C:検算をしない — 統合後に「合計が保存されているか」を見ない
縦結合(concat)なら行数の足し算(④の 47+47=94)、 横結合ならキーの一意性と一致件数、 集約を挟むなら合計値の保存(統合前後で総人口の合計が一致するか)を、 結合直後に必ず検算します。 assert len(merged) == 期待値 や、 merged["総人口"].sum() の前後比較を 1 行入れるだけで、 静かな脱落・増殖の大半は水際で止まります。

🚀 発展: 一意性の「宣言」から SQL・ETL の自動化へ

ここで使った validate は、 「このキーは一意なはず」という設計上の前提を機械に宣言する営みです。 これはデータベースの世界では主キー制約・UNIQUE 制約、 外部キー制約として最初から仕組み化されています。 発展の方向は次の通りです。

1 対多の結合 — 合計が 48 倍になる実例

ここまでは「1 対 1」で結べる表を見てきました。実務でいちばん事故が多いのは 片方のキーが重複している 1 対多の結合です。 行数の変化が直感と合わないうえ、例外もエラーも出ません

同梱データで実際に起こしてみます。左は SSDSE-B-2026 の 都道府県ごとに 1 行(47 行)、右は SSDSE-A-2025 の 市区町村ごとに 1 行(1,741 行)。都道府県名で結合します。

見るところ 意味
左の行数47都道府県は 1 県 1 行
右の行数1,741市区町村は 1 県あたり平均 37.0 行
結合後の行数1,741左の 47 行が市区町村の数だけ複製された
県総人口の単純合計6,010,224,000複製された値をそのまま足した誤りの値
正しい全国人口124,353,000結合前に合計した値
倍率48.3 倍1 県あたりの市区町村数の平均に対応する

日本の人口が 60 億人になりました。 ここまで極端だと気づけますが、複製されたのが「県の平均気温」や「県の面積」だったら、 平均を取っても値がもっともらしく見えてしまい、まず気づけません。 市区町村数は北海道 179、富山県 15 と県によって大きく違うので、 市区町村数が多い県ほど重く数えられるという偏りまで生まれます。

防ぎ方は 3 つです。 (1) 結合の前に粒度を揃える(市区町村を県に 集約してから結ぶ)。 (2) 結合の直後に行数を数える(47 のはずが 1,741 なら即座に分かる)。 (3) 複製された列を合計しない(どうしても必要なら重複を除いてから足す)。 下のコードは、この事故と直し方をそのまま再現します。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 SSDSE-B-2026(年度) Prefecture(都道府県) A1101(総人口) 北海道 2,023 北海道 5,092,000 東京都 2,023 東京都 14,086,000 沖縄県 2,023 沖縄県 1,468,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ── 1 対多の結合で合計が壊れる様子を実データで見る ──
import pandas as pd

# 左:都道府県ごとに 1 行(47 行)
b = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932')
b = b[b['年度'] == 2023][['都道府県', '総人口']].rename(columns={'総人口': '県総人口'})

# 右:市区町村ごとに 1 行(1,741 行)
a = pd.read_csv('data/raw/SSDSE-A-2025.csv', skiprows=2, encoding='cp932')
a = a[['都道府県', '市区町村', '総人口']]

print(f'左 = {len(b)} 行(都道府県)/ 右 = {len(a)} 行(市区町村)')
print('1 県あたりの市区町村数:', round(len(a) / 47, 1))

m = b.merge(a, on='都道府県', how='inner')
print(f'結合後 = {len(m)} 行  ← 左の 47 行が市区町村の数だけ複製された')

# ここが事故のもと:複製された県総人口をそのまま合計してしまう
print(f'県総人口の単純合計(誤り)= {m["県総人口"].sum():,}')
print(f'正しい全国人口          = {b["県総人口"].sum():,}')
print(f'倍率 = {m["県総人口"].sum() / b["県総人口"].sum():.1f} 倍')

# 正しくは、複製された列は重複を除いてから合計する
print(f'重複を除いた合計        = {m.drop_duplicates("都道府県")["県総人口"].sum():,}')

📤 実行すると次の出力が得られる

左 = 47 行(都道府県)/ 右 = 1741 行(市区町村) 1 県あたりの市区町村数: 37.0 結合後 = 1741 行 ← 左の 47 行が市区町村の数だけ複製された 県総人口の単純合計(誤り)= 6,010,224,000 正しい全国人口 = 124,353,000 倍率 = 48.3 倍 重複を除いた合計 = 124,353,000

💬 読み方:最後の 2 行が対になっています。drop_duplicates で県の重複を落としてから合計すれば、結合前と同じ 124,353,000 に戻ります。つまり値が壊れていたのではなく、同じ値を 48 回数えていただけです。1 対多の結合をしたら「この列は複製されているか」を必ず自問してください。

🧮 実値で計算してみる — 4 種類の結合で行数がどう変わるか

結合を口で説明すると分かった気になりますが、行数と欠損の数を数えると一発で腑に落ちます。 同梱の 2 つの表を実際に結合して確かめます。

行数が 47 と 48 で 1 つ違います。この 1 行の差がどこへ消えるかが、結合の種類の違いそのものです。

Step 1: 4 種類を数える

結合 結果の行数 欠損を含む行 何が起きたか
inner(内部結合)470両方にある 47 県だけが残り、「全国」は黙って消える
left(左外部結合)470左の 47 行を保つ。右にしかない「全国」は捨てられる。
right(右外部結合)481右の 48 行を保つ。「全国」の総人口が欠損になる。
outer(完全外部結合)481どちらかにあれば残す。差分が欠損として見える化される。

Step 2: 消えた 1 行の正体

indicator=True を付けて _merge 列を見ると、 right_only に入っていたのは 「全国」 の 1 行でした。 SSDSE-D には 47 都道府県に加えて全国計の行が入っており、SSDSE-B には入っていません。

ここが実務でいちばん危ないところです。inner で結合すると、 「全国」が消えたことに気づかないまま 47 行の表ができあがります。 行数が 47 で揃っているので、一見なにも問題がないように見えます。 逆に、もし全国計を残したまま平均を取れば、全国計が 1 県として二重に数えられ、 平均も相関も狂います。

結合したら必ず行数を数える。 そして「増えた/減った」なら、その 1 行が誰なのかを名指しで確認する。 この 2 つを習慣にすれば、統合工程の事故のほとんどは防げます。

Step 3: 同じ計算を Python で再現する

このコードでやること:上の表を実際に作ります。 4 種類の結合それぞれで行数・欠損数・_merge の内訳を出し、 最後に「右にしかない行」を名指しで表示します。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 SSDSE-B-2026(年度) Prefecture(都道府県) A1101(総人口) 北海道 2,023 北海道 5,092,000 東京都 2,023 東京都 14,086,000 沖縄県 2,023 沖縄県 1,468,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ── この抜粋で使うデータを用意します ──
# SSDSE-B(経済社会)47 行と SSDSE-D(学習・自己啓発)48 行を、都道府県名で結合する。
# D 側には「全国」という集計行が 1 つ余分に入っている。
import pandas as pd

b = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932')
b = b[b['年度'] == 2023][['都道府県', '総人口']]
d = pd.read_csv('data/raw/SSDSE-D-2023.csv', skiprows=1, encoding='cp932')
d = d[d['男女の別'] == '0_総数'][['都道府県', '推定人口(10歳以上)']]
for t in (b, d):
    t['都道府県'] = t['都道府県'].astype(str).str.strip()

print(f'左表 B = {len(b)} 行 / 右表 D = {len(d)} 行')

# 4 種類の結合で行数と欠損がどう変わるか
for how in ('inner', 'left', 'right', 'outer'):
    m = b.merge(d, on='都道府県', how=how, indicator=True)
    n_nan = int(m.isna().any(axis=1).sum())
    cnt = m['_merge'].value_counts()
    print(f'{how:6s} 行数={len(m):3d}  欠損を含む行={n_nan:2d}  '
          f'both={cnt.get("both", 0)} left_only={cnt.get("left_only", 0)} '
          f'right_only={cnt.get("right_only", 0)}')

# 右にしか居ないのは誰か
only_right = b.merge(d, on='都道府県', how='outer', indicator=True)
print('右にだけある行:', only_right.loc[only_right['_merge'] == 'right_only', '都道府県'].tolist())

📤 実行すると次の出力が得られる

左表 B = 47 行 / 右表 D = 48 行 inner 行数= 47 欠損を含む行= 0 both=47 left_only=0 right_only=0 left 行数= 47 欠損を含む行= 0 both=47 left_only=0 right_only=0 right 行数= 48 欠損を含む行= 1 both=47 left_only=0 right_only=1 outer 行数= 48 欠損を含む行= 1 both=47 left_only=0 right_only=1 右にだけある行: ['全国']

💬 一致の確認:Step 1 の表と数字がそのまま一致しました。最後の 1 行が決定的で、消えていたのは「全国」という集計行だと名指しで分かります。inner と left が同じ 47 行になるのは「左にしかない行が 0 件だったから」であって、いつも同じになるわけではありません。左に余分な行があれば inner だけが減ります。

🐍 Python で実践(SSDSE-B-2026 実データ)

ここからは実データ SSDSE-B-2026(564 行 = 47 都道府県 × 12 年 2012–2023、 112 列)を使い、 データ統合の 4 つの基本操作を順に動かします。 CSV は cp932 で、 1 行目が英語コード(A1101 等)、 2 行目が日本語見出しなので、 読み込み時に skiprows=[1] で日本語行を捨て、 英語コードを列名として使います。

① 2 表を JOIN する(内部結合で死亡率を作る)

🎯 目的:別ソースを模した「人口表」と「死亡表」を、 年 + 都道府県のキーで内部結合し、 同じ行に人口と死亡数を並べて死亡率(人口千人あたり)を計算します。

🔗 橋渡し:SSDSE は 1 枚の表ですが、 総人口 A1101 と死亡数 A4200 を別々に切り出すことで「2 つのソースを統合する」状況を再現します。 結合キーは ["年", "Prefecture"] の 2 列です。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4200(死亡数) 北海道 5,092,000 75,120 東京都 14,086,000 137,241 沖縄県 1,468,000 15,110 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd

# SSDSE-B-2026 を読み込む(1行目=英語コード, 2行目=日本語見出しを除外)
df = pd.read_csv("data/raw/SSDSE-B-2026.csv", encoding="cp932", skiprows=[1])
df = df[df["SSDSE-B-2026"] == 2023]          # 2023年だけ抽出

# 別々のソースから来た2つの表を想定して切り出す
pop   = df[["SSDSE-B-2026", "Prefecture", "A1101"]].rename(
            columns={"SSDSE-B-2026": "年", "A1101": "A1101"})
death = df[["SSDSE-B-2026", "Prefecture", "A4200"]].rename(
            columns={"SSDSE-B-2026": "年", "A4200": "A4200"})

# キー(年 + 都道府県)で内部結合(INNER JOIN)
merged = pd.merge(pop, death, on=["年", "Prefecture"], how="inner")
merged["死亡率パーミル"] = (merged["A4200"] / merged["A1101"] * 1000).round(2)

print("pop:", pop.shape, " death:", death.shape, " merged:", merged.shape)
print(merged.head(3).to_string(index=False))
print("全国平均死亡率(‰):",
      round(merged["A4200"].sum() / merged["A1101"].sum() * 1000, 2))

📤 実行結果:

pop: (47, 3) death: (47, 3) merged: (47, 5) 年 Prefecture 総人口 死亡数 死亡率パーミル 2023 北海道 5092000 75120 14.75 2023 青森県 1184000 20835 17.60 2023 岩手県 1163000 19612 16.86 全国平均死亡率(‰): 12.67

💬 読み取り:人口表 47 行と死亡表 47 行が、 キー完全一致で 47 行に統合されました(1 行も落ちていない=キーが綺麗に対応している証拠)。 統合できて初めて「死亡数 ÷ 人口」という複数ソースにまたがる計算が可能になります。 2023 年の全国平均死亡率は 12.67‰。 北海道 14.75、 青森 17.60 と、 高齢化の進む県で高い傾向が読み取れます。

② 名寄せ — 表記ゆれで JOIN が全滅する例と、その修復

🎯 目的:外部システム由来の表が「北海道→北海」「東京都→東京」のように末尾を落とした表記ゆれを持つとき、 素朴な結合が 0 件になることを見せ、 正規化キーを作る名寄せで 47 件を回復させます。

🔗 橋渡し:高齢人口 A1303 の表を、 わざと末尾の「都道府県」を除いた 地域 列に加工します。 これで「同じ都道府県なのにキー文字列が違う」状況を人工的に作ります。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) 北海道 5,092,000 1,681,000 東京都 14,086,000 3,205,000 沖縄県 1,468,000 350,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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]

pop = df[["Prefecture", "A1101"]].rename(columns={"A1101": "A1101"})

# 外部システム由来の表は末尾の都/道/府/県を省いた表記ゆれを持つと想定
ext = df[["Prefecture", "A1303"]].rename(columns={"A1303": "高齢人口"}).copy()
ext["地域"] = ext["Prefecture"].str.replace(r"(都|道|府|県)$", "", regex=True)
ext = ext[["地域", "高齢人口"]]

# そのまま結合 → キーの表記が揃わず1件も一致しない
naive = pd.merge(pop, ext, left_on="Prefecture", right_on="地域", how="inner")
print("素朴な結合(表記ゆれ)一致件数:", len(naive))

# 名寄せ: 両表に正規化キーを作ってから結合
pop["key"] = pop["Prefecture"].str.replace(r"(都|道|府|県)$", "", regex=True)
fixed = pd.merge(pop, ext, left_on="key", right_on="地域", how="inner")
print("名寄せ後 一致件数:", len(fixed))
print(fixed[["Prefecture", "A1101", "高齢人口"]].head(3).to_string(index=False))

📤 実行結果:

素朴な結合(表記ゆれ)一致件数: 0 名寄せ後 一致件数: 47 Prefecture 総人口 高齢人口 北海道 5092000 1681000 青森県 1184000 417000 岩手県 1163000 407000

💬 読み取り:「北海道」と「北海」は人間には同じでも、 文字列としては別物なので一致件数は 0。 統合結果が空になったら、 まずキーの表記を疑うのが鉄則です。 両表に同じ正規化ルール(末尾の都/道/府/県を除去)でキーを作り直すと、 一気に 47 件全部が突き合いました。 これが名寄せの最小形です。

③ 内部結合 vs 外部結合 — 行が「消える」か「残る」か

🎯 目的:47 都道府県のマスターと、 一部の県しか観測されていない実績表を結合し、 内部結合(inner)だと行が減り、 左外部結合(left)だと欠損付きで全件残ることを比較します。

🔗 橋渡し:「総人口 300 万人以上の県だけ観測できた」という欠損シナリオを作り、 マスター 47 行に貼り付けます。 どちらの how を選ぶかで結果件数がどう変わるかを見ます。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) 北海道 5,092,000 東京都 14,086,000 沖縄県 1,468,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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]

# マスター: 47都道府県すべて / 実績: 総人口300万人以上の県のみ観測されたと想定
master = df[["Prefecture"]].copy()
subset = df[df["A1101"] >= 3_000_000][["Prefecture", "A1101"]].rename(
             columns={"A1101": "A1101"})
print("マスター:", len(master), "行  実績:", len(subset), "行")

inner = pd.merge(master, subset, on="Prefecture", how="inner")   # 両方にある県だけ
left  = pd.merge(master, subset, on="Prefecture", how="left")    # マスターは全件残す
print("内部結合(inner):", len(inner), "行")
print("左外部結合(left):", len(left), "行  総人口が欠損(NaN):",
      int(left["A1101"].isna().sum()), "件")

📤 実行結果:

マスター: 47 行 実績: 10 行 内部結合(inner): 10 行 左外部結合(left): 47 行 総人口が欠損(NaN): 37 件

💬 読み取り内部結合は 10 行に激減 — 実績のない 37 県が黙って消えました。 集計対象を「全 47 県」のつもりでいると、 気づかぬうちに一部だけを分析してしまう典型的な事故です。 一方左外部結合は 47 行を維持し、 実績のない県は総人口が NaN(欠損)で残ります。 「母集団を減らしたくない」なら外部結合+欠損の明示が安全です。

④ 縦結合(concat)— 複数年ファイルを積んでパネル化

🎯 目的:年ごとに分かれて配布される想定のファイルを縦方向に積み上げ(UNION)、 1 本のパネルデータにまとめます。 列を増やす JOIN と違い、 行を積むのが concat です。

🔗 橋渡し:SSDSE から 2012 年分と 2023 年分を切り出し、 同じ列構成のまま pd.concat で縦に連結します。 スキーマ(列名・型)が揃っていることが前提です。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) 北海道 5,092,000 東京都 14,086,000 沖縄県 1,468,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd

df = pd.read_csv("data/raw/SSDSE-B-2026.csv", encoding="cp932", skiprows=[1])

# 別ファイルとして配布された年度データを縦方向に積み上げる想定(UNION / concat)
y2012 = df[df["SSDSE-B-2026"] == 2012][["SSDSE-B-2026", "Prefecture", "A1101"]]
y2023 = df[df["SSDSE-B-2026"] == 2023][["SSDSE-B-2026", "Prefecture", "A1101"]]
panel = pd.concat([y2012, y2023], ignore_index=True).rename(
            columns={"SSDSE-B-2026": "年", "A1101": "A1101"})

print("concat後 行数:", len(panel), "( =", len(y2012), "+", len(y2023), ")")
print(panel.groupby("年")["A1101"].sum())

📤 実行結果:

concat後 行数: 94 ( = 47 + 47 ) 年 2012 127589000 2023 124353000 Name: 総人口, dtype: int64

💬 読み取り:47 行 + 47 行がそのまま 94 行に積み上がりました。 縦結合は「同じ意味の列が同じ列名・同じ単位で並んでいる」ことが前提で、 ここがずれているとスキーマ統合(列名や単位の事前統一)が必要になります。 積み上げた結果、 全国総人口は 2012 年の 1 億 2759 万人から 2023 年の 1 億 2435 万人へと約 324 万人減少しており、 年をまたいだ推移分析ができるようになりました。

🎮 触って理解する — JOIN シミュレータ

上の①〜③で見た「結合タイプで行数が変わる」「表記ゆれで突合が全滅する」を、 実データ(SSDSE-B-2026・2023 年)の 8 都道府県で体感するシミュレータです。 左表は 8 県のマスター(人口表 A1101)、 右表は「人口の多い順に上位だけ観測できた」死亡表(A4200)。 図の右半分を上下にドラッグ(タッチ対応)すると観測範囲が変わり、 結合タイプ・表記ゆれ・名寄せを切り替えると、 結合結果の行数と欠損がリアルタイムに更新されます。

結合タイプ:

結合結果(リアルタイム更新)

※ 数値はすべて SSDSE-B-2026(2023 年)の実測値。 死亡率‰ = 死亡数 ÷ 総人口 × 1000(両方そろった行のみ計算可能)。

💡 シミュレータで確かめたい 3 つのこと

🔍 具体例・ケーススタディ

ケース 1: 名寄せの難しさ(同一実体・別表記)

名寄せは「見た目が違うが同じもの」を同一キーに束ねる作業で、 実務で最も工数がかかります。 典型的なゆれ:

対策の定石は「まず正規化ルール(トリム・全半角統一・接尾辞除去)でキーを揃える → それでも残る揺れはマスタ(対応表)で明示的に対応づける → 共通 ID が無い場合は氏名・住所の類似度で突き合わせるレコードリンケージ」という段階を踏むことです。 上の②のコードは、 この最初の「正規化」段階を最小構成で示したものです。

ケース 2: 内部結合と外部結合の使い分け

「顧客マスタ(全顧客)」と「今月の購入ログ(買った人だけ)」を統合するとします。

③のコードが示す通り、 同じデータでも how 次第で 10 行にも 47 行にもなります。 「行数が意図通りか」を結合直後に必ず確認しましょう。

ケース 3: キーの粒度不一致

片方が「都道府県別」、 もう片方が「市区町村別」だと、 そのままでは結合できません(1 対多になる)。 粒度を揃えるには、 細かい側を集約して粗い側に合わせる(市区町村を都道府県で合計する)か、 粗い側を按分して細かくします。 統合の前に「両表のキーは同じ粒度か」を確認するのが鉄則です。

⚠️ よくある誤解・落とし穴

誤解 1:内部結合しても件数は変わらないはず
内部結合はキーが一致しない行を黙って捨てます。 ③のように 47 → 10 行になっても警告は出ません。 結合の前後で行数を print(len(df)) し、 減っていたら「キーの表記ゆれ」か「相手にない行」を疑ってください。
誤解 2:キーが重複していても大丈夫
結合キーが片方で重複していると、 多対多結合で行が増殖します(2×3=6 行など)。 意図しない行数爆発の温床です。 pd.merge(..., validate="one_to_one")validate="one_to_many" で関係を宣言し、 違反したら例外を出させるのが安全です。
誤解 3:見た目が同じなら結合できる
「東京都」と「東京 」(末尾に空白)、 数値の 13 と文字列の "13"、 全角の「13」と半角の「13」は、 人間には同じでも機械は別キー扱い。 結合前に str.strip()・型合わせ・全半角正規化でキーの正規化を必ず行います(②参照)。
誤解 4:列名が同じなら単位・定義も同じ
「人口」がソース A では千人単位、 B では実数、 C では推計値かもしれません。 スキーマ統合では列名だけでなく単位・定義・基準時点まで揃える必要があります。 縦結合(concat)で年をまたぐ場合は特に、 定義変更がないかを確認しましょう。

結合の前後で必ず見る 7 項目

結合の事故は、どれも「例外が出ないまま静かに間違う」のが厄介なところです。 裏を返せば、決まった数を毎回見るだけで大半は防げます。 結合を書いたら、次の 7 つをその場で出力する習慣をつけてください。

いつ 見るもの おかしいと分かるサイン
結合前左右それぞれの行数想定と違うなら、絞り込み条件が効きすぎている
結合前キーの重複数 df[key].duplicated().sum()0 でないなら 1 対多。行数が増える覚悟が要る
結合前キーの欠損数と型欠損キーは結合されない。数値と文字列の混在も一致しない
結合前キーの集合の重なり len(set(a) & set(b))0 なら表記ゆれを疑う。結合しても空になるだけ
結合後結果の行数増えたら 1 対多、減ったら不一致。どちらも理由を言えること
結合後indicator=True の内訳left_only / right_only が想定外に多い
結合後主要な列の合計・平均結合前と桁が変わっていたら、値が複製されている

とくに 4 つめの「キーの集合の重なり」は、結合を書く前に一度だけ見ておく価値があります。 重なりが 0 でも merge は例外を出さず、列だけが揃った空の表を返します。 「なぜか結果が空になる」の原因は、ほとんどがここです。 全角と半角、前後の空白、都・道・府・県 の有無、 数値キーが片方だけ文字列になっている — 原因はいつも同じ顔ぶれです。

7 つめの「結合前後で合計を比べる」も強力です。 1 対多で値が複製されていれば合計は必ず増えますし、 不一致で行が落ちていれば必ず減ります。 合計が結合前と一致していれば、少なくとも行の増減で値は壊れていないと言えます。 たった 1 行の print で、この安心が手に入ります。

❓ FAQ

Q. 「JOIN」「merge」「結合」「マージ」は違うもの?

A. ほぼ同義です。 SQL では JOIN、 pandas では mergejoin、 日本語では「結合」「マージ」と呼びますが、 いずれも「キーで表を横に突き合わせる」操作を指します。

Q. mergeconcat はどう使い分ける?

A. 列を増やす(属性を足す)なら merge(=JOIN)、 行を増やす(同じ形の表を積む)なら concat(=UNION)。 「人口表に死亡列を足す」は merge、 「2012 年と 2023 年を積む」は concat です。

Q. どの結合タイプをデフォルトにすべき?

A. pandas の merge のデフォルトは how="inner" です。 ただし「母集団を減らしたくない」分析では、 基準となるマスタ側を left に置いた左外部結合の方が事故が少ないことが多いです。 いずれにせよ結合後の行数確認を習慣にしましょう。

Q. 共通の ID 列がありません。 どう統合する?

A. まず氏名・住所・都道府県など複数列を組み合わせてキーを作り、 正規化して突き合わせます。 それでも一意に決まらない場合は、 文字列の類似度(編集距離など)で確率的に対応づけるレコードリンケージを使います。

🗺 学習ロードマップ

  1. レベル 1 — 横結合(JOIN/merge)と縦結合(concat/UNION)の違いを理解する。
  2. レベル 2 — 内部結合・外部結合の 4 タイプと、 結合後の行数チェックを習慣化する。
  3. レベル 3 — キーの正規化・名寄せ(表記ゆれ・全半角・接尾辞)を実装する。
  4. レベル 4 — スキーマ統合(列名・単位・型・粒度の統一)とマスターデータ管理。
  5. レベル 5 — 共通 ID なしのレコードリンケージ、 ETL/データパイプラインでの統合の自動化。

データ統合を中心に、前後の工程結合の 4 種類を 1 枚に置きます。 矢印は「これができてから次へ進む」順序です。

データ統合 2 つの表を 1 枚に ① キーを整える クレンジング・名寄せ ② 一意性を確かめる 主キー・外部キー ④ 行数を数える 増減の理由を名指しで ⑤ 工程に載せる ETL / ELT ③ 結合の種類を選ぶ inner / left / right / outer どれを選ぶかで残る行が変わる

この 5 つは順番に意味があります。 ①②を飛ばして③へ行くと、結合そのものは成功したように見えて中身が空になります。 ④を飛ばすと、消えた行に気づかないまま分析が進みます。 ⑤は「毎回同じ結果になること」を保証する段階で、ここまで来て初めて統合が仕組みになります。

🔗 隣接手法への橋渡し

データ統合は分析パイプラインの中盤に位置します。 バラバラのソースを 1 枚の分析テーブルに束ねる工程です。

🌳 手法選択フロー — どの結合を選ぶか

迷ったら「分析の母集団はどちらの表か」から考えます。 結合の種類は好みではなく、答えたい問いで決まります。

  1. 両方に揃っている対象だけを分析したい?
    inner。ただし何行落ちたかを必ず数える。 落ちた対象に偏りがあれば、それは選択バイアスになります。
  2. 左の表が母集団(例:全 47 都道府県)で、右は付加情報?
    left。母集団の行数を保ったまま、付かなかったところは欠損で残す。 実務でいちばん多い形です。
  3. どちらが欠けているかを知りたい/突合検査をしたい?
    outerindicator=True。 left_only と right_only を数えるのが目的そのものになります。
  4. キーが重複している(1 対多・多対多)?
    → 結合の前に 集約して粒度を揃えるか、 結合後の行数が「左の行数 × 平均一致数」になることを承知のうえで進む。 多対多は原則として設計ミスを疑う
  5. そもそもキーの表記が揃っていない?
    → 結合の前に 名寄せ。 揃える前に結合すると、一致 0 件でも例外は出ず、空の表が静かにできあがります。

どれを選んでも、結合の直後に必ず行数を出力する。 これだけで、統合工程の事故はほとんど水際で止まります。

なお本ページで使った 2 つの表は、どちらも 1 行が 1 つの都道府県に対応するという点で結合しやすい形でした。 実務では片方が「取引 1 件 1 行」「ログ 1 件 1 行」のように粒度が細かいことが多く、 そのままでは 1 対多になります。結合の設計とは、突き詰めれば粒度の設計です。 どの単位で 1 行にしたいのかを先に決めてから、そこへ向けて集約と結合を並べてください。