「差の差分析(DID)」を理解するうえで必要なキーワードを 10 件以上提示します。 各チップから対応セクションへ移動できます。
30 秒結論 文脈 直感 数式 記号読み解き 実値計算 Python 実装 落とし穴 関連手法 関連用語 グループ教材 概念マップ
🍰 まずはやさしく
2つのグループの変化を比べる方法です。
ある対策の効果を正しく知るために使います。
スマホの利用制限で成績が変わったか調べます。
この章では結論を短くまとめました。
🍰 まずはやさしく
データ分析で使う特別な道具のようなものです。
原因と結果の関係をはっきりさせるために使います。
都市のルール変更で生活がどう変わったか調べます。
この用語がどこで使われるかを説明します。
論文中に 「差分の差分法(DiD)」として登場する用語。
差分の差分法(DiD) とは:処置群と対照群の前後差を比較する準実験的因果推論手法。共通トレンド仮定が鍵。
このセクションは「差の差分析(DID)」を扱う 用語ページ です。 統計データ分析コンペティション(2026)の再現教材における中核用語のひとつで、政令指定都市と非政令指定都市の比較(前後差の差) という観点で SSDSE-B-2026(47 都道府県 × 複数年 × 100 超列)に紐づけられます。
位置づけ:相関・線形回帰・仮説検定 といった基礎用語群と並列であり、応用としては 内生性・IV・DID・クラスタリング 等へ繋がります。
🍰 まずはやさしく
データを読み解くための眼鏡のようなものです。
隠れた変化を見つけ出すために使います。
部活の練習法を変えて、上達したか比べます。
直感的にイメージして理解していきましょう。
差の差分析(DID) を一言でいえば「政令指定都市と非政令指定都市の比較(前後差の差)」。 47 都道府県という小さな母集団でも、 SSDSE-B-2026 の A1101 列に注目すると、 大都市圏と地方の差・人口規模に伴う相対比較など、 様々なパターンが見えてきます。
比喩でいうと、 差の差分析(DID) はデータ分析の「眼鏡」のようなもの。 同じデータでも眼鏡を変えれば、 平均(中心)・分散(ばらつき)・相関(連動)・因果(影響)と、 異なる情報が浮かび上がります。 SSDSE-B-2026 を題材に、 この眼鏡をかけてみるのが本ページの狙いです。
差分の差分法(DiD)は「処置群と対照群の前後変化の差」で因果効果を推定する。 時系列の平行トレンド・処置効果のジャンプ・パネルデータ構造の 3 枚で、 DiD の幾何学的本質を把握する。



→ DiD 図で「2 群 × 2 期間」の本質、 時系列図で「トレンドの観察」、 パネル図で「データ構造」を整理。 平行トレンド仮定の検証がカギ。
→ すべて即答できれば、 DiD を政策評価・経営判断で適切に使い分けられる基礎力は十分。
🍰 まずはやさしく
計算式で効果を導き出す方法です。
正確な数字で影響を測るために使います。
買い物でのポイント還元で支出が変わったか調べます。
ここからは具体的な数式について読みます。
差の差分析(DID) の代表的な定義式は次のとおりです。
$$ \hat{\tau}_{DID} = (\bar{Y}_{T,\text{post}} - \bar{Y}_{T,\text{pre}}) - (\bar{Y}_{C,\text{post}} - \bar{Y}_{C,\text{pre}}) $$ここで使われる記号や演算の意味は次節で言葉に翻訳します。
上の DID 定義式の各記号を、日本語の意味に変換します。
回帰で書くと Y = β₀ + β₁·treated + β₂·post + β₃·(treated×post) + ε の交差項係数 β₃ が τDID に対応します。
SSDSE-B-2026 は 47 都道府県 × 12 年(2012〜2023)× 100 超列のパネルデータで、 DID の格好の練習台です。 実在の政策評価ではなく 手法デモ(疑似 DID)として、 「大都市圏(東京都・大阪府・愛知県)を仮の処置群、 残り 44 県を対照群」と見立て、 2018 年を境に 高齢化率(65 歳以上人口比率 = A1303 ÷ A1101 × 100)の推移を比較します。 以下の数値はすべて SSDSE-B-2026 の実測値から算出したものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import pandas as pd # SSDSE-B-2026(1 行目=列コード, 2 行目=日本語名)を読み込む df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932') df = df.rename(columns={'SSDSE-B-2026': 'year'}) # 先頭列=年度 # 結果変数:高齢化率(65歳以上人口 A1303 ÷ 総人口 A1101 × 100, %) df['aging'] = df['A1303'] / df['A1101'] * 100 # 疑似処置:大都市圏 3 都府県を処置群、 2018 年以降を処置後とみなす treat = ['東京都', '大阪府', '愛知県'] df['treated'] = df['Prefecture'].isin(treat).astype(int) df['post'] = (df['year'] >= 2018).astype(int) df['treat_post'] = df['treated'] * df['post'] print(df[['year', 'Prefecture', 'aging', 'treated', 'post']].head()) |
処置群と対照群の、 2018 年(処置前)と 2023 年(処置後)の高齢化率の実測平均は次のとおりです。
| 2018(前) | 2023(後) | 前後差 | |
|---|---|---|---|
| 処置群(大都市圏 3 都府県平均) | 25.07 | 25.38 | +0.31 |
| 対照群(他 44 県平均) | 30.35 | 32.01 | +1.66 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import statsmodels.formula.api as smf # ②と同じ 2 時点(2018=前, 2023=後)に絞り、 post を「2023 年ダミー」に定義し直す d2 = df[df['year'].isin([2018, 2023])].copy() d2['post'] = (d2['year'] == 2023).astype(int) d2['treat_post'] = d2['treated'] * d2['post'] # 高齢化率 ~ 処置 + 時期 + 交差項(treat_post の係数が DID 推定値) m = smf.ols('aging ~ treated + post + treat_post', data=d2).fit() print(round(m.params['treat_post'], 2)) # -1.36(2×2 の手計算と一致) # 県・年の固定効果を入れ、 県単位のクラスタ頑健 SE で再推定 m2 = smf.ols('aging ~ treat_post + C(Prefecture) + C(year)', data=d2) \ .fit(cov_type='cluster', cov_kwds={'groups': d2['Prefecture']}) print(round(m2.params['treat_post'], 4), round(m2.bse['treat_post'], 4)) # -1.3561 0.3841 |
②と同じ 2 時点(2018・2023)に絞って回帰すると、 交差項 treat_post の係数は −1.3561(≒ −1.36)となり、 ②の手計算と厳密に一致します(2×2 DID と飽和回帰の代数的同値性)。 県・年の固定効果を入れても係数は −1.3561 のままで、 県単位のクラスタ頑健標準誤差は 0.3841。 パネルでは誤差が県内で時系列相関するため、 県単位のクラスタ頑健標準誤差で有意性を評価するのが定石です。 なお、 ①で作った 12 年分のパネル全体(post=2018 年以降)に同じ回帰を当てると係数は −1.76 になりますが、 これは「処置後 6 年間の平均差」という別の量を推定しているためで、 ②の 2×2 手計算と突き合わせる際は 2 時点に絞る必要があります。
1 2 3 4 5 6 7 8 | # 年ごとの群平均で「処置前トレンドが平行か」を目視確認する trend = df.pivot_table(index='treated', columns='year', values='aging', aggfunc='mean').round(2) print(trend[[2012, 2015, 2018, 2021, 2023]]) # 処置群(1): 2012=22.12 → 2018=25.07(処置前 +2.95) # 対照群(0): 2012=25.86 → 2018=30.35(処置前 +4.49) # → 処置前から傾きが違う = 平行トレンド仮定は疑わしい。 # イベントスタディ(処置前ダミーの係数が 0 近傍か)で厳密に検証する。 |
💬 解釈と注意:DID は −1.36 ポイントですが、 処置前(2012→2018)から両群の傾きが違う(対照群のほうが速く高齢化)ため、 平行トレンド仮定が成り立っていません。 したがってこの −1.36 を「政策効果」と断定はできず、 むしろ本例は平行トレンド検証の重要性を示す教材です。 これは実在の政策ではない疑似 DID であり、 実データで DID を行うときは必ずプレトレンド検定・イベントスタディをセットで実施してください。
DiD の心臓部は 2×2 表(処置群/対照群 × 処置前/処置後 の 4 つの平均)です。 下のスライダー(または右のグラフ上の丸い点を直接ドラッグ)で 4 つの値と さらに前の基準時点 を動かすと、 DID =(処置群の変化)−(対照群の変化) がリアルタイムで再計算され、 反実仮想(対照群のトレンドを処置群に当てはめた破線)と実測の縦の差=処置効果が可視化されます。 初期値は本ページ上の SSDSE-B-2026 実測値(高齢化率, %)です。
| 群 \ 時点 | 2012 (基準) |
2018 (処置前) |
2023 (処置後) |
|---|---|---|---|
| 処置群 | 22.12 |
25.07 |
25.38 |
| 対照群 | 25.86 |
30.35 |
32.01 |
💡 スライダーは 0.01 刻み。 グラフ上の丸い点を直接ドラッグ(スマホはタップして上下スワイプ)しても値が変わります。 青の破線は「もし処置群が対照群と同じトレンドで推移したら」を表し、 青の実線(実測)との縦の差(緑)が処置効果=DID です。 「プレトレンドを平行にする」を押すと、 水準差を保ったまま 2012→2018 の傾きだけを揃えられます。 DID の値は変わらないのに判定が「信頼できる」に変わることを確かめてください。
処置群の前後差(初期値 +0.31)は「時間による自然な変化 + 政策効果」が混ざったもの。 このうち「時間による自然な変化」を、 政策の影響を受けていない対照群の前後差(+1.66)で代理し、 引き算で消去する。 残った −1.35 が処置効果の推定値です。 自然実験で得た擬似的な処置群・対照群に対し、 この「差の差」で反実仮想を近似するのが DiD の核心です。
1 2 3 4 5 | import statsmodels.formula.api as smf # 結果変数は 'y' ではなく、このページで作った 'aging'(高齢化率)。 # 交差項は treated:post と書けば、その場で作ってくれる m = smf.ols('aging ~ treated + post + treated:post', data=df).fit() print(round(m.params['treated:post'], 4)) # DID 推定値 |
1 2 3 4 5 6 7 8 9 10 | from linearmodels.panel import PanelOLS # 被説明変数はこのページで作った 'aging'(高齢化率)。df には # year / Prefecture / aging / treated / post / treat_post が入っている df_p = df.set_index(['Prefecture', 'year']) m = PanelOLS.from_formula( 'aging ~ 1 + treat_post + EntityEffects + TimeEffects', data=df_p).fit(cov_type='clustered', cluster_entity=True) print(m.params.round(4)) print('p 値:', round(m.pvalues['treat_post'], 4)) # 都道府県固定効果・年固定効果とクラスタ頑健 SE を一行で |
1 2 3 4 5 6 | # pip install differences from differences import ATTgt att = ATTgt(data=df, cohort_name='treat_year', strata_name=None, base_period='varying', anticipation=0).fit(formula='y ~ 1') print(att.aggregate('simple')) # 集約 ATT と動的 ATT を頑健に推定 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import pandas as pd # ── Synth に渡すパネルを用意する(都道府県 × 年 の高齢化率)── _d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df_pivot = pd.DataFrame({ 'Prefecture': _d['Prefecture'], 'year': _d['SSDSE-B-2026'], 'aging': _d['A1303'] / _d['A1101'] * 100, }).sort_values(['Prefecture', 'year']).reset_index(drop=True) from SyntheticControlMethods import Synth scm = Synth(df_pivot, 'aging', 'Prefecture', 'year', 2015, '東京都', n_optim=10).fit() scm.plot(['original', 'pointwise', 'cumulative']) # 「合成東京都」を構築して、 実際の東京都との差で処置効果を推定 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import numpy as np import pandas as pd from doubleml import DoubleMLData # ── DoubleML に渡すデータを用意する ── _d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _aging = _d['A1303'] / _d['A1101'] * 100 _raw = pd.DataFrame({ 'y': _d['L3221'].astype(float), # 消費支出 'treat': (_d['SSDSE-B-2026'] >= 2016).astype(int), # 2016 年以降を処置後 'x1': np.log(_d['A1101'].astype(float)), # 総人口の対数 'x2': _aging, # 高齢化率 }) data = DoubleMLData(_raw, y_col='y', d_cols='treat', x_cols=['x1', 'x2']) from doubleml import DoubleMLDID from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier dml_did = DoubleMLDID(data, y='y', d='treat', x_cols=['x1', 'x2'], ml_g=RandomForestRegressor(), ml_m=RandomForestClassifier()) dml_did.fit() print(dml_did.summary) # 機械学習で nuisance を推定する高度版 DID |
fit(cov_type='cluster', cov_kwds={'groups': prefecture})。 47県のように少クラスター時はワイルドブートストラップ (wild cluster bootstrap) も検討。差の差分析(DID) を実務で扱う際に踏みやすい落とし穴を 5 件挙げます。
差分の差分法(DiD) がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。
🌐 統計・データサイエンス › 因果推論 › 因果手法 › DiD
中心に 差分の差分法(DiD) を置き、 そこから IV・傾向スコア・内生性・重回帰・ロジスティック回帰 計 5 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語とあとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。
大きな円が小さな円を包含する Circle Packing 図。 「差分の差分法(DiD)」は緑色でハイライト。
長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「差分の差分法(DiD)」は緑色でハイライト。
| マップ | 分かること | こんな時に見る |
|---|---|---|
| 🔗 関係マップ | 手法間の横の関係(前提→発展→応用) | 「次に何を学べばよい?」 学習順序の判断 |
| ⭕ 包含マップ | 分類体系の入れ子構造(上位⊃下位) | 「この手法はどんなジャンルに属する?」 |
| 🌳 ツリーマップ | 分野の規模比較(面積=ボリューム) | 「データサイエンス全体の俯瞰像」 |
💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは DiD の隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス → 因果手法 → DiD という入れ子の位置を示します。 「因果手法には他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。
「差分の差分法 (DID)」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。 具体的には次の 3 方向と密接につながる:
DID は介入前後の差を treatment と control で比較し、 共通トレンド仮定下で因果効果を推定。
DID を含む因果推論の手法選択は、 データ構造・処置の入り方・識別仮定の妥当性で決まる。 典型シナリオと候補手法を示す。
| シナリオ | 重視する観点 | 候補手法 |
|---|---|---|
| 2 群 × 前後 2 期間のパネル | 平行トレンド仮定が妥当 | 標準 DID(2×2 / TWFE) |
| 処置時点が群ごとに異なる(staggered) | TWFE のバイアス回避 | Callaway-Sant'Anna / Sun-Abraham |
| 処置単位が 1 つ(例:1 県だけ) | 対照群を合成で構築 | 合成統制法(Synthetic Control) |
| 平行トレンドが崩れる | 別の識別戦略が必要 | IV / RDD / 傾向スコア |
DiD の心臓部である 2×2 表(上のウィジェットと同じ実測値)を、 処置群/対照群と処置前後で切ると 4 つのセルができます。 この 4 セルの各平均が「何の足し算でできているか」を分解すると、 なぜ 2 回引き算するのか(=差の差)が腑に落ちます。 記号は 群固有の水準を α、 その年の時間効果を λ、 求めたい処置効果を τ とします。
| 2018(前)実測 | 2023(後)実測 | 中身の分解 | |
|---|---|---|---|
| 処置群(大都市圏 3 都府県) | 25.07 | 25.38 | 前=αT+λpre 後=αT+λpost+τ |
| 対照群(他 44 県) | 30.35 | 32.01 | 前=αC+λpre 後=αC+λpost |
C(Prefecture)+C(year))。反実仮想の言葉で言い換えると、 「処置群がもし処置を受けなかったら 2023 年にどうなっていたか」を 25.07+(対照群の変化 +1.66)=26.73 と見積もり、 実測 25.38 との差 −1.35 を効果と呼んでいます。 対照群は自然実験的に用意した「反実仮想の代理」です。
上の 落とし穴集を、 推定量にどう効くか(符号の向き)の観点で整理し直します。 とくに DiD 初学者が見落としがちな 時変交絡(time-varying confounder)を明示的に加えます。
| 脅威 | 壊すメカニズム | 典型的な症状・見つけ方 | 対策 |
|---|---|---|---|
| ① 平行トレンド違反 (pre-trend) |
処置前から群間で λ の傾きが違う。 その傾き差が処置効果 τ に丸ごと混入。 | 本ページ ④ のとおり処置前(2012→2018)で傾きが違う。 イベントスタディの処置前係数が 0 から外れる(下表)。 | プレトレンド検定、 プラセボ処置年、 群固有の線形トレンド投入、 崩れるなら合成対照・IV・RDD へ。 |
| ② 処置タイミングのずれ (staggered) |
処置年が群ごとに違うと、 TWFE が「すでに処置された群」を対照に使う禁じられた比較を含む。 重みが負になりうる。 | 効果が異質かつ時間変化するとき、 TWFE 推定値が個別 ATT の外側・逆符号になる。 Goodman-Bacon 分解で診断。 | Callaway-Sant'Anna、 Sun-Abraham、 de Chaisemartin-d'Haultfoeuille 等の現代的 staggered 推定量。 |
| ③ 対照群の選択 | 処置群と異なるダイナミクスを持つ群を対照にすると、 λ の代理が失敗し偽の τ が出る。 | 対照群を替えると符号・大きさが不安定。 本例の「他 44 県」は大都市圏の代理として粗すぎる。 | 近い共変量の群に絞る、 傾向スコアで重み付け(DiD と併用)、 単一処置なら合成対照で加重平均の対照を作る。 |
| ④ SUTVA 違反 (波及・スピルオーバー) |
処置群の効果が対照群に漏れると、 対照群も動き、 差の差が縮む(効果を過小評価)。 | 県境・通勤圏・サプライチェーンで隣接するほど汚染が強い。 距離別に効果が変わる。 | 汚染しうる対照を除外、 距離・隣接ダミーで波及を明示的に測る、 空間計量。 |
| ⑤ 時変交絡 (time-varying confounder) |
群ごとに時間変化する別要因(例:ある年に処置群だけを襲った産業ショック)は、 固定効果でも年ダミーでも吸収されず τ に混ざる。 | 固定効果は「時間で変わらない群の違い α」しか消せない。 時間で変わる交絡は残る点が盲点。 | 時変共変量を制御、 同時期の別ショックを別変数化、 期間分割で頑健性確認、 プラセボ結果変数。 |
💡 要点:固定効果(または 変量効果)が消せるのは「時間で変わらない個体差 α」だけ。 ①と⑤はいずれも「時間で変わる群差」であり、 固定効果を入れても自動では消えません。 DiD を『固定効果を入れたから因果』と誤解しない、 が最大の勘所です。 より体系的な位置づけは パネルデータと因果推論を参照(因果推論の全体像・潜在的結果モデルは本サイト内の関連ページ群にも散在)。
2×2 DiD は「処置前 1 点・処置後 1 点」に情報を圧縮しています。 これを各年に開いたのがイベントスタディです。 処置年(2018)を基準(係数 0)に固定し、 各年ダミー×処置群の交互作用係数を並べます。 処置前の係数が 0 近傍ならば平行トレンドを支持、 処置後の係数は動的な効果の推移を表します。 下表は SSDSE-B-2026 の実測群平均から算出した、 2 群飽和イベントスタディの係数(=群平均ギャップの 2018 基準正規化。 実測値のみ)です。
| 年 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2018 基準 |
2019 | 2020 | 2021 | 2022 | 2023 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 係数 | +1.53 | +1.44 | +1.28 | +0.85 | +0.64 | +0.33 | 0 | −0.34 | −0.85 | −0.86 | −1.11 | −1.36 |
読み方:処置後 2023 の係数 −1.36 は、 本ページ ②の 2×2 DiD(−1.36)と一致します(イベントスタディの「処置年基準・特定年係数」は 2×2 DiD の一般化だから)。 ところが処置前の係数が 0 でなく、 +1.53→+0.33 と単調に下がっています。 これは処置前から両群のギャップが縮み続けていた(=平行トレンドが成り立っていない)動かぬ証拠で、 −1.36 の相当部分は処置ではなく既存トレンドの延長である疑いが濃厚です。 実務では、 処置前係数が 0 近傍に収まっているかを図で示すことが、 DiD の信頼性の生命線になります。 → 平行トレンド。 なお本例はあくまで手法デモ(疑似 DiD)であり、 実在の政策効果ではありません。
「個体固定効果+年固定効果+処置ダミー」の TWFE 回帰は、 全群が同時に処置される 2×2 設計では 2×2 DiD と厳密に一致します(本ページ ③)。 しかし処置年が群ごとに異なる staggered 設計では話が変わります。 Goodman-Bacon (2021) は、 TWFE 推定値が「多数の 2×2 比較の加重平均」に分解でき、 その中に「すでに処置された群」を対照に使う禁じられた比較が混じることを示しました。
※ 本ページの疑似 DiD は「全処置群が 2018 年に同時処置」の設計なので、 この staggered バイアスは生じません(③で FE 入り係数が 2×2 と一致するのはそのため)。 上記は「もし処置年がバラバラだったら」という一般論としての注意です。
処置単位が 1 つしかない(例:ある 1 県だけが政策を導入)とき、 「どの 1 県を対照にするか」で結果が揺れます。 合成対照法はこの選択を、 複数の対照候補の加重平均で「合成された対照単位」を作ることで自動化します。 重みは処置前の結果変数(と共変量)の軌跡が処置単位に最もよく一致するように決めます。
📝 関連ページ(html/glossary 内・実在確認済):平行トレンド/パネルデータと因果推論/固定効果/変量効果/パネルデータ/自然実験/内生性/操作変数法(IV)/回帰不連続(RDD)/処置群/対照群/選択バイアス/交絡/因果関係/A/B テスト。 なお 合成対照法・イベントスタディ・傾向スコア・因果推論総論・潜在的結果モデルの独立ページは現時点で未作成のため、 本文中でテキスト説明としています。