DiD(差の差法)とは?
(画像は、Geminiで作成されたものです)
DiDの概要
DiD(Difference-in-Differences:差の差法) は、計量経済学や統計学の分野で古くから用いられている、政策やビジネス施策の「因果効果」を測るための代表的な疑似実験アプローチです。
高度な時系列手法であるCausalImpactのベースとなった手法でもあり、CausalImpactのベースとなる論文内でも「広く使われている『差の差の分析(DID)』を状態空間モデルへと拡張」したと述べられています。DiDは、シンプルながら非常に強力なロジックで「反事実(もし施策を行っていなかったらどうなっていたか)」を推定します。
施策の効果を測る際、私たちはよく以下のような「単純な比較」をしてしまいがちです。
- 前後比較: 施策を実施したグループの「施策前」と「施策後」の数値を比べる。
- グループ間比較: 施策を実施した時期に、「実施したグループ」と「実施していないグループ」の数値を比べる。
しかし、これらの単純な比較には大きな落とし穴があります。前後比較では「季節要因や市場全体のトレンド変化(例:たまたま繁忙期だった)」という外部要因を施策効果と誤認してしまいますし、グループ間比較では「そもそも両グループが持っていた潜在的な実力差や特性の違い」を無視してしまいます。
DiDは、この2つの比較の弱点を補い合うため、データを 「介入群(施策を受けたグループ)」 と 「統制群(施策を受けていないグループ)」 の2つに分け、さらにそれらを 「施策前」 と 「施策後」 の2つの期間で観測します。
統制群の「時間的な変化(トレンド)」を観察することで、市場全体の自然な成長や季節要因によるノイズを把握し、それを介入群の「時間的な変化」から引き算(差の差をとる)します。これにより、「もし施策を行っていなかったら、介入群も統制群と同じトレンドを辿っていたはずだ」という仮定(平行トレンドの仮定)のもと、ノイズを取り除いた「純粋な施策の効果」を浮き彫りにするのです。
DiDの基本ロジック:「平行トレンドの仮定」と「差の差」の数理
DiDがやっていることを一言で言えば、「統制群(施策をしていないグループ)の成長幅を、介入群(施策をしたグループ)の成長幅から引き算する」ことです。
具体的な効果測定のメカニズムと、それを支える数理モデルの構造を紐解いていきましょう。
施策効果を導く「差の差」のシンプルな引き算
DiDにおいて、施策による因果効果(Treatment Effect) は、以下のような非常にシンプルな四則演算(差の差)で定義されます。
- :介入群(Treated)の、施策後(Post)の平均値
- :介入群(Treated)の、施策前(Pre)の平均値
- :統制群(Control)の、施策後(Post)の平均値
- :統制群(Control)の、施策前(Pre)の平均値
前半の括弧 は、介入群の「施策前後の変化量」を表します。しかし、この変化量には「市場全体の自然な成長」や「季節要因」が含まれています。 そこで、後半の括弧 で計算した統制群の「施策前後の変化量(自然な成長分)」を差し引くことで、純粋な施策効果 を導き出すのです。
DiDの最重要ルール:「平行トレンドの仮定」
この美しい引き算が成立するためには、一つの強力な前提条件を満たしている必要があります。それが「平行トレンドの仮定(Parallel Trend Assumption)」です。
“もし施策が行われていなかったとしたら、介入群と統制群は、時間の経過とともに「同じ傾き(平行)」で変化していたはずだ”
という仮定です。 言い換えれば、両グループの間で「元々の売上の規模感(ベースライン)」に差があっても構いませんが、「成長のペース(トレンド)」は施策前において同じでなければならず、施策がなかった場合、施策後もその平行な関係が維持される、と考えます。DiDにおける「反事実(施策がなかった場合の介入群の姿)」は、この仮定によって描かれることになります。
回帰モデルによるDiDの表現
実務では、単なる引き算ではなく、「回帰分析(最小二乗法など)」を使ってDiDを表現することが一般的です。回帰モデルを使うことで、他の要因(共変量)を統制したり、効果の「統計的な有意差(p値)」を簡単に計算できたりするメリットがあるためです。
基本となるDiDの回帰方程式は以下のようになります。
- :介入群なら1、統制群なら0となるダミー変数(グループ間の元々の実力差 を吸収する)
- :施策後なら1、施策前なら0となるダミー変数(市場全体の時間的なトレンド を吸収する)
- :介入群かつ施策後である場合のみ1となる交差項
この回帰式が、先ほど定義した4つの状態(、、、)とどのように対応しているのか、ダミー変数にそれぞれ 0 と 1 を当てはめて確認してみましょう。
(※ここでは誤差項 の期待値を0として、各グループの平均値を導き出します)
① 統制群・施策前()
の場合
② 統制群・施策後()
の場合
③ 介入群・施策前()
の場合
④ 介入群・施策後()
の場合
これら4つの式を使って、先ほどの「差の差(DiD)」の計算式をもう一度組み立ててみます。
このように、ベースライン()、グループ間の元々の実力差()、時間経過によるトレンド変化()が綺麗に相殺され、最後に交差項の係数である だけが残ります。 この数理的な構造があるからこそ、回帰モデルにおいて「(交差項の係数)こそが、私たちが知りたい純粋な因果効果 となる」のです。
検証用ダミーデータの生成コード
今回作成するデータは、先ほど解説した「平行トレンドの仮定」を満たす時系列データです。全期間を100日間とし、70日目を施策の実行日として定義するシナリオを想定します。
データの生成プロセスは以下の数式に基づいています。時点を ()とします。
① 共通のトレンド(自然成長 + 季節要因)
市場全体が影響を受けるベースのトレンド は、時間経過による右肩上がりの成長と、30日周期の波(Sinカーブ)を足し合わせたものです。
② 統制群(Control)のデータ生成式
統制群 は、共通トレンドにベースライン値 と、標準偏差 の観測ノイズ を加えたものです。施策の有無にかかわらず、全期間この数式で推移します。
③ 介入群(Treated)のデータ生成式
介入群 は、統制群と同じ共通トレンドを持ちつつも、元々のベースライン値が と高い設定です。これが「グループ間の実力差」となります。さらに、施策が行われる70日目()以降は一律で の施策効果(True Effect)が加わります。
施策前():
施策後():
以下のPythonコードで、このデータ生成プロセスをシミュレーションします。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib
# 1. 乱数シードの固定
np.random.seed(123)
# 2. 期間パラメータの設定
n_periods = 100 # 全期間(100日間)
intervention_point = 70 # 施策実行日(70日目)
time = np.arange(n_periods)
# 3. 共通のトレンド(自然成長 + 季節要因)の作成
# 両グループが同じ影響を受ける市場全体のトレンド (Trend_t)
common_trend = 0.5 * time + 5 * np.sin(2 * np.pi * time / 30)
# 4. 統制群(Control: 施策なし)のデータ生成
# 共通トレンドに、ベースライン(10)と観測ノイズを加える
y_control = 10 + common_trend + np.random.normal(loc=0, scale=2, size=n_periods)
# 5. 介入群(Treated: 70日目から施策あり)のベースデータ生成
# 共通トレンドに、ベースライン(30)と観測ノイズを加える
# (この時点ではControlと平行なトレンドを描く反事実のデータ)
y_treated_base = 30 + common_trend + np.random.normal(loc=0, scale=2, size=n_periods)
y_treated = y_treated_base.copy()
# 6. 施策効果(介入効果)の注入
# 70日目以降の介入群データにのみ、一律で「+15」の効果を加える
true_effect = 15
y_treated[intervention_point:] += true_effect
# DataFrameにまとめる
df = pd.DataFrame({
'Time': time,
'Control': y_control,
'Treated': y_treated,
'Treated_Counterfactual': y_treated_base # 反事実(正解データ)
})
# 7. 生成したデータの可視化
plt.figure(figsize=(10, 6))
# 実際の観測値のプロット
plt.plot(df['Time'], df['Treated'], label='Treated (介入群: 施策あり)', color='darkorange', linewidth=2)
plt.plot(df['Time'], df['Control'], label='Control (統制群: 施策なし)', color='royalblue', linewidth=2)
# 介入群の反事実(もし施策がなかったら)のプロット
plt.plot(df['Time'][intervention_point:], df['Treated_Counterfactual'][intervention_point:],
label='Counterfactual (介入群の反事実)', color='darkorange', linestyle='--', alpha=0.7)
# 施策実行日の縦線
plt.axvline(x=intervention_point, color='red', linestyle=':', linewidth=2, label='Intervention (施策実行日)')
plt.title('Generated Dummy Data for DiD (差の差法)')
plt.xlabel('Time (Days)')
plt.ylabel('Value (Target KPI)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
ここでは、DiDの検証に用いるダミーデータの生成と可視化を行っています。

まず、common_trend として、時間経過に比例して上昇する要素(0.5 * time)と、30日周期で波打つ季節要因(5 * np.sin(...))を組み合わせた市場全体のトレンドを作成します。 次に、統制群(Control)はこの共通トレンドにベースライン値 10 とノイズを足して生成します。一方、介入群(Treated)の本来の推移である y_treated_base は、同じ共通トレンドにベースライン値 30 とノイズを足して生成しています。これにより、施策前の期間において両グループの波形が完全に「平行(Parallel)」になる状態を作り出しています。
そして、施策効果を反映するため、施策日である70日目以降の介入群のデータに対してのみ、y_treated[intervention_point:] += true_effect で一律に +15 の底上げ(True Effect)を注入しています。
上記のコードを実行すると以下の結果が得られます。グラフでは、70日目以降の介入群が、点線で示された「反事実(本来の推移)」から上に +15 分だけ乖離している様子が視覚的に確認できます。
statsmodelsを用いたDiDの実装と結果の解釈
データが準備できたところで、Pythonの統計モデリングライブラリである statsmodels を用いて、DiDの回帰分析を実行してみましょう。
回帰分析を行うためには、先ほど作成した「横持ち(Wide Format)」のデータを、1行に1つの観測値が入る「縦持ち(Long Format)」に変換し、ダミー変数(Treat, Post)を追加する必要があります。
回帰分析の実行コード
以下のコードでは、データの整形から回帰分析の実行、そして結果サマリーの出力までを行います。
import statsmodels.formula.api as smf
# 1. データを縦持ち(Long Format)に変換
# ['Time', 'Control', 'Treated'] のデータフレームを、1行1観測になるように整形
df_melted = pd.melt(df, id_vars=['Time'], value_vars=['Control', 'Treated'],
var_name='Group', value_name='Y')
# 2. ダミー変数の作成
# Treat: 介入群(Treated)なら1、統制群(Control)なら0
df_melted['Treat'] = (df_melted['Group'] == 'Treated').astype(int)
# Post: 施策実行日(70日目)以降なら1、それより前なら0
df_melted['Post'] = (df_melted['Time'] >= intervention_point).astype(int)
# 3. 回帰分析(最小二乗法: OLS)の実行
# formula(数式)で 'Y ~ Treat + Post + Treat:Post' と指定します。
# `:` は交差項(掛け算)を意味し、これがDiDにおける施策効果になります。
model = smf.ols(formula='Y ~ Treat + Post + Treat:Post', data=df_melted)
results = model.fit()
# 4. 結果のサマリーを表示
print(results.summary())
ここでは、pd.melt を使用してデータを縦持ち(Long Format)に変換し、回帰分析に適した構造にしています。
その後、介入群であれば 1 となるダミー変数 Treat と、施策日以降であれば 1 となるダミー変数 Post を作成します。
モデルの定義には statsmodels.formula.api を用いることで、R言語のように直感的な数式(Y ~ Treat + Post + Treat:Post)でモデルを記述できます。
ここで指定した Treat:Post が、先ほど数式で解説した交差項 に該当します。最後に model.fit() で最小二乗法(OLS)によるパラメータ推定を行います。
実行結果の解釈上記のコードを実行すると、以下のような回帰分析のサマリーテーブルが出力されます(※数値は乱数シードにより多少前後する場合があります)。
OLS Regression Results
==============================================================================
Dep. Variable: Y R-squared: 0.798
Model: OLS Adj. R-squared: 0.795
Method: Least Squares F-statistic: 258.5
Date: Sun, 12 Jul 2026 Prob (F-statistic): 7.39e-68
Time: 01:35:56 Log-Likelihood: -737.53
No. Observations: 200 AIC: 1483.
Df Residuals: 196 BIC: 1496.
Df Model: 3
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept 27.8897 1.167 23.895 0.000 25.588 30.192
Treat 19.9613 1.651 12.093 0.000 16.706 23.217
Post 24.1654 2.131 11.340 0.000 19.963 28.368
Treat:Post 14.8182 3.014 4.917 0.000 8.875 20.761
==============================================================================
Omnibus: 10.631 Durbin-Watson: 0.206
Prob(Omnibus): 0.005 Jarque-Bera (JB): 7.074
Skew: 0.322 Prob(JB): 0.0291
Kurtosis: 2.341 Cond. No. 6.35
==============================================================================
私たちが一番注目すべきは、下部のパラメータ一覧にある Treat:Post の行です。
- coef(偏回帰係数): 14.8182 となっており、データ生成時に仕込んだ「True Effect( )」をほぼ完璧に逆算(推定)できていることが分かります。
- P>|t|(p値): 0.000 となっており、統計的有意水準である0.05を大きく下回っています。つまり、「この という差は、偶然のばらつきによって生まれたものではない(統計的に有意である)」と結論づけることができます。
- ちなみに Treat の coef(19.9613)は、データ生成時に設定した「統制群(ベース10)と介入群(ベース30)の元々の実力差( )」を正確に捉えています。
このように、回帰分析を用いることで、単なる引き算では分からない「推定の確からしさ(p値や信頼区間)」まで同時に算出できるのがDiDの大きな強みです。
CausalImpactとの使い分け・比較
ここまで解説した「DiD(差の差法)」とは別に、時系列データを用いた因果推論の強力な手法として「CausalImpact」があります。
CausalImpactは2015年にGoogleの研究者らによって提案された手法であり、DiDのアプローチを「ベイズ構造時系列モデル(BSTS)」へと拡張したものです。DiDのように特定の「統制群」を用意するのではなく、施策の影響を受けていない他のデータ(共変量:検索トレンドや競合の動きなど)をモデルに組み込みます。 これにより、「もし施策を行っていなかったら今頃どうなっていたか」というパラレルワールドのタイムライン(反事実)を高精度に予測し、実際の観測値とのギャップから効果を測定します。
どちらも「反事実」を推定して因果効果を測るアプローチですが、実務ではどのように使い分ければ良いのでしょうか?
両者の特徴を比較テーブルにまとめました。
| 比較項目 | DiD (差の差法) | CausalImpact (BSTS) |
|---|---|---|
| ベースとなるアプローチ | 頻度論(主に回帰分析) | ベイズ統計(状態空間モデル) |
| 必要なデータ | 介入群と 「平行トレンドを満たす」統制群 | ターゲット変数と 「施策の影響を受けない」共変量 |
| トレンド・季節性の扱い | ダミー変数による固定的な平均のシフトとして捉える(時間変数を入れない限り柔軟性はない) | ランダムウォークや季節成分として、時間経過に伴う動的な変化を柔軟にモデリングする |
| 不確実性の表現 | p値、標準誤差による信頼区間 | 信用区間(事後分布)による直感的な予測幅の提示 |
| モデルの解釈性 | 極めてシンプルで、ステークホルダーへの説明が容易 | 内部構造が複雑(ブラックボックス化しやすい) |
どちらを選ぶべきか?
💡 DiDをおすすめするケース
- 完璧な「比較対象」が用意できる場合: 例えば「A県ではテレビCMを流し、属性がそっくりなB県では流さない」といった、明確なコントロールグループが存在する場合は、シンプルで強力なDiDが適しています。
- 施策前後の「平均的なリフト」を手っ取り早く知りたい場合: モデルが軽量で直感的なため、経営陣などへロジックを説明する際のコストが低く済みます。
💡 CausalImpactをおすすめするケース
- 「平行トレンド」を満たす統制群が見つからない場合: 現実のビジネスでは、全く同じトレンドを描く別グループを見つけるのは困難です。CausalImpactは「複数地域のデータ」や「検索トレンド」「競合の動き」などを共変量として複数組み合わせ、疑似的に精度の高いコントロールを作ることができます。
- 季節要因やトレンドの変化が激しい場合: DiDでは長期間のトレンド変化を取りこぼすリスクがありますが、CausalImpactは「状態空間モデル」によって日々のトレンド変動を柔軟に学習・予測できます。
まとめ
本記事では、因果効果測定の代表的な手法である DiD(差の差法) について、その基本ロジックからPythonによる実装、そしてCausalImpactとの比較までを解説しました。
- DiDの基本: 介入群の変化量から、統制群の変化量(自然なトレンド)を差し引くことで、純粋な施策効果を炙り出すシンプルなアプローチです。
- 平行トレンドの仮定: この手法が成立するためには、「もし施策がなければ、両グループは同じ傾きで成長していたはずだ」という強力な前提が不可欠です。
- 回帰モデルによる実装: ダミー変数を用いた回帰分析により、交差項(
Treat:Post)から効果量を推定し、p値による統計的な有意性も同時に評価できます。 - CausalImpactとの使い分け: 明確な統制群が存在し、ロジックの透明性が求められる場合はDiDが、平行トレンドを満たす統制群を見つけるのが難しい場合や、複雑な時系列変動をモデル化したい場合はCausalImpactが適しています。
DiDは、シンプルながらも「反事実をどのように推定するか」という因果推論のコアとなる考え方を学べる素晴らしいフレームワークです。データの性質や満たすべき仮定を正しく理解し、状況に応じて適切な手法を選択することが重要です。
本記事の文章・構成の一部に生成AIを使用しています。