Regression Discontinuity Design
Statistics
Exploiting Threshold Rules for Causal Estimation
Regression discontinuity exploits cutoff-based treatment assignment. Units just above and just below the threshold are nearly identical, so the jump in outcomes at the cutoff reveals the causal effect.
-
Education β Estimate scholarship effects using GPA eligibility cutoffs
-
Policy Evaluation β Assess income-based benefit thresholds on employment outcomes
-
Healthcare β Evaluate age-based screening programs at eligibility boundaries
At the cutoff, treatment assignment is as good as random β the discontinuity is the causal effect.
Regression discontinuity (RD) exploits a threshold rule that assigns treatment based on whether a running variable crosses a cutoff. Units just above and just below the cutoff are assumed to be comparable.
Sharp RD
Treatment is deterministically assigned: everyone above the cutoff is treated, everyone below is not.
Fuzzy RD
Treatment assignment is probabilistic but has a jump at the cutoff. This is analyzed using IV-like local estimation.
Key Assumption
| Violation | Consequence |
|-----------|------------|
| Manipulation of running variable | Bias β people sort around cutoff |
| Discrete running variable | Binning required; may introduce bias |
| Covariate imbalance at cutoff | Suggests manipulation or confounding |
Local Estimation
In practice, estimate local polynomial regressions on each side of the cutoff.
Bandwidth Selection
The bandwidth determines the window around the cutoff used for estimation.
Covariate Balance Check
Before interpreting results, check that baseline covariates are continuous at the cutoff:
If covariates show discontinuities at the cutoff, the identifying assumption may be violated.
McCrary Density Test
Tests for manipulation of the running variable at the cutoff. If people can precisely control their running variable, they may sort around the cutoff.
Python Implementation
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from rdrobust import rdrobust
from rdrobust import rddensity
np.random.seed(42)
# Simulate sharp RD data
n = 1000
X = np.random.uniform(-1, 1, n) # Running variable
T = (X >= 0).astype(int) # Treatment
Y = 2.0 * T + 3.0 * X + np.random.randn(n) * 0.5
# Main RD estimate
result = rdrobust(Y, X, c=0)
print("RD Estimate:")
print(result)
# Covariate balance check
Z = np.random.randn(n) # Covariate
print("\nCovariate balance at cutoff:")
rd_z = rdrobust(Z, X, c=0)
print(rd_z)
# McCrary density test
density = rddensity(X, c=0)
print("\nMcCrary density test:")
print(density)
# Plot
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Outcome
axes[0].scatter(X, Y, alpha=0.3, s=10)
axes[0].axvline(x=0, color='red', linestyle='--')
axes[0].set_title('Outcome vs Running Variable')
axes[0].set_xlabel('Running Variable')
axes[0].set_ylabel('Outcome')
# Density
axes[1].hist(X, bins=50, edgecolor='black')
axes[1].axvline(x=0, color='red', linestyle='--')
axes[1].set_title('Density of Running Variable')
plt.tight_layout()
plt.show()
Worked Example
Key Takeaways
Related Topics
-
See Instrumental Variables for another quasi-experimental method
-
See Difference-in-Differences for policy evaluation
-
See Causal Inference for the potential outcomes framework