📌

Pythonで可視化する差の差(DiD)分析

2023/06/14に公開

差の差分析の直感的理解

差の差(Difference in Differences: DiD)分析は、ある介入の効果を推定するための統計的手法です。具体的には、介入が行われた群(トリートメント群)と介入が行われなかった群(コントロール群)の、介入前後でのアウトカム(結果)の変化を比較します。その名前が示す通り、その手法は二つの差の差を計算することから来ています。

それぞれの差の意味は次の通りです:

  1. 最初の「差」: 介入前と介入後のアウトカム(例えば、売上高、テストのスコア、治療の効果など)の変化を示します。これは時間による変化を示しています。
  2. 二つ目の「差」: これはトリートメント群とコントロール群間の変化の差を示します。これは群間の比較を示しています。
    したがって、差の差は「時間の経過によるアウトカムの変化」の「群間の比較」を示すことになります。これにより、介入の影響を他の時間による変動から分離し、介入によるネットの影響を推定することができます。

数式を用いて具体的に説明すると、以下のようになります。

\text{DiD} = (\overline{Y}_{\text{after, treatment}} - \overline{Y}_{\text{before, treatment}}) - (\overline{Y}_{\text{after, control}} - \overline{Y}_{\text{before, control}})

ここで、
\overline{Y}_{\text{after, treatment}} は介入後のトリートメント群の平均アウトカム
\overline{Y}_{\text{before, treatment}} は介入前のトリートメント群の平均アウトカム
\overline{Y}_{\text{after, control}} は介入後のコントロール群の平均アウトカム
\overline{Y}_{\text{before, control}} は介入前のコントロール群の平均アウトカム
となります。

満たすべき仮定

差の差分析を行う際には、データに満たさなければならない仮定があります。以下がその仮定となります。

  • 平行トレンド仮定:政策導入前に介入群とコントロール群の傾向が平行であること。つまり、政策がなければ両グループの変化は同じであること。
  • 共通ショック仮定:政策導入後に介入群とコントロール群に共通する外的要因がないこと。つまり、政策以外に両グループに影響を与える要因がないこと。
  • 組成不変仮定:政策導入前後で介入群とコントロール群の組成が変わらないこと。つまり、政策によって両グループに移動する人がいないこと。

仮想的な応用事例

ある町に新たな政策を導入したと仮定する状況についてのシナリオとその解析の例です。政策は、「地元の小規模企業に対する金利補助」としましょう。これにより、地元の小規模企業は低金利での融資を受けられるようになり、その結果、経済活動が活性化し、売上が向上すると想定されます。

我々はこの政策が導入される前後で、その町(treatment群)と似た経済状況の他の町(control群)の売上の変化を比較することで、政策の効果を評価します。

以下にPythonコードとplotlyを用いた可視化の一例を示します。

import pandas as pd
import numpy as np
import plotly.graph_objects as go

# 売上の平均を年度と町のタイプごとに計算
df_grouped = df.groupby(['Year', 'Town_type']).mean().reset_index()

# 可視化
fig = go.Figure()

# コントロール群の売上変化
fig.add_trace(go.Scatter(
    x=df_grouped[df_grouped['Town_type'] == 0]['Year'], 
    y=df_grouped[df_grouped['Town_type'] == 0]['Sales'],
    mode='lines+markers',
    name='Control'
))

# トリートメント群の売上変化
fig.add_trace(go.Scatter(
    x=df_grouped[df_grouped['Town_type'] == 1]['Year'], 
    y=df_grouped[df_grouped['Town_type'] == 1]['Sales'],
    mode='lines+markers',
    name='Treatment'
))

# 政策導入年度を明示する
fig.add_shape(
    type="line",
    x0=4,
    y0=df_grouped['Sales'].min(),
    x1=4,
    y1=df_grouped['Sales'].max(),
    line=dict(
        color="Red",
        width=1.5,
        dash="dashdot",
    )
)

# 政策導入前後の売上差(コントロール群とトリートメント群の差)を計算
diff_pre_policy = df_grouped[(df_grouped['Year'] == 4) & (df_grouped['Town_type'] == 1)]['Sales'].values[0] - df_grouped[(df_grouped['Year'] == 4) & (df_grouped['Town_type'] == 0)]['Sales'].values[0]
diff_post_policy = df_grouped[(df_grouped['Year'] == 5) & (df_grouped['Town_type'] == 1)]['Sales'].values[0] - df_grouped[(df_grouped['Year'] == 5) & (df_grouped['Town_type'] == 0)]['Sales'].values[0]

# 差の差(DiD)を計算する
did = diff_post_policy - diff_pre_policy

# 差の差(DiD)を表示する
fig.add_annotation(
    x=7,
    y=df_grouped['Sales'].max(),
    text=f"DiD estimate: {did:.2f}",
    showarrow=False,
    font=dict(
        size=16,
        color="Black"
    ),
)

fig.update_layout(
    title='Sales over Years for Control vs Treatment',
    xaxis_title='Year',
    yaxis_title='Average Sales'
)

fig.show()

政策導入前と導入後の売上差(コントロール群とトリートメント群の差)を計算し、その差(差の差、DiD)をプロット上にテキストとして表示しています。このDiDが、介入(政策導入)の効果を推定する値となります。極端な疑似データを生成したためだいぶうまくいった政策になっていますが、政策に効果があった場合は以上のようなグラフになります。

Discussion