Propensity Score
Instituto de Matemática, Estatística e Computação Científica (IMECC),
Universidade Estadual de Campinas (UNICAMP).
Assumindo uma amostragem IID, temos quatro variáveis aleatórias associadas a cada unidade \(i\):
Ademais, \[\begin{align} P(X, Z, Y(1), Y(0)) &= P(Z, Y(1), Y(0) | X) \times P(X), \\ &= P(Z | X, Y(1), Y(0)) \times P(Y(1), Y(0) | X) \times P(X) \end{align}\]
Definição (Propensity score):
Definimos \[e(X, Y(1), Y(0)) = P(Z = 1| X, Y(1), Y(0))\] como o propensity score. Sob ignorabilidade forte, \[e(X, Y(1), Y(0)) = P(Z = 1| X, Y(1), Y(0)) = P(Z = 1 | X)\] que é simplesmente denotado por \(e(X)\) e é a probabilidade condicional de receber o tratamento dado as covariáveis observadas
Teorema
Se \(Z \perp\!\!\!\perp \{ Y(1), Y(0)\} | X\), então \(Z \perp\!\!\!\perp \{ Y(1), Y(0)\} | e(X)\)
O Teorema anterior motiva um método para estimar efeitos causais: Propensity Score Stratification.
Exemplo
Dataset disponível aqui. Chang et al. (2016) estudam se a participação no programa da merenda nas escolas (School_meal
) leva a um aumento no índice de massa muscular (BMI
) das crianças. 12 outras covariáveis também estão disponíveis no dataset.
Estratificamos e estimamos o efeito causal médio.
neyman_sre <- function(y, z, x) {
x_levels <- unique(x)
K <- length(x_levels)
pi_k <- rep(0, K)
tau_k <- rep(0, K)
v_k <- rep(0, K)
n <- length(z)
for (k in 1:K) {
x_k <- x_levels[k]
z_k <- z[x == x_k]
y_k <- y[x == x_k]
pi_k[k] <- length(z_k)/n
tau_k[k] <- mean(y_k[z_k == 1]) - mean(y_k[z_k == 0])
v_k[k] <- var(y_k[z_k == 1]) / sum(z_k) + var(y_k[z_k == 0]) / sum(1 - z_k)
}
tau_s <- sum(pi_k * tau_k)
v_s <- sum(pi_k^2 * v_k)
return(c(tau_s, sqrt(v_s)))
}
K <- c(5, 10, 20, 50, 80)
resultados <- sapply(K, FUN = function(k){
q_prop_score = quantile(prop_score, (1:(k - 1))/k)
ps_estrato = cut(prop_score, breaks = c(0, q_prop_score, 1), labels = 1:k)
neyman_sre(y, z, ps_estrato)
})
colnames(resultados) <- K
rownames(resultados) <- c("Estim", "SE")
resultados
5 10 20 50 80
Estim -0.1160964 -0.1776036 -0.1996877 -0.2647423 -0.2037701
SE 0.2833304 0.2824724 0.2789719 0.2724072 NA
Como seriam os resultados se não utilizamos propensity score?
library(car)
naive <- lm(y ~ z)
fisher <- lm(y ~ z + x)
lin <- lm(y ~ z*x)
resultados <- matrix(c(coef(naive)[2], sqrt(hccm(naive)[2, 2]),
coef(fisher)[2], sqrt(hccm(fisher)[2, 2]),
coef(lin)[2], sqrt(hccm(lin)[2, 2])), ncol = 3)
colnames(resultados) <- c("Naive", "Fisher", "Lin")
rownames(resultados) <- c("Estim", "SE")
resultados
Naive Fisher Lin
Estim 0.5339044 0.06124785 -0.01695389
SE 0.2254186 0.22673582 0.22587296
O estimador de Lin e o método de propensity score stratification fornecem, qualitativamente, os mesmo resultados. Já o estimador Naive difere bastante dos outros métodos (Why?) enquanto que Fisher devolve um efeito positivo, porém, insignificante.
[,1]
[1,] 619.0012
O (alto) nível de desbalanceameto explica o motivo pelo qual o método Naive difere tanto dos outros métodos.
Teorema
Se \(Z \perp\!\!\!\perp \{ Y(1), Y(0)\} | X\) e \(0 < e(X) < 1\), então: \[\mathbb{E}[Y(1)] = \mathbb{E} \Big [ \dfrac{ZY}{e(X)}\Big], \quad \mathbb{E}[Y(0)] = \mathbb{E} \Big [ \dfrac{(1 - Z)Y}{1 - e(X)}\Big] \quad e\] \[\tau = \mathbb{E}[Y(1) - Y(0)] = \mathbb{E} \Big [\dfrac{ZY}{e(X)} - \dfrac{(1-Z)Y}{1-e(X)} \Big]\]
Demostração (no quadro)
O estimador IPW (Inverse Propensity Score Weighting), também conhecido como estimador HT (Horvitz-Thompson) é motivado pelo Teorema anterior e é dado por \[\hat{\tau}^{IPW} = \dfrac{1}{n} \displaystyle \sum_{i = 1}^n \dfrac{Z_i Y_i}{\hat{e}(X_i)} - \dfrac{1}{n} \displaystyle \sum_{i = 1}^n \dfrac{(1 - Z_i)Y_i}{1- \hat{e}(X_i)},\] em que \(\hat{e}(X_i)\) é o propensity score estimado.
O estimador \(\hat{\tau}^{IPW}\) apresenta um sério problema: não é invariante a transformações de locação de \(Y\) 🙍.
Implementação
estimador_ipw <- function(z, y, x, alphas = c(0, 1)) {
prop_score <- glm(z ~ x, family = binomial)$fitted.values
prop_score <- pmax(alphas[1], pmin(prop_score, alphas[2]))
tau_ipw <- mean(z * y / prop_score) - mean((1 - z) * y / (1 - prop_score))
tau_hajek <- mean(z * y / prop_score) / mean(z / prop_score) - mean((1 - z) * y / (1 - prop_score)) / mean((1 - z) / (1 - prop_score))
out <- c(tau_ipw, tau_hajek)
return(out)
}
Implementação
bootstrap_ipw <- function(z, y, x, n_boot = 500, alphas = c(0, 1)) {
poit_estim <- estimador_ipw(z, y, x, alphas)
n <- length(z)
boot_estim <- replicate(n_boot, {
sample_boot <- sample(1:n, n, replace = TRUE)
estimador_ipw(z[sample_boot], y[sample_boot], x[sample_boot, ], alphas)
})
boot_se <- apply(boot_estim, 1, sd)
out <- cbind(poit_estim, boot_se)
colnames(out) <- c("Est", "SD")
rownames(out) <- c("IPW", "Hajek")
return(out)
}
Exemplo (Merenda e BMI)
Chang et al. (2016) estudam se a participação no programa da merenda nas escolas (School_meal
) leva a um aumento no índice de massa muscular (BMI
) das crianças. 12 outras covariáveis também estão disponíveis no dataset.
A maneira de exemplo, utilizaremos diversos valores de \(\alpha_L\) e \(\alpha_U\):
Exemplo (Merenda e BMI)
$alpha_01
Est SD
IPW -1.516 0.481
Hajek -0.156 0.242
$alpha_02
Est SD
IPW -1.516 0.487
Hajek -0.156 0.239
$alpha_03
Est SD
IPW -1.499 0.460
Hajek -0.152 0.244
$alpha_04
Est SD
IPW -0.713 0.411
Hajek -0.054 0.231
Teorema
O propensity score satisfaz \[Z \perp\!\!\!\perp X | e(X).\] Ademais, para qualquer função \(h(\cdot)\), temos que \[\mathbb{E} \Big [\dfrac{Zh(X)}{e(X)} \Big] = \mathbb{E} \Big[\dfrac{(1 - Z) h(X)}{1- e(X)} \Big],\] desde que ambas as \(\mathbb{E}(\cdot)\) existam.
library(ggplot2)
balance_check <- sapply(1:ncol(x),
FUN = function(px){
q_prop_score = quantile(prop_score, (1:4)/5)
prop_score_strata = cut(prop_score, breaks = c(0, q_prop_score, 1), labels = 1:5)
neyman_sre(x[, px], z, prop_score_strata)
})
dat_balance <- data.frame(est = balance_check[1, ],
upper = balance_check[1, ] + 1.96*balance_check[2, ],
lower = balance_check[1, ] - 1.96*balance_check[2, ],
cov = factor(1:11))
ggplot(dat_balance) +
geom_errorbar(aes(x = cov, ymin = lower, ymax = upper), alpha = 0.6) +
geom_point(aes(x = cov, y = est), alpha = 0.6) +
geom_hline(aes(yintercept = 0), alpha = 0.3) +
theme_bw() +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.title.y = element_blank()) +
xlab("Verificando balanceamento com base na estratificação (K = 5)")
balance_check <- sapply(1:dim(x)[2],
FUN = function(px){
bootstrap_ipw(z, x[, px], x)[2, ]
})
dat_balance <- data.frame(est = balance_check[1, ],
upper = balance_check[1, ] + 1.96*balance_check[2, ],
lower = balance_check[1, ] - 1.96*balance_check[2, ],
cov = factor(1:11))
ggplot(dat_balance) +
geom_errorbar(aes(x = cov, ymin = lower, ymax = upper), alpha = 0.6) +
geom_point(aes(x = cov, y = est), alpha = 0.6) +
geom_hline(aes(yintercept = 0), alpha = 0.3) +
theme_bw() +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.title.y=element_blank()) +
xlab("balance check based on weighting")
Carlos Trucíos (IMECC/UNICAMP) | ME920/MI628 - Inferência Causal | ctruciosm.github.io