Propensity Score Methods (PSM, IPTW)
A family of methods that use the estimated probability of treatment given measured baseline covariates, e(X)=Pr(A=1|X), to balance those covariates across treatment groups before estimating a causal contrast, via matching, inverse-probability-of-treatment weighting, overlap weighting, stratification, or covariate adjustment.
On this page
A propensity score is a single number — the probability that a patient would receive the study drug, given everything measurable about them at baseline (age, diagnoses, prior medications, etc.). Once every patient has that score, you can pair treated patients with comparators who had nearly the same probability of being treated, or you can reweight the groups so their measured characteristics look alike. The goal is to make the two treatment arms resemble each other the way randomization would, so that any difference in outcomes can be attributed to the drug rather than to the kinds of patients who happened to receive it. This approach can only account for differences you actually measured — confounders hidden in the data, like disease severity not recorded in claims, are not removed.
The propensity score is the conditional probability of receiving the treatment of interest given measured baseline covariates, e(X)=Pr(A=1|X). Under the identification assumptions of conditional exchangeability (no unmeasured confounding given X), positivity (0<e(X)<1 for every covariate pattern), and consistency, conditioning on the scalar e(X) recovers the same covariate balance as conditioning on the full vector X (the balancing property of Rosenbaum and Rubin). In pharmacoepidemiology the PS is the standard tool for making initiators of two treatments comparable on measured confounders. It is an analysis step layered on top of design — it presupposes that time zero, eligibility, new-user status, the active comparator, and the outcome/censoring rules are already correct. A perfectly balanced PS analysis built on a wrong time zero is still biased; the PS cannot repair immortal time, depletion of susceptibles, or a comparator that itself causes the outcome.
Core estimand distinction
. The choice of PS method is the choice of estimand, and the four common methods target four different populations and answer four different questions:
- ATT (average treatment effect in the treated) — what would happen to the treated population if untreated. Targeted by 1:1 PS matching (matched-set ATT) and by SMR weighting (treated weight 1, comparator weight e(X)/(1-e(X))). This is usually the policy-relevant estimand for a safety signal of a drug already in use.
- ATE (average treatment effect) — what would happen to the whole eligible population if all vs none were treated. Targeted by (stabilized) IPTW: treated weight Pr(A=1)/e(X), comparator weight Pr(A=0)/(1-e(X)). Most sensitive to near-positivity violations because tail subjects with e(X)→0 or 1 receive enormous weights.
- ATO (average treatment effect in the overlap population) — the effect in patients with genuine clinical equipoise. Targeted by overlap weighting (treated weight 1-e(X), comparator weight e(X)); exactly balances every covariate mean, yields the smallest variance among balancing weights, and never produces extreme weights. The estimand is a smoothly-weighted population, not a fixed clinical group, which must be stated.
- ATM (matching weights) — a weighting analogue of pair matching targeting the "matchable" subpopulation. None of these is a free choice cosmetically: ATT, ATE, and ATO can differ in magnitude and even sign when effect modification by the PS is present, so the estimand must be pre-specified, not chosen after seeing results.
Pros, cons, and trade-offs
- vs covariate adjustment / outcome regression on X: PS reduces the confounder set to one dimension, decouples the "design" stage (fit and balance the PS, blinded to outcomes) from the "analysis" stage, and lets you assess overlap and balance explicitly before touching the outcome — discipline that pure outcome regression hides. Cost: outcome regression can be more efficient when the outcome model is correct and is not derailed by positivity violations. Prefer PS when you want an outcome-blind design stage and transparent balance diagnostics in a regulated study.
- vs doubly-robust estimation (AIPW / TMLE; see g-estimation and MSM entries): single-PS methods are simpler to specify, explain to reviewers, and audit. Cost: they are consistent only if the PS model is correct — a single line of defense. Doubly-robust estimators stay consistent if either the PS or the outcome model is correct and achieve smaller asymptotic variance. Prefer plain PS for transparency and when the confounder structure is well understood; escalate to doubly-robust/g-methods for time-varying confounding affected by prior treatment, which PS cannot handle.
- vs high-dimensional PS (hdPS): an investigator-specified PS is interpretable and easy to defend clause-by-clause. Cost: it can miss empirical proxy confounders captured by hundreds of claims codes. Prefer hdPS (or hdPS as a sensitivity analysis) in large claims data where unmeasured confounding by proxy is plausible.
- matching vs weighting, head to head: matching yields a concrete, inspectable matched cohort and is robust to extreme e(X) (poor-overlap subjects are simply unmatched), but it discards data, can depend on greedy sort order, and shifts the estimand to the matched region. Weighting uses all data and targets a clean estimand, but inherits the full danger of extreme weights (IPTW) unless you use stabilized weights, truncation, or overlap weights. Overlap weighting is the modern default when positivity is shaky.
When NOT to use — and when it is actively misleading or dangerous
- Unmeasured or mismeasured confounding dominates. PS balances only what is in X. In claims, frailty, disease severity, BMI, smoking, and over-the-counter use are typically unmeasured; a beautifully balanced PS table provides false reassurance. Pair PS with negative-control outcomes, E-values, or quantitative bias analysis — do not present a balance table as proof of no confounding.
- Positivity is violated. If one drug is reserved for sicker or renally-impaired patients, e(X) piles up near 0/1, IPTW weights explode, matching discards most of one arm, and the surviving estimand no longer maps to any clinically meaningful population. Trimming/restriction or overlap weighting changes the question; say so.
- Time-varying confounding affected by prior treatment. Conditioning on post-baseline confounders that are also mediators induces collider/over-adjustment bias; a baseline PS cannot fix this. Use marginal structural models with IPTW over time or g-estimation instead.
- Treating the PS as a substitute for design. Using post-index variables in the PS, or applying PS to a prevalent-user cohort with immortal time, launders a biased cohort into a balanced-looking one — the most dangerous failure mode because the diagnostics look clean.
- Few outcome events. With sparse events, weighting inflates variance and instability; matched or stratified analyses with firth-penalized or exact methods are safer.
Data-source operational depth
- Claims (FFS vs MA vs commercial): Covariates are built from demographics, calendar time, baseline diagnoses/procedures, prior drug classes, utilization intensity, and cost in the lookback window. Failure mode: Medicare Advantage person-time lacks fee-for-service encounter claims, so confounders measured from claims are systematically under-captured for MA enrollees — the PS is fit on degraded covariates and balance looks fine while real confounding persists. Restrict to enrollees with complete Parts A/B/D (or commercial medical+pharmacy) and exclude MA-only person-time. Failure mode: in elderly cohorts, death is a competing risk that often differs by exposure; a PS-weighted Kaplan-Meier or cause-specific hazard answers a different question than a subdistribution (Fine-Gray) analysis, and the two diverge — pre-specify which (see competing-risks entry). Failure mode: procedure-anchored index dates can smuggle in immortal time that no PS will remove.
- EHR: Labs, vitals, smoking, and BMI sharpen the PS but are missing not-at-random — captured only when ordered. A missingness indicator vs multiple imputation are different estimands of e(X); site and encounter-frequency are themselves confounders (sicker, more-monitored patients have more covariate data). Include site/visit-intensity terms and treat loss to follow-up as potentially informative.
- Registry: Strong on clinical confounders (stage, biomarker, ECOG, device details) that improve exchangeability, but weak on complete pharmacy exposure — link to claims for fills and to a death index for censoring before fitting the PS.
- Linked claims–EHR–vital records: The ideal substrate, but order vs fill vs service-date discrepancies must be reconciled before fixing time zero, or baseline covariates leak across the index boundary and contaminate e(X).
Worked claims example
Question: risk of hospitalized heart failure with an SGLT2 inhibitor vs a DPP-4 inhibitor in a commercial + Medicare FFS database (ATT, because the signal concerns SGLT2i initiators).
- Build the cohort: adults with type 2 diabetes, 365 days of continuous medical+pharmacy FFS enrollment before the first qualifying `fill_date`, and no SGLT2i or DPP-4i fill in that 365-day washout (incident users of both arms); index_date = that first fill; arm = the NDC dispensed on index_date; exclude MA-only person-time.
- Measure covariates only in `[index_date-365, index_date]`: age, sex, baseline HbA1c proxies, prior insulin, CKD/HF/MI diagnosis codes, prior-year inpatient count, and total paid cost.
- Fit e(X) by logistic regression (or hdPS).
- Because the estimand is ATT, use SMR weighting (treated=1, comparator=e/(1-e)) or 1:1 caliper matching on the logit-PS (caliper 0.2 SD); confirm every post-balancing standardized mean difference <0.1 and inspect the e(X) overlap plot.
- Define follow-up from index_date to first validated HF hospitalization, censoring at disenrollment, death, end of data, and — for an as-treated estimand — the end of the last `days_supply` plus a 30-day grace period or a switch to the other class.
- Estimate the effect with a weighted Cox model using robust (sandwich) standard errors, report the effective sample size, and run sensitivity analyses on washout length, weight truncation at the 1st/99th percentile, and a negative-control outcome to probe residual confounding. Reporting must include the PS model specification, pre/post balance, the overlap plot, the weight distribution and ESS, the trimming rule, the targeted estimand, and whether outcome modeling was additionally used (never use post-index variables in the PS unless you are explicitly fitting a longitudinal g-method).
Interpreting the output
Consider a PSM analysis of Drug A versus Drug B — the readmission scenario above — reporting HR = 0.82 (95% CI 0.71–0.95) for 30-day hospital readmission after matching on 28 covariates, with post-match SMDs all below 0.10.
Formal interpretation: Among matched Drug A initiators — the subset of treated patients for whom a comparably similar Drug B patient existed in the data — the instantaneous readmission rate was 18% lower (HR 0.82, 95% CI 0.71–0.95). This is the average treatment effect in the treated (ATT) within the matched population, not among all Drug A initiators. Under IPTW targeting the full target population, the analogous estimate applies to the inverse-probability-weighted pseudo-population (ATE) or the ATT-weighted pseudo-population, depending on the weight specification. Both are valid causal estimates only under two untestable conditions: no unmeasured confounding — every covariate that jointly predicts drug choice and readmission is captured and balanced — and positivity — every treated patient had a realistic probability of receiving the comparator. The 95% CI means results this extreme would arise in fewer than 5% of identically designed studies if the true HR were 1.0.
Practical interpretation: Drug A initiators who closely resembled Drug B initiators had readmissions arriving at an 18% slower rate during follow-up. This is not a statement that 18% fewer total events occurred — the HR is not a risk ratio or a cumulative probability. Under PSM, patients without a suitable match are excluded; under IPTW, they are down-weighted. The finding speaks to the matched or reweighted population, not all Drug A users. Unmeasured factors — such as disease severity not captured in claims — remain uncontrolled and could bias the estimate in either direction.
Decision diagram
flowchart TD
X[Baseline covariates X<br/>measured before time zero] --> PS[Estimate e of X = Pr treated given X]
PS --> OV{Assess overlap of e of X<br/>and positivity}
OV -->|Good overlap| EST{Choose estimand}
OV -->|Poor overlap / extreme tails| FIX[Trim/restrict OR use overlap weights]
FIX --> EST
EST -->|ATT: treated population| MS[1:1 matching or SMR weighting]
EST -->|ATE: whole population| IPTW[Stabilized + truncated IPTW]
EST -->|ATO: equipoise population| OW[Overlap weighting]
MS --> BAL[Check standardized differences < 0.1<br/>report ESS + weight distribution]
IPTW --> BAL
OW --> BAL
BAL --> OUT[Weighted/matched outcome model<br/>robust SEs in the target population]flowchart LR U[Unmeasured confounders U<br/>frailty, severity, BMI, smoking] -.-> A[Treatment A] U -.-> Y[Outcome Y] X[Measured confounders X] --> A X --> Y A --> Y PS[PS balancing on e of X] === X
Worked example
Scenario
A researcher wants to compare 30-day hospital readmission rates between patients who started Drug A (a newer diabetes medication) versus Drug B (an older one). Before comparing outcomes, they need to check whether the two groups look similar on key baseline characteristics. The raw data show that Drug A patients tend to be older and have more diabetes complications — a direct reflection of prescribing patterns, not drug effects. The researcher fits a propensity score model (logistic regression predicting Drug A vs. Drug B from age, diabetes diagnosis, and other baseline covariates) and then either matches each Drug A patient to the most similar Drug B patient, or reweights the sample so the groups balance. The table below shows covariate balance before and after that adjustment.
Dataset
Balance table: two covariates before and after propensity-score matching/weighting. SMD = standardized mean difference; values below 0.1 indicate acceptable balance.
| covariate | drug_a_before | drug_b_before | smd_before | drug_a_after | drug_b_after | smd_after |
|---|---|---|---|---|---|---|
| Mean age (years) | 67.4 | 61.2 | 0.52 | 64.8 | 64.3 | 0.04 |
| % with diabetes diagnosis | 78 | 54 | 0.5 | 71 | 69 | 0.04 |
Steps
Result
After propensity-score adjustment both covariates achieve SMD < 0.1, indicating the treated and comparator arms are now balanced on these measured characteristics. The researcher can proceed to compare readmission rates in the balanced sample, knowing that age and diabetes burden are no longer driving any observed difference. Note: unmeasured characteristics — such as disease severity not coded in claims — are still uncontrolled.
Trade-offs
Runnable example
Full PS workflow on a claims-style analytic table, one row per new initiator, all covariates measured in the pre-index window. Required input columns: person_id : unique subject id treated : 1 = study drug initiator, 0 = active comparator initiator (arm assigned at index_date) <xcols> : baseline covariates from...
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from lifelines import CoxPHFitter
xcols = ["age", "sex", "cci", "ckd", "prior_hf", "prior_insulin", "prior_hosp", "prior_cost"]
# --- Stage 1 (design, outcome-blind): estimate e(X) = Pr(treated | X) ---
ps = LogisticRegression(max_iter=2000, C=1e6) # near-unpenalized logistic PS
ps.fit(df[xcols], df["treated"])
df["e"] = np.clip(ps.predict_proba(df[xcols])[:, 1], 1e-6, 1 - 1e-6)
p = df["treated"].mean()
# Stabilized IPTW -> ATE (average treatment effect in the whole eligible population)
df["w_ate"] = np.where(df["treated"] == 1, p / df["e"], (1 - p) / (1 - df["e"]))
df["w_ate"] = df["w_ate"].clip(upper=df["w_ate"].quantile(0.99)) # truncate extreme weights
# Overlap weights -> ATO (clinical-equipoise population; no extreme weights by construction)
df["w_ato"] = np.where(df["treated"] == 1, 1 - df["e"], df["e"])
# SMR weights -> ATT (effect in the treated; comparator reweighted to look like treated)
df["w_att"] = np.where(df["treated"] == 1, 1.0, df["e"] / (1 - df["e"]))
def weighted_smd(x, t, w):
m1 = np.average(x[t == 1], weights=w[t == 1]); m0 = np.average(x[t == 0], weights=w[t == 0])
v1 = np.average((x[t == 1] - m1) ** 2, weights=w[t == 1])
v0 = np.average((x[t == 0] - m0) ** 2, weights=w[t == 0])
return (m1 - m0) / np.sqrt((v1 + v0) / 2)
wcol = "w_ate" # pick the estimand here; balance below should be checked on the SAME weights
bal = {c: weighted_smd(df[c].values, df["treated"].values, df[wcol].values) for c in xcols}
ess = df[wcol].sum() ** 2 / (df[wcol] ** 2).sum() # effective sample size after weighting
print("max |SMD| =", max(abs(v) for v in bal.values()), "| ESS =", round(ess, 1))
assert max(abs(v) for v in bal.values()) < 0.1, "covariate imbalance remains; revisit the PS model"
# --- Stage 2 (analysis): marginal weighted Cox with robust (sandwich) SEs ---
cox = CoxPHFitter()
cox.fit(df[["time", "event", "treated", wcol]], duration_col="time", event_col="event",
weights_col=wcol, robust=True) # robust=True accounts for the estimated weights
print(cox.summary[["coef", "exp(coef)", "se(coef)", "p"]])Full PS workflow with WeightIt + cobalt for weight construction and balance, then a marginal weighted outcome model via the survey package (correct sandwich SEs for weighted estimators). Input data.frame `df` columns mirror the Python version: treated (0/1), the baseline covariates, and time/event for the outcome.
library(WeightIt); library(cobalt); library(survey)
xform <- treated ~ age + sex + cci + ckd + prior_hf + prior_insulin + prior_hosp + prior_cost
# --- Estimate the PS and weights; estimand sets the target (ATE shown; use "ATT" or "ATO" to switch) ---
w <- weightit(xform, data = df, method = "ps", estimand = "ATE", stabilize = TRUE)
# --- Balance + overlap diagnostics on the weighted sample (must precede any outcome look) ---
bal.tab(w, un = TRUE, thresholds = c(m = .1)) # standardized mean differences pre/post weighting
love.plot(w, abs = TRUE, thresholds = c(m = .1))
cat("ESS:", summary(w)$effective.sample.size, "\n")
# --- Marginal weighted Cox model; survey design gives robust variance for the weighted estimator ---
df$ipw <- w$weights
des <- svydesign(ids = ~1, weights = ~ipw, data = df)
fit <- svycoxph(Surv(time, event) ~ treated, design = des)
summary(fit) # exp(coef) = weighted hazard ratio for the chosen estimandFull PS pipeline in SAS/STAT (14.2+). Required input dataset work.analytic has one row per new initiator: person_id, treated (1/0), the baseline covariates (measured in [index_date-365, index_date]), futime (follow-up days from index), event (1=outcome, 0=censored).
/* --- 1:1 caliper matching on the logit-PS -> ATT; ASSESS emits pre/post standardized differences --- */
proc psmatch data=work.analytic region=cs; /* region=cs restricts to common support (overlap) */
class treated sex ckd prior_hf prior_insulin;
psmodel treated(Treated='1') = age sex cci ckd prior_hf prior_insulin prior_hosp prior_cost;
match method=greedy(k=1) distance=lps caliper=0.2; /* 0.2 SD logit-PS caliper */
assess lps var=(age cci prior_hosp prior_cost) / weight=none plots=(stddiff boxplot);
output out(obs=match)=matched matchid=mid ps=ps;
run;
/* --- IPTW for ATE with a doubly-robust outcome model as a built-in sensitivity check --- */
proc causaltrt data=work.analytic method=ipw; /* method=aipw for the doubly-robust estimate */
class treated sex ckd prior_hf prior_insulin;
psmodel treated(ref='0') = age sex cci ckd prior_hf prior_insulin prior_hosp prior_cost;
model event = age sex cci ckd prior_hf prior_insulin prior_hosp prior_cost; /* outcome model for AIPW */
run;
/* --- Build stabilized IPTW explicitly, then fit a weighted Cox model with robust SEs --- */
proc logistic data=work.analytic noprint;
class treated sex ckd prior_hf prior_insulin / param=ref;
model treated(event='1') = age sex cci ckd prior_hf prior_insulin prior_hosp prior_cost;
output out=ps_out p=e;
run;
proc sql noprint; select mean(treated) into :pa from work.analytic; quit;
data wtd; set ps_out;
e = min(max(e,1e-6),1-1e-6);
if treated=1 then iptw = &pa / e; else iptw = (1-&pa) / (1-e); /* stabilized IPTW -> ATE */
run;
proc phreg data=wtd covs(aggregate); /* robust sandwich variance for the weighted estimator */
class treated(ref='0');
model futime*event(0) = treated;
weight iptw;
id person_id;
run;Citations
- [1]Rosenbaum PR, Rubin DB. The central role of the propensity score in observational studies for causal effects. Biometrika. 1983;70(1):41-55.
- [2]Austin PC. An introduction to propensity score methods for reducing the effects of confounding in observational studies. Multivariate Behavioral Research. 2011;46(3):399-424.
- [3]Austin PC, Stuart EA. Moving towards best practice when using inverse probability of treatment weighting (IPTW) using the propensity score to estimate causal treatment effects in observational studies. Statistics in Medicine. 2015;34(28):3661-3679.
- [4]Hernán MA, Robins JM. Using big data to emulate a target trial when a randomized trial is not available. American Journal of Epidemiology. 2016;183(8):758-764.
- [5]Schneeweiss S, Rassen JA, Glynn RJ, Avorn J, Mogun H, Brookhart MA. High-dimensional propensity score adjustment in studies of treatment effects using health care claims data. Epidemiology. 2009;20(4):512-522.