NewTurn any lesson into a live trading bot with one prompt, using AgenKitagenkit.xyz

Part 3 · Data scienceLesson 1314 minQuant Bible §4.6

Causality: selection bias, randomisation and omitted variables

Why correlation isn’t causation, how a coin flip fixes it, and how to size the bias you couldn’t remove.

Prediction asks “what will happen?” Econometrics asks “what will happen if I change something?” Does health insurance make people healthier? Does this execution algorithm actually reduce slippage, or do we just use it on easier orders? Answering needs more than a good fit.

The selection-bias problem

Let Di=1D_i = 1 if person ii has insurance and 00 otherwise. Each person has two potential outcomes: Y1iY_{1i} with insurance and Y0iY_{0i} without. We only ever see one. Comparing group averages gives

Avg⁡[Y1i∣D=1]−Avg⁡[Y0i∣D=0]⏟what you measure=Avg⁡[Y1i−Y0i∣D=1]⏟causal effect on the insured+Avg⁡[Y0i∣D=1]−Avg⁡[Y0i∣D=0]⏟selection bias.\underbrace{\operatorname{Avg}[Y_{1i}\mid D=1] - \operatorname{Avg}[Y_{0i}\mid D=0]}_{\text{what you measure}} = \underbrace{\operatorname{Avg}[Y_{1i} - Y_{0i} \mid D=1]}_{\text{causal effect on the insured}} + \underbrace{\operatorname{Avg}[Y_{0i}\mid D=1] - \operatorname{Avg}[Y_{0i}\mid D=0]}_{\text{selection bias}} .

The selection-bias term asks: would the insured have been healthier even without insurance? Usually yes: richer, more educated people are both more likely to buy insurance and healthier anyway. The naive comparison mixes the two.

Figure 13.1Selection-bias simulator
When people choose, healthier people buy insurance, and the naive gap (grey) is the true effect (green) plus selection bias (red). Switch to a coin flip and the bias collapses towards zero. Set the true effect to zero to watch a completely useless treatment “work”.

Randomise

If a coin decides who is treated, treatment can’t be related to anything else about people. By the law of large numbers both groups have the same average Y0Y_0, so the selection-bias term goes to zero and the simple difference in means is the causal effect. After randomising, “check for balance”: compare the groups’ averages on age, income and so on; they should match.

Statistics for comparing two groups

  • Sample mean Yˉ\bar Y is unbiased for E[Y]E[Y], with sampling variance σY2/n\sigma_Y^2/n and standard error SE⁡^(Yˉ)=S(Y)/n\widehat{\operatorname{SE}}(\bar Y) = S(Y)/\sqrt n.
  • tt-statistic for “the mean is μ\mu”: t=(Yˉ−μ)/SE⁡^(Yˉ)t = (\bar Y - \mu)/\widehat{\operatorname{SE}}(\bar Y); ∣t∣>2|t| > 2 is the usual bar, and Yˉ±2 SE⁡^\bar Y \pm 2\,\widehat{\operatorname{SE}} is an approximate 95% interval.
  • Two groups: SE⁡^(Yˉ1−Yˉ0)=S(Y)1n1+1n0\widehat{\operatorname{SE}}(\bar Y^1 - \bar Y^0) = S(Y)\sqrt{\tfrac{1}{n_1} + \tfrac{1}{n_0}} (pooled), and t=(Yˉ1−Yˉ0)/SE⁡^t = (\bar Y^1 - \bar Y^0)/\widehat{\operatorname{SE}}.

A large tt needs both a real effect and a small standard error. A small tt may mean there is no effect, or just not enough data to see it.

Regression as a substitute for randomisation

Often you can’t randomise. Regression controls for observed differences:

Yi=α+βDi+γ⊤Ai+εi,Y_i = \alpha + \beta D_i + \gamma^\top A_i + \varepsilon_i ,

where AiA_i holds control variables (age, income, …). The key assumption is the conditional independence assumption: once you compare people with the same controls, treatment is as good as random. Continuous variables enter as they are; categories enter as one-hot (dummy) variables. Regressing on log⁡Y\log Y makes β\beta read as an approximate percentage effect.

Omitted-variable bias

What if you left out an important control? Compare the short regression (without the control AA) and the long one (with it):

βs−βl=π1×γ,\beta^{s} - \beta^{l} = \pi_1 \times \gamma ,

where γ\gamma is AA’s effect on YY in the long regression and π1\pi_1 is the slope from regressing AA on the treatment. Bias needs both: the omitted variable must move with the treatment (π1≠0\pi_1 \ne 0) and matter for the outcome (γ≠0\gamma \ne 0). Even without data on AA, you can reason about the sign of the bias.

Figure 13.2Omitted-variable bias
Simulated data with a hidden confounder A. The short regression (red) leaves A out; the long one (green) includes it. The bars show the identity β_short = β_long + π₁·γ holding exactly. Set either π₁ or γ to zero and the two lines agree.

Regression facts worth knowing

  • Regression gives the best linear approximation to the conditional expectation function E[Y∣X]E[Y\mid X], which is itself the minimum-mean-squared-error predictor: E[Y∣X]=arg⁡min⁡mE[(Y−m(X))2]E[Y\mid X] = \arg\min_{m} E\big[(Y - m(X))^2\big].
  • Residuals have mean zero and are uncorrelated with every input and with the fitted values.
  • ANOVA identity: Var⁡(Y)=Var⁡(E[Y∣X])+E[Var⁡(Y∣X)]\operatorname{Var}(Y) = \operatorname{Var}\big(E[Y\mid X]\big) + E\big[\operatorname{Var}(Y\mid X)\big].
  • If the error variance changes with XX (heteroskedasticity, which any nonlinear CEF or a 0/1 outcome produces), use robust standard errors. They are usually close to the conventional ones.
  • Sampling weights (inverse sampling probabilities) fix non-random samples; heteroskedasticity alone is not a reason to use weighted least squares.
  • A saturated model (a dummy for every combination of discrete inputs) fits the CEF exactly, at the cost of noisy coefficients.

In code

import numpy as np
rng = np.random.default_rng(11)

n = 100_000
health = rng.standard_normal(n)                        # unobserved
chooses = rng.random(n) < 1 / (1 + np.exp(-2.2 * health))
y0 = 70 + 7 * health + 3 * rng.standard_normal(n)
effect = 2.0

naive = (y0[chooses] + effect).mean() - y0[~chooses].mean()
print("people choose:", round(naive, 2))              # far above 2: selection bias

coin = rng.random(n) < 0.5
print("coin flip:", round((y0[coin] + effect).mean() - y0[~coin].mean(), 2))   # ≈ 2

Check yourself

Stocks that a broker’s analysts cover outperform uncovered stocks by 3% a year. Which omitted variable would most plausibly bias the ‘coverage effect’ upwards?

Progress is saved in this browser only.