Causal Inference — Potential Outcomes Framework
Statistics
The Gold Standard for Answering Causal Questions
The potential outcomes framework (Rubin Causal Model) defines causal effects as comparisons between what happened and what would have happened. It provides the conceptual foundation for all modern causal inference methods.
-
Medical Research — Define treatment effects rigorously in clinical trial analysis
-
Policy Evaluation — Measure program impacts by comparing actual and counterfactual outcomes
-
Social Science — Establish clear criteria for when causal claims are justified
The fundamental problem — we never observe both potential outcomes — drives all of causal inference.
Causal inference asks: What would have happened if a different treatment had been applied? The potential outcomes framework (Rubin Causal Model) provides a rigorous framework for answering this question.
Potential Outcomes
Fundamental Problem of Causal Inference
We can only estimate population-level effects (average treatment effects).
Average Treatment Effects
SUTVA
The Stable Unit Treatment Value Assumption has two parts:
| Component | Meaning |
|-----------|---------|
| No interference | One unit's treatment does not affect another unit's outcome |
| Treatment variation irrelevance | There is only one version of each treatment level |
Selection Bias
The naive comparison of treated and control groups may be biased because treatment assignment is often not random.
Causal Identifying Assumptions
| Assumption | Description | Violation Consequence |
|-----------|------------|----------------------|
| Unconfoundedness | | Selection bias |
| Overlap | for all X | Cannot estimate effects for some subgroups |
| SUTVA | No interference; one treatment version | Spillover effects bias estimates |
Estimation Under Unconfoundedness
When treatment is unconfounded given covariates , we can use matching, weighting, or regression adjustment.
Python Implementation
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
import matplotlib.pyplot as plt
np.random.seed(42)
# Simulate data with confounding
n = 1000
X = np.random.randn(n, 3)
propensity = 1 / (1 + np.exp(-(0.5*X[:,0] + 0.3*X[:,1] - 0.2*X[:,2])))
T = np.random.binomial(1, propensity)
Y0 = 2*X[:,0] + X[:,1] + np.random.randn(n)
Y1 = Y0 + 1.5 # True ATE = 1.5
Y = T * Y1 + (1 - T) * Y0
df = pd.DataFrame({'Y': Y, 'T': T, 'X1': X[:,0], 'X2': X[:,1], 'X3': X[:,2]})
# Naive comparison (biased)
naive_diff = df[df['T']==1]['Y'].mean() - df[df['T']==0]['Y'].mean()
print(f"Naive difference: {naive_diff:.3f}")
# IPW estimator
prop_model = LogisticRegression().fit(df[['X1','X2','X3']], df['T'])
e_hat = prop_model.predict_proba(df[['X1','X2','X3']])[:, 1]
ipw = np.mean(T * Y / e_hat - (1 - T) * Y / (1 - e_hat))
print(f"IPW estimate: {ipw:.3f}")
print(f"True ATE: 1.500")
Worked Example
Key Takeaways
Related Topics
-
See Randomized Controlled Trials for the gold standard of causal inference
-
See Propensity Score Matching for observational study methods
-
See Instrumental Variables for when unconfoundedness fails