交絡のコントロールについて

  • 因果推論したいとき、交絡をいかにコントロールするかは大切
  • 交絡のコントロールの方法はたくさんある
    • 層別分析
    • 多変量解析(重回帰分析など)
    • 傾向スコア(マッチング、逆確率重みづけ、など) などなど
  • 交絡が生じているときにコントロールしないとどうなるか実験

交絡が生じていない状況では?

# 処置あり(x = 1)のとき、処置なし(x = 0)と比べて、yが平均50だけ上がるモデル

set.seed(1234)

d <- tibble(
  x = rbinom(n = sample_size, size = 1, prob = 0.5),
  y = rnorm(n = sample_size, mean = 10 + 50 * x, sd = 10)
)

# xとyの関係を可視化
ggplot(data = d %>% mutate(x = as.character(x))) + 
  geom_histogram(aes(x = y, fill = x), alpha = 2/6)

# 単純な線形回帰で処置効果を推定
model_lm <- lm(y ~ x, data = d)
broom::tidy(model_lm)
# A tibble: 2 x 5
  term        estimate std.error statistic p.value
  <chr>          <dbl>     <dbl>     <dbl>   <dbl>
1 (Intercept)     10.1     0.140      72.2       0
2 x               50.0     0.198     252.        0
  • xのestimateが真値の50付近なので、うまく推定できとる
  • 交絡が生じていないのだから当然

交絡が生じている状況では?

# 処置あり(x = 1)のとき、処置なし(x = 0)と比べてyが平均50だけ上がるモデル
# ただし、交絡因子cがある状況
set.seed(1234)

d <- tibble(
  c = rnorm(n = sample_size, mean = 10, sd = 5),
  x = rbinom(n = sample_size, size = 1, prob = 1 / (1 + exp(-1 * (- 10 + c)))),
  y = rnorm(n = sample_size, mean = 10 + 50 * x + 20 * c, sd = 10)
)

# 処置効果を推定
model_lm <- lm(y ~ x, data = d)

broom::tidy(model_lm)
  term        estimate std.error statistic p.value
  <chr>          <dbl>     <dbl>     <dbl>   <dbl>
1 (Intercept)     136.     0.941      145.       0
2 x               198.     1.33       149.       0
  • 真値の50からめちゃ逸脱。過大評価ワロタ
  • 交絡が生じているときにコントロールしてないから当然

重回帰モデルでコントロール

# 処置効果を推定
model_lm_with_covariate <- lm(y ~ x + c, data = d)

broom::tidy(model_lm_with_covariate)
  term        estimate std.error statistic p.value
  <chr>          <dbl>     <dbl>     <dbl>   <dbl>
1 (Intercept)     9.73    0.240       40.5       0
2 x              49.9     0.303      165.        0
3 c              20.0     0.0307     653.        0
  • 交絡因子を共変量として組み込めばよい
  • 真値の50とほぼ一致。ええ感じ。

傾向スコアをつかったマッチング

m_near <- MatchIt::matchit(
  formula = x ~ c,
  data    = d,
  method  = 'nearest',
  replace = T
)

match_data <- MatchIt::match.data(m_near)

model_lm_with_ps_score_matching <- lm(y ~ x, data = match_data)

broom::tidy(model_lm_with_ps_score_matching)

love.plot(m_near, threshold = 0.1, abs = T)
  term        estimate std.error statistic p.value
  <chr>          <dbl>     <dbl>     <dbl>   <dbl>
1 (Intercept)     210.      2.20      95.7       0
2 x               124.      2.37      52.2       0
  • 傾向スコアを求めて最近隣法でマッチング
  • 処置ありだけでの層別分析と枠組みは同じ(らしい)
  • 過大評価のときよりは推定は多少マシになっている。ただし不完全。
  • ATTだから??う~む勉強不足

傾向スコアを使った逆確率重みづけ

weight <- WeightIt::weightit(
  formula  = x ~ c, 
  data     = d, 
  method   = 'ps',
  estimand = 'ATE'
  )

model_lm_with_ps_score <- lm(y ~ x + c, data = d, weights = weight$weights)

broom::tidy(model_lm_with_ps_score)
  term        estimate std.error statistic   p.value
  <chr>          <dbl>     <dbl>     <dbl>     <dbl>
1 (Intercept)     6.18    0.268       23.0 2.00e-114
2 x              46.4     0.207      224.  0        
3 c              20.5     0.0233     878.  0   
  • 傾向スコアが小さいデータは欠損しやすいと考えられる
  • 傾向スコアの逆数で欠損値補完をしている(らしい)
  • ATEなので推定値が真値がかなり近づいた