Causal Inference Treatment Effects Causal Discovery
# Causal Inference: Treatment Effects & Causal Discovery
## Introduction & Motivation
Causal Inference: identify causal relationships. Treatment effects; observational data. Applications: policy evaluation, medical studies, A/B testing.
Motivation: Understand causality; move beyond correlation.
Applications: Policy, medicine, econometrics.
---
## Core Concepts & Theory
### Potential Outcomes
Counterfactual outcomes; Rubin framework.
### Confounding
Unobserved variables affecting both treatment and outcome.
### Propensity Score
Probability of treatment assignment.
---
## Mathematical Formulation
Average Treatment Effect:
$$ ext{ATE} = \mathbb{E}[Y_1 - Y_0]$$
Propensity Score:
$$e(x) = P(T=1|X=x)$$
IPW Estimator:
$$ ext{ATE} = \mathbb{E}\left[\frac{TY}{e(X)} - \frac{(1-T)Y}{1-e(X)}
ight]$$
---
## Advanced Theory & Extensions
### Doubly Robust
Combine regression and IPW.
### Causal Forests
ML for heterogeneous effects.
### Instrumental Variables
Handle endogeneity.
---
## Computational Considerations
Propensity matching: O(N²).
IPW: O(N).
Causal forests: O(N log N).
---
## Practical Implementation Strategies
### Overlap Assumption
Common support for propensity.
### Matching
Match similar units.
### Stratification
Group by propensity quintile.
---
## Benchmark Datasets & Evaluation
NSW Job Training: Classic causal study.
IHDP: Synthetic benchmark.
LaLonde Dataset: Policy evaluation.
---
## Key Challenges & Limitations
### Unobserved Confounders
Hidden variables affect results.
### Extrapolation
Limited overlap; poor estimation.
### Identifiability
Assumptions needed for causality.
---
## Hyperparameter Tuning
Propensity trimming: 0.01-0.1.
Bandwidth (matching): Data-dependent.
Forest parameters: Leaf size, depth.
---
## Summary & Key Takeaways
Causal Inference via propensity scores and matching enables treatment effect estimation from observational data.
Principles:
1. Potential outcomes: Rubin model.
2. Confounding: unobserved bias.
3. Propensity: overlap assumption.
4. ATE: average effect.
5. IPW: inverse weighting.
---
---
## Appendix: Practical Labs
### Lab 1: Propensity Score
import numpy as np
def estimate_propensity_score(X, T, model_type='logistic'):
"""Estimate propensity score P(T=1|X)"""
# Simplified: logistic regression approximation
if model_type == 'logistic':
# Compute score (simplified)
score = 1 / (1 + np.exp(-(X.mean(axis=1) - T.mean())))
return score
# Test
np.random.seed(42)
X = np.random.randn(100, 5)
T = np.random.randint(0, 2, 100)
e = estimate_propensity_score(X, T)
assert e.shape == (100,), "Propensity shape"
print("✓ Propensity score working")
if __name__ == "__main__":
print("Lab 1: PropensityScore - PASSED")### Lab 2: IPW Estimator
import numpy as np
def ipw_ate(Y, T, e):
"""Inverse Probability Weighting ATE"""
ate = np.mean(T * Y / e - (1 - T) * Y / (1 - e + 1e-8))
return ate
# Test
np.random.seed(42)
Y = np.random.randn(100)
T = np.random.randint(0, 2, 100)
e = np.random.rand(100)
ate = ipw_ate(Y, T, e)
assert np.isfinite(ate), "ATE finite"
print("✓ IPW ATE working")
if __name__ == "__main__":
print("Lab 2: IPWATE - PASSED")### Lab 3: Matching
import numpy as np
def matching_estimator(Y, T, X, caliper=0.1):
"""Nearest neighbor matching"""
treated_idx = np.where(T == 1)[0]
control_idx = np.where(T == 0)[0]
ate = 0
for i in treated_idx:
# Find nearest control
distances = np.linalg.norm(X[i] - X[control_idx], axis=1)
best_j = control_idx[np.argmin(distances)]
# Add treatment effect
ate += Y[i] - Y[best_j]
return ate / len(treated_idx)
# Test
np.random.seed(42)
Y = np.random.randn(100)
T = np.random.randint(0, 2, 100)
X = np.random.randn(100, 3)
ate = matching_estimator(Y, T, X)
assert np.isfinite(ate), "ATE finite"
print("✓ Matching working")
if __name__ == "__main__":
print("Lab 3: Matching - PASSED")### Lab 4: Balance Check
import numpy as np
def check_covariate_balance(X, T):
"""Check balance of covariates after matching"""
treated = X[T == 1]
control = X[T == 0]
# Standardized difference
mean_diff = np.abs(treated.mean(axis=0) - control.mean(axis=0))
std_pool = np.sqrt((treated.std(axis=0)**2 + control.std(axis=0)**2) / 2)
std_diff = mean_diff / (std_pool + 1e-8)
return std_diff
# Test
np.random.seed(42)
X = np.random.randn(100, 5)
T = np.random.randint(0, 2, 100)
balance = check_covariate_balance(X, T)
assert balance.shape == (5,), "Balance shape"
print("✓ Balance check working")
if __name__ == "__main__":
print("Lab 4: BalanceCheck - PASSED")