このページで扱う主要キーワード(クリックで該当セクションへ):
🍰 まずはやさしく
バラバラの表を1つにまとめるパズルのような操作です。
データを分析しやすい形にするために使います。
スマホの連絡先とSNSのアカウントを紐付けるようなものです。
この章ではデータのまとめ方について読みます。
🍰 まずはやさしく
バラバラに保存されたデータを1つにする方法です。
正しい分析を始めるための準備として使います。
学校の出席簿とテストの結果をまとめる時に似ています。
ここでは統合の流れと関連する言葉について読みます。
「データ統合」 (Data Integration) は、 SSDSE-B-2026 などの公的統計データを使った教材・分析で頻出するキーワードです。 現実のデータは「人口の表」「死亡の表」「所得の表」のように別々のファイル・別々のシステムに分かれて存在します。 分析の第一歩は、 それらを共通のキー(年・都道府県など)で 1 枚の表に束ねること。 本ページでは、 まず直感、 次に結合の種類の定義、 そして 47 都道府県の実データで確かめる、 という流れで整理します。
関連用語(前提・並列・発展)も末尾にまとめてあるので、 用語の地図として活用してください。
🍰 まずはやさしく
2枚の名簿を突き合わせるイメージです。
共通の目印を使って情報を付け足すために使います。
部活の名簿に、個人の連絡先を書き足すようなものです。
ここでは横に貼る方法と縦に積む方法について読みます。
データ統合のいちばん素朴なイメージは「2 枚の名簿を突き合わせる」こと。 片方に「都道府県ごとの人口」、 もう片方に「都道府県ごとの死亡数」があるとき、 都道府県名という共通の目印(キー)で 2 枚を横に貼り合わせれば、 「人口と死亡数が同じ行に並んだ 1 枚の表」ができます。 あとは割り算するだけで死亡率が出ます。
SSDSE では都道府県ごとに人口 A1101 と死亡数 A4200 が並んでいます。 これを別ソース由来の 2 表だと考えると、 「都道府県」というキーで突き合わせて列を増やす操作が JOIN です。 表計算ソフトの VLOOKUP と同じ発想で、 「キーが一致した行だけ、 相手の列を持ってくる」わけです。
統合には 2 方向あります。 横結合(JOIN/merge)は「同じ対象について列を増やす」操作。 縦結合(concat/UNION)は「同じ列構成の表を行方向に積む」操作で、 2012 年ファイルと 2023 年ファイルを積んでパネルデータを作るときに使います。 列を増やすのか、 行を増やすのか — ここを取り違えないことが第一歩です。
JOIN はキーの完全一致で結びつきます。 だから「東京都」と「東京」、 「株式会社ABC」と「(株)ABC」のように表記がぶれると、 見た目は同じでも機械的には別物と判定され、 突き合わせが 1 件も成立しないことがあります。 この表記を揃える作業が名寄せで、 データ統合で最も手間のかかる部分です。
「両方の表にキーがある行だけ残す」のが内部結合(inner)、 「片方(基準表)の行は全部残し、 相手にない部分は欠損にする」のが外部結合(left/right/full outer)。 内部結合は静かに行を落とすので、 「なぜか件数が減った」の多くはこれが原因です。
🍰 まずはやさしく
共通の項目を基準に表をくっつける操作のことです。
どの行を残してどの行を捨てるかを決めるために使います。
買い物リストと商品の値段表を合わせるようなものです。
ここでは結合の詳しい種類について読みます。
データ統合とは、 異なるソースの複数の表 $R, S$ を、 共通の属性(キー)$k$ の一致に基づいて 1 つの表に結合する操作の総称です。 関係代数では横結合を結合演算 $\bowtie$ で表します。
両表でキー $k$ が一致する組だけを残す。 一致しない行は捨てられる。
左表 $R$ の行はすべて残し、 右に相手がなければ欠損(NULL / NaN)で埋める。
| 種類 | pandas の how | 残る行 | 典型的な用途 |
|---|---|---|---|
| 内部結合 INNER | "inner" | 両表にキーがある行のみ | 両方そろって初めて意味を持つ分析(人口×死亡→死亡率) |
| 左外部結合 LEFT | "left" | 左表は全件、 右は一致分のみ | マスター(47都道府県)を軸に、 実績を貼り付ける |
| 右外部結合 RIGHT | "right" | 右表は全件、 左は一致分のみ | left の左右を入れ替えただけ |
| 完全外部結合 FULL | "outer" | どちらかにあれば全部 | 2 ソースの差分・欠けを洗い出す突合 |
| 観点 | 横結合(JOIN / merge) | 縦結合(concat / UNION) |
|---|---|---|
| 増える方向 | 列(属性を足す) | 行(レコードを積む) |
| 前提 | 共通キーがある | 列構成(スキーマ)が揃っている |
| 例 | 人口表 + 死亡表 → 死亡率表 | 2012年 + … + 2023年 → パネル |
| pandas | pd.merge(a, b, on=...) | pd.concat([a, b]) |
| 用語 | 一言定義 |
|---|---|
| キー(結合キー) | 表と表を突き合わせるための共通の目印となる列(年・都道府県など)。 |
| 主キー | 1 つの表の中で行を一意に識別する列(またはその組)。 重複・欠損があってはいけない。 |
| 外部キー | 別の表の主キーを参照する列。 表と表の関係を張る。 |
| マスターデータ | 都道府県一覧・商品台帳のように、 分析の基準となる「正」の参照表。 これを軸に外部結合することが多い。 |
| 名寄せ | 「東京」「東京都」のように表記の異なる同一実体を同じキーに揃える作業。 |
| スキーマ統合 | ソースごとに違う列名・単位・型・粒度を、 共通の定義に合わせて揃えること。 |
| レコードリンケージ | 共通 ID がないデータ同士を、 氏名・住所などの類似度で確率的に突き合わせる名寄せの発展形。 |
ここまでの①〜④とシミュレータで「結合タイプで行が消える」「表記ゆれで突合が全滅する」を見ました。 最後に、 データ統合で起きる行数の変化(脱落と増殖)を一本の式で串刺しにし、 実務で刺さりやすい残りの落とし穴(複合キーの取り違え・時点整合・同名異義列・検算)まで踏み込みます。
横結合(JOIN)の後の行数は、 難しく考えなくても次の一行で説明できます。 あるキー値 $k$ について、 左表でそのキーを持つ行が $a_k$ 個、 右表で $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 以下か)」を確かめることに尽きます。
誤解 2 の行増殖を、 SSDSE-B-2026 の実データで再現します。 このデータは 564 行 = 47 都道府県 × 12 年(2012–2023)で、 1 行を一意に決めるキーは「年 + 都道府県」の複合キーです。 うっかり「年」を落として Prefecture だけで結合すると、 各都道府県が 12 回重複する非一意キーになり、 掛け算で行が膨らみます。
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 行(各年 1 行)。 結合キーから「年」を落とすと Prefecture が 12 回重複する非一意キーになり、 $12\times12=144$ 行に膨れ上がりました(脱落と真逆の事故)。 複合キーの一部を書き忘れるのが、 実務で最も多い増殖原因です。 正しく「年 + 都道府県」で結合すれば 12 行のまま。 そして validate="one_to_one" を付けておけば、 非一意キーでの結合を MergeError で実行前に叩き落とせます。 「結合したら行数が想定と違う」を検知する最も安いガードです。
pop_oct1)にしてから統合します。_x / _y が生える人口)があると、 pd.merge は自動で 人口_x / 人口_y にリネームします。 これを見落とすと、 後段でどちらを使っているか分からなくなります。 結合前に suffixes=("_住基", "_推計") で由来を明示するか、 事前に列名を一意化しておきましょう。assert len(merged) == 期待値 や、 merged["総人口"].sum() の前後比較を 1 行入れるだけで、 静かな脱落・増殖の大半は水際で止まります。ここで使った validate は、 「このキーは一意なはず」という設計上の前提を機械に宣言する営みです。 これはデータベースの世界では主キー制約・UNIQUE 制約、 外部キー制約として最初から仕組み化されています。 発展の方向は次の通りです。
JOIN と PRIMARY KEY / UNIQUE 制約、 ETL パイプラインで統合を再現可能な処理として固定する。ここまでは「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) 複製された列を合計しない(どうしても必要なら重複を除いてから足す)。 下のコードは、この事故と直し方をそのまま再現します。
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():,}') |
📤 実行すると次の出力が得られる:
💬 読み方:最後の 2 行が対になっています。drop_duplicates で県の重複を落としてから合計すれば、結合前と同じ 124,353,000 に戻ります。つまり値が壊れていたのではなく、同じ値を 48 回数えていただけです。1 対多の結合をしたら「この列は複製されているか」を必ず自問してください。
結合を口で説明すると分かった気になりますが、行数と欠損の数を数えると一発で腑に落ちます。 同梱の 2 つの表を実際に結合して確かめます。
都道府県 と 総人口。47 行。都道府県 と 推定人口(10歳以上)。48 行。行数が 47 と 48 で 1 つ違います。この 1 行の差がどこへ消えるかが、結合の種類の違いそのものです。
| 結合 | 結果の行数 | 欠損を含む行 | 何が起きたか |
|---|---|---|---|
| inner(内部結合) | 47 | 0 | 両方にある 47 県だけが残り、「全国」は黙って消える。 |
| left(左外部結合) | 47 | 0 | 左の 47 行を保つ。右にしかない「全国」は捨てられる。 |
| right(右外部結合) | 48 | 1 | 右の 48 行を保つ。「全国」の総人口が欠損になる。 |
| outer(完全外部結合) | 48 | 1 | どちらかにあれば残す。差分が欠損として見える化される。 |
indicator=True を付けて _merge 列を見ると、
right_only に入っていたのは 「全国」 の 1 行でした。
SSDSE-D には 47 都道府県に加えて全国計の行が入っており、SSDSE-B には入っていません。
ここが実務でいちばん危ないところです。inner で結合すると、 「全国」が消えたことに気づかないまま 47 行の表ができあがります。 行数が 47 で揃っているので、一見なにも問題がないように見えます。 逆に、もし全国計を残したまま平均を取れば、全国計が 1 県として二重に数えられ、 平均も相関も狂います。
結合したら必ず行数を数える。 そして「増えた/減った」なら、その 1 行が誰なのかを名指しで確認する。 この 2 つを習慣にすれば、統合工程の事故のほとんどは防げます。
このコードでやること:上の表を実際に作ります。
4 種類の結合それぞれで行数・欠損数・_merge の内訳を出し、
最後に「右にしかない行」を名指しで表示します。
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()) |
📤 実行すると次の出力が得られる:
💬 一致の確認:Step 1 の表と数字がそのまま一致しました。最後の 1 行が決定的で、消えていたのは「全国」という集計行だと名指しで分かります。inner と left が同じ 47 行になるのは「左にしかない行が 0 件だったから」であって、いつも同じになるわけではありません。左に余分な行があれば inner だけが減ります。
ここからは実データ SSDSE-B-2026(564 行 = 47 都道府県 × 12 年 2012–2023、 112 列)を使い、 データ統合の 4 つの基本操作を順に動かします。 CSV は cp932 で、 1 行目が英語コード(A1101 等)、 2 行目が日本語見出しなので、 読み込み時に skiprows=[1] で日本語行を捨て、 英語コードを列名として使います。
🎯 目的:別ソースを模した「人口表」と「死亡表」を、 年 + 都道府県のキーで内部結合し、 同じ行に人口と死亡数を並べて死亡率(人口千人あたり)を計算します。
🔗 橋渡し:SSDSE は 1 枚の表ですが、 総人口 A1101 と死亡数 A4200 を別々に切り出すことで「2 つのソースを統合する」状況を再現します。 結合キーは ["年", "Prefecture"] の 2 列です。
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)) |
📤 実行結果:
💬 読み取り:人口表 47 行と死亡表 47 行が、 キー完全一致で 47 行に統合されました(1 行も落ちていない=キーが綺麗に対応している証拠)。 統合できて初めて「死亡数 ÷ 人口」という複数ソースにまたがる計算が可能になります。 2023 年の全国平均死亡率は 12.67‰。 北海道 14.75、 青森 17.60 と、 高齢化の進む県で高い傾向が読み取れます。
🎯 目的:外部システム由来の表が「北海道→北海」「東京都→東京」のように末尾を落とした表記ゆれを持つとき、 素朴な結合が 0 件になることを見せ、 正規化キーを作る名寄せで 47 件を回復させます。
🔗 橋渡し:高齢人口 A1303 の表を、 わざと末尾の「都道府県」を除いた 地域 列に加工します。 これで「同じ都道府県なのにキー文字列が違う」状況を人工的に作ります。
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 件全部が突き合いました。 これが名寄せの最小形です。
🎯 目的:47 都道府県のマスターと、 一部の県しか観測されていない実績表を結合し、 内部結合(inner)だと行が減り、 左外部結合(left)だと欠損付きで全件残ることを比較します。
🔗 橋渡し:「総人口 300 万人以上の県だけ観測できた」という欠損シナリオを作り、 マスター 47 行に貼り付けます。 どちらの how を選ぶかで結果件数がどう変わるかを見ます。
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()), "件") |
📤 実行結果:
💬 読み取り:内部結合は 10 行に激減 — 実績のない 37 県が黙って消えました。 集計対象を「全 47 県」のつもりでいると、 気づかぬうちに一部だけを分析してしまう典型的な事故です。 一方左外部結合は 47 行を維持し、 実績のない県は総人口が NaN(欠損)で残ります。 「母集団を減らしたくない」なら外部結合+欠損の明示が安全です。
🎯 目的:年ごとに分かれて配布される想定のファイルを縦方向に積み上げ(UNION)、 1 本のパネルデータにまとめます。 列を増やす JOIN と違い、 行を積むのが concat です。
🔗 橋渡し:SSDSE から 2012 年分と 2023 年分を切り出し、 同じ列構成のまま pd.concat で縦に連結します。 スキーマ(列名・型)が揃っていることが前提です。
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()) |
📤 実行結果:
💬 読み取り:47 行 + 47 行がそのまま 94 行に積み上がりました。 縦結合は「同じ意味の列が同じ列名・同じ単位で並んでいる」ことが前提で、 ここがずれているとスキーマ統合(列名や単位の事前統一)が必要になります。 積み上げた結果、 全国総人口は 2012 年の 1 億 2759 万人から 2023 年の 1 億 2435 万人へと約 324 万人減少しており、 年をまたいだ推移分析ができるようになりました。
上の①〜③で見た「結合タイプで行数が変わる」「表記ゆれで突合が全滅する」を、 実データ(SSDSE-B-2026・2023 年)の 8 都道府県で体感するシミュレータです。 左表は 8 県のマスター(人口表 A1101)、 右表は「人口の多い順に上位だけ観測できた」死亡表(A4200)。 図の右半分を上下にドラッグ(タッチ対応)すると観測範囲が変わり、 結合タイプ・表記ゆれ・名寄せを切り替えると、 結合結果の行数と欠損がリアルタイムに更新されます。
※ 数値はすべて SSDSE-B-2026(2023 年)の実測値。 死亡率‰ = 死亡数 ÷ 総人口 × 1000(両方そろった行のみ計算可能)。
名寄せは「見た目が違うが同じもの」を同一キーに束ねる作業で、 実務で最も工数がかかります。 典型的なゆれ:
R01000 と、 総務省の団体コード、 郵便番号ベースなど参照系がバラバラ対策の定石は「まず正規化ルール(トリム・全半角統一・接尾辞除去)でキーを揃える → それでも残る揺れはマスタ(対応表)で明示的に対応づける → 共通 ID が無い場合は氏名・住所の類似度で突き合わせるレコードリンケージ」という段階を踏むことです。 上の②のコードは、 この最初の「正規化」段階を最小構成で示したものです。
「顧客マスタ(全顧客)」と「今月の購入ログ(買った人だけ)」を統合するとします。
③のコードが示す通り、 同じデータでも how 次第で 10 行にも 47 行にもなります。 「行数が意図通りか」を結合直後に必ず確認しましょう。
片方が「都道府県別」、 もう片方が「市区町村別」だと、 そのままでは結合できません(1 対多になる)。 粒度を揃えるには、 細かい側を集約して粗い側に合わせる(市区町村を都道府県で合計する)か、 粗い側を按分して細かくします。 統合の前に「両表のキーは同じ粒度か」を確認するのが鉄則です。
print(len(df)) し、 減っていたら「キーの表記ゆれ」か「相手にない行」を疑ってください。pd.merge(..., validate="one_to_one") や validate="one_to_many" で関係を宣言し、 違反したら例外を出させるのが安全です。13 と文字列の "13"、 全角の「13」と半角の「13」は、 人間には同じでも機械は別キー扱い。 結合前に str.strip()・型合わせ・全半角正規化でキーの正規化を必ず行います(②参照)。結合の事故は、どれも「例外が出ないまま静かに間違う」のが厄介なところです。 裏を返せば、決まった数を毎回見るだけで大半は防げます。 結合を書いたら、次の 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 で、この安心が手に入ります。
A. ほぼ同義です。 SQL では JOIN、 pandas では merge/join、 日本語では「結合」「マージ」と呼びますが、 いずれも「キーで表を横に突き合わせる」操作を指します。
merge と concat はどう使い分ける?A. 列を増やす(属性を足す)なら merge(=JOIN)、 行を増やす(同じ形の表を積む)なら concat(=UNION)。 「人口表に死亡列を足す」は merge、 「2012 年と 2023 年を積む」は concat です。
A. pandas の merge のデフォルトは how="inner" です。 ただし「母集団を減らしたくない」分析では、 基準となるマスタ側を left に置いた左外部結合の方が事故が少ないことが多いです。 いずれにせよ結合後の行数確認を習慣にしましょう。
A. まず氏名・住所・都道府県など複数列を組み合わせてキーを作り、 正規化して突き合わせます。 それでも一意に決まらない場合は、 文字列の類似度(編集距離など)で確率的に対応づけるレコードリンケージを使います。
データ統合を中心に、前後の工程と結合の 4 種類を 1 枚に置きます。 矢印は「これができてから次へ進む」順序です。
この 5 つは順番に意味があります。 ①②を飛ばして③へ行くと、結合そのものは成功したように見えて中身が空になります。 ④を飛ばすと、消えた行に気づかないまま分析が進みます。 ⑤は「毎回同じ結果になること」を保証する段階で、ここまで来て初めて統合が仕組みになります。
データ統合は分析パイプラインの中盤に位置します。 バラバラのソースを 1 枚の分析テーブルに束ねる工程です。
迷ったら「分析の母集団はどちらの表か」から考えます。 結合の種類は好みではなく、答えたい問いで決まります。
indicator=True。
left_only と right_only を数えるのが目的そのものになります。どれを選んでも、結合の直後に必ず行数を出力する。 これだけで、統合工程の事故はほとんど水際で止まります。
なお本ページで使った 2 つの表は、どちらも 1 行が 1 つの都道府県に対応するという点で結合しやすい形でした。 実務では片方が「取引 1 件 1 行」「ログ 1 件 1 行」のように粒度が細かいことが多く、 そのままでは 1 対多になります。結合の設計とは、突き詰めれば粒度の設計です。 どの単位で 1 行にしたいのかを先に決めてから、そこへ向けて集約と結合を並べてください。