Bayesian Inverse Probability Weighting

Summary

Propensity scores and inverse probability treatment weights (IPTW) cannot be naively inserted into a Bayesian model because weights are not part of the data-generating likelihood. The Liao-Zigler (2020) method resolves this by treating propensity scores as a parameter : draw samples of propensity scores from the posterior of a Bayesian treatment model, compute weights for each draw, run the outcome model times, and combine the results with Rubin’s rules. This propagates treatment-model uncertainty into the ATE estimate.

Overview

The problem: We want to use both Bayesian inference (posterior distributions, no null hypotheses) and inverse probability weighting (to close DAG backdoor paths). Inverse probability weighting (IPW) adjusts for confounding by creating pseudo-populations where treated and untreated groups have similar covariate distributions. When combined with Bayesian inference, however, a fundamental incompatibility arises: weights are not a parameter in any Bayesian likelihood.

The solution (Liao & Zigler 2020): Marginalize over the posterior distribution of propensity scores.

This note documents the frequentist IPW workflow, the conceptual problem with Bayesian weights, and the Liao & Zigler solution with a full R/brms implementation.

Mosquito Nets and Malaria Risk

Setup: 1,752 individuals; binary treatment (mosquito net use); outcome (malaria risk 0–100). Confounders identified by DAG: income, health, temperature. True effect (simulated): −10 malaria risk points. Results (derived in the sections below):

  • Frequentist IPTW: ATE = −10.1 ± 0.66 SE
  • Bayesian Liao-Zigler: ATE = −10.1 ± 1.02 SE (wider, correctly incorporating treatment model uncertainty)

Standard Frequentist IPW Workflow

Inverse Probability of Treatment Weights (IPTW)

For a binary treatment with propensity score , estimated as :

Using these weights in an outcome model creates pseudo-populations: treated and control groups with matched covariate distributions across the confounders , as if randomized.

The frequentist workflow consists of four steps:

  1. Treatment model (design stage): Fit logistic regression net ~ confounders to get .
  2. Propensity scores: = predicted probability of treatment.
  3. Weights: Compute IPTW from .
  4. Outcome model (analysis stage): Fit weighted regression outcome ~ treatment with IPTW weights; the coefficient on treatment is the ATE.

Frequentist IPW: Mosquito Net and Malaria Risk

Setup: Observational data on mosquito net use () and malaria risk (, 0-100). Confounders: income, health, temperature. True ATE = −10 (nets reduce malaria risk by 10 points).

library(tidyverse); library(brms); library(broom)
 
# Step 1: Treatment model — predict net use from confounders
model_treatment_freq <- glm(net ~ income + temperature + health,
                            data = nets, family = binomial(link = "logit"))
 
# Steps 2-3: Propensity scores → IPTW
nets_with_weights <- augment(model_treatment_freq, nets,
                             type.predict = "response") %>%
  rename(propensity = .fitted) %>%
  mutate(iptw = (net_num / propensity) + ((1 - net_num) / (1 - propensity)))
 
# Step 4: Outcome model weighted by IPTW
model_outcome_freq <- lm(malaria_risk ~ net,
                         data = nets_with_weights, weights = iptw)
tidy(model_outcome_freq)
# netTRUE: estimate = -10.1 ✓

The IPTW successfully recovers the −10 ATE by creating pseudo-populations of comparable treated and untreated individuals.

What IPTW is Doing: Pseudo-Populations

The weights rescale the sample to make treated and untreated groups comparable. Visualizing the weighted propensity score distributions shows that IPTW makes the two groups look alike:

  • Treated, low propensity → high weight (surprising to be treated)
  • Untreated, high propensity → high weight (surprising to not be treated)
  • Result: after weighting, the two groups mirror each other’s propensity score distributions, allowing causal interpretation of the outcome model coefficient.

Why Bayesian IPW is Hard

Average Treatment Effect (ATE)

The estimand depends on treatment , covariates , and outcome .

A correct Bayesian model for this estimand would use:

The weights are nowhere in this expression. IPTWs would conceptually appear in the likelihood, but they are not part of the data-generating process.

The Fundamental Problem

Robins, Hernán, and Wasserman (2015) show that propensity scores cannot be incorporated into standard Bayesian inference because:

  1. The Bayesian estimand is
  2. Propensity scores (and weights) have no role in the likelihood
  3. We cannot set a prior on a weight parameter — there is no weight parameter in the model

Conclusion: “Bayesian inference must ignore the propensity score” (Robins et al. 2015)

Liao & Zigler (2020) treat the propensity score as a latent parameter and, instead of using a single set of weights, marginalize over its posterior distribution:

Liao-Zigler Marginalization

The propensity score is estimated Bayesianly in the treatment model, then integrated out to produce a -free ATE.

Liao-Zigler Two-Stage Method

Practical algorithm for evaluating the marginalization integral:

  1. Fit a Bayesian treatment model to get a posterior distribution over propensity scores
  2. Draw samples of propensity scores from the posterior
  3. For each draw : compute IPTW and run the frequentist outcome model → ATE estimate
  4. Combine the ATEs using Rubin’s rules: , with SEs combined to capture between-model variance

This propagates treatment-model uncertainty into the final ATE estimate.

This is analogous to multiple imputation (or bootstrapping): run the same model on slightly different data (different weights) and combine the results.

R/brms Implementation

Step 1: Bayesian Treatment Model

library(brms)
library(tidyverse)
 
model_treatment <- brm(
  bf(net ~ income + temperature + health,
     decomp = "QR"),       # QR decomposition for numerical stability
  family = bernoulli(),    # logistic regression
  data = nets,
  chains = 4, cores = 4, iter = 1000,
  seed = 1234, backend = "cmdstanr"
)

Step 2: Extract K Posterior Propensity Score Samples

# posterior_epred gives P(net=1 | confounders, theta^(k)) for each posterior draw k
pred_probs_chains <- posterior_epred(model_treatment)
# dim: (2000 draws) × (1752 people)

Step 3: Nest Propensity Scores, Compute Weights, Run K Outcome Models

# Nest each draw's propensity scores into its own row
pred_probs_nested <- pred_probs_chains %>%
  as_tibble(.name_repair = "unique") %>%
  mutate(draw = 1:n()) %>%
  pivot_longer(-draw, names_to = "row", values_to = "prob") %>%
  mutate(row = as.numeric(str_remove(row, "\\.\\.\\."))) %>%
  group_by(draw) %>%
  nest() %>%
  ungroup()
 
# For each draw: compute IPTW and run outcome model
outcome_models <- pred_probs_nested %>%
  mutate(outcome_model = map(data, ~{
    df <- bind_cols(nets, .) %>%
      mutate(iptw = (net_num / prob) + ((1 - net_num) / (1 - prob)))
    lm(malaria_risk ~ net, data = df, weights = iptw)
  })) %>%
  mutate(
    tidied  = map(outcome_model, tidy),
    ate     = map_dbl(tidied, ~filter(., term == "netTRUE") %>% pull(estimate)),
    ate_se  = map_dbl(tidied, ~filter(., term == "netTRUE") %>% pull(std.error))
  )

Step 4: Combine with Rubin’s Rules

# Average ATE (≈ -10, matching the true effect)
mean(outcome_models$ate)
# [1] -10.1
 
# Combine standard errors with Rubin's rules
rubin_se <- function(ates, sigmas) {
  sqrt(mean(sigmas^2) + var(ates))
}
rubin_se(outcome_models$ate, outcome_models$ate_se)
# [1] 1.02  (larger than naive average of SEs: 0.659)

Bayesian IPW: Mosquito Nets (R/brms) — Result

With posterior draws of propensity scores, the combined ATE is ≈ −10.1, matching the frequentist estimate.

Key insight: The combined SE (1.02) is larger than the naive mean SE (0.659) because Rubin’s rules add the variance of the ATEs across draws. This correctly inflates the SE to account for uncertainty in the treatment model’s propensity scores — uncertainty that a purely frequentist approach ignores.

Rubin’s Rules for Combining Results

Rubin's Rules

When combining results from models fitted to slightly different datasets (or draws), the combined ATE and standard error are:

The between-draw variance captures the uncertainty from the treatment model. Simply averaging SEs is incorrect; Rubin’s rules give the proper pooled SE.

Limitations and Open Questions

  1. Is the result truly Bayesian? The outcome model is still frequentist (lm()/OLS). The distribution of 2,000 ATEs resembles a posterior and is a mathematical transformation of the treatment model’s posterior, but it is not formally one — the uncertainty all comes from the treatment model, and the uncertainty in the outcome model is not quantified Bayesianly. Strictly speaking, this is only quasi-Bayesian.

  2. Full Bayesian outcome model: Running brm() 2,000 times — one model per set of weights — is computationally prohibitive. A technically correct, fully Bayesian solution uses a single brm() run where the weights change per iteration (possible via custom brms Stan code, subsequently developed by Jordan Nafa; see the follow-up post).

  3. Efficiency: This approach is equivalent to bootstrapping the treatment uncertainty. The interpretation of the resulting “posterior-like” distribution as a credible interval is debatable.

Comparison: Approaches to Bayesian Causal Inference

MethodTreatment of ConfoundersFully Bayesian?
Standard IPW (frequentist)Single propensity scoreNo
Liao-ZiglerPosterior propensity scores, Rubin’s rulesQuasi-Bayesian
BART + propensity scoresBART model; see Nonparametric Causal InferenceYes
Structural/outcome modelDirect Bayesian regression on outcomesYes (but requires correct model)

Connections

See Also