library(sensemakr)
library(tidyverse)
Sensitive Analysis
일반적으로 관찰 데이터로 인과추론 분석을 할 때에는 관측되지 않은 교란 요인이 없다는 가정 하에서 분석을 진행합니다. 일반적으로 이러한 가정은 만족될 수 없습니다. 따라서 가정이 위반되었을 때, 인과 관계에 미치는 영향을 정량적으로 확인하는 절차, 즉 강건성을 확인하는 절차가 필요합니다.
이것을 측정하기 위해 다음과 같은 질문을 해볼 수 있습니다.
관찰 데이터로 추정한 인과효과를
민감도분석은 이러한 질문에 답을 하기 위한 정량적인 분석을 수행하는 것입니다.
Sensitivity Basiscs in Linear Setting
먼저 가장 간단한 세팅으로 linear setting을 고려해보겠습니다. 오른쪽 그림을 보면
linear setting에서
먼저
하지만
Sensitivity Contour Plots
true ATE인
에 의해 생기는 bias인 이 크다면 는 상쇄되고, 는 에 가까워짐 에 의해 생기는 bias인 이 작다면 는 크게 변화하지 않고, 는 과 큰 차이가 없음
이에 대해서 그래프로 나타내보면 다음과 같습니다.
그림은 (
따라서 green curve일 경우 (
결론적으로 green curve에 가까워지거나 혹은 green curve를 넘어설 경우 교란요인에 의해 영향을 많이 받으므로, 강건성이 떨어진다고 볼 수 있습니다.
정리하면, 관찰되지 않은 교란요인이 treatment와 outcome에 대해 미치는 영향의 방향은 알 수 없습니다. 이에 따라 민감도 분석에서는 관찰되지 않은 교란요인의 크기를 고려합니다. 위의 그림 예시처럼 민감도 분석을 통해 추정된 효과를 없앨 정도로 추정량을 변경하려면 관찰되지 않은 교란요인의 크기가 어느 정도여야 하는지를 확인할 수 있고, 도메인 지식을 활용하여 추정된 효과의 강건성을 주관적으로 결정할 수 있습니다.
Sensemakr
paper에서는 partial
주요 기여사항
Robustness value 개발
partial
를 활용한 민감도분석 툴 개발extreme scenario에 대한 분석 그래프 개발
해당 논문 저자가 개발한 툴은 R, Python, Stata 등에서 이용할 수 있습니다.
- python : dowhy, PySensemakr
- R : sensemakr
Example
교육기간과 임금 사이의 관계에 관심이 있음
교육기간과 임금 사이에는 많은 unobserved confounder가 존재함
예시를 위해 unobserved confounder로 ability가 있다고 가정
ability는 omitted variable이지만 education과 wage에 영향을 미친다는 것을 알고 있다고 가정함
#setwd("./posts/2023-02-07-sensitivity-analysis")
<- read.csv("ex_data.csv")[, -1] %>%
df mutate(gender = as.factor(gender))
%>% head(2) df
age gender education wage
1 62 male 6 3800
2 44 male 8 4500
<- lm(wage ~ ., df)
fit summary(fit)
Call:
lm(formula = wage ~ ., data = df)
Residuals:
Min 1Q Median 3Q Max
-958.27 -194.90 -1.32 256.00 967.54
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 2657.89 445.00 5.973 3.18e-07 ***
age 12.31 6.11 2.015 0.0498 *
gendermale 335.11 132.69 2.526 0.0151 *
education 95.94 38.75 2.476 0.0170 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 455.1 on 46 degrees of freedom
Multiple R-squared: 0.2549, Adjusted R-squared: 0.2063
F-statistic: 5.246 on 3 and 46 DF, p-value: 0.003379
회귀분석 결과, 추정된 ATE는 약
관찰되지 않은 교란요인의 크기에 따라 추정된 ATE의 강건성을 분석하기 위해 민감도 분석을 수행해보겠습니다. sensemakr 패키지의 sensemakr()
함수를 이용해서 간단히 할 수 있습니다.
<- sensemakr(model = fit, treatment = "education") sens
summary(sens)
Sensitivity Analysis to Unobserved Confounding
Model Formula: wage ~ age + gender + education
Null hypothesis: q = 1 and reduce = TRUE
-- This means we are considering biases that reduce the absolute value of the current estimate.
-- The null hypothesis deemed problematic is H0:tau = 0
Unadjusted Estimates of 'education':
Coef. estimate: 95.9437
Standard Error: 38.7521
t-value (H0:tau = 0): 2.4758
Sensitivity Statistics:
Partial R2 of treatment with outcome: 0.1176
Robustness Value, q = 1: 0.3044
Robustness Value, q = 1, alpha = 0.05: 0.0627
Verbal interpretation of sensitivity statistics:
-- Partial R2 of the treatment with the outcome: an extreme confounder (orthogonal to the covariates) that explains 100% of the residual variance of the outcome, would need to explain at least 11.76% of the residual variance of the treatment to fully account for the observed estimated effect.
-- Robustness Value, q = 1: unobserved confounders (orthogonal to the covariates) that explain more than 30.44% of the residual variance of both the treatment and the outcome are strong enough to bring the point estimate to 0 (a bias of 100% of the original estimate). Conversely, unobserved confounders that do not explain more than 30.44% of the residual variance of both the treatment and the outcome are not strong enough to bring the point estimate to 0.
-- Robustness Value, q = 1, alpha = 0.05: unobserved confounders (orthogonal to the covariates) that explain more than 6.27% of the residual variance of both the treatment and the outcome are strong enough to bring the estimate to a range where it is no longer 'statistically different' from 0 (a bias of 100% of the original estimate), at the significance level of alpha = 0.05. Conversely, unobserved confounders that do not explain more than 6.27% of the residual variance of both the treatment and the outcome are not strong enough to bring the estimate to a range where it is no longer 'statistically different' from 0, at the significance level of alpha = 0.05.
ovb_minimal_reporting(sens)
Unadjusted Estimates of ’ education ’:는 기존 회귀분석 결과와 같습니다. Sensitivity Statistics:을 보면 partial
: 측정되지 않은 교랸요인이 교육과 임금의 잔차 변동의 30.44%를 설명한다면 추정량을 0으로 만들기 충분함 or 충분하지 않음
30.44%가 충분한지 or 충분하지 않은지는 분석가의 판단에 의존합니다. 또는 benchmark_covariates
옵션을 통해 관찰된
Robustness value
Z Unobserved confounder, D Treatment, Y Outcome
일 경우 가 와 의 모든 잔차 변동을 설명한다는 것을 의미함- strong confounder
일 경우 가 와 의 모든 잔차 변동을 설명하지 못한다는 것을 의미함- weak confonder
Sensitivity contour plot
Robustness Value는
plot(sens, xlab = "Partial R^2 of ability with education",
ylab = "Partial $R^2$ of ability with wage")
그래프를 보면 Unadjusted는 관찰되지 않은 교란요인(ability)가 영향을 주지 않을 때를 의미합니다. 즉, 회귀분석 결과와 동일합니다. 우측 상단으로 갈수록 관찰되지 않은 교란요인의 효과가 증가하고, 빨간색 dot line에 도달했을 때,
그래프를 보면 빨간색 dot line 위에 있는 값으로
논문에서 partial
= 0.3
R_YZ = 0.3
R_DZ
<- lm(education ~ age + gender, df)$residuals
DperpX <- lm(wage ~ ., df)$residuals
YperpXD
<- sqrt((R_YZ*R_DZ/(1 - R_DZ)))*(sd(YperpXD)/sd(DperpX)) bias
95.94 - bias
[1] 1.697537
Sensitivity contour plot using benchmark covariates
sensitivity contour plot을 봤을 때, 관찰되지 않은 교란요인의 treatment와 outcome에 미치는 영향에 따른 bias 효과를 확인해볼 수 있지만, 실제로 이 bias가 큰지 혹은 작은지는 주관적인 판단에 의존합니다. 즉, 이전 예시에서
이러한 기준 설정의 어려움을 극복하기 위해서 관찰된 설명변수를 벤치마크하여 관찰되지 않은 교란요인이 미치는 영향의 크기를 대략적으로 추측합니다.
<- sensemakr(model = fit, treatment = "education",
sens2 benchmark_covariates = "age",
kd = c(0.5, 1, 2),
ky = c(0.5, 1, 2))
plot(sens2)
sensemakr() 함수에는 benchmark_covariates
옵션이 존재하며,
옵션을 추가할 경우 해당 plot에서 통계적 유의성 또한 체크해볼 수 있습니다.
plot(sens2, sensitivity.of = "t-value")
통계적 유의성을 보면 관찰되지 않은 교란요인(ability)가
plot(sens2)
add_bound_to_contour(r2dz.x = 0.3,
r2yz.dx = 0.3,
bound_label = "Something related to\nboth outcome and treatment")
추가적으로 임의로 partial
Sensitivity plots of extreme scenarios
extreme scenario는 outcome의 거의 모든 잔차 변동을 관찰되지 않은 교란요인이 설명한다는 의미로,
<- sensemakr(model = fit, treatment = "education",
sens3 benchmark_covariates = "age",
kd = c(1, 2, 3, 4),
ky = c(1, 2, 3, 4))
plot(sens3, type = "extreme", lim = 0.5)
<- plot(sens3, type = "extreme", lim = 0.5) result
$scenario_r2yz.dx_1[117:119,] result
r2dz.x r2yz.dx adjusted_estimate
117 0.116 1 0.7348541
118 0.117 1 0.2712232
119 0.118 1 -0.1912156
- x axis :
- y axis :
- line :
그래프를 보면 빨간색 dot line은 추정된 회귀계수가 0이 되는 경우에 해당합니다. solid line은
또한 왼쪽 하단에 빨간색 vertical line은 benchmark로 설정한 변수에 비해 confounder가 몇 배 더 강력한지를 나타내는 것으로 kd = c(0.5, 1, 2)
옵션에서 설정한 것과 같습니다.
관찰된 설명변수 age가 education에 미치는 효과에 비해서 관찰되지 않은 교란요인이 education에 미치는 효과가 네 배정도 클 때,
상식적으로 교육기간과 나이는 충분한 양의 상관관계가 존재한다고 생각할 수 있습니다. 따라서 관찰된 설명변수 age가 education에 미치는 효과에 비해서 관찰되지 않은 교란요인이 education에 미치는 효과가 네 배정도 클 때,
dowhy tutorial
Sys.setenv(RETICULATE_PYTHON="/Users/sangdon/pytorch-test/env/bin/python")
::use_condaenv(condaenv = 'env') reticulate
import dowhy
from dowhy import CausalModel
import pandas as pd
import numpy as np
import dowhy.datasets
import os
from dowhy import CausalModel
import graphviz
from dowhy.causal_refuter import CausalRefutation
from dowhy.causal_refuter import CausalRefuter
#os.getcwd()
= pd.read_csv("posts/2023-02-07-sensitivity-analysis/ex_data.csv", index_col = 0)
dat dat.head()
= graphviz.Digraph()
dot
'age', 'age')
dot.node('gender', 'gender')
dot.node('education', 'education')
dot.node('wage', 'wage')
dot.node(
'education', 'wage')
dot.edge('gender', 'wage')
dot.edge('age', 'wage')
dot.edge(
print(dot.source)
#dot.view()
#dot.source.replace("\t", ' ').replace("\n", ' ')
= """graph[directed 1 node[id "age" label "age"]
gdot node[id "gender" label "gender"]
node[id "education" label "education"]
node[id "wage" label "wage"]
edge[source "education" target "wage"]
edge[source "gender" target "wage"]
edge[source "age" target "wage"]]"""
= CausalModel(
model = dat,
data ="education",
treatment="wage",
outcome= gdot
graph
)
model.view_model()
= model.identify_effect(proceed_when_unidentifiable=True)
identified_estimand print(identified_estimand)
= model.estimate_effect(identified_estimand, method_name="backdoor.linear_regression")
estimate print(estimate)
= model.refute_estimate(identified_estimand,
refute
estimate,= "add_unobserved_common_cause",
method_name = "linear-partial-R2",
simulated_method_name = "age",
benchmark_common_causes = [1,2,3]
effect_fraction_on_treatment )
참고 자료
omitted variable bias : https://matteocourthoud.github.io/post/ovb/
unobserved confounding example : https://evalf21.classes.andrewheiss.com/example/confounding-sensitivity
R tutorial : https://cran.r-project.org/web/packages/sensemakr/vignettes/sensemakr.html
Citation
@online{don2023,
author = {Don, Don and Don, Don},
title = {Sensitivity Analysis},
date = {2023-02-20},
url = {https://dondonkim.netlify.app/posts/2023-02-07-sensitivity-analysis/sensitivity.html},
langid = {en}
}