--- title: "Average Treatment Effect Estimation" author: Klaus Kähler Holst date: "`r Sys.Date()`" format: html: toc: true html-math-method: mathjax vignette: > %\VignetteIndexEntry{Average Treatment Effect Estimation} %\VignetteEngine{quarto::html} %\VignetteEncoding{UTF-8} execute: echo: true warning: false collapse: true comment: "#>" --- ```{r, include = FALSE} library("targeted") ``` # Introduction Consider observed data $(Y, A, W)$ where $Y$ is the outcome, $A \in \{0,1\}$ is the binary treatment, and $W$ is a vector of covariates. Under the potential outcomes framework, we define the *average treatment effect* (ATE) as $$\tau = E[Y(1)] - E[Y(0)]$$ where $Y(a)$ denotes the outcome that would have been observed under treatment $A = a$. Identification of $\tau$ from observed data requires standard causal assumptions: consistency, positivity, and no unmeasured confounding. The `cate` function in the `targeted` package estimates the ATE using the *augmented inverse probability weighted* (AIPW) estimator, also known as the doubly-robust (DR) *one-step estimator*. For each treatment level $a$, the efficient influence function for the marginal mean $\psi_a = E[Y(a)]$ is $$\varphi_a(Y, A, W) = \frac{I(A=a)}{\pi_a(W)}\{Y - \mu_a(W)\} + \mu_a(W) - \psi_a$$ where $\pi_a(W) = P(A = a \mid W)$ is the propensity score and $\mu_a(W) = E[Y \mid A = a, W]$ is the outcome regression. The resulting estimator is *doubly robust*: it is consistent if either $\pi_a$ or $\mu_a$ is correctly specified. When both models are correctly specified, the estimator achieves the semiparametric efficiency bound. # Continuous outcome ## Simulated data We generate data from a model with a known treatment effect. The outcome depends on confounders $W_1$ and $W_2$, with an interaction between treatment and $W_2$: $$ Y = \cos(W_1) + W_2 A + 0.2 W_2^2 + A + \varepsilon, \quad \varepsilon \sim N(0,1) $$ The true ATE is $E[W_2 + 1] = 1$ since $W_2 \sim N(0,1)$. ```{r sim-continuous} sim_data <- function(n = 2000) { w1 <- rnorm(n) w2 <- rnorm(n) a <- rbinom(n, 1, plogis(-1 + w1)) y <- cos(w1) + w2 * a + 0.2 * w2^2 + a + rnorm(n) data.frame(y = y, a = a, w1 = w1, w2 = w2) } set.seed(2025) d <- sim_data(2000) ``` ## Parametric estimation To estimate the ATE, we call `cate` with `cate.model = ~1` (an intercept-only model, which targets the marginal ATE rather than conditional effects). We specify parametric models for both the outcome regression and the propensity score: ```{r ate-parametric} est <- cate( response.model = y ~ a * (w1 + w2), treatment.model = a ~ w1 + w2, cate.model = ~1, data = d ) est ``` The output shows estimates of the potential outcome means $E[Y(1)]$ and $E[Y(0)]$, followed by the ATE (labelled `(Intercept)`). With correctly specified parametric models and no cross-fitting, the AIPW estimator is $\sqrt{n}$-consistent and asymptotically normal. By default, `cate` includes a second-order correction term (`second.order = TRUE`) that improves robustness to misspecification of the outcome model when the propensity model is a GLM. ## Cross-fitting When flexible or data-adaptive models are used for the nuisance parameters, the AIPW estimator can suffer from overfitting bias. *Cross-fitting* (sample splitting) resolves this: the data are partitioned into $K$ folds, nuisance models are fitted on $K-1$ folds and predictions are made on the held-out fold. This relaxes the Donsker conditions that would otherwise be required for $\sqrt{n}$-consistency. With parametric models, cross-fitting is not strictly necessary but introduces no harm: ```{r ate-crossfit} est_cf <- cate( response.model = y ~ a * (w1 + w2), treatment.model = a ~ w1 + w2, cate.model = ~1, data = d, nfolds = 5 ) est_cf ``` ## Flexible nuisance models One of the main strengths of the AIPW framework is that nuisance models can be replaced with flexible or machine learning estimators without sacrificing valid inference for the target parameter. The `learner` class in `targeted` provides a unified interface for specifying these models. Here we use a generalized additive model (GAM) for the outcome regression, stratified by treatment arm, combined with a logistic GLM for the propensity score. Stratification (`stratify = TRUE`) fits separate outcome models for treated and untreated units: ```{r ate-gam} est_gam <- cate( response.model = learner_gam(y ~ s(w1) + s(w2)), treatment.model = learner_glm(a ~ w1 + w2, family = binomial), cate.model = ~1, data = d, nfolds = 5, stratify = TRUE ) est_gam ``` For even greater flexibility, an ensemble learner (superlearner) can combine multiple candidate models. The superlearner selects the optimal convex combination of learners via cross-validation: ```{r ate-sl} outcome_model <- learner_sl( list( glm = learner_glm(y ~ w1 * w2), gam = learner_gam(y ~ s(w1) + s(w2)) ), nfolds = 5 ) est_sl <- cate( response.model = outcome_model, treatment.model = learner_glm(a ~ w1 + w2, family = binomial), cate.model = ~1, data = d, nfolds = 5, stratify = TRUE ) est_sl ``` # Binary outcome The AIPW estimator applies equally to binary outcomes. Here the ATE is a *risk difference*: the difference in marginal probabilities of the event under treatment versus control. ## Simulated data We generate binary outcomes from a logistic model with a true log-odds ratio of $0.8$: ```{r sim-binary} sim_binary <- function(n = 2000) { w1 <- rnorm(n) w2 <- rnorm(n) a <- rbinom(n, 1, plogis(-0.5 + 0.5 * w1)) p1 <- plogis(-1 + 0.5 * w1 - 0.3 * w2 + 0.8 * a) y <- rbinom(n, 1, p1) data.frame(y = y, a = a, w1 = w1, w2 = w2) } set.seed(2025) db <- sim_binary(3000) ``` ## Estimation ```{r ate-binary} est_bin <- cate( response.model = learner_glm(y ~ a * (w1 + w2), family = binomial), treatment.model = learner_glm(a ~ w1 + w2, family = binomial), cate.model = ~1, data = db, nfolds = 5 ) est_bin ``` The output shows the estimated marginal event probabilities $E[Y(1)]$ and $E[Y(0)]$ and their difference (the risk difference). To obtain an odds ratio, we can transform the potential outcome estimates using `lava::estimate`: ```{r ate-or} or <- with(lava::estimate(est_bin$estimate), lava::logit(`E[y(1)]`) - lava::logit(`E[y(0)]`)) merge(or, exp(or), labels = c("log(OR)", "OR")) ``` # Conditional average treatment effects The examples above all use `cate.model = ~1`, targeting the marginal ATE. The `cate` function also supports estimation of *conditional* average treatment effects (CATE) by specifying a richer projection model. For instance, `cate.model = ~1 + w2` projects the individual-level treatment effect onto a linear model in $W_2$, allowing the estimated effect to vary across levels of $W_2$. See `?cate` for details and examples. # Session info ```{r sessioninfo} sessionInfo() ```