- このセクションで使っている
R packages
library(DT)
library(gapminder)
library(gghighlight)
library(ggrepel)
library(tidyverse)
library(stargazer)
通常の調査・観察データではあり得ないことだが、ここでは「通院すること (D)」が「健康状態 (Y)」に 0.6 の因果効果があるとわかっているとする(通常は「真の因果関係」は神様だけが知っていることだが、ここでは、我々もこのことを知っていると想定して話を進める)
ここでは R を使って次の 3 つの変数を人工的に作り出す(観察数 = 10,000)
変数 | 変数名 | 詳細 |
目的変数 | Y |
: 健康状態 (1 = 最悪 〜 5 = 最良) |
説明変数 | D |
: 1 = 通院する、0 = 通院しない |
交絡変数 | h_hidden |
: もともとの健康状態 (1 = 最悪、2 = 悪い、3 = 普通、4 = 良い、5 = 最良) |
N
を決める<- 10000 N
もともとの健康状態 (1 = 最悪、2 = 悪い、3 = 普通、4 = 良い、5 = 最良)をランダムに決める
これは、通院する前の「隠れた」健康状態であり、通常の「調査・観察データ」では観測されないため我々には入手できない変数である
library(tidyverse)
set.seed(2021-03-23)
<- tibble(h_hidden = sample(1:5,
df1 size = N,
replace = TRUE,
prob = c(1, 2, 3, 2, 1)))
table(df1)
df1
1 2 3 4 5
1052 2190 3316 2290 1152
names(df1)
[1] "h_hidden"
h_hidden
である%>%
df1 ggplot() +
geom_bar(aes(x = h_hidden), fill = "deepskyblue") +
labs(x = "健康状態", y = "人数") +
theme_bw(base_family = "HiraKakuProN-W3") # 文字化け対策
ceteris paribus
)、健康状態が悪いほど通院するはずR
で以下を実行して、各個人の通院確率を決める<- c(0.8, 0.6, 0.5, 0.3, 0.2)
mu <- 0.1
sigma <- df1 %>%
df1 ::mutate(prob = rnorm(n(),
dplyrmean = mu[h_hidden],
sd = sigma),
prob = case_when(
> 1 ~ 1,
prob < 0 ~ 0,
prob TRUE ~ prob
))
::datatable(df1) DT
<- ggplot(df1, aes(x = prob)) +
hist_prob geom_histogram(binwidth = 0.05, color = "black") +
facet_grid(rows = vars(h_hidden)) +
labs(x = "通院する確率", y = "人数") +
theme_bw(base_family = "HiraKakuProN-W3") # 文字化け対策
plot(hist_prob)
Di∈{0,1}
の値を決める<- df1 %>%
df1 mutate(D = rbinom(n(),
size = 1,
prob = prob))
table(df1$D)
0 1
5334 4666
mean(df1$D)
[1] 0.4666
10,000人中 46.66% (4666人)が「通院する」ことがわかる
ここで「通院が健康状態に与える平均処置効果 (ATE
)」=
beta
を設定する
単純化のため、ATE = ITE
とする
→ 「処置効果はどの個人にとっても同じ」だと仮定する
試しに、beta = 0.6
に設定してみる
→ 隠れた健康状態 1 から 5 の範囲内で「通院する」ことで 0.6
ポイント健康状態が改善されるという意味
例)隠れた健康状態が 3 の人が通院すると健康状態が 3.6 になるということ
実際には、健康状態は 1 から 5 の整数で観測される
→ 隠れた健康状態が 5 の 人が通院しても健康状態は 5 のままのはず
しかし、ここでは健康状態が 5.6 に改善されることを許容する
シミュレーションを単純化するための妥協
ここでは、便宜的に隠れた健康状態を「 1 から 5
の整数」と設定したが、現実的に考えると、人々の健康状態は「連続変数」のはず
→ 人々が健康状態を問われた時、実際の感覚として 5.2
だとすれば、その値に最も近い 5 を選ぶはず
→ シミュレーションにとって、それほど大きな問題ではない
<- 0.6 beta
Y
を計算する<- df1 %>%
df1 mutate(Y = h_hidden + beta*D)
::datatable(df1) DT
人工的に生成されたデータにはセレクションバイアスがないという前提で(つまり「通院する効果」(ATE
)
が計算できるという前提で)シミュレーションしてみる
通院した人と行かなかった人の健康状態を単純に比較する
<- df1 %>%
df1_D mutate(D = factor(D,
label = c("通院しなかった", "通院した"))) %>%
group_by(D) %>%
summarize(health = mean(Y)) %>%
print()
# A tibble: 2 × 2
D health
<fct> <dbl>
1 通院しなかった 3.40
2 通院した 3.21
<- df1 %>%
go_not_go mutate(D = factor(D,
label = c("通院しなかった", "通院した"))) %>%
group_by(D) %>%
summarize(health = mean(Y)) %>%
ggplot() +
geom_bar(aes(x = D, y = health, fill = D),
stat = "identity", position = "dodge") +
labs(x = "通院の有無", y = "体調") +
theme_minimal(base_family = "HiraKakuProN-W3")
go_not_go
3.40
で、「通院した」集団の健康状況は 3.21
得られた「通院する効果」(ATE
) = 3.21 - 3.40 = −
0.19
しかし、実際の「通院する効果」(ATE
) は
0.6 のはず
\[E[Y_i(0)|D = 1] - E[Y_i(0)|D = 0]\]
<- df1 %>%
e1 filter(D == 1) %>%
pull(h_hidden) %>%
mean() %>%
print()
[1] 2.606515
<- df1 %>%
e0 filter(D == 0) %>%
pull(h_hidden) %>%
mean() %>%
print()
[1] 3.40045
<- e1 - e0
s_bias s_bias
[1] -0.7939347
−0.7939347
) が生じているATE
とセレクションバイアスを足した値は+ s_bias beta
[1] -0.1939347
diff(df1_D$health)
[1] -0.1939347
シミュレーションの結果✔ セレクションバイアスのあるデータを使って「通院の効果」を推定した結果、本来
0.6
のはずなのに − 0.19
と過小評価された
x 軸
を D
(「通院しない
= 0」「通院する = 1」)、y 軸
を
Y
(体調)に設定した散布図を描いてみる%>%
df1 ggplot(aes(D, Y)) +
geom_point() +
stat_smooth(method = lm) +
geom_jitter(width = 0.04)
「通院 (D
)」は「健康状態 (Y
)」に 0.6
の因果効果を与えているはず(なぜなら、我々がそのようなデータを意図的に作成したのだから)
しかし、散布図の回帰直線の傾きはマイナス
→ なぜなら、セレクションバイアスがあるから(我々はセレクションバイアスの存在も知っている)
単回帰分析を行ってみる
<- lm(Y ~ D, data = df1)
fit_1
summary(fit_1)
Call:
lm(formula = Y ~ D, data = df1)
Residuals:
Min 1Q Median 3Q Max
-2.4005 -0.6065 0.3935 0.5996 2.3935
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 3.40045 0.01482 229.398 <2e-16 ***
D -0.19393 0.02170 -8.937 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1.083 on 9998 degrees of freedom
Multiple R-squared: 0.007925, Adjusted R-squared: 0.007826
F-statistic: 79.87 on 1 and 9998 DF, p-value: < 2.2e-16
D
の傾きが -.019
=>
「通院する」と「健康状態」が 0.19
だけ悪くなる得られた「通院する効果」(ATE
) = −
0.19
しかし、実際の「通院する効果」(ATE
) は
0.6 のはず
0.001
で統計的に有意 (p値 = 2e-16
)<- function(beta = 0.6, # 「通院」の因果効果を 0.6 に設定
sim_hospital N = 1e4, # 繰り返し回数を 10000 と指定
mu = c(0.8, 0.6, 0.5, 0.3, 0.2), # もともとの健康状態ごとに各個人が通院する確率を指定
sigma = rep(0.1, 5)) { # sigma は、健康状態ごとに変えても良いことにする
if (length(sigma == 1)) sigma <- rep(sigma, 5)
<- sample(1:5, size = N, replace = TRUE,
h_hidden prob = c(1, 2, 3, 2, 1))
<- rnorm(N, mean = mu[h_hidden], sd = sigma[h_hidden])
prob <- case_when(
prob > 1 ~ 1,
prob < 0 ~ 0,
prob TRUE ~ prob
)<- rbinom(N, size = 1, prob = prob)
D <- h_hidden + beta * D
Y <- lm(Y ~ D)
fit return(coef(fit)[2])
}
beta = 0.6
と設定し、この関数を 1
度だけ実行してみるsim_hospital(beta = 0.6)
D
-0.2280425
<- replicate(1000, sim_hospital()) sim_1000
head(sim_1000)
D D D D D D
-0.2079258 -0.2356270 -0.2424896 -0.1953436 -0.1960446 -0.1928219
<- tibble(beta = sim_1000) %>%
p_s2 ggplot(aes(x = beta, y = after_stat(density))) +
geom_histogram(color = "black", binwidth = 0.01) +
labs(x= "単純比較から得られる「効果」", y = "確率密度") +
theme_bw(base_family = "HiraKakuProN-W3") +
geom_vline(xintercept = 0.6, color = "tomato") # 本当の因果効果 (0.6) に縦線を引く
plot(p_s2)
summary(sim_1000)
Min. 1st Qu. Median Mean 3rd Qu. Max.
-0.2702 -0.2139 -0.2001 -0.1999 -0.1865 -0.1383
0.6
-0.199
-0.200
であり、因果効果を大幅に過小推定→ セレクションバイアスは因果推論の敵
Y
)」と「説明変数
(D
)」の両方に影響を与えている「交絡変数」(h_hidden
)
をモデルに含めずに回帰分析した結果、実際とはかけ離れた因果効果を得たセレクションバイアスへの対処法 → 重回帰分析を行う
fit_1
) と重回帰分析 (fit_2
)
の結果を比べてみる<- lm(Y ~ D, data = df1)
fit_1
<- lm(Y ~ D + h_hidden, data = df1) fit_2
stargazer(fit_1, fit_2, type = "html")
Dependent variable: | ||
Y | ||
(1) | (2) | |
D | -0.194*** | 0.600*** |
(0.022) | (0.000) | |
h_hidden | 1.000*** | |
(0.000) | ||
Constant | 3.400*** | 0.000*** |
(0.015) | (0.000) | |
Observations | 10,000 | 10,000 |
R2 | 0.008 | 1.000 |
Adjusted R2 | 0.008 | 1.000 |
Residual Std. Error | 1.083 (df = 9998) | 0.000 (df = 9997) |
F Statistic | 79.866*** (df = 1; 9998) | 317,075,267,268,840,811,402,874,585,088.000*** (df = 2; 9997) |
Note: | p<0.1; p<0.05; p<0.01 |
単回帰分析の結果
\[Y = 3.4 - 0.194D\]
D
)」の因果効果を(0.6
と設定した因果効果を)誤って −0.19
と推定している重回帰分析の結果
\[Y = 0 + 0.6D + 1h.hidden\]
h_hidden
」をモデルに加えるとD
)」の因果効果を(0.6
と設定した因果効果を)正しく 0.6
と推定できるset.seed(2021-03-23)
と設定したが、この設定をせず、「1.4
単回帰を繰り返すシミュレーション」に習ってシミュレーションを実行して可視化しなさい