Answer

Can you test the exclusion restriction of an instrumental variable?

Inspired by a question on Cross Validated ·

causal inferenceregression

The short answer

Not directly. With one instrument for one treatment, the IV estimate is chosen so that the residuals are exactly uncorrelated with the instrument, so a valid and an invalid instrument look the same in the data. You defend the exclusion restriction with design arguments, placebo tests in groups where the instrument cannot move the treatment, and a sensitivity analysis that shows how the estimate changes under a plausible direct effect.

The short answer

An instrumental variable (IV) analysis rests on two conditions. Relevance: the instrument must shift the treatment, which you can check with the first-stage regression. Exclusion: the instrument must affect the outcome only through the treatment, and must not share unmeasured causes with the outcome. The second condition is about paths you cannot observe, and in the common case of a single instrument and a single endogenous treatment (an exactly identified model) no statistical test can confirm it.

That does not leave you empty-handed. You can make the restriction more believable with subject-matter reasoning and falsification checks, and you can show readers how much the conclusion depends on it. The rest of this page explains why the data are silent, then demonstrates a placebo test and a sensitivity analysis on simulated data.

Why the data cannot check it

With one instrument Z, treatment X and outcome Y, the IV estimate is the ratio cov(Z, Y) / cov(Z, X): the effect of the instrument on the outcome, scaled by its effect on the treatment. That ratio is exactly the value that makes the residuals uncorrelated with Z. So the natural check, "are the residuals related to the instrument?", always returns zero, whether or not the instrument has a direct effect on Y. Any direct effect is silently absorbed into the estimate, which is biased by roughly the direct effect divided by the first-stage coefficient. A weak first stage therefore magnifies even a small violation.

Overidentification tests (Sargan or Hansen J) need more instruments than endogenous variables, so they are unavailable here. Even when you can run them, they only test whether the instruments agree with each other. If every instrument is invalid in the same direction, they agree and the test passes. A control-function approach does not escape the problem either: in a linear model with one instrument it gives the same estimate as two-stage least squares and relies on the same exclusion restriction.

See it in R and Python

The simulation creates 10,000 units with an unobserved confounder u, a binary instrument z and a treatment x whose true effect on y is 1. Half the units are eligible: only for them can the instrument change the treatment. Two outcomes are built from the same draws: in y_valid the instrument works only through x; in y_invalid it also raises y directly by 0.3. The code (1) compares OLS with IV, (2) runs a placebo test among ineligible units, and (3) runs a sensitivity analysis over assumed direct effects.

R

set.seed(677522)
n <- 10000
eligible <- rbinom(n, 1, 0.5)           # z can shift x only for eligible units
z <- rbinom(n, 1, 0.5)                  # binary instrument
u <- rnorm(n)                           # unobserved confounder
x <- 0.6 * z * eligible + u + rnorm(n)  # treatment; its true effect on y is 1
y_valid   <- x + u + rnorm(n)           # exclusion restriction holds
y_invalid <- y_valid + 0.3 * z          # same data, but z also affects y by 0.3

# Just-identified IV (2SLS) estimate with a conventional 95% CI
iv_fit <- function(y, x, z) {
  b  <- cov(z, y) / cov(z, x)
  e  <- (y - mean(y)) - b * (x - mean(x))   # structural residuals
  se <- sqrt(sum(e^2) / (length(y) - 2) * sum((z - mean(z))^2)) /
        abs(sum((z - mean(z)) * (x - mean(x))))
  c(est = b, lo = b - 1.96 * se, hi = b + 1.96 * se, cor_resid_z = cor(e, z))
}

# 1. OLS is confounded; IV recovers 1 only when the exclusion holds
coef(lm(y_valid ~ x))["x"]
round(rbind(valid = iv_fit(y_valid, x, z), invalid = iv_fit(y_invalid, x, z)), 3)

# 2. Zero-first-stage placebo: among ineligible units z cannot move x,
#    so any association between z and y must come through another path
in0 <- eligible == 0
sum(in0)
round(summary(lm(x[in0] ~ z[in0]))$coefficients[2, c(1, 2, 4)], 3)
round(rbind(valid   = summary(lm(y_valid[in0] ~ z[in0]))$coefficients[2, c(1, 2, 4)],
            invalid = summary(lm(y_invalid[in0] ~ z[in0]))$coefficients[2, c(1, 2, 4)]), 3)

# 3. Sensitivity: assume a direct effect gamma of z on y, remove it, re-estimate
gammas <- seq(0, 0.4, by = 0.1)
round(t(sapply(gammas, function(g) c(gamma = g, iv_fit(y_invalid - g * z, x, z)[1:3]))), 3)

Python

import numpy as np
from scipy import stats

rng = np.random.default_rng(677522)
n = 10_000
eligible = rng.binomial(1, 0.5, n)            # z can shift x only for eligible units
z = rng.binomial(1, 0.5, n)                   # binary instrument
u = rng.normal(size=n)                        # unobserved confounder
x = 0.6 * z * eligible + u + rng.normal(size=n)  # treatment; true effect on y is 1
y_valid = x + u + rng.normal(size=n)          # exclusion restriction holds
y_invalid = y_valid + 0.3 * z                 # same data, but z also affects y by 0.3

# Just-identified IV (2SLS) estimate with a conventional 95% CI
def iv_fit(y, x, z):
    zc, xc, yc = z - z.mean(), x - x.mean(), y - y.mean()
    b = np.sum(zc * yc) / np.sum(zc * xc)
    e = yc - b * xc                           # structural residuals
    se = np.sqrt(np.sum(e**2) / (len(y) - 2) * np.sum(zc**2)) / abs(np.sum(zc * xc))
    return np.round([b, b - 1.96 * se, b + 1.96 * se, np.corrcoef(e, z)[0, 1]], 3)

# 1. OLS is confounded; IV recovers 1 only when the exclusion holds
print("OLS slope:", round(stats.linregress(x, y_valid).slope, 3))
print("est, lo, hi, cor_resid_z")
print("valid  ", iv_fit(y_valid, x, z))
print("invalid", iv_fit(y_invalid, x, z))

# 2. Zero-first-stage placebo among ineligible units
in0 = eligible == 0
print("ineligible n:", in0.sum())
for name, v in [("first stage", x), ("valid", y_valid), ("invalid", y_invalid)]:
    r = stats.linregress(z[in0], v[in0])
    print(name, round(r.slope, 3), round(r.stderr, 3), round(r.pvalue, 3))

# 3. Sensitivity: assume a direct effect gamma of z on y, remove it, re-estimate
for g in np.arange(0, 0.41, 0.1):
    print("gamma", round(g, 1), iv_fit(y_invalid - g * z, x, z)[:3])
       x 
1.474639 
          est    lo    hi cor_resid_z
valid   0.892 0.687 1.097           0
invalid 1.943 1.751 2.136           0
[1] 5022
  Estimate Std. Error   Pr(>|t|) 
     0.017      0.039      0.669 
        Estimate Std. Error Pr(>|t|)
valid      0.062      0.068    0.362
invalid    0.362      0.068    0.000
     gamma   est    lo    hi
[1,]   0.0 1.943 1.751 2.136
[2,]   0.1 1.593 1.422 1.764
[3,]   0.2 1.242 1.066 1.418
[4,]   0.3 0.892 0.687 1.097
[5,]   0.4 0.541 0.292 0.791

All figures come from one seeded run of the R code. Python's random numbers differ from R's, so its figures differ (a single IV estimate is noisy: 1.16 instead of 0.89 for the valid instrument, with a 95% CI that still contains 1), but they show the same pattern: residual correlations of zero in both cases, a placebo effect only for the invalid instrument, and a steady drop in the estimate as the assumed direct effect grows.

What the numbers show

What to do instead of a test

For background on why confounding needs a design solution in the first place, see what it means to control for a variable. Other quasi-experimental designs are compared in interrupted time series vs event study vs difference-in-differences, and the DASS blog covers planning and reporting analyses more broadly.

How to report an instrumental variable estimate in APA style (7th edition)

Report the IV estimate with its confidence interval, describe the first stage, and state the exclusion restriction as an assumption together with the checks you ran. Using the valid-instrument results above:

"Using z as an instrument for x in a two-stage least squares model, the estimated effect of x on y was b = 0.89, 95% CI [0.69, 1.10]. The estimate assumes that z affects y only through x. Consistent with this assumption, among the 5,022 units for whom z could not affect x, z was not associated with y (b = 0.06, SE = 0.07, p = .362)."

Add the sensitivity analysis in a sentence or a table: "Allowing z a direct effect on y of up to x.xx reduced the estimate to x.xx, 95% CI [LL, UL]." Give the first-stage coefficient and its F statistic in the method section or a table, and say which software and estimator you used.

Related tools and guides

More answered questions

Working with your own data?

General answers only go so far. Send us your situation and we'll reply by email within two business days.

Ask your question

Written by AskStats with AI assistance. This is general information, not advice for your specific data or study. When the results matter, check your approach with a qualified statistician.