Math for Causal Inference

Day 28. Frisch–Waugh–Lovell & Partialling Out

What "controlling for" really does. Every multiple-regression coefficient is a simple regression on residualised data.

Video coming soon
Learning outcomes
  • ●State and prove the Frisch–Waugh–Lovell (FWL) theorem.
  • ●Compute a multiple-regression coefficient by partialling out, and read it from an added-variable plot.
  • ●Show that fixed-effects (within) estimators are FWL applied to group dummies.
  • ●Explain how OLS with controls weights heterogeneous treatment effects, and preview partialling out with machine learning.
2-HOUR SESSION PLAN
Theory 25′Warm-up (1–5) 20′Proofs (6–10) 35′Applied (11–15) 25′Python & Lab 15′
Colab notebook (.ipynb) Jump to the simulator

Theory

The theorem

Split the regressors: y=X1β1+X2β2+εy = X_1\beta_1 + X_2\beta_2 + \varepsilon. Let M2=I−X2(X2⊤X2)−1X2⊤M_2 = I - X_2(X_2^{\top}X_2)^{-1}X_2^{\top}, the residual-maker for X2X_2 (Day 20). Define the residualised variables

X~1=M2X1,y~=M2y.\tilde X_1 = M_2X_1, \qquad \tilde y = M_2y.

FWL theorem. The OLS coefficient on X1X_1 in the full regression equals the coefficient from regressing y~\tilde y on X~1\tilde X_1:

β^1=(X~1⊤X~1)−1X~1⊤y~=(X~1⊤X~1)−1X~1⊤y,\hat\beta_1 = (\tilde X_1^{\top}\tilde X_1)^{-1}\tilde X_1^{\top}\tilde y = (\tilde X_1^{\top}\tilde X_1)^{-1}\tilde X_1^{\top}y,

and the residuals of the two regressions are identical.

What it means

"Controlling for X2X_2" means: strip out of both yy and X1X_1 everything linearly predictable from X2X_2, then relate what is left. For a single regressor this is the regression anatomy formula

β^1=Cov⁡^(y,x~1)Var⁡^(x~1).\hat\beta_1 = \frac{\widehat{\operatorname{Cov}}(y, \tilde x_1)}{\widehat{\operatorname{Var}}(\tilde x_1)}.

Consequences you will use repeatedly:

  • Fixed effects: with X2X_2 = group dummies, M2M_2 subtracts group means, so the within (demeaned) regression gives the same slope as including the dummies.
  • Precision: Var⁡(β^1)=σ2/x~1⊤x~1\operatorname{Var}(\hat\beta_1) = \sigma^2/\tilde x_1^{\top}\tilde x_1, and x~1⊤x~1=(1−R12)∑(x1−xˉ1)2\tilde x_1^{\top}\tilde x_1 = (1 - R_1^2)\sum(x_1 - \bar x_1)^2, giving the VIF of Day 18.
  • Added-variable plot: scatter y~\tilde y against x~1\tilde x_1; the fitted slope is β^1\hat\beta_1.
  • Double machine learning (Day 90) replaces the linear projections with flexible machine-learning predictions: residualise yy and the treatment on XX, then regress residual on residual.

Part 1 · Warm-up

Exercise 1 · What M₂ does

In words, what is M2X1M_2X_1?

Show solution

The residuals from regressing each column of X1X_1 on X2X_2: the part of X1X_1 that X2X_2 cannot linearly explain.

Exercise 2 · The intercept is partialled out too

Show that the slope in y=β0+β1xy = \beta_0 + \beta_1x equals the slope from regressing demeaned yy on demeaned xx without an intercept.

Show solution

Take X2=1X_2 = \mathbf 1: M2M_2 is the centring matrix, so x~=x−xˉ\tilde x = x - \bar x and y~=y−yˉ\tilde y = y - \bar y. FWL gives β^1=x~⊤y~/x~⊤x~\hat\beta_1 = \tilde x^{\top}\tilde y/\tilde x^{\top}\tilde x, the Cov/Var formula.

Exercise 3 · Partialling out by hand

Four workers: x2=(0,0,1,1)x_2 = (0, 0, 1, 1) marks urban residence, x1=(1,2,3,5)x_1 = (1, 2, 3, 5) is years of post-secondary schooling and y=(2,3,6,9)y = (2, 3, 6, 9) is hourly earnings (in $10). Regress earnings on an intercept, schooling and the urban dummy. Compute the schooling coefficient β^1\hat\beta_1 by FWL.

Show solution

Step 1. Partialling out (1,x2)(1, x_2) means subtracting the rural and urban means (FWL with group dummies).

Step 2. Schooling means: rural (1+2)/2=1.5(1 + 2)/2 = 1.5, urban (3+5)/2=4(3 + 5)/2 = 4. So x~1=(−0.5,0.5,−1,1)\tilde x_1 = (-0.5, 0.5, -1, 1).

Step 3. Earnings means: rural 2.52.5, urban 7.57.5. So y~=(−0.5,0.5,−1.5,1.5)\tilde y = (-0.5, 0.5, -1.5, 1.5).

Step 4. Residual-on-residual slope:

β^1=x~1⊤y~x~1⊤x~1=0.25+0.25+1.5+1.50.25+0.25+1+1=3.52.5=1.4.\hat\beta_1 = \frac{\tilde x_1^{\top}\tilde y}{\tilde x_1^{\top}\tilde x_1} = \frac{0.25 + 0.25 + 1.5 + 1.5}{0.25 + 0.25 + 1 + 1} = \frac{3.5}{2.5} = 1.4.

Answer: each extra year of schooling goes with $14/hour more earnings within the same area.

Exercise 4 · The short regression

For the same data, compute the slope from regressing yy on x1x_1 alone (with an intercept).

Show solution

Step 1. Means: xˉ1=11/4=2.75\bar x_1 = 11/4 = 2.75 and yˉ=20/4=5\bar y = 20/4 = 5.

Step 2. Deviations: (−1.75,−0.75,0.25,2.25)(-1.75, -0.75, 0.25, 2.25) and (−3,−2,1,4)(-3, -2, 1, 4).

Step 3. Cross-product 5.25+1.5+0.25+9=165.25 + 1.5 + 0.25 + 9 = 16; squared deviations 3.0625+0.5625+0.0625+5.0625=8.753.0625 + 0.5625 + 0.0625 + 5.0625 = 8.75.

Step 4. Slope =16/8.75≈1.83= 16/8.75 \approx 1.83.

Answer: 1.83 without the urban control versus 1.40 with it: part of schooling's raw association with earnings is the urban premium.

Exercise 5 · Only one side needs residualising

Show that X~1⊤y=X~1⊤y~\tilde X_1^{\top}y = \tilde X_1^{\top}\tilde y.

Show solution

Step 1. X~1⊤y~=X1⊤M2⊤M2y=X1⊤M2y=X~1⊤y\tilde X_1^{\top}\tilde y = X_1^{\top}M_2^{\top}M_2y = X_1^{\top}M_2y = \tilde X_1^{\top}y, since M2M_2 is symmetric and idempotent.

Step 2. So regressing raw yy on X~1\tilde X_1 gives the same coefficient (but not the same residuals or SEs).

Part 2 · Proofs

Exercise 6 · Proving FWL

Prove the FWL theorem.

Show solution

Step 1. Write the full OLS fit: y=X1β^1+X2β^2+ey = X_1\hat\beta_1 + X_2\hat\beta_2 + e, with X1⊤e=0X_1^{\top}e = 0 and X2⊤e=0X_2^{\top}e = 0 (Day 20).

Step 2. Multiply through by M2M_2. M2X2=0M_2X_2 = 0, and M2e=eM_2e = e because ee is already orthogonal to X2X_2 (P2e=0P_2e = 0):

M2y=M2X1β^1+e,i.e.y~=X~1β^1+e.M_2y = M_2X_1\hat\beta_1 + e, \quad\text{i.e.}\quad \tilde y = \tilde X_1\hat\beta_1 + e.

Step 3. Pre-multiply by X~1⊤=X1⊤M2\tilde X_1^{\top} = X_1^{\top}M_2: X~1⊤y~=X~1⊤X~1β^1+X1⊤M2e\tilde X_1^{\top}\tilde y = \tilde X_1^{\top}\tilde X_1\hat\beta_1 + X_1^{\top}M_2e.

Step 4. X1⊤M2e=X1⊤e=0X_1^{\top}M_2e = X_1^{\top}e = 0 (Step 1).

Step 5. So β^1=(X~1⊤X~1)−1X~1⊤y~\hat\beta_1 = (\tilde X_1^{\top}\tilde X_1)^{-1}\tilde X_1^{\top}\tilde y. ■\blacksquare

Exercise 7 · Identical residuals

Show that the residuals from regressing y~\tilde y on X~1\tilde X_1 equal the full-regression residuals ee.

Show solution

From Exercise 6, y~−X~1β^1=e\tilde y - \tilde X_1\hat\beta_1 = e. So e⊤ee^{\top}e is the same, but the residualised regression should still use n−kn - k (all regressors), not n−k1n - k_1, when computing s2s^2.

Exercise 8 · Regression anatomy

For a single regressor x1x_1, show β^1=Cov⁡^(y,x~1)/Var⁡^(x~1)\hat\beta_1 = \widehat{\operatorname{Cov}}(y, \tilde x_1)/\widehat{\operatorname{Var}}(\tilde x_1).

Show solution

Step 1. By FWL and Exercise 5, β^1=x~1⊤y/x~1⊤x~1\hat\beta_1 = \tilde x_1^{\top}y/\tilde x_1^{\top}\tilde x_1.

Step 2. If X2X_2 includes an intercept, x~1\tilde x_1 has mean zero, so x~1⊤y=∑x~1i(yi−yˉ)\tilde x_1^{\top}y = \sum\tilde x_{1i}(y_i - \bar y), i.e. n−1n - 1 times the sample covariance; the denominator is n−1n - 1 times the variance.

Exercise 9 · Orthogonal controls change nothing

Prove that if X1⊤X2=0X_1^{\top}X_2 = 0, adding X2X_2 leaves β^1\hat\beta_1 unchanged.

Show solution

Step 1. X~1=X1−X2(X2⊤X2)−1X2⊤X1=X1\tilde X_1 = X_1 - X_2(X_2^{\top}X_2)^{-1}X_2^{\top}X_1 = X_1.

Step 2. FWL then gives (X1⊤X1)−1X1⊤y(X_1^{\top}X_1)^{-1}X_1^{\top}y, the short-regression coefficient.

Step 3. Randomised treatments are (approximately) orthogonal to pre-treatment covariates, which is why adding covariates changes the point estimate little while improving precision (Day 27).

Exercise 10 · Fixed effects are FWL

Show that including a full set of group dummies DD is equivalent to demeaning yy and X1X_1 by group.

Show solution

Step 1. With X2=DX_2 = D (no separate intercept), D⊤D=diag⁡(ng)D^{\top}D = \operatorname{diag}(n_g) and D⊤yD^{\top}y stacks group sums, so PDyP_Dy replaces each yiy_i by its group mean.

Step 2. Hence MDy=yi−yˉg(i)M_Dy = y_i - \bar y_{g(i)} and likewise for X1X_1; FWL gives the within estimator.

Part 3 · Applied

Exercise 11 · Interpreting the change

In Exercises 3–4 the coefficient on x1x_1 falls from 1.83 to 1.40 when x2x_2 is added. Explain using the OVB formula.

Show solution

Step 1. Urban workers have more schooling (group means 4 versus 1.5).

Step 2. Urban residence raises earnings at a given schooling level (the full regression gives a coefficient of 1.5 on the dummy, i.e. $15/hour).

Step 3. Omitting it loads part of the urban premium onto schooling: OVB = (effect of urban) × (slope of urban on schooling) > 0.

Answer: the drop from 1.83 to 1.40 removes that bias, provided urban residence is a confounder. If schooling caused people to move to cities (a mediator), controlling for it would instead remove part of schooling's effect.

Exercise 12 · Two-way fixed effects

In a balanced panel, show that partialling out unit and time dummies transforms xitx_{it} into xit−xˉi⋅−xˉ⋅t+xˉ⋅⋅x_{it} - \bar x_{i\cdot} - \bar x_{\cdot t} + \bar x_{\cdot\cdot}.

Show solution

Step 1. Projecting on unit dummies replaces xitx_{it} by the unit mean xˉi⋅\bar x_{i\cdot}; projecting on time dummies replaces it by the period mean xˉ⋅t\bar x_{\cdot t}.

Step 2. In a balanced panel the two sets of dummies are orthogonal once the overall mean is removed, so the projection onto both adds the two parts and subtracts the grand mean once (otherwise it would be counted twice): xˉi⋅+xˉ⋅t−xˉ⋅⋅\bar x_{i\cdot} + \bar x_{\cdot t} - \bar x_{\cdot\cdot}.

Step 3. The residual is xit−xˉi⋅−xˉ⋅t+xˉ⋅⋅x_{it} - \bar x_{i\cdot} - \bar x_{\cdot t} + \bar x_{\cdot\cdot}.

Answer: two-way FE = double demeaning. With unbalanced panels or staggered treatment this simple form fails, part of the story of Day 70.

Exercise 13 · OLS weights heterogeneous effects by treatment variance

Two strata of equal size have treatment effects 1 and 3. The treatment probability is 0.5 in stratum A and 0.1 in stratum B. Regressing yy on TT and stratum dummies gives approximately ∑swsτs\sum_sw_s\tau_s with ws∝P(s) ps(1−ps)w_s \propto P(s)\,p_s(1 - p_s). Compute it and compare with the ATE.

Show solution

Step 1. Conditional treatment variances: stratum A 0.5×0.5=0.250.5 \times 0.5 = 0.25; stratum B 0.1×0.9=0.090.1 \times 0.9 = 0.09.

Step 2. Weights (equal stratum sizes): wA=0.25/(0.25+0.09)≈0.735w_A = 0.25/(0.25 + 0.09) \approx 0.735, wB≈0.265w_B \approx 0.265.

Step 3. Regression estimate: 0.735(1)+0.265(3)≈1.530.735(1) + 0.265(3) \approx 1.53.

Step 4. ATE: 0.5(1)+0.5(3)=20.5(1) + 0.5(3) = 2.

Answer: 1.53 versus 2. With heterogeneous effects, OLS with controls estimates a variance-weighted average (Angrist, 1998), favouring strata where treatment is closest to 50/50.

Exercise 14 · Partialling out with machine learning: 401(k) eligibility

To estimate the effect of 401(k) eligibility DD on household savings yy, controlling flexibly for income, age and education XX, double ML assumes y=θD+g(X)+εy = \theta D + g(X) + \varepsilon and regresses y−E^[y∣X]y - \hat E[y \mid X] on D−E^[D∣X]D - \hat E[D \mid X], with both predictions from machine learning. Why is this FWL?

Show solution

Step 1. FWL removes from yy and DD everything linearly predictable from the controls, then regresses residual on residual.

Step 2. Here the linear projection is replaced by the conditional expectation E[⋅∣X]E[\cdot \mid X], estimated flexibly (e.g. random forests), so g(X)g(X) need not be linear.

Step 3. The residualised DD is the variation in eligibility not explained by income, age and education; its relation to residualised savings identifies θ\theta.

Answer: it is FWL with machine-learning projections (Robinson, 1988; Day 90).

Exercise 15 · An added-variable plot

Why can an added-variable plot reveal an influential observation that a raw scatter of yy against x1x_1 hides?

Show solution

Step 1. By FWL, β^1\hat\beta_1 depends only on the residualised data (x~1,y~)(\tilde x_1, \tilde y).

Step 2. A unit can look ordinary in raw x1x_1 yet be unusual given X2X_2 (a large x~1\tilde x_1), which gives it high leverage on β^1\hat\beta_1.

Step 3. A raw scatter of yy on x1x_1 hides this; the added-variable plot of y~\tilde y against x~1\tilde x_1 shows it.

Answer: the added-variable plot shows the data the coefficient actually uses (see the FWL simulator).

Verify in Python

import numpy as np

x2 = np.array([0, 0, 1, 1.]); x1 = np.array([1, 2, 3, 5.]); y = np.array([2, 3, 6, 9.])
X = np.column_stack([np.ones(4), x1, x2])
b_full = np.linalg.lstsq(X, y, rcond=None)[0]
X2 = np.column_stack([np.ones(4), x2]); M2 = np.eye(4) - X2 @ np.linalg.pinv(X2)
x1t, yt = M2 @ x1, M2 @ y
print("full regression:", b_full.round(4))
print("FWL slope:", round(x1t @ yt / (x1t @ x1t), 4), " raw y on residualised x1:", round(x1t @ y / (x1t @ x1t), 4))
print("short regression slope:", round(np.polyfit(x1, y, 1)[0], 4))
e_full = y - X @ b_full; e_fwl = yt - x1t * (x1t @ yt / (x1t @ x1t))
print("same residuals:", np.allclose(e_full, e_fwl))

# Variance-weighted effects (Exercise 13)
rng = np.random.default_rng(28); n = 400_000
s = rng.random(n) < 0.5                                       # stratum B
p = np.where(s, 0.1, 0.5); T = rng.random(n) < p
Y = np.where(s, 5, 0) + np.where(s, 3, 1) * T + rng.normal(size=n)
Z = np.column_stack([np.ones(n), T, s])
print("regression estimate:", np.linalg.lstsq(Z, Y, rcond=None)[0][1].round(3), " ATE: 2")

Key takeaways

  • A multiple-regression coefficient is a simple regression of residualised yy on residualised xx.
  • Fixed effects, demeaning and "controlling for" are all FWL.
  • With heterogeneous effects, OLS with controls weights strata by the conditional variance of treatment, not by population share.

Simulation Lab: Partialling Out (FWL)

Open full screen →

A confounded scatter beside its added-variable plot: the residual-on-residual slope equals the multiple-regression coefficient.

RAW DATA
-10-50510-4-2024x₁y
AFTER PARTIALLING OUT x₂
-4-202-202x̃₁ = x₁ residualised on x₂ỹ
darker = larger x₂naive slopeFWL slope = multiple-regression β̂₁
naive slope
2.084
OVB ≈ β₂γ/(γ²+1) = 1.00
FWL slope
1.000
truth β₁ = 1
share of x₁ variance left
0.41
1 − R²₁ (sets the SE)

Left: raw y against x₁; the naive slope absorbs the effect of the confounder x₂. Right: the added-variable plot, residuals of y on x₂ against residuals of x₁ on x₂. Its slope is exactly the multiple-regression coefficient (true value 1).

Day 27Day 29