Causal inference with MORIE

Overview

This vignette walks through the causal-inference surface of MORIE: ATE, ATT, ATC, augmented IPW (doubly robust), per-unit CATE, group GATE, and sensitivity analysis (the E-value). Each estimator follows the same calling convention — data, treatment, outcome, covariates — so swapping among them is a matter of changing one function name.

A reproducible synthetic example

library(rmorie)
set.seed(2026)

n <- 800
X1 <- rnorm(n)
X2 <- rnorm(n)
# Confounded treatment assignment
ps    <- plogis(0.5 * X1 - 0.3 * X2)
treat <- as.integer(ps > runif(n))
# True ATE = +1.0
y <- 1.0 * treat + 0.7 * X1 - 0.2 * X2 + rnorm(n, sd = 0.5)

df <- data.frame(y = y, treat = treat, X1 = X1, X2 = X2)

Single-robust ATE / ATT / ATC

ate <- morie_estimate_ate(df, treatment = "treat", outcome = "y", covariates = c("X1", "X2"))
att <- morie_estimate_att(df, treatment = "treat", outcome = "y", covariates = c("X1", "X2"))
atc <- morie_estimate_atc(df, treatment = "treat", outcome = "y", covariates = c("X1", "X2"))

ate$estimate
#> NULL
att$estimate
#> NULL
atc$estimate
#> NULL

The three quantities estimate slightly different things:

  • ATE (average treatment effect): averaged over the full population.
  • ATT (treated): averaged over the treated subgroup.
  • ATC (controls): averaged over the untreated subgroup.

Under no effect heterogeneity in the covariates, all three converge. Under heterogeneity, they diverge in informative ways.

Doubly robust: augmented IPW

morie_estimate_aipw() is the augmented inverse-probability-weighting estimator. It is consistent if either the propensity model or the outcome model is correctly specified — the doubly-robust guarantee.

aipw <- morie_estimate_aipw(df, treatment = "treat", outcome = "y", covariates = c("X1", "X2"))
aipw$estimate
#> NULL
aipw$se
#> [1] 0.04033896

Per-unit CATE (T-learner / S-learner)

cate <- morie_estimate_cate(df, treatment = "treat", outcome = "y", covariates = c("X1", "X2"),
                      meta_learner = "t_learner")
head(cate)
#> [1] 1.0534077 0.9977113 1.0363683 1.0293321 0.9726929 0.9714638

Each row of the returned data frame contains a per-unit conditional average treatment effect.

Group-level GATE

df$g <- sample(c("A", "B", "C"), nrow(df), replace = TRUE)
gate <- morie_estimate_gate(df, treatment = "treat", outcome = "y",
                      covariates = c("X1", "X2"),
                      group_col = "g")
gate
#>   group       ate         se  ci_lower ci_upper   n
#> 1     B 1.0249270 0.06242292 0.9025781 1.147276 274
#> 2     A 1.0514470 0.07314839 0.9080762 1.194818 265
#> 3     C 0.9340143 0.06686665 0.8029557 1.065073 261

The result is one row per group level with ate, se, and CI.

Sensitivity analysis: the E-value

# Suppose the observed risk ratio is 2.0; how large would an
# unmeasured confounder need to be to explain it away?
evalue <- morie_e_value(rr = 2.0)
evalue
#> $morie_e_value
#> [1] 3.414214
#> 
#> $e_value_ci
#> [1] NA

The E-value is the minimum strength (on the risk-ratio scale) that an unmeasured confounder would need on both treatment and outcome to fully explain the observed effect.

Where to go next

  • The companion mrm-otis-walkthrough vignette applies the full ten-estimator MRM ensemble to OTIS provincial data.
  • For survey-weighted versions of these estimators, see the survey-weighted vignette.