| Time | Block |
|---|---|
| 0:00 – 0:20 | 71.1 Correlation, prediction and causation in macroeconomics |
| 0:20 – 0:48 | 71.2 Potential outcomes: the language of causal effects |
| 0:48 – 0:55 | Break |
| 0:55 – 1:20 | 71.3 Identification: the assumptions that make effects estimable |
| 1:20 – 1:45 | 71.4 Simulation lab: watching confounding happen |
| 1:45 – 1:53 | Break |
| 1:53 – 2:10 | 71.5 Experiments, natural experiments and observational macro data |
| 2:10 – 2:30 | 71.6 Mini-project: breaking and fixing the simulation |
71.1 Correlation, Prediction and Causation in Macroeconomics (20 min)
Three different questions about the same data:
| Question | Type | Answered by |
|---|---|---|
| Are high rates and strong housing seen together? | association | a correlation |
| Given today's data, what will housing starts be next year? | prediction | Module 7's models |
| If the Bank raised rates by 25bp, what would housing starts be? | causation | Module 8 |
A good predictive model can be a terrible causal model. If rate hikes reliably occur in booms, a forecasting model should use "rates are rising" as a signal of a strong economy, and may predict more housing starts after a hike. That's a correct forecast and a wrong policy conclusion.
Why macro causality is especially hard
| Difficulty | In our question |
|---|---|
| Policy responds to the economy | the Bank sets rates because of inflation, growth and housing (a reaction function) |
| Expectations and anticipation | markets and builders anticipate hikes (forward guidance), so effects can appear before the decision |
| Few independent observations | one country, a few hundred months, a handful of tightening cycles |
| Everything moves together | business-cycle variables are highly correlated |
| Lags | monetary policy affects housing over months to years |
Macroeconomists have developed specific strategies for these problems, for example narrative identification of policy shocks (Romer & Romer, 2004), high-frequency identification around announcements (e.g. Gürkaynak, Sack & Swanson, 2005), and local projections for dynamic effects (Jordà, 2005). This module uses a selection-on-observables design: control for the information the central bank reacted to, and use machine learning to do so flexibly (DoubleML). You'll also learn exactly what that design assumes, and therefore where it can fail.
71.2 Potential Outcomes: The Language of Causal Effects (28 min)
The potential outcomes framework (associated with Neyman and Rubin) defines causal effects precisely.
For each unit (here, a month t) and a binary treatment D (e.g. "the Bank hiked this month"):
- Y(1) = housing starts that would occur if the Bank hiked;
- Y(0) = housing starts that would occur if it didn't.
The causal effect for that month is Y(1) − Y(0).
The fundamental problem of causal inference
In any month we observe only one of the two: the one corresponding to what the Bank actually did. The other is a counterfactual, never observed. Causal inference is therefore always about estimating something we can't see, using assumptions to fill the gap.
| Month | Hiked? | Y(1) | Y(0) | Effect |
|---|---|---|---|---|
| 2022-07 | yes | observed | ? | ? |
| 2019-07 | no | ? | observed | ? |
Average effects
Since individual effects are unobservable, we estimate averages:
| Estimand | Definition | Question |
|---|---|---|
| ATE | E[Y(1) − Y(0)] | average effect over all months |
| ATT | E[Y(1) − Y(0) | D = 1] | average effect in months when the Bank actually hiked |
| CATE | E[Y(1) − Y(0) | X = x] | effect in particular conditions (e.g. when inflation is high) |
Continuous treatments
Policy rates change by different amounts (25bp, 50bp, 100bp). With a continuous treatment D (the change in the overnight rate, in percentage points), the natural estimand is the effect per unit: how much housing starts change per 1pp (or per 25bp) rate increase, holding the economic situation fixed. That's exactly what the partially linear model in DoubleML estimates (Days 74–77):
Y = θ · D + g(X) + ε
where θ is the causal effect per unit of D, and g(X) captures everything the economic situation X does to Y, in any flexible form.
Defining the outcome over horizons
Monetary policy works with lags, so the outcome must specify a horizon h:
Y_{t,h} = 100 × [log(housing starts_{t+h}) − log(housing starts_{t−1})]
the percentage change in housing starts from just before the decision to h months after it. Estimating θ for h = 3, 6, 12 and 24 months traces out a dynamic response, the same idea as local projections (Jordà, 2005).
71.3 Identification: The Assumptions That Make Effects Estimable (25 min)
Identification means: under which assumptions can the causal effect be computed from the data we have, given infinite data? Estimation (how to compute it well from finite data) comes later.
For a selection-on-observables design, the key assumptions are:
| Assumption | Statement | In our question |
|---|---|---|
| Unconfoundedness (conditional ignorability) | given controls X, treatment is "as good as random": (Y(1), Y(0)) ⫫ D | X | once we control for what the Bank reacted to (inflation, growth, labour market, housing, global rates), remaining variation in rate changes is unrelated to what housing would otherwise have done |
| Overlap (positivity) | every combination of X has some probability of each treatment level | we observe hikes and non-hikes across a range of economic conditions |
| SUTVA | one month's treatment doesn't change another month's potential outcomes, and the treatment is well defined | strained in macro: policy has persistent effects across months; we address this by defining dynamic outcomes and controlling for past policy |
| No anticipation | outcomes before the treatment aren't affected by it | strained by forward guidance; tested with placebo outcomes (Day 73.5) |
Unconfoundedness is untestable. No statistic can prove that you controlled for every common cause. You can only make it plausible (with economic reasoning and a DAG, Day 73), probe it (placebo and negative-control tests), and quantify how strong an unobserved confounder would need to be to overturn your result (sensitivity analysis, Days 78–80). Machine learning helps you control for observed confounders flexibly. It does nothing about unobserved ones.
Where machine learning fits
Classical regression with controls assumes g(X) is linear. Reaction functions and housing dynamics are not obviously linear: the Bank may respond more strongly when inflation is far from target, and housing may react differently in booms and busts. DoubleML (Chernozhukov et al., 2018) uses flexible ML models to learn g(X) and the reaction function m(X) = E[D | X], while still delivering a valid estimate and standard error for θ. That combination is why it suits this question.
71.4 Simulation Lab: Watching Confounding Happen (25 min)
The best way to understand bias is to build a world where you know the true effect, then see which methods recover it.
Create causal/simulate.py:
"""Synthetic monthly economies where the true causal effect is known."""
import numpy as np
import pandas as pd
def confounded_world(T: int = 600, true_effect: float = -5.0, cycle_on_rates: float = 0.5,
cycle_on_housing: float = 10.0, seed: int = 42) -> pd.DataFrame:
"""A business cycle drives both rate changes (policy reaction) and housing.
D = rate change (pp), Y = housing-starts growth (%), C = cycle (confounder).
True causal effect of D on Y: `true_effect` per 1pp.
"""
rng = np.random.default_rng(seed)
c = np.zeros(T)
for t in range(1, T):
c[t] = 0.9 * c[t - 1] + rng.normal(0, 1) # persistent business cycle
d = cycle_on_rates * c + rng.normal(0, 0.5, T) # the Bank reacts to the cycle
y = cycle_on_housing * c + true_effect * d + rng.normal(0, 3, T)
return pd.DataFrame({"cycle": c, "rate_change": d, "housing_growth": y})
Now estimate the effect two ways:
import statsmodels.formula.api as smf
from causal.simulate import confounded_world
df = confounded_world()
naive = smf.ols("housing_growth ~ rate_change", data=df).fit()
adjusted = smf.ols("housing_growth ~ rate_change + cycle", data=df).fit()
print(f"true effect: -5.00")
print(f"naive estimate: {naive.params['rate_change']:+.2f} (se {naive.bse['rate_change']:.2f})")
print(f"adjusted estimate:{adjusted.params['rate_change']:+.2f} (se {adjusted.bse['rate_change']:.2f})")
The naive estimate is typically positive and large: the regression "finds" that rate hikes raise housing starts. Controlling for the cycle recovers approximately −5.
Why: the omitted-variable bias formula
From econometrics: if the true model is Y = θD + βC + ε, and you omit C, the naive coefficient converges to
θ_naive = θ + β · δ, where δ = Cov(D, C) / Var(D)
the coefficient from regressing the omitted C on D. Here θ = −5, β = 10 (the cycle raises housing), and δ > 0 (the Bank hikes in booms), so the bias β·δ is large and positive, enough to flip the sign.
Verify the formula numerically:
delta = smf.ols("cycle ~ rate_change", data=df).fit().params["rate_change"]
print(f"θ + β·δ = -5 + 10 × {delta:.2f} = {-5 + 10 * delta:+.2f} vs naive {naive.params['rate_change']:+.2f}")
The economist's sign test: before estimating anything, reason about the direction of likely bias. Common causes that raise both rate hikes and housing activity bias the naive estimate upwards (towards zero or positive). If a naive estimate shows that hikes raise housing, suspect confounding before believing it.
71.5 Experiments, Natural Experiments and Observational Macro Data (18 min)
| Design | How treatment is assigned | Unconfoundedness | Feasible for our question? |
|---|---|---|---|
| Randomised experiment | by a coin flip | guaranteed by design | no: central banks don't randomise policy |
| Natural experiment | by an "as-if random" event (e.g. an unexpected policy surprise) | plausible if the event is truly unrelated to outcomes | partly: monetary policy surprises (narrative or market-based) |
| Instrumental variables | via a variable affecting treatment only | requires a valid instrument | hard to find in macro |
| Selection on observables | by the decision-maker, based on observed information | assumed, given good controls | yes: this module's main design, with ML controls |
Selection on observables is plausible here for a specific reason: the Bank of Canada publishes its reasoning. Its decisions respond to a fairly well-understood set of indicators (inflation and its outlook, the output gap, the labour market, global conditions, housing and financial stability). If we control for that information set, much of the remaining variation in rate changes reflects timing, judgement and surprises, which is closer to "as good as random". This is the logic behind estimating policy shocks as residuals from a reaction function, an idea DoubleML formalises (Day 74).
The design's main weaknesses are worth stating up front:
- variables the Bank saw but we don't (internal forecasts, market intelligence) are unobserved confounders;
- anticipation through forward guidance blurs the timing of the treatment;
- few tightening cycles limit overlap and precision.
Days 72–73 and 78–80 address each one explicitly.
71.6 Mini-Project: Breaking and Fixing the Simulation (20 min)
Extend causal/simulate.py and answer each question in notebooks/day71.ipynb, with numbers.
- Weak vs strong reaction. Set
cycle_on_ratesto 0.1, then 2.0. How does the naive bias change? Explain with δ in the OVB formula. - A noisy confounder. Suppose we only observe
cycle_obs = cycle + noise(measurement error, like a GDP-based output-gap estimate). Control forcycle_obsinstead ofcycle. Is the bias fully removed? Why not? - Non-linear reaction. Make the Bank react only to strong booms:
d = 0.8 * np.maximum(c, 0) + noise, and make housing respond non-linearly:y = 3 * c**2 + .... Does a linear control forcyclestill recover −5? What does this suggest about flexible ML controls? - Sample size. With
T = 150(about 12 years of monthly data), run 200 simulations with different seeds and plot the distribution of the adjusted estimate. How precise can we hope to be with real Canadian data?
Answers in brief
- δ grows with the strength of the reaction, so the bias grows. With a weak reaction, naive is closer to −5.
- No: measurement error in a confounder leaves residual confounding. Controlling for a noisy proxy removes only part of the bias. This is an important reason to use several indicators of the cycle.
- A linear control under-fits the non-linear confounding, so bias remains. Adding
cycle**2(or using a flexible model of g(X)) fixes it. That's the motivation for DoubleML's ML nuisance functions. - The estimates spread widely. With roughly a decade of monthly data and modest variation in rate changes, confidence intervals will be wide. Expect that with real data too, and report it honestly.
Day 71 checklist
- I can distinguish association, prediction and causation, and explain why good forecasts can mislead policy
- I can define potential outcomes, ATE/ATT/CATE and a per-unit effect for a continuous treatment
- I can state unconfoundedness, overlap, SUTVA and no anticipation, and why unconfoundedness is untestable
- I built a simulation where confounding flips the sign of a naive estimate, and verified the OVB formula
- I can explain why measurement error and non-linearity leave residual bias
git add causal notebooks forecasting/requirements-ml.txt
git commit -m "Day 71: potential outcomes, identification, confounding simulations"
git push