IPW deep-dive (Hajek and Horvitz–Thompson)

Overview

Inverse-probability weighting (IPW) is the simplest of the single-robust causal estimators. This vignette shows propensity-score modelling, the Hajek-stabilised IPW estimator that morie_estimate_ate() computes, the unnormalised Horvitz–Thompson form for comparison, and weight diagnostics.

Setting up

library(rmorie)
set.seed(2026)
n <- 500
X1 <- rnorm(n)
X2 <- rnorm(n)
ps_true <- plogis(0.4 * X1 - 0.3 * X2)
treat   <- as.integer(ps_true > runif(n))
y       <- 1.0 * treat + 0.6 * X1 - 0.2 * X2 + rnorm(n, sd = 0.5)
df <- data.frame(y = y, treat = treat, X1 = X1, X2 = X2)

Estimating propensities

morie_estimate_ate() fits a logistic propensity model internally and returns the IPW estimate. To inspect or supply the propensities yourself, fit them first and pass the column name as propensity_col:

ps_fit <- glm(treat ~ X1 + X2, family = binomial(), data = df)
df$ps  <- predict(ps_fit, type = "response")
summary(df$ps)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>  0.1604  0.3727  0.4549  0.4580  0.5374  0.7422

Hajek-stabilised IPW

morie_estimate_ate() computes the Hajek estimator, which divides each weighted sum by the corresponding sum of weights. This stabilises the estimator in finite samples when the propensity tails are heavy. By default the propensities are trimmed to [0.01, 0.99] (argument trim):

ate_hajek <- morie_estimate_ate(df, treatment = "treat", outcome = "y", covariates = c("X1", "X2"),
                          propensity_col = "ps")
ate_hajek$ate
#> [1] 0.9285338
ate_hajek$se
#> [1] 0.08026755
c(ate_hajek$ci_lower, ate_hajek$ci_upper)
#> [1] 0.7712094 1.0858582

The result is a list with ate, se, ci_lower, ci_upper, the number of rows used and discarded by propensity trimming (n_used, n_discarded), and the effective sample size of the weights (ess).

The Horvitz–Thompson form does not normalise the weights. It has no separate function here; in base R it is:

with(df, mean(treat * y / ps) - mean((1 - treat) * y / (1 - ps)))
#> [1] 0.9220492

On these simulated data the two forms are close. Hajek is the more stable of the two when a few weights are large.

Weight diagnostics

In practice, IPW is sensitive to extreme propensities. Two common diagnostics:

# ATE weights: 1 / ps for treated units, 1 / (1 - ps) for controls
w <- ifelse(df$treat == 1, 1 / df$ps, 1 / (1 - df$ps))

# Effective sample size after weighting
morie_effective_sample_size(w)
#> [1] 467.7416

# Range of weights (extreme values suggest trimming)
range(w)
#> [1] 1.191048 4.115298

If the effective sample size collapses dramatically, the analysis should consider:

  • Trimming propensities to a sensible interval (e.g. [0.05, 0.95])
  • Switching to a doubly-robust estimator (morie_estimate_aipw())
  • Adding more covariates to better separate the treatment groups

AIPW for protection against IPW failure

aipw <- morie_estimate_aipw(df, treatment = "treat", outcome = "y", covariates = c("X1", "X2"))
aipw$ate
#> [1] 0.9278963
aipw$se
#> [1] 0.04513966
c(aipw$ci_lower, aipw$ci_upper)
#> [1] 0.8394225 1.0163700

When propensities are well-behaved, IPW and AIPW should agree to within Monte Carlo noise. Disagreement is informative: it suggests either model misspecification or a fragile propensity model.

Where to go next

  • The causal-inference vignette covers ATT / ATC / CATE / GATE.
  • The survey-weighted vignette covers IPW under complex-sample designs (when survey weights and propensities both apply).