Causal Inference Treatment Effect Estimation Confounding

# Causal Inference: Treatment Effect Estimation & Confounding

## Introduction & Motivation

Causal inference estimates effect of intervention (treatment) on outcome. Observational data confounded; confounder C influences both T and Y. Propensity score: P(T|C) balances treated/control. Instrumental variables handle unmeasured confounding. Critical for policy decisions, A/B testing, medical trials.

Motivation: Correlation ≠ causation. Confounding biases naive estimators. Causal methods isolate treatment effect.

Applications: Policy evaluation, personalized medicine, marketing effectiveness, economic analysis.

---

## Core Concepts & Theory

### Confounder

Variable affecting both treatment and outcome; creates spurious correlation.

### Propensity Score

P(T=1|C). Match on propensity score to balance confounders.

### Average Treatment Effect (ATE)

E[Y(1) - Y(0)]; expected effect if everyone treated vs. untreated.

---

## Mathematical Formulation

ATE via stratification:
$$ ext{ATE} = \sum_c P(C=c)[E[Y|T=1,C=c] - E[Y|T=0,C=c]]$$

Propensity score matching:
$$E[Y(1) - Y(0)] \approx E[Y|T=1, P(T|C)] - E[Y|T=0, P(T|C)]$$

Doubly robust estimation:
$$\hat{ ext{ATE}} = \mathbb{E}[\hat{m}_1(X) - \hat{m}_0(X) + \frac{T(Y - \hat{m}_1(X))}{\hat{e}(X)} - \frac{(1-T)(Y - \hat{m}_0(X))}{1 - \hat{e}(X)}]$$

---

## Advanced Theory & Extensions

### Heterogeneous Treatment Effects (HTE)

Causal forest: treatment effect varies by subgroup.

### Instrumental Variables

Confounder unobserved; use instrument Z (affects T, not Y directly).

### Difference-in-Differences

Temporal: E[Y_post - Y_pre | T=1] - E[Y_post - Y_pre | T=0].

---

## Computational Considerations

Propensity scoring: O(n) logistic regression + O(n) matching.

Causal forests: O(n log n) tree training.

Cross-fitting: O(folds × estimation time).

---

## Practical Implementation Strategies

### Overlap Check

Ensure 0 < P(T|C) < 1; common support for treated/control.

### Balance Assessment

Check covariate balance after matching/weighting.

### Sensitivity Analysis

Assess robustness to unmeasured confounding.

---

## Benchmark Datasets & Evaluation

IHDP: Infant health; 747 samples, 25 covariates.

Jobs: Job training program evaluation.

Synthetic: Known ground truth treatment effect.

Metrics: ATE bias, variance, MSE vs. true effect.

---

## Key Challenges & Limitations

### Unmeasured Confounding

Confounders unobserved; sensitivity analysis required.

### High-Dimensional Confounders

Many covariates; model selection crucial.

### Overlap Violation

Some T=1, C values have no T=0 match (or vice versa).

---

## Hyperparameter Tuning

Caliper (matching distance): 0.01-0.1 of propensity range.

Number of matches: 1-5 per unit.

Regularization (doubly robust): Ridge parameter λ.

---

## Real-World Applications & Case Studies

A/B Testing: Estimate causal lift via propensity weighting.

Medical Trials: Adjust for baseline confounders via AIPW.

Economics: Job training effect on earnings via IV.

---

## Integration with Other Methods

Causal Inference + ML → targeted maximum likelihood, TMLE.

Causal Inference + RL → policy learning with causal discovery.

---

## Summary & Key Takeaways

Causal inference estimates treatment effects while accounting for confounding via propensity scores, matching, weighting, or instrumental variables.

Principles:
1. Identify confounders; account for selection bias.
2. Propensity score balances treated/control on observables.
3. ATE = average of stratum-specific treatment effects.
4. Doubly robust combines outcome + propensity models.
5. Overlap and sensitivity analysis assess validity.

---

---

## Appendix: Practical Labs

### Lab 1: Propensity Score Estimation

import numpy as np
from sklearn.linear_model import LogisticRegression

def estimate_propensity_scores(X, T):
 """Estimate P(T=1|X)"""
 model = LogisticRegression(max_iter=200)
 model.fit(X, T)
 propensity_scores = model.predict_proba(X)[:, 1]
 return propensity_scores

# Data
np.random.seed(42)
n = 200
X = np.random.randn(n, 5)
T = (0.5 + X[:, 0] + np.random.randn(n) * 0.5 > 0).astype(int)

ps = estimate_propensity_scores(X, T)

print(f"Propensity score range: [{ps.min():.3f}, {ps.max():.3f}]")
assert 0 <= ps.min() and ps.max() <= 1, "Scores should be in [0,1]"
assert (ps > 0).sum() > 0, "Should have some positive"
print("✓ Propensity score estimation working")

if __name__ == "__main__":
 print("Lab 1: Propensity - PASSED")

### Lab 2: ATE via Stratification

import numpy as np

def ate_via_stratification(Y, T, strata, n_strata=5):
 """Estimate ATE by stratifying on confounder"""
 ate = 0
 for s in range(n_strata):
 mask = strata == s
 Y_s = Y[mask]
 T_s = T[mask]
 
 if (T_s == 1).sum() > 0 and (T_s == 0).sum() > 0:
 ate_s = Y_s[T_s == 1].mean() - Y_s[T_s == 0].mean()
 weight = mask.sum() / len(Y)
 ate += weight * ate_s
 
 return ate

# Data
np.random.seed(42)
n = 300
C = np.random.randint(0, 5, n) # Confounder
T = (np.random.rand(n) > 0.4 * (C + 1) / 5).astype(int)
Y = T * 2 + C * 0.5 + np.random.randn(n)

ate = ate_via_stratification(Y, T, C, n_strata=5)
print(f"Estimated ATE: {ate:.3f}")
assert isinstance(ate, (int, float, np.number)), "Should be scalar"
print("✓ ATE stratification working")

if __name__ == "__main__":
 print("Lab 2: ATE - PASSED")

### Lab 3: Propensity Score Weighting

import numpy as np

def ate_via_weighting(Y, T, propensity_scores):
 """Estimate ATE via inverse propensity weighting (IPW)"""
 ps = propensity_scores
 
 # IPW estimator
 ipw_t1 = (T * Y / ps).sum() / (T / ps).sum()
 ipw_t0 = ((1 - T) * Y / (1 - ps)).sum() / ((1 - T) / (1 - ps)).sum()
 
 ate = ipw_t1 - ipw_t0
 return ate

# Data
np.random.seed(42)
n = 200
X = np.random.randn(n, 3)
T = (X[:, 0] > 0).astype(int)
ps = 1 / (1 + np.exp(-X[:, 0])) # True propensity
Y = T * 3 + np.random.randn(n)

ate_weighted = ate_via_weighting(Y, T, ps)
print(f"IPW ATE: {ate_weighted:.3f}")
assert isinstance(ate_weighted, (int, float, np.number)), "Should be scalar"
print("✓ IPW weighting working")

if __name__ == "__main__":
 print("Lab 3: IPW - PASSED")

### Lab 4: Overlap Check

import numpy as np
import matplotlib.pyplot as plt

def check_overlap(T, propensity_scores, caliper=0.1):
 """Check common support: overlap of PS distributions"""
 ps_treated = propensity_scores[T == 1]
 ps_control = propensity_scores[T == 0]
 
 # Common support range
 min_ps_treated = ps_treated.min()
 max_ps_treated = ps_treated.max()
 min_ps_control = ps_control.min()
 max_ps_control = ps_control.max()
 
 overlap_min = max(min_ps_treated, min_ps_control)
 overlap_max = min(max_ps_treated, max_ps_control)
 
 n_treated_overlap = (ps_treated >= overlap_min) & (ps_treated <= overlap_max)
 n_control_overlap = (ps_control >= overlap_min) & (ps_control <= overlap_max)
 
 print(f"Treated with overlap: {n_treated_overlap.sum()}/{len(ps_treated)}")
 print(f"Control with overlap: {n_control_overlap.sum()}/{len(ps_control)}")
 
 return overlap_min, overlap_max

# Data
np.random.seed(42)
T = np.concatenate([np.ones(100), np.zeros(100)])
ps = np.concatenate([np.random.beta(5, 2, 100), np.random.beta(2, 5, 100)])

overlap_min, overlap_max = check_overlap(T, ps)
print(f"Common support: [{overlap_min:.3f}, {overlap_max:.3f}]")
assert overlap_min < overlap_max, "Should have overlap"
print("✓ Overlap check working")

if __name__ == "__main__":
 print("Lab 4: Overlap - PASSED")

Go deeper with CFSGPT

Get AI-powered deep-dives, save terms, and run advanced simulations — free account.

Create Free Account