Accelerated Foundation in Applied Causal InferenceFor Economists · 15 modules · 45 lessons

9.1 Instruments, the Wald Estimator and Compliers

about 2 hoursEducation economics: distance to college and the exclusion restriction
Lesson 9.1 · Module 9
Instruments, the Wald Estimator and Compliers
Video coming soon. The written lesson and the Colab notebook are ready below.
Colab notebook (.ipynb)
By the end of this lesson you can
  • ✓State the three IV conditions: relevance, independence and the exclusion restriction
  • ✓Compute the Wald estimator as reduced form divided by first stage
  • ✓Classify units as compliers, always-takers, never-takers and defiers
  • ✓Show by simulation that IV estimates the LATE for compliers, not the ATE
Lesson plan (120 minutes)
Written lesson

When nothing observable closes the backdoor

In Module 5 you met a DAG with an unobserved confounder and no valid adjustment set. Selection on observables, matching and DML are then all biased. An instrumental variable Z offers a way out: a source of variation in the treatment D that is as good as random and affects Y only through D.

Text
Z ──► D ──► Y
      ▲     ▲
      └─ U ─┘      (U unobserved)
ConditionMeaningTestable?
RelevanceZ shifts DYes: first-stage F-statistic
IndependenceZ is as good as randomly assignedPartly: balance on observables
ExclusionZ affects Y only through DNo: defend it with institutional knowledge
Monotonicity (for LATE)Z never pushes anyone away from DNo: argue it
Python · Colab cell
!pip install -q numpy pandas statsmodels linearmodels matplotlib
import numpy as np, pandas as pd
import statsmodels.formula.api as smf
rng = np.random.default_rng(101)

An encouragement design

A city randomly mails half its residents a voucher encouraging them to enrol in an adult-education course. Not everyone who gets the letter enrols, and some enrol without one. Motivation (unobserved) drives both enrolment and earnings. The letter itself cannot raise earnings, except by getting people into the course.

Python · Colab cell
N = 20_000
motivation = rng.normal(size=N)
z = rng.binomial(1, 0.5, N)                                         # random encouragement letter

# Each person's type, determined by motivation (never defiers here: monotonicity holds)
u = rng.uniform(size=N)
high = motivation > 0
always = u < np.where(high, 0.30, 0.10)
never = (~always) & (u > np.where(high, 0.80, 0.45))
complier = ~(always | never)
d = np.where(always, 1, np.where(never, 0, z))                       # compliers follow the letter

effect = np.where(complier, 2_000, np.where(always, 4_000, 3_000))   # heterogeneous effects by type
y = 30_000 + 5_000 * motivation + effect * d + rng.normal(0, 4_000, N)
df = pd.DataFrame({"z": z, "d": d, "y": y, "type": np.select([always, never, complier], ["always-taker", "never-taker", "complier"], default="")})

print(df.type.value_counts(normalize=True).round(3))
print(f"Naive difference (enrolled vs not): {df.y[df.d == 1].mean() - df.y[df.d == 0].mean():,.0f}")
print(f"True ATE: {effect.mean():,.0f} | true LATE (compliers): 2,000")

The Wald estimator

With a binary instrument the IV estimate is a ratio:

IV = (effect of Z on Y) / (effect of Z on D) = reduced form / first stage

The reduced form (also called the intention-to-treat effect) is diluted because only some people respond to the letter. Dividing by the share who respond scales it back up to the effect per person treated because of the letter.

Python · Colab cell
first_stage = df.d[df.z == 1].mean() - df.d[df.z == 0].mean()
reduced_form = df.y[df.z == 1].mean() - df.y[df.z == 0].mean()
wald = reduced_form / first_stage
print(f"First stage (enrolment gap):   {first_stage:.3f}")
print(f"Reduced form / ITT (earnings):  {reduced_form:,.0f}")
print(f"Wald IV estimate:               {wald:,.0f}")

# The same number from regressions
fs = smf.ols("d ~ z", data=df).fit().params["z"]
rf = smf.ols("y ~ z", data=df).fit().params["z"]
print(f"Ratio of regression slopes:     {rf / fs:,.0f}")

Compliers and the LATE

Imbens and Angrist showed that, under monotonicity, the Wald ratio identifies the local average treatment effect (LATE): the average effect for compliers, the people whose enrolment was changed by the letter.

  • Always-takers enrol regardless; the letter changes nothing for them.
  • Never-takers never enrol; the letter changes nothing for them either.
  • Defiers would do the opposite of the letter; monotonicity rules them out.

The IV estimate is close to 2,000 (the compliers' effect) and not to the ATE, because always-takers (who gain 4,000) and never-takers (who would gain 3,000) never move with the instrument.

The why

LATE is not a flaw; it is a feature to report precisely. "The course raises earnings by about $2,000 for people who enrol only when encouraged." Whether that generalises to always-takers, or to a mandatory programme, is a separate argument. Huber's Causal Analysis (Ch. 6) shows how to describe compliers' characteristics so the reader can judge.

You cannot tell who is a complier, but you can describe them. For example, the share of compliers is the first stage, and the mean of any baseline characteristic among compliers can be estimated with the same Wald trick applied to d × characteristic.

Python · Colab cell
df["motivated"] = (motivation > 0).astype(int)
complier_share_motivated = (
    (df.d * df.motivated)[df.z == 1].mean() - (df.d * df.motivated)[df.z == 0].mean()
) / first_stage
print(f"Share motivated: compliers ≈ {complier_share_motivated:.2f} vs everyone {df.motivated.mean():.2f}")
Exercise

Introduce 5% defiers (people who enrol only if they do not get the letter) with an effect of −3,000. Recompute the Wald estimate. Why does it no longer equal the compliers' effect?

Show a solution
Python · Colab cell
defier = (~always) & (~never) & (rng.uniform(size=N) < 0.08)
complier2 = complier & ~defier
d2 = np.where(always, 1, np.where(never, 0, np.where(defier, 1 - z, z)))
eff2 = np.where(defier, -3_000, effect)
y2 = 30_000 + 5_000 * motivation + eff2 * d2 + rng.normal(0, 4_000, N)
wald2 = (y2[z == 1].mean() - y2[z == 0].mean()) / (d2[z == 1].mean() - d2[z == 0].mean())
print(f"Wald with defiers: {wald2:,.0f}")
# Defiers' effects enter with the opposite sign and the ratio is no longer a weighted average of
# anyone's effect. Monotonicity is essential for the LATE interpretation.

Checklist and recap

  • I can state relevance, independence, exclusion and monotonicity, and say which are testable.
  • I can compute the Wald estimator from a first stage and a reduced form.
  • I can explain why IV identifies a LATE and describe compliers.