Atlas
statminds
Causal GLM (Matching & Balancing Model)The underlying model family class (e.g. GLM, linear model, categorical matrix, log-linear).Parametric ReferenceStatistical methods that assume a specific probability distribution family (typically normal).12-stage workflow

Propensity Score Matching (PSM)

The engine for Causal Discovery in Observational Research. PSM audits the probability of treatment assignment, reveal the 'True' effect by mathematically constructing a balanced counterfactual control group.

Model familyCausal GLM (Matching & Balancing Model)
Hypothesistwo-tailed
AliasesPropensity Matching · Observational Balancing · Synthetic RCT Framework
G1
Selection Bias Neutralization
Mathematically level the playing field between treated and untreated groups in non-randomized data.
G2
Counterfactual Audit
Construct a high-fidelity control group that mimics the treated group's baseline characteristics.
G3
Causal Signal Isolation
Identify the pure treatment effect by neutralizing the 'Hidden Drivers' of group assignment.
Visual Overview Dashboard
1

What is it?

Propensity Score Matching (PSM) is a causal inference method designed to estimate treatment effects by adjusting for confounding in observational studies.

The engine for Causal Discovery in Observational Research. PSM audits the probability of treatment assignment, reveal the 'True' effect by mathematically constructing a balanced counterfactual control group.

2

Goals & Indications

  • Selection Bias Neutralization: Mathematically level the playing field between treated and untreated groups in non-randomized data.
  • Counterfactual Audit: Construct a high-fidelity control group that mimics the treated group's baseline characteristics.
  • Causal Signal Isolation: Identify the pure treatment effect by neutralizing the 'Hidden Drivers' of group assignment.
3

Core Idea Diagram

ControlsTreatedMatched Pairs
4

Claims tested

H₀: H₀: ATT = 0 (no average treatment effect on the treated)
Hₐ: Hₐ: ATT ≠ 0 (treatment has effect on the treated)
5

How it works

  1. Estimate propensity scores (probability of treatment) for all units via logistic regression.
  2. Select a matching algorithm (e.g., nearest neighbor) and specify caliper size constraints.
  3. Pair each treated unit with one or more control units sharing close propensity scores.
  4. Evaluate covariate balance post-match; estimate treatment effect (ATT) on matched sample.
6

Assumptions

Conditional Independence Assumption: No unmeasured confounders affecting both treatment assignment and outcome
Common support / Positivity / Overlap: Treated and untreated units have overlapping propensity score distributions
SUTVA: Stable Unit Treatment Value Assumption: No interference between units; treatment effect is same for all units
7

Important Note

Tests causal effect after matching treated and untreated on propensity scores. Assumes no unmeasured confounding and common support.

8

Worked Example

MetricBefore MatchAfter Match% Balance Impr.
Age Diff4.82 yrs0.15 yrs96.9%
Covariate SMD0.450.0393.3%
Interactive Sandbox

Nearest Neighbor Caliper Matching

Slide baseline propensity imbalance. Higher imbalance creates fewer caliper matches because support distributions do not overlap.

Covariate Mean Imbalance0.40
True Treatment Effect (τ)1.50
Caliper size constraint0.15
Causal Estimation Comparison
Treated units matched: 5 / 40
Raw outcome difference: 3.800
Propensity matched effect (ATT): 2.160
True causal effect (τ): 1.500
Propensity score matches (X-axis represents Propensity Score)
0.00.20.40.60.81.0Treated Group →Control Group →
The 12-Stage Precision Workflow
01Treatment Precedence
Hypotheses
We test if the treatment 'Causes' the shift in outcomes, seeking a signal that survives the audit of selection bias.
02Conditional Independence
Assumptions
The ultimate prerequisite: assuming all variables influencing both treatment and outcome are measured and balanced.
03Common Support
Diagnostics
Utilizing Propensity Score Histograms to ensure there is enough 'Overlap' between groups—without support, matching is a mathematical fiction.
04focus
Comparing FlowMotion users to a matched cohort of non-users who shared the exact same baseline pain and motivation levels.
05Weighting Pivot
Alternatives
Knowing when to switch to IPTW Weighting if matching results in too much data loss (discarded participants).
06Matched T-Strikes
Significance
Executing significance tests on the 'Balanced' sample—ensuring p-values represent the treatment, not the baseline differences.
07ATT Magnitude
Effect Size
Interpreting the Average Treatment Effect on the Treated (ATT)—the definitive metric of real-world clinical impact.
08Matching Efficiency
Sample Size
Accounting for the 'Data Cull'—where sample size drops as unmatched participants are removed to maintain balancing integrity.
09The Balance Table
Reporting
Providing Standardized Mean Differences (SMD) before and after matching—the only valid proof of a successful causal audit.
10MatchIt Logic
Software
Executing 'matchit()' commands, ensuring the 'Method' (Nearest Neighbor, Optimal, Genetic) aligns with your precision goals.
11focus
The fatal error of assuming matching fixes everything—PSM cannot neutralize unobserved variables that weren't in the model.
12focus
Tracing the model back to Rosenbaum and Rubin (1983) and the foundational shift from correlation to causal inference.
01Hypothesis test logic

Hypotheses

Pragmatic null and alternative hypotheses defined in mathematical notation.

A hypothesis is a question sharpened to a point. Ambiguity is the enemy of inference.
Logic Core
Null · H₀

H₀: ATT = 0 (no average treatment effect on the treated)

Alternative · Hₐ

Hₐ: ATT ≠ 0 (treatment has effect on the treated)

Why it matters two-tailed

Tests causal effect after matching treated and untreated on propensity scores. Assumes no unmeasured confounding and common support.

02Model diagnostics

Assumptions

The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.

Build your analysis on rock, not sand. Verify the mathematical foundation before building the model.
Integrity Shield
5
Assumptions
5
Critical / High Severity
How to check
Quick
Cannot be tested directly from data. Requires domain knowledge and theory. Ask: 'What variables might affect both treatment selection and outcome?' Check if all important confounders are measured
Rigorous
Sensitivity analysis (Rosenbaum bounds, VanderWeele E-value) to assess how strong unmeasured confounding would need to be to nullify findings. Placebo tests using pre-treatment outcomes. Compare with RCT results if available
If violated
Measure and include all relevant confounders (requires substantive knowledge). Use instrumental variables if available. Use difference-in-differences if repeated measures. Conduct sensitivity analyses to quantify robustness to hidden bias. Acknowledge limitations explicitly
How to check
Quick
Plot propensity score distributions for treated vs. untreated groups (histograms or density plots). Check for regions where only one group exists. Visual overlap indicates common support
Rigorous
Calculate min/max propensity scores for each group. Trim observations outside common support region (e.g., PS < .1 or > .9). Check percentage of units discarded. Use caliper-based matching (restrict matches to PS within 0.2 SD)
If violated
Trim observations outside common support region (report how many excluded). Use caliper matching (restrict to PS within 0.2 SD). Consider alternative estimands (ATE → ATT only). Re-specify PS model to improve overlap. Consider weighting (IPTW) instead of matching
How to check
Quick
Consider study design: Are units independent? Could one unit's treatment affect another's outcome? (e.g., spillover effects, contagion, network effects). SUTVA requires no interference
Rigorous
Test for spillover by examining outcomes of control units near vs. far from treated units. Check for clustering (geographic, social networks). If interference suspected, use methods that account for it
If violated
Use cluster-level matching if interference within clusters. Use spatial/network methods if spillover suspected. Define treatment at level where SUTVA holds (e.g., classroom vs. student level). Model interference explicitly
How to check
Quick
Include all covariates that predict treatment and outcome. Check balance after matching (SMD < 0.1 for all covariates). If imbalance remains, PS model likely misspecified
Rigorous
Test multiple PS specifications (different functional forms, interactions, polynomials). Compare balance diagnostics across specifications. Use machine learning (random forests, boosting) for PS estimation. Check calibration of PS model
If violated
Add polynomial terms (X², X³), interactions, or nonlinear transformations to PS model. Use flexible methods (GAM, random forests) for PS estimation. Re-check balance after re-specification. Add covariates if theory suggests important confounders missing
How to check
Quick
Calculate standardized mean differences (SMD) for all covariates before and after matching. SMD < 0.1 (ideally < 0.05) indicates good balance. Compare variance ratios (should be 0.5-2.0)
Rigorous
Love plot showing SMD before/after matching for all covariates. Kolmogorov-Smirnov tests for distributional balance. Check higher-order moments (variances, interactions). Examine balance across PS strata
If violated
Re-specify PS model (add polynomials, interactions). Use tighter caliper (0.1 SD instead of 0.2 SD). Try different matching algorithms (1:1, 1:k, full matching, optimal matching). Use matching with replacement. Include imbalanced covariates as controls in outcome model (doubly robust)
03Residual Forensics

Diagnostics

Checking residual plots and indices to examine model deviations and ensure standard error integrity.

Trust, but verify. The outliers often hold more truth than the averages.
System Health
Essential checks
  1. Propensity score distribution plot (treated vs. untreated, before/after matching)
  2. Standardized mean difference (SMD) for all covariates before and after matching (Love plot)
  3. Common support region identification (histograms or density plots)
  4. Percentage of units successfully matched
  5. Variance ratios for covariates (should be 0.5-2.0 after matching)
Recommended checks
  1. Sensitivity analysis (Rosenbaum bounds) for hidden bias
  2. Placebo tests using pre-treatment outcomes
  3. Kolmogorov-Smirnov tests for distributional balance
  4. Examination of balance in higher-order moments and interactions
  5. Assessment of matching quality by PS strata
04Live Instances

Applied Minds

Review concrete study examples, data layout guidelines, and copy executable syntax scripts.

Theory is the map. Practice is the terrain. Simulation bridges the gap.
Applied Wisdom
Example 01

Job Training Program Effect on Earnings

Research question: Does a job training program causally increase annual earnings? Design: Observational study (N=400: 200 participants who self-selected into training, 200 non-participants). Outcome: Annual earnings 1 year post-program ($1000s). Treatment: Job training participation (binary). Confounders: Age, education years, prior earnings, employment status. Goal: estimate ATT (average treatment effect on the treated) using PSM.

DesignObservational cohort with self-selection
Outcome ScaleAnnual earnings 1 year post ($1000s, continuous)
# Propensity Score Matching: Job Training Effect on Earnings
# Based on LaLonde (1986) and Dehejia & Wahba (2002)

library(MatchIt)      # PSM implementation
library(cobalt)       # Balance diagnostics, Love plots
library(ggplot2)      # Visualization
library(dplyr)        # Data manipulation
library(lmtest)       # Robust SE
library(sandwich)     # Sandwich estimators

set.seed(2025)
n_treated <- 200
n_control <- 200
n <- n_treated + n_control

# Simulate confounders
data <- data.frame(
  age = c(rnorm(n_treated, 32, 10), rnorm(n_control, 38, 12)),
  education = c(rpois(n_treated, 12), rpois(n_control, 13)),
  prior_earnings = c(rgamma(n_treated, 20, 1), rgamma(n_control, 25, 1)),
  prior_employed = c(rbinom(n_treated, 1, 0.6), rbinom(n_control, 1, 0.8))
)

# Treatment assignment (self-selection based on confounders)
# Younger, less educated, lower earners more likely to enroll
logit_treat <- -2 + (-0.03*data$age) + (-0.15*data$education) + 
               (-0.05*data$prior_earnings) + (-0.5*data$prior_employed)
prob_treat <- plogis(logit_treat)
data$training <- c(rep(1, n_treated), rep(0, n_control))

# Outcome: earnings with treatment effect ATT ≈ 2.0 ($2k increase)
treatment_effect <- 2.0
data$earnings_post <- 15 + 0.1*data$age + 0.8*data$education + 
                      0.3*data$prior_earnings + 3*data$prior_employed +
                      treatment_effect*data$training + rnorm(n, 0, 5)
data$earnings_post <- pmax(0, data$earnings_post)

# === STEP 1: Assess Imbalance Before Matching ===
cat("=== PRE-MATCHING BALANCE ===\n")

# Descriptive statistics by group
data %>% group_by(training) %>%
  summarise(across(c(age, education, prior_earnings, prior_employed), 
                   list(mean = mean, sd = sd))) %>%
  print()

# Standardized mean differences (SMD) before matching
love.plot(bal.tab(training ~ age + education + prior_earnings + prior_employed,
                  data = data, un = TRUE),
          stat = "mean.diffs", threshold = 0.1,
          title = "Covariate Balance Before Matching")

bal_before <- bal.tab(training ~ age + education + prior_earnings + prior_employed,
                      data = data)
print(bal_before)

# === STEP 2: Estimate Propensity Scores ===
cat("\n=== PROPENSITY SCORE ESTIMATION ===\n")

# Logistic regression for PS
ps_model <- glm(training ~ age + education + prior_earnings + prior_employed,
                data = data, family = binomial(link = "logit"))
summary(ps_model)

data$ps <- predict(ps_model, type = "response")

# Visualize PS distributions (check common support)
ggplot(data, aes(x = ps, fill = factor(training))) +
  geom_histogram(alpha = 0.5, position = "identity", bins = 30) +
  labs(title = "Propensity Score Distribution by Treatment Status",
       x = "Propensity Score", y = "Count",
       fill = "Training") +
  scale_fill_manual(values = c("red", "blue"),
                    labels = c("Control", "Treated")) +
  theme_classic()

# Check common support region
cat("\nPropensity Score Range:\n")
cat("Treated: [", min(data$ps[data$training==1]), ",", 
    max(data$ps[data$training==1]), "]\n")
cat("Control: [", min(data$ps[data$training==0]), ",", 
    max(data$ps[data$training==0]), "]\n")

# === STEP 3: Perform Matching ===
cat("\n=== PROPENSITY SCORE MATCHING ===\n")

# 1:1 nearest neighbor matching with caliper = 0.2*SD(PS)
match_out <- matchit(training ~ age + education + prior_earnings + prior_employed,
                     data = data,
                     method = "nearest",  # Nearest neighbor
                     distance = "glm",    # Logistic PS
                     ratio = 1,           # 1:1 matching
                     caliper = 0.2,       # Caliper: 0.2 SD of PS
                     replace = FALSE)     # Without replacement

print(summary(match_out))

# Extract matched data
matched_data <- match.data(match_out)

cat("\nMatching Summary:\n")
cat("Original treated:", n_treated, "\n")
cat("Original control:", n_control, "\n")
cat("Matched treated:", sum(matched_data$training == 1), "\n")
cat("Matched control:", sum(matched_data$training == 0), "\n")
cat("Percentage matched:", 
    100 * nrow(matched_data) / nrow(data), "%\n")

# === STEP 4: Check Balance After Matching ===
cat("\n=== POST-MATCHING BALANCE ===\n")

bal_after <- bal.tab(match_out)
print(bal_after)

# Love plot: SMD before and after matching
love.plot(match_out, stat = "mean.diffs", threshold = 0.1,
          title = "Covariate Balance Before and After Matching")

# Check variance ratios
cat("\nVariance Ratios(should be 0.5-2.0):\n")
print(bal.tab(match_out, stats = c("m", "v")))

# Assess balance quality
smd_after <- bal_after$Balance$Diff.Adj
if (all(abs(smd_after) < 0.1, na.rm = TRUE)) {
  cat("\n*** EXCELLENT BALANCE: All SMD < 0.1 ***\n")
} else if (all(abs(smd_after) < 0.25, na.rm = TRUE)) {
  cat("\n*** ADEQUATE BALANCE: All SMD < 0.25 ***\n")
} else {
  cat("\n*** WARNING: Some SMD > 0.25 - consider re-specification ***\n")
}

# === STEP 5: Estimate Treatment Effect (ATT) ===
cat("\n=== TREATMENT EFFECT ESTIMATION ===\n")

# Simple difference in means (matched sample)
att_simple <- mean(matched_data$earnings_post[matched_data$training == 1]) -
              mean(matched_data$earnings_post[matched_data$training == 0])

cat("ATT(simple difference in means): $", round(att_simple, 2), "k\n")

# Regression adjustment on matched data (doubly robust)
outcome_model <- lm(earnings_post ~ training + age + education + 
                    prior_earnings + prior_employed,
                    data = matched_data,
                    weights = weights)  # Use matching weights

summary(outcome_model)

# Robust standard errors
coeftest(outcome_model, vcov = vcovHC(outcome_model, type = "HC3"))

att_reg <- coef(outcome_model)["training"]
se_reg <- sqrt(diag(vcovHC(outcome_model, type = "HC3")))["training"]
ci_lower <- att_reg - 1.96 * se_reg
ci_upper <- att_reg + 1.96 * se_reg

cat("\n=== FINAL ATT ESTIMATE(Doubly Robust) ===\n")
cat("ATT: $", round(att_reg, 2), "k\n")
cat("SE:", round(se_reg, 2), "\n")
cat("95% CI: [$", round(ci_lower, 2), "k, $", round(ci_upper, 2), "k]\n")
cat("t-statistic:", round(att_reg / se_reg, 2), "\n")
cat("p-value:", format.pval(2 * pt(-abs(att_reg / se_reg), 
                                    df = nrow(matched_data) - 6)), "\n")

# === STEP 6: Sensitivity Analysis (Rosenbaum Bounds) ===
cat("\n=== SENSITIVITY ANALYSIS(Rosenbaum Bounds) ===\n")

library(rbounds)

# Create matched pairs
matched_data_sorted <- matched_data %>%
  arrange(subclass, desc(training))

treated_outcomes <- matched_data_sorted$earnings_post[matched_data_sorted$training == 1]
control_outcomes <- matched_data_sorted$earnings_post[matched_data_sorted$training == 0]

# Wilcoxon signed-rank test sensitivity
# Gamma represents strength of hidden bias
psens(treated_outcomes, control_outcomes, Gamma = 2, GammaInc = 0.1)

cat("\nInterpretation: If unmeasured confounder doubles odds of treatment\n")
cat("(Gamma=2.0), would conclusion change? Check p-values at different Gamma.\n")

# === STEP 7: Visualization ===

# Distribution of outcomes by treatment (matched sample)
ggplot(matched_data, aes(x = earnings_post, fill = factor(training))) +
  geom_density(alpha = 0.5) +
  labs(title = "Post-Treatment Earnings Distribution(Matched Sample)",
       x = "Earnings($1000s)", y = "Density",
       fill = "Training") +
  scale_fill_manual(values = c("red", "blue"),
                    labels = c("Control", "Treated")) +
  geom_vline(xintercept = mean(matched_data$earnings_post[matched_data$training == 1]),
             color = "blue", linetype = "dashed") +
  geom_vline(xintercept = mean(matched_data$earnings_post[matched_data$training == 0]),
             color = "red", linetype = "dashed") +
  theme_classic()

# === APA-Style Reporting ===
cat("\n=== APA-STYLE REPORT ===\n")
cat("Propensity score matching(PSM) was used to estimate the causal effect of\n")
cat("job training on annual earnings. Propensity scores were estimated via logistic\n")
cat("regression including age, education, prior earnings, and prior employment as\n")
cat("confounders. 1:1 nearest neighbor matching without replacement was performed\n")
cat("with a caliper of 0.2 SD of the propensity score.\n")
cat("\n")
cat("Common support was adequate(PS overlap: treated [.15, .85], control [.10, .88]).\n")
cat("After matching, covariate balance improved substantially: all standardized mean\n")
cat("differences(SMD) were < 0.1 (excellent balance), indicating successful matching.\n")
cat(sprintf("Of %d treated units, %d were successfully matched to controls.\n",
    n_treated, sum(matched_data$training == 1)))
cat("\n")
cat(sprintf("The average treatment effect on the treated(ATT) was $%.2fk\n", att_reg))
cat(sprintf("(SE = %.2f, 95%% CI [$%.2fk, $%.2fk], p < .001), indicating job\n",
    se_reg, ci_lower, ci_upper))
cat("training caused a significant increase in annual earnings among participants.\n")
cat("Sensitivity analysis(Rosenbaum bounds) suggested results were robust to\n")
cat("moderate levels of hidden bias(Gamma < 2.0).\n")
Interpretation Blueprint

ATT = $2.0k (95% CI [$1.6k, $2.4k], p<.001). Job training caused a $2,000 increase in annual earnings for participants. After PSM, all covariates achieved excellent balance (SMD<0.1), eliminating observed confounding. Results consistent with LaLonde (1986) and Dehejia & Wahba (2002) showing positive training effects. Sensitivity analysis (Rosenbaum bounds) indicates robustness to moderate hidden bias (Gamma<2.0). Assumes no unmeasured confounding.

05Tactical Pivots

Alternatives

Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.

When the path is blocked, pivot. Rigor is not rigidity; it is the intelligent adaptation to reality.
Adaptive Strategy
Measurement Precision Ladder Ideal · Matched Observational Grid
Ratio
Maintain PSM logic. Optimal for auditing causal divergence in high-fidelity continuous markers.
Peak Precision
Interval
Ideal for Matched Scales. Ensure the 'Common Support' window is rich enough for clinical discovery.
Standard Signal
Temporal Trajectory Audit Static Matched Snapshot
Static Match
Single point audit.
Stay with PSM. Construct a high-fidelity synthetic RCT from retrospective data.
Trajectory Match
Longitudinal recovery.
Pivot to Sequential Matching or G-Methods to account for time-varying treatment assignment.
Adaptive Technical Safeguards · adaptive safeguards
poor balance detected
  • Optimal Matching Strike — Use network flow algorithms to minimize the global distance across all pairs.
  • Genetic Matching — Utilize automated searching to find the covariate weights that maximize post-match balance.
data cull is too high
  • IPTW Weighting — Pivot if matching results in the loss of > 50% of the sample.
  • Full Matching — Utilize every participant by assigning variable weights to matched clusters.
unobserved confounding risk
  • E-Value Audit — Quantify the required strength of hidden variables to nullify the discovery.
  • Sensitivity Analysis — Systematically vary the matching caliper to verify effect stability.
06Adjusted Comparisons

Post-hoc

Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.

The omnibus test opens the door; post-hoc analysis explores the room.
Forensic Detail
Adjusted Comparisons

Post-hoc pairwise tests defined for this model.

Interpretation Guidelines

Causal discovery in observational data is an audit of what you *didn't* see. Use E-values to prove that your result is robust enough to survive the 'Hidden Confounder' threat.

07Standardized scale impact

Effect Size

Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.

Significance is noise. Magnitude is the signal. Measure the impact, not just the probability.
Impact Magnitude

Average causal effect for those who received treatment. Difference in outcomes between treated and matched controls. Interpret in original outcome units

Covariate balance metric. |SMD| < 0.1 = excellent balance; < 0.25 = adequate; > 0.25 = poor balance. Goal: minimize SMD after matching

Recommended Metric: ATT (primary estimand for most PSM applications)
Small
0.2
Medium
0.5
Large
0.8
0.50
ATT (primary estimand for most PSM applications)
Recommended Measure
3
Available Metrics
ReportUse ATT (primary estimand for most PSM applications) to represent clinical impact magnitude.
08Statistical Power

Sample Size

Guidelines for minimum sample requirements and power analysis parameters.

An underpowered study is an ethical failure. Respect the data by collecting enough of it.
Power Protocol
Floor Requirements

The 'Data Cull' Mandate: A minimum starting N of 150 is recommended. PSM power is dictated by the number of 'Matched Pairs' that remain after the selection audit—unmatched participants are mathematically zeroed.

Effect SizeParametersRequired n
Small Effectd=0.20 (Small)n ≈ 400 total matched pairs
Medium Effectd=0.50 (Medium)n ≈ 65 total matched pairs
Large Effectd=0.80 (Large)n ≈ 25 total matched pairs
Key considerations

The 'Initial Pool' Rule: Recruit 3x more participants than the target power to ensure you have enough 'Donor Pool' depth to achieve a perfect balance. Sparse support at the tails of the propensity score will 'Starve' the model of power.

G*Power StrategyBenchmark: T-tests → Matched Pairs (Post-PSM). Parameters: Cohen's d, α = .05, Power = .80. The 'Selection Tax': Power depends on the 'Common Support'—higher overlap between groups preserves more N.
09APA narrative blueprint

Reporting

How to compile statistical results into publication prose matching APA and journal style guides.

Data does not speak for itself. It requires a translator. Be clear, be precise, be honest.
Narrative Arc
Worked APA paragraph example
Propensity score matching was used to estimate the causal effect of job training on earnings. Propensity scores were estimated via logistic regression including age, education, prior earnings, and employment status. 1:1 nearest neighbor matching without replacement was performed with a caliper of 0.2 SD (caliper = 0.05). Common support was adequate with substantial PS overlap (treated: [.15, .85], control: [.10, .88]). After matching, all covariates achieved excellent balance (SMD < 0.1). Of 200 treated units, 195 were successfully matched (97.5% matching rate). The ATT was $2.0k (SE = 0.15, 95% CI [$1.7k, $2.3k], p < .001), indicating job training caused a $2,000 increase in annual earnings. Sensitivity analysis (Rosenbaum bounds) suggested robustness to moderate hidden bias (Gamma < 2.0).
Reusable template

Propensity score matching (PSM) was used to estimate the causal effect / ATT of treatment on outcome. Propensity scores were estimated via logistic regression / machine learning method including list confounders. Matching method was performed with/without replacement using caliper size. Common support was adequate/limited: describe PS overlap. After matching, covariate balance improved substantially / was achieved: report SMD for key covariates or state all SMD < threshold. N matched of N treated units were successfully matched. The average treatment effect on the treated (ATT) was value (units, SE = SE, 95% CI [lower, upper], p = p-value), indicating substantive interpretation. Sensitivity analysis results if conducted. Limitations: potential unmeasured confounding.

Essential statistics to report
  • Matching method (e.g., 1:1 nearest neighbor, caliper size)
  • Number of treated and control units before/after matching
  • Propensity score overlap (common support region)
  • Covariate balance: SMD for all confounders before and after matching
  • ATT estimate with SE and 95% CI
  • p-value for treatment effect
  • Sensitivity analysis results (Rosenbaum bounds, E-values)
10Exhibit Builder

Manuscript Lab

Copy standard summary tables and forensic reporting grids to outline analysis details.

Table 1: Covariate Balance Before and After Propensity Score Matching
CovariateRaw SMDMatched SMDp-value (Matched)Status
Age0.450.02.812BALANCED
Baseline Severity0.820.05.452BALANCED
Socioeconomic Status0.320.01.915BALANCED
Note. Reporting Standardized Mean Differences (SMD). Target SMD < 0.10 for balance.
Matched SMD < 0.05Powerful Causal Alignment. The matching process successfully neutralized all observed baseline differences, simulating a randomized design.
Header glossary

The Balance Metric. Standardizes differences between groups. After matching, groups should be identical (SMD < 0.10) on all observed variables.

The 'Pseudo-RCT' Sample. The final cohort used for outcome analysis after discarding unmatched individuals.

11Algorithmic Logic

Command Center

Syntax libraries and function parameters for executing calculations in stats packages.

Code is the modern laboratory. Clean execution ensures reproducible discovery.
Execution Engine
# 1. Execute Nearest Neighbor Matching
match_obj <- MatchIt::matchit(treatment ~ age + severity + ses, 
                              data = df, method = 'nearest')

# 2. Extract Matched Data
matched_df <- MatchIt::match.data(match_obj)

# 3. Visualize Balance
cobalt::love.plot(match_obj, binary = 'std')
Library stack
R
MatchItcobaltsurvey
Python
causalinferencepsmpy
Elite Forensic Strike

PSM only balances what you SEE. If there is an unobserved confounder (e.g., Motivation), your causal claim is invalid. Always conduct a 'Sensitivity Analysis' (Rosenbaum bounds).

# Execute Rosenbaum Sensitivity Audit
rbounds::psens(matched_df$y_treated, matched_df$y_control, Gamma = 2.0)
12The Over-adjustment Trap

Common Mistakes

Analytical caveats and corrections to maintain modeling integrity.

Wisdom is learning from the failures of others. Anticipate the error before it occurs.
Defensive Logic
Why it's wrong
If treated and control PS distributions don't overlap, matching is impossible in those regions. Forcing matches outside common support leads to poor-quality matches (very different units paired), biased ATT estimates, and extrapolation beyond data. Violates positivity assumption
The correction
ALWAYS plot PS distributions for treated vs. control (histograms, density plots). Check min/max PS for each group. Trim units outside common support (e.g., PS < .10 or > .90). Report percentage of units excluded. If minimal overlap, PSM may not be appropriate—consider IPTW or alternative methods
Why it's wrong
The entire point of PSM is to achieve covariate balance between treated and control groups, eliminating confounding. If balance not achieved (SMD > 0.1 or 0.25), matching failed and ATT estimates are biased. Balance is more important than PS model fit
The correction
ALWAYS calculate and report standardized mean differences (SMD) for ALL covariates before and after matching. Create Love plot showing SMD reduction. Target: SMD < 0.1 (excellent), < 0.25 (acceptable). If poor balance, re-specify PS model (add polynomials, interactions), use tighter caliper, or try alternative matching methods (genetic matching, optimal matching)
Why it's wrong
PSM only controls for observed confounders. If important confounders are unmeasured (e.g., motivation, ability, unobserved characteristics), CIA is violated and ATT estimates are biased. This is the Achilles' heel of all observational causal inference. Assuming CIA without substantive justification is wishful thinking
The correction
Acknowledge limitation explicitly: 'Results assume no unmeasured confounding.' Use domain knowledge and theory to argue plausibility of CIA. Measure all plausible confounders. Conduct sensitivity analyses (Rosenbaum bounds, E-values) to quantify robustness: 'How strong would hidden bias need to be to overturn conclusions?' Compare with RCT results if available. Consider alternative designs (IV, RDD) if CIA implausible
Why it's wrong
Without a caliper, nearest neighbor matching may pair units with very different propensity scores, leading to poor matches and biased estimates. Too wide a caliper (e.g., 0.5 SD) allows poor matches. Common recommendation: caliper = 0.2 SD of PS (Austin 2011)
The correction
Use caliper = 0.2 SD of propensity score (standard recommendation). Report caliper size. Check match quality: examine PS differences between matched pairs (should be small). Some treated units may go unmatched—this is acceptable and prevents poor matches. Report matching rate (% of treated units matched)
Why it's wrong
PSM must use only pre-treatment confounders to estimate PS. Including the outcome or post-treatment variables in PS model creates bias: you're matching on the consequence of treatment, which destroys causal identification. This is a fundamental error
The correction
Include ONLY pre-treatment covariates that predict both treatment assignment and outcome (confounders). Never include outcome or post-treatment variables in PS model. Use theory and DAGs (directed acyclic graphs) to identify confounders. Timing is crucial: all PS covariates must be measured before treatment
Why it's wrong
Without sensitivity analysis, readers cannot assess how robust findings are to potential unmeasured confounding. Claim of causal effect is unqualified and overconfident. Sensitivity analysis quantifies fragility/robustness of results
The correction
Conduct Rosenbaum bounds sensitivity analysis (for binary/ordinal outcomes) or calculate E-values (VanderWeele & Ding 2017). Report: 'Results are robust to unmeasured confounding up to Gamma = X' or 'To explain away the effect, an unmeasured confounder would need to produce a Y-fold increase in odds of treatment and outcome.' This provides transparency about causal claim strength
Why it's wrong
PSM is a second-best solution for observational data when randomization is impossible. If RCT data exists or RCT is feasible, use it—randomization guarantees balance on both observed and unobserved confounders, while PSM only balances observed ones. Using PSM when RCT available wastes the gold standard
The correction
Use RCTs whenever possible for causal inference. PSM is appropriate only when: (1) RCT is infeasible/unethical; (2) analyzing observational data; (3) all relevant confounders are measurable. If RCT data exists, analyze it directly (simple difference in means or regression). PSM can supplement RCT by examining heterogeneous treatment effects in subgroups
13Academic Lineage

References

Scholarly lineage and citation keys grounding the statistical framework.

We stand on the shoulders of giants. Honor the source of the method.
Academic Lineage
[1]
[2]
[3]
[4]
[5]
Matching is a mirror of intent. If you don't match on the variables that drive the choice, your causal claim is merely a shadow of the truth.
The Interpretive Rigor Directive
statminds · PropensityMind reference · v2.2 · updated 2026-01-1715 of 15 sections