Correlation vs Causation
A predictive ML model answers: "Given X, what is the likely value of Y?"
A causal model answers: "If I change X, what will happen to Y?"
Why the Distinction Matters
| Setting | Wrong (Correlation) | Right (Causal) |
|---|---|---|
| Ad targeting | "Users who saw the ad converted more" | "Showing the ad caused conversions to rise" |
| Medicine | "Patients who took aspirin had better outcomes" | "Aspirin reduced cardiovascular events" |
| Policy | "Cities with more police have more crime" | "Adding police changes crime rates" |
| Product | "Users with premium features churn less" | "Giving users premium features reduces churn" |
Spurious correlation: ice cream sales and drowning deaths are correlated (both driven by summer temperature). Restricting ice cream would not reduce drownings.
Confounding: a hidden variable causes both and , creating an observed correlation with no direct causal link.
Potential Outcomes Framework (Rubin Causal Model)
Define for each unit :
- : potential outcome if treated ()
- : potential outcome if untreated ()
Individual Treatment Effect (ITE):
The Fundamental Problem of Causal Inference: we only ever observe one of the two potential outcomes for each unit. The other is the counterfactual — it cannot be directly observed.
Population-Level Estimands
| Estimand | Formula | When to Use |
|---|---|---|
| ATE (Average Treatment Effect) | Population-wide policy | |
| ATT (Avg Treatment Effect on Treated) | Assess effect on those who opted in | |
| ATC (Avg Treatment Effect on Control) | Assess effect if untreated had been treated | |
| CATE (Conditional ATE) | Heterogeneous effects by subgroup |
Key Assumptions
- SUTVA (Stable Unit Treatment Value Assumption): no interference between units; treatment is well-defined
- Ignorability / Unconfoundedness: — no hidden confounders after conditioning on
- Overlap / Positivity: — every unit has a nonzero probability of either treatment
A/B Testing (Randomized Controlled Trials)
Randomization is the gold standard because it ensures treatment assignment is independent of all confounders — both observed and hidden.
Designing a Valid A/B Test
- Define hypothesis: vs
- Choose metric: primary (conversion rate) + guardrail metrics (revenue per user)
- Compute sample size: based on baseline rate, MDE (minimum detectable effect), , power
- Randomize: at the user/session level, avoiding network effects
- Run for full business cycles: capture weekly seasonality
- Analyze: two-sample t-test, z-test for proportions, or regression with covariates
where = MDE, = outcome variance, and = critical values.
Common Pitfalls
- Peeking: checking significance before experiment ends inflates Type I error
- Multiple testing: running 20 A/B tests simultaneously → 1 false positive at
- SUTVA violations: spillover (users in both groups interact), novelty effects, carryover
- SRM (Sample Ratio Mismatch): treatment/control split differs from intended → bug
import numpy as np
from scipy import stats
from statsmodels.stats.power import NormalIndPower
# ---- Sample size calculation ----
baseline_rate = 0.10 # 10% conversion
mde = 0.02 # detect a 2pp lift (10% → 12%)
alpha = 0.05
power = 0.80
effect_size = mde / np.sqrt(baseline_rate * (1 - baseline_rate))
analysis = NormalIndPower()
n_per_group = analysis.solve_power(effect_size=effect_size, alpha=alpha, power=power, alternative='two-sided')
print(f"Required sample size per group: {int(np.ceil(n_per_group))}")
# ---- Simulate experiment results ----
np.random.seed(42)
n = int(np.ceil(n_per_group))
control = np.random.binomial(1, baseline_rate, n)
treatment = np.random.binomial(1, baseline_rate + mde, n)
p_control = control.mean()
p_treatment = treatment.mean()
print(f"
Control conversion: {p_control:.4f}")
print(f"Treatment conversion: {p_treatment:.4f}")
print(f"Observed lift: {p_treatment - p_control:.4f}")
# ---- Two-proportion z-test ----
from statsmodels.stats.proportion import proportions_ztest
count = np.array([treatment.sum(), control.sum()])
nobs = np.array([n, n])
z_stat, p_value = proportions_ztest(count, nobs)
print(f"
Z-statistic: {z_stat:.3f}")
print(f"P-value: {p_value:.4f}")
print(f"Significant (α=0.05): {p_value < alpha}")
# ---- Confidence interval on lift ----
se = np.sqrt(p_control*(1-p_control)/n + p_treatment*(1-p_treatment)/n)
z_crit = stats.norm.ppf(1 - alpha/2)
lift = p_treatment - p_control
ci_lo, ci_hi = lift - z_crit*se, lift + z_crit*se
print(f"
95% CI on lift: [{ci_lo:.4f}, {ci_hi:.4f}]")Observational Studies: When You Can't Randomize
Many important decisions cannot be randomized (ethical, legal, or practical constraints). Causal inference methods recover causal effects from observational data under assumptions.
Propensity Score Matching (PSM)
The propensity score is the probability of treatment given observed covariates:
Propensity score theorem (Rosenbaum & Rubin, 1983): if ignorability holds given , it also holds given — reducing matching to one dimension regardless of covariate count.
Steps:
- Estimate via logistic regression (or gradient boosting)
- Match treated units to control units with similar
- Estimate ATT on matched sample
Variants: matching, stratification, inverse probability weighting (IPW), doubly-robust estimators.
Difference-in-Differences (DiD)
Compares the change in outcomes over time between treated and control groups:
Key assumption: Parallel trends — without treatment, treated and control groups would have followed the same trend.
Used widely in economics and policy evaluation (e.g., effect of minimum wage laws).
Instrumental Variables (IV)
When hidden confounders remain, use an instrument that:
- Affects treatment (relevance)
- Affects outcome only through (exclusion restriction)
- Is independent of confounders (exogeneity)
Classic example: draft lottery as IV for military service → effect of service on earnings.
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
# ---- Simulate observational data with confounding ----
np.random.seed(42)
n = 2000
age = np.random.normal(40, 10, n)
health_score = np.random.normal(70, 15, n)
# Treatment: older, healthier people more likely to exercise (confounder)
log_odds = -3 + 0.05 * age + 0.02 * health_score
p_treat = 1 / (1 + np.exp(-log_odds))
treatment = np.random.binomial(1, p_treat, n)
# Outcome: exercise (treatment) improves health, but older/healthier start higher
noise = np.random.normal(0, 5, n)
outcome = 10 * treatment + 0.1 * health_score - 0.05 * age + noise
df = pd.DataFrame({'age': age, 'health_score': health_score,
'treatment': treatment, 'outcome': outcome})
# ---- Naive estimate (biased by confounding) ----
naive_ate = df[df.treatment==1].outcome.mean() - df[df.treatment==0].outcome.mean()
print(f"Naive ATE (biased): {naive_ate:.2f} (true = 10.00)")
# ---- Estimate propensity scores ----
X = StandardScaler().fit_transform(df[['age', 'health_score']])
ps_model = LogisticRegression()
ps_model.fit(X, df['treatment'])
df['propensity'] = ps_model.predict_proba(X)[:, 1]
# ---- Nearest-neighbor matching (manual) ----
treated = df[df.treatment == 1].copy()
control = df[df.treatment == 0].copy()
matched_controls = []
for _, t_row in treated.iterrows():
distances = (control['propensity'] - t_row['propensity']).abs()
matched_controls.append(control.loc[distances.idxmin(), 'outcome'])
att_psm = treated['outcome'].values.mean() - np.mean(matched_controls)
print(f"PSM ATT (debiased): {att_psm:.2f} (true = 10.00)")
# ---- Inverse Probability Weighting (IPW) ----
eps = 1e-6
df['weight'] = np.where(
df.treatment == 1,
1 / (df.propensity + eps),
1 / (1 - df.propensity + eps),
)
ipw_ate = (
(df[df.treatment==1]['outcome'] * df[df.treatment==1]['weight']).sum() / df[df.treatment==1]['weight'].sum()
- (df[df.treatment==0]['outcome'] * df[df.treatment==0]['weight']).sum() / df[df.treatment==0]['weight'].sum()
)
print(f"IPW ATE (debiased): {ipw_ate:.2f} (true = 10.00)")Knowledge check
An analyst observes that users who use a premium feature have 30% lower churn. The product team wants to give all users the premium feature to reduce churn. What causal inference concern applies here?
Summary
- Correlation ≠ causation — predictive ML answers "what is likely?" not "what would happen if?"
- Pearl's causal ladder: association → intervention (do-calculus) → counterfactuals
- Potential outcomes: is the ITE; the unobserved outcome is the counterfactual
- Randomized A/B tests are the gold standard — randomization eliminates confounders
- Observational methods: propensity score matching, IPW, DiD, and IV recover causal effects when RCTs are impossible
- Causal inference is essential for responsible AI: understanding what your model's recommendations actually cause
Next: Bayesian Machine Learning — probabilistic reasoning, uncertainty quantification, and Gaussian Processes.