viewof bw = Inputs.range([1, 12], {value: 4, step: 0.25, label: "Bandwidth h (% poverty)"})
viewof true_tau = Inputs.range([-0.2, 0.6], {value: 0.20, step: 0.02, label: "True ATT at cutoff"})
viewof noise_rdd = Inputs.range([0.05, 0.40], {value: 0.20, step: 0.01, label: "Noise SD"})
viewof n_pts = Inputs.range([200, 3000], {value: 1500, step: 100, label: "Sample size (tracts)"})Chapter 5. Regression Discontinuity Design
Inference Lab · Applied causal inference for the spatial social sciences Dr. Ian Helfrich
Interactive: RDD bandwidth picker
A sharp RDD on a simulated NMTC-style poverty-rate threshold at 20%. The DGP has a true treatment effect of 0.20 in log-investment-per-capita. The plot shows the simulated tracts, the local-linear fit on each side of the cutoff, and the point estimate (with bias-corrected CI approximation) at the bandwidth you choose. Slide the bandwidth to see how the estimate moves; this is the sensitivity check every clean RDD paper has to do.
5.1 Why RDD is the workhorse for blended finance and rural development
Most programs an applied researcher will evaluate are not assigned by lottery. They are assigned by a rule. A census tract qualifies for the New Markets Tax Credit (NMTC) if its poverty rate is at or above 20 percent, or if its median family income is at or below 80 percent of the relevant area median. A Brazilian smallholder qualifies for PRONAF (Programa Nacional de Fortalecimento da Agricultura Familiar) credit lines if family income falls below a published ceiling and the property is below a hectare cap. A NUTS-3 region in the European Union qualifies as a “less developed region” under the Cohesion Fund, with the most generous co-financing rates, if its GDP per capita falls below 75 percent of the EU average. A Portuguese Comunidade Intermunicipal (CIM) is eligible for additional rural-development envelopes when its population density or aging index crosses a published threshold.
Every one of those programs has the same shape. There is a continuous running variable (poverty rate, GDP per capita, income, density), a known cutoff, and a treatment that flips on at the cutoff. That shape is the setting for Regression Discontinuity Design.
RDD is attractive for blended finance because it is the closest thing to a natural experiment that bureaucracies routinely produce. You do not need to find a clever instrument. You do not need to argue for parallel trends. You need the eligibility rule, the running variable, and a sensible neighborhood around the cutoff. Identification rests on a single, defensible assumption (more on that in a minute), and the literature on how to estimate it cleanly is mature and software-supported.
The foundational papers are: Hahn, Todd, and Van der Klaauw (2001), who gave us the formal identification result and the local-linear estimator; Imbens and Lemieux (2008), the canonical survey that turned RDD into a standard tool; and Calonico, Cattaneo, and Titiunik (2014), who solved the bandwidth-and-bias problem that had been quietly making applied papers’ confidence intervals wrong. Read those three carefully and you have the spine of modern RDD. Everything since has been refinement.
A practical heuristic for the applied researcher: every time you see a program manual that says “eligible if X meets threshold c,” tag it as an RDD candidate. The tag is worth real money in policy work, because it tells you what you can credibly claim about causal impact.
5.2 The identifying assumption, in one paragraph
Let Y_i(1) and Y_i(0) denote the potential outcomes under treatment and control, let X_i be the running variable, and let c be the cutoff. The RDD identifying assumption is continuity of the conditional expectations of potential outcomes at the cutoff: E[Y(1) \mid X = x] and E[Y(0) \mid X = x] are continuous in x at x = c. In plain language: units whose running variable lands just above c and units whose running variable lands just below c are comparable in expectation. Any jump in the observed outcome at c is therefore attributable to the treatment, not to any other factor that varies smoothly with X.
This is much weaker than the assumptions you need for difference-in-differences or matching. You are not assuming the two groups are similar globally. You are assuming they are similar locally, right at the boundary. That is the magic.
5.4 Estimation: local linear (or local quadratic) regression
The modern estimator is local polynomial regression. You pick a bandwidth h, a kernel K(\cdot) (the triangular kernel is the standard choice, since it is MSE-optimal for boundary problems), and a polynomial order p (almost always p = 1; sometimes p = 2). You then fit two separate weighted regressions, one on each side of c: \hat{\alpha}_+, \hat{\beta}_+ = \arg\min_{\alpha, \beta} \sum_{i: X_i \geq c} K\!\left(\frac{X_i - c}{h}\right) (Y_i - \alpha - \beta (X_i - c))^2, \hat{\alpha}_-, \hat{\beta}_- = \arg\min_{\alpha, \beta} \sum_{i: X_i < c} K\!\left(\frac{X_i - c}{h}\right) (Y_i - \alpha - \beta (X_i - c))^2. The point estimate is the difference of the two intercepts at the cutoff: \hat{\tau}_{\text{SRD}} = \hat{\alpha}_+ - \hat{\alpha}_-. Geometrically: you fit a line to a neighborhood of points just to the right of the cutoff, fit a separate line just to the left, and read off the vertical gap at x = c. Simple.
The hard part, the part that wasted a decade of applied RDD papers, is choosing h. Too small and your variance explodes. Too big and your linear approximation eats nonlinearity, biasing \hat{\tau}. The Imbens and Kalyanaraman (2012) bandwidth was the first principled solution: it minimizes the asymptotic mean squared error of \hat{\tau}. It works, but the resulting confidence intervals are not coverage-correct, because at the MSE-optimal bandwidth the bias of the local-linear estimator is non-negligible relative to its standard error. You end up with confidence intervals that nominally cover 95 percent but actually cover something closer to 80 percent. That is bad.
Calonico, Cattaneo, and Titiunik (CCT, 2014) fixed this with the robust bias-corrected confidence interval. The procedure has three steps. First, estimate \hat{\tau} with a local linear regression at the MSE-optimal bandwidth h_{\text{MSE}}. Second, estimate the leading bias term with a local quadratic regression at a pilot bandwidth b_{\text{MSE}}, and subtract it: \hat{\tau}^{\text{bc}} = \hat{\tau} - \hat{B}. Third, construct the standard error that accounts for both the sampling variability of \hat{\tau} and the variability introduced by the bias correction step. The resulting confidence interval has correct asymptotic coverage. It is also, in finite samples, often slightly wider than the naive interval, which is honest rather than embarrassing.
In practice: use the rdrobust package, accept the CCT defaults, and report the “robust” confidence interval. If a referee asks about bandwidth sensitivity, run it again at h/2 and 2h and put the results in an appendix. The IK bandwidth still appears in older papers; if you cite an old result, note which bandwidth was used. Do not use a global high-order polynomial. Gelman and Imbens (2019) showed it produces inflated, noisy estimates and is not a defensible substitute for local methods.
5.5 Validation: McCrary density test and covariate balance
The continuity assumption is not testable in general (you cannot see the missing potential outcomes), but you can stress-test it with two diagnostics.
The first is the McCrary (2008) density discontinuity test. The intuition: if agents can manipulate X_i to land on the favorable side of c, you would expect to see a pile-up of observations just above (or below) the cutoff. A discontinuity in the density of X at c is therefore evidence of sorting, which would break continuity of potential outcomes. McCrary’s test estimates the densities on each side of c with a local linear estimator and tests whether the log-difference is zero. The modern implementation is rddensity (Cattaneo, Jansson, and Ma 2018, 2020), which is more robust than the original. You want a high p-value here. A failed test does not always kill the paper (some thresholds, like geographic boundaries, are immune to manipulation), but it is a flag you cannot ignore.
The second is covariate balance. Run the RDD machinery with each predetermined covariate (baseline poverty, baseline population, prior investment, etc.) as if it were the outcome. If you see significant jumps in covariates at the cutoff, the comparability claim is suspect. The interpretation is the same as a randomization check in an RCT: if the “as if random” story is true, predetermined characteristics should be balanced at the boundary.
A clean RDD paper always includes both diagnostics. Without them a referee will ask. Better to have them ready.
5.6 Common traps
These are the recurring errors that cost real points in seminars and referee reports.
Sharing slope parameters across the cutoff. The whole point of RDD is to let the data on each side of c speak for itself. If you fit a single regression with Y = \alpha + \tau D + \gamma (X - c) + \varepsilon, you are forcing the slope of the outcome in the running variable to be the same on both sides. That is not local nonparametric. That is parametric and wrong. The correct specification is fully interacted: separate slopes (and ideally separate quadratics, if you go to p = 2) on each side. The local-polynomial machinery in rdrobust does this automatically. If you write your own regression by hand, write it as
Y_i = \alpha + \tau D_i + \beta_- (X_i - c) + \beta_+ (X_i - c) D_i + \varepsilon_i,
inside the bandwidth window.
Using too wide a bandwidth. The further you go from c, the more your linear approximation has to do, and the more parametric assumptions creep in through the back door. Trust the CCT-optimal bandwidth. If you must deviate, do it in both directions and report.
Heaping. Self-reported income clusters at round numbers. Household survey respondents say 20000, 25000, 30000 with implausible frequency. Census tracts have integer-valued poverty-rate cells when the underlying ACS sample is small. Heaping in the running variable can create artificial discontinuities. Always plot a histogram of X with fine bins before estimation. If you see spikes at round numbers near c, consider a “donut RDD” (drop observations within a small window of c) or move to an alternative running variable.
Treating fuzzy as sharp. If take-up is voluntary or eligibility is imperfectly applied, the sharp estimand is the intent-to-treat effect, not the average treatment effect for the treated. Both are valid quantities, but they answer different questions. Be explicit. If your readers expect a treatment-on-treated effect, run fuzzy RDD and report LATE.
Global high-order polynomials. Don’t. Cubic and quartic polynomials of X over the entire support produce estimates that swing wildly with the data near the boundary. Gelman and Imbens (2019) is the definitive criticism. Local linear or local quadratic, with CCT-optimal bandwidth, is the modern standard.
Forgetting that LATE is local. Fuzzy RDD estimates the effect for compliers near the cutoff. If the policy question is about a treatment effect for the broader eligible population, RDD alone cannot answer it. That is a feature, not a bug. State the estimand clearly.
5.7 Worked example: an NMTC-style poverty-rate cutoff
Suppose you have data on 2000 US census tracts. Each tract has an observed poverty rate (the running variable), a binary eligibility indicator equal to 1 if the poverty rate is at or above 20 percent, and a post-period log investment per capita outcome. The data frame columns are tract_id, poverty_rate, eligible, log_inv_pc.
The estimand: the causal effect of NMTC eligibility on local investment, identified at the 20 percent cutoff.
R code (rdrobust, rddensity, ggplot2)
# install.packages(c("rdrobust", "rddensity", "ggplot2"))
library(rdrobust)
library(rddensity)
library(ggplot2)
# Assume data is loaded as a data.frame named `data`
# with columns: tract_id, poverty_rate, eligible, log_inv_pc
# 1. Visual inspection: bin-and-scatter plot around the cutoff
rdplot(
y = data$log_inv_pc,
x = data$poverty_rate,
c = 20,
p = 1, # local linear fit
binselect = "esmv", # evenly-spaced, mimicking-variance bins
x.label = "Poverty rate (%)",
y.label = "Log investment per capita",
title = "NMTC eligibility cutoff"
)
# 2. McCrary-style density test (manipulation diagnostic)
dens <- rddensity(X = data$poverty_rate, c = 20)
summary(dens)
# Look at the "Robust" p-value. You want it above 0.10.
# 3. Sharp RDD point estimate with CCT robust bias-corrected CI
fit <- rdrobust(
y = data$log_inv_pc,
x = data$poverty_rate,
c = 20,
p = 1, # local linear
q = 2, # local quadratic for bias correction
kernel = "triangular",
bwselect = "mserd" # MSE-optimal, common bandwidth on both sides
)
summary(fit)
# Read three things from the output:
# (a) Coef "Conventional" = the standard local-linear point estimate.
# (b) Coef "Robust" or the bias-corrected CI = the inference you report.
# (c) BW est. (h) = the CCT-optimal bandwidth, in units of the running variable.
# (d) Eff. Number of obs = the number of tracts inside the bandwidth window.
# 4. Covariate balance: run RDD with each baseline covariate as the outcome
# Example: baseline population (assume column baseline_pop exists)
# rdrobust(y = data$baseline_pop, x = data$poverty_rate, c = 20)
# Repeat for each predetermined covariate. None should jump at the cutoff.
# 5. Donut RDD robustness: drop observations within 0.5 pp of the cutoff
mask <- abs(data$poverty_rate - 20) > 0.5
fit_donut <- rdrobust(
y = data$log_inv_pc[mask],
x = data$poverty_rate[mask],
c = 20
)
summary(fit_donut)
# 6. Placebo cutoffs: estimate the "effect" at 15 and 25
fit_placebo_15 <- rdrobust(y = data$log_inv_pc, x = data$poverty_rate, c = 15)
fit_placebo_25 <- rdrobust(y = data$log_inv_pc, x = data$poverty_rate, c = 25)
summary(fit_placebo_15)
summary(fit_placebo_25)
# Neither should show a significant effect.Stata code (rdrobust, rddensity ados)
* ssc install rdrobust
* ssc install rddensity
* 1. Visual inspection
rdplot log_inv_pc poverty_rate, c(20) p(1) binselect(esmv) ///
graph_options(title("NMTC eligibility cutoff") ///
xtitle("Poverty rate (%)") ///
ytitle("Log investment per capita"))
* 2. Density discontinuity test (manipulation diagnostic)
rddensity poverty_rate, c(20)
* 3. Sharp RDD with CCT robust bias-corrected inference
rdrobust log_inv_pc poverty_rate, c(20) p(1) q(2) ///
kernel(triangular) bwselect(mserd)
* Output gives Conventional, Bias-corrected, and Robust columns.
* Report the Robust CI. h_l and h_r are the CCT bandwidths on each side.
* 4. Covariate balance (example with baseline_pop)
* rdrobust baseline_pop poverty_rate, c(20)
* 5. Donut RDD
preserve
drop if abs(poverty_rate - 20) <= 0.5
rdrobust log_inv_pc poverty_rate, c(20)
restore
* 6. Placebo cutoffs
rdrobust log_inv_pc poverty_rate, c(15)
rdrobust log_inv_pc poverty_rate, c(25)How to read the output
When rdrobust prints, you will see three rows of estimates: Conventional, Bias-corrected, and Robust. The Conventional row is the raw local-linear point estimate and its naive standard error. The Bias-corrected row subtracts the leading bias term from the point estimate but uses the conventional standard error (so its coverage is wrong). The Robust row is what you report: bias-corrected point estimate, plus a standard error that accounts for the variability of the bias estimate itself. The Robust 95 percent CI is the one with correct asymptotic coverage.
You will also see two bandwidths: h (the main bandwidth, used for the local-linear point estimate) and b (the pilot bandwidth, used for the bias correction). The “effective number of observations” is the count of tracts inside h on each side. If that count is small (say, fewer than 50 per side), you have a precision problem, and you should be transparent about it.
For the worked example: imagine the output reports a Robust point estimate of 0.12 with a 95 percent CI of [0.03, 0.21], CCT bandwidth of 4.3 percentage points, and 380 effective observations. That translates to: tracts that just qualified for NMTC eligibility saw roughly 12 percent higher post-period investment per capita than tracts that just missed, with the effect statistically distinguishable from zero. The implied policy interpretation is bounded to tracts near the 20 percent threshold; you cannot extrapolate to a tract at 40 percent poverty.
5.8 Reporting checklist for a clean RDD paper
When you write this up, the methods section needs to hit each of these. Referees check.
- Eligibility rule. State the rule, the cutoff, and the running variable. Cite the program manual or statute.
- Sharp or fuzzy. State which, and show the first stage if fuzzy.
- Density test. Report the
rddensityp-value. If it fails, discuss why the test might fail despite valid identification (or admit the design is in trouble). - Covariate balance. Show a table or figure with RDD estimates on predetermined covariates.
- Main estimate. Report CCT robust bias-corrected point estimate, robust CI, MSE-optimal bandwidth, polynomial order, kernel, and effective sample size.
- Bandwidth sensitivity. Show estimates at h/2, h, and 2h.
- Polynomial sensitivity. Show local linear and local quadratic.
- Donut RDD. Drop observations within a small window of the cutoff and re-estimate.
- Placebo cutoffs. Estimate at fake cutoffs where no treatment changes. None should be significant.
- Covariate adjustment. Report the estimate with and without covariates. They should not move the point estimate much (Calonico, Cattaneo, Farrell, and Titiunik 2019 give the formal procedure).
The bible for the practical workflow is Cattaneo, Idrobo, and Titiunik’s two-volume A Practical Introduction to Regression Discontinuity Designs (Cambridge Elements, 2019 and 2024). If you have ten dollars and a weekend, buy them and work the examples. They are short, opinionated, and exactly correct.
5.9 Why this matters for an applied blended-finance career
The programs an applied researcher will spend the next decade evaluating are dense with eligibility cutoffs. Four worth knowing by name.
EU Cohesion Fund (NUTS-3 GDP-per-capita threshold). Less-developed regions, defined as NUTS-2 (and sometimes NUTS-3) regions with GDP per capita below 75 percent of the EU-27 average, qualify for the highest co-financing rates and the broadest list of eligible interventions. The threshold is set in the Multiannual Financial Framework and is mechanical at the regional level. Becker, Egger, and von Ehrlich (2010, 2012, 2018) have a series of papers using this exact discontinuity to identify the impact of EU structural funds on regional GDP growth, employment, and convergence. This is the first stop for any Portugal or peripheral-EU project. The running variable is GDP per capita as a share of the EU average; the cutoff is 75; the outcome is whatever you care about (regional GDP, firm-level investment, employment, migration).
Portugal’s CIM rural-development envelopes. The Comunidades Intermunicipais aggregate municipalities for purposes of regional planning, and several rural-development allocations are conditioned on demographic thresholds (population density below a certain value per km^2, aging index above a certain value, or peripherality indices). These thresholds shift over funding periods. Each one is a candidate RDD. Read the program manual carefully (in Portuguese, ideally), confirm whether the rule is sharp or fuzzy, and proceed.
Brazil’s PRONAF. Smallholder credit lines are gated on family income ceilings and property-size caps. The income ceiling has changed over time (consult the Manual de Crédito Rural, MCR, for the relevant year). Take-up is voluntary, so this is a fuzzy RDD. Magalhães, Mendes, and Lima (various working papers, 2010s) and others have used PRONAF cutoffs to identify impacts on agricultural output, deforestation, and rural household welfare. The empirical setting is messy (income is self-reported and there is heaping), but the design is canonical.
US NMTC, as a reference design. NMTC is the cleanest published RDD in place-based credit, and Freedman (2012, 2015) and Harger and Ross (2016) are essential reading. They use the poverty-rate-at-20-percent and median-income-at-80-percent thresholds. The cleanness comes from the running variable being a published statistic (ACS poverty rate) that tract residents cannot manipulate. The author’s own work on NMTC (Helfrich 2026) shows that the raw rural penalty of -0.262*** in log investment collapses to -0.047 (not significant, p=0.64) once Community Development Entity fixed effects are included. Read these papers as a template for a Portuguese or Brazilian analog.
In every case the workflow is the same: identify the rule, get the data with the running variable, plot it, run McCrary, run rdrobust, write the checklist.
5.10 References
Methodological
Calonico, Sebastian, Matias D. Cattaneo, and Rocio Titiunik. 2014. “Robust Nonparametric Confidence Intervals for Regression-Discontinuity Designs.” Econometrica 82 (6): 2295–2326.
Calonico, Sebastian, Matias D. Cattaneo, Max H. Farrell, and Rocio Titiunik. 2019. “Regression Discontinuity Designs Using Covariates.” Review of Economics and Statistics 101 (3): 442–451.
Cattaneo, Matias D., Nicolás Idrobo, and Rocio Titiunik. 2019. A Practical Introduction to Regression Discontinuity Designs: Foundations. Cambridge Elements in Quantitative and Computational Methods for the Social Sciences. Cambridge University Press.
Cattaneo, Matias D., Nicolás Idrobo, and Rocio Titiunik. 2024. A Practical Introduction to Regression Discontinuity Designs: Extensions. Cambridge Elements. Cambridge University Press.
Cattaneo, Matias D., Michael Jansson, and Xinwei Ma. 2018. “Manipulation Testing Based on Density Discontinuity.” Stata Journal 18 (1): 234–261.
Cattaneo, Matias D., Michael Jansson, and Xinwei Ma. 2020. “Simple Local Polynomial Density Estimators.” Journal of the American Statistical Association 115 (531): 1449–1455.
Gelman, Andrew, and Guido Imbens. 2019. “Why High-Order Polynomials Should Not Be Used in Regression Discontinuity Designs.” Journal of Business and Economic Statistics 37 (3): 447–456.
Hahn, Jinyong, Petra Todd, and Wilbert Van der Klaauw. 2001. “Identification and Estimation of Treatment Effects with a Regression-Discontinuity Design.” Econometrica 69 (1): 201–209.
Imbens, Guido W., and Karthik Kalyanaraman. 2012. “Optimal Bandwidth Choice for the Regression Discontinuity Estimator.” Review of Economic Studies 79 (3): 933–959.
Imbens, Guido W., and Thomas Lemieux. 2008. “Regression Discontinuity Designs: A Guide to Practice.” Journal of Econometrics 142 (2): 615–635.
McCrary, Justin. 2008. “Manipulation of the Running Variable in the Regression Discontinuity Design: A Density Test.” Journal of Econometrics 142 (2): 698–714.
Applied to blended finance, place-based credit, and rural development
Becker, Sascha O., Peter H. Egger, and Maximilian von Ehrlich. 2010. “Going NUTS: The Effect of EU Structural Funds on Regional Performance.” Journal of Public Economics 94 (9–10): 578–590.
Becker, Sascha O., Peter H. Egger, and Maximilian von Ehrlich. 2012. “Too Much of a Good Thing? On the Growth Effects of the EU’s Regional Policy.” European Economic Review 56 (4): 648–668.
Becker, Sascha O., Peter H. Egger, and Maximilian von Ehrlich. 2018. “Effects of EU Regional Policy: 1989–2013.” Regional Science and Urban Economics 69: 143–152.
Freedman, Matthew. 2012. “Teaching New Markets Old Tricks: The Effects of Subsidized Investment on Low-Income Neighborhoods.” Journal of Public Economics 96 (11–12): 1000–1014.
Freedman, Matthew. 2015. “Place-Based Programs and the Geographic Dispersion of Employment.” Regional Science and Urban Economics 53: 1–19.
Harger, Kaitlyn, and Amanda Ross. 2016. “Do Capital Tax Incentives Attract New Businesses? Evidence Across Industries from the New Markets Tax Credit.” Journal of Regional Science 56 (5): 733–753.
End of Chapter 5. Next: Chapter 6, Difference-in-Differences, including the two-way fixed-effects critique (Goodman-Bacon 2021; Callaway and Sant’Anna 2021; de Chaisemartin and D’Haultfœuille 2020) and the modern staggered-adoption estimators.