Regression Discontinuity Designs

The causal effect of rules

Merlin Schaeffer · Department of Sociology

2026-07-24

By the end of today you can …

  1. explain the RDD logic — why falling just above or below a strict rule’s threshold is “as good as random”, and how that connects to the natural experiments of Lecture 7;

  2. estimate a parametric RDD with OLS — a centred running variable, a treatment dummy, an interaction — and weigh the bias–reliability trade-off a bandwidth controls;

  3. run a non-parametric local-linear RDD with rdrobust() — optimal bandwidth and triangular-kernel weights — and read what it estimates: a LATE, local to the cutoff.

One part per goal. Our running question: what is the causal effect of legal drinking on mortality?

The logic of a discontinuity

Part 1 of 3

No randomiser, no instrument — just a strict rule that nature draws a sharp line through.

Remember RCTs

If we randomly split people into treatment and control, they come from the same population — similar on average in every way, including their untreated potential outcome \(Y_0\).

\[E[Y_{0i}\mid D=1] = E[Y_{0i}\mid D=0]\]

\[\begin{aligned} E&[Y_{1i}\mid D=1] - E[Y_{0i}\mid D=0] \\[4pt] &= E[\color{red}{Y_{0i}+\kappa}\mid D=1] - E[Y_{0i}\mid D=0] \\[4pt] &= \color{red}{\kappa} + \underbrace{E[Y_{0i}\mid D=1] - E[Y_{0i}\mid D=0]}_{=\,0\ \text{(if randomisation worked)}} \\[4pt] &= \color{red}{\kappa}. \end{aligned}\]

Balanced groups \(\rightarrow\) the raw difference is the causal effect \(\kappa\). The whole course has been about buying that balance.

RDD is different: rules, not randomness

We don’t use randomness, but strict rules. A rule creates a sharp threshold on an otherwise continuous variable.

Societies are full of thresholds that decide who gets what:

  • Pension age — one day older and you may draw your pension without penalty.
  • GPA cut-off — just above it you study sociology; just below, you don’t.
  • Electoral threshold — 5 % of the vote and the party enters parliament; 4.9 % and it gets nothing.

Can you think of other examples?

The intuition

Compare everyone either side of the line? The two groups differ in all sorts of ways — a poor GPA student and a top one are simply different people. That is selection bias.

Compare only those just barely either side? A student on 6.09 and one on 6.11 are, for all practical purposes, the same — bar the luck of the line.

Just below vs. barely above the threshold is often as good as random.

Two assumptions

The rule is strict, not random — yet barely below vs. barely above is as good as random, if two things hold.

  1. Continuity. Without the rule, the running variable and the outcome would relate smoothly across the cutoff — no natural jump would sit exactly there.

  2. No sorting. Cases cannot manipulate which side of the line they land on. If people can nudge themselves just over, the ones who do are self-selected — and the magic is gone.

The central RDD trade-off

Compare all cases either side:

  1. Maximum sample \(\rightarrow\) a reliable (precise) estimate.
  2. But the comparison is invalidated by selection bias.

Compare only cases just barely either side:

  1. Tiny sample \(\rightarrow\) an unreliable (noisy) estimate.
  2. But the comparison is as good as random \(\rightarrow\) unbiased — a local average treatment effect \((\lambda, \text{LATE})\).

The entire craft of RDD is finding the best trade-off between an unbiased \(\lambda\) and a reliable one.

You already know this \(\lambda\)

In Lecture 7 the effect was a ratio, \(\lambda = \rho/\phi\), because only some people complied with the instrument — we had to divide out the non-compliers.

Ask yourself: in a sharp RDD, everyone above the cutoff is treated and everyone below is not. What happens to \(\phi\)?

\(\phi = 1\) — the rule moves treatment perfectly. So there is nothing to divide out:

\[\lambda = \frac{\rho}{\phi} = \frac{\text{jump at the cutoff}}{1} = \textbf{the jump itself.}\]

Sharp RDD is IV with perfect compliance. The vertical gap at the threshold is the causal effect. And “local” now means local to the cutoff — not “for compliers” as in L7.

Estimating it with OLS

Part 2 of 3

Turning 21 makes drinking legal. Does it also make you more likely to die?

The research question of the day

In the US you may drink legally at 21. Nothing else about a person changes overnight on that birthday — but their legal access to alcohol jumps.

Research question of the day: what is the causal effect of legal drinking on mortality?

The running variable is age (in month-wide cells); the cutoff is 21; the treatment is legal drinking.

Data: US deaths per 100,000, ages 20–22, 1997–2003 — the mlda data from Angrist & Pischke’s Mastering ’Metrics.

The data

data("mlda", package = "masteringmetrics")
mlda <- drop_na(mlda)          # one row per one-month age cell
head(mlda)
# # A tibble: 6 × 5
#   agecell   all   mva     D  age0
#     <dbl> <dbl> <dbl> <dbl> <dbl>
# 1    19.1  92.8  35.8     0 -1.93
# 2    19.2  95.1  35.6     0 -1.85
# 3    19.2  92.1  34.2     0 -1.77
# 4    19.3  88.4  32.3     0 -1.68
# 5    19.4  88.7  32.7     0 -1.60
# 6    19.5  90.2  32.7     0 -1.52

ggplot(mlda, aes(y = all, x = agecell)) +
  geom_vline(xintercept = 21, linetype = "dashed") +
  geom_point(aes(colour = agecell >= 21)) +
  labs(y = "Deaths per 100,000", x = "Age (years + months)")

OLS finds the trade-off

  1. Use all the data to fit the lines \(\rightarrow\) reliable.
  2. Let OLS compare treated vs. control exactly at 21 \(\rightarrow\) unbiased.
  3. The effect is still local: it might look different if the cutoff were 16.

Linear parametric RDD

\[Y_i = \alpha + \color{red}{\lambda} D_i + \beta_1 a_i + \beta_2 (D_i \times a_i) + e_i\]

  • \(D_i\) — above (1) or below (0) the threshold.
  • \(a_i\) — the running variable (age), centred at the cutoff so \(\lambda\) reads at 21.
  • \(D_i \times a_i\) — lets the slope differ on each side.

\(\color{red}{\lambda}\) is the jump at the cutoff: 7.7 more deaths per 100,000 the moment drinking becomes legal.

mlda <- mlda %>%
  mutate(D    = if_else(agecell < 21, 0, 1),
         age0 = agecell - 21)          # centre the running variable

RDD_lin <- lm_robust(all ~ D * age0, data = mlda)
Deaths per 100,000
(Intercept) 93.618***
(0.627)
Legal to drink (λ) 7.663***
(1.294)
Age (centred) 0.827
(0.728)
Legal × age −3.603**
(1.146)
Num.Obs. 48
R2 0.668
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Problem 1: is a straight line right?

Nothing guarantees the relationship is linear. If the true curve bends, two straight lines will miss at the cutoff — and mistake curvature for a jump.

Recall Lecture 12. A poly(age0, 2, raw = TRUE) term adds a squared term to bend the line — same tool, and the effect of age is again no longer one number.

Beware (also L12): chasing \(\max(R^2)\) with ever-higher polynomials overfits — a high-degree curve swings wildly exactly at the boundary we care about. Overfitting there becomes bias in \(\lambda\).

Linear vs. quadratic \(\lambda\)

\[\small Y_i = \alpha + \color{red}{\lambda} D_i + \beta_1 a_i + \beta_2 a_i^2 + \beta_3 (a_i D_i) + \beta_4 (a_i^2 D_i) + e_i\]

RDD_poly <- lm_robust(
  all ~ D * poly(age0, 2, raw = TRUE),
  data = mlda)

Allowing a curve moves the jump from 7.7 to 9.5. The functional form is a real modelling choice — not a detail.

Linear Quadratic
Legal to drink (λ) 7.663*** 9.548***
(1.294) (1.948)
Num.Obs. 48 48
R2 0.668 0.682
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Same data, same cutoff — a different curve, a different \(\lambda\). Which do we believe? That question drives Part 3.

Problem 2: which cases should count?

Should a 19-year-old, two years from the cutoff, really shape the estimated jump at 21?

In RDD we care about the boundary. Cases far away carry little information about it — and force the curve to bend for reasons irrelevant to the cutoff.

\(\rightarrow\) Restrict to a bandwidth around the threshold.

Number of Obs.                   48
BW type                       mserd
Kernel                   Triangular
VCE method                       NN
=======================================================
                  BW est. (h)    BW bias (b)
            Left of c Right of c  Left of c Right of c
=======================================================
     mserd     0.493      0.493      0.780      0.780
=======================================================
rdbwselect(mlda$all, mlda$agecell, c = 21) %>%
  summary()

The algorithm picks the window that best balances \(\frac{\text{reliability}}{\text{bias}}\): here about ± 0.49 years around 21.

Break

Your turn: exercise 1

Can a government buy the loyalty of the poor? You replicate Manacorda, Miguel & Vigorito (2011): Uruguay’s PANES cash transfer went to households below an income score. Did the money buy political support for the government that sent it? Fit a parametric RDD and read the local effect.

Open exercise 1 in a new tab ↗

Doing it properly: rdrobust

Part 3 of 3

Drop the global polynomial. Fit a straight line locally, and weight the cases that matter.

Prefer local linear to global polynomial

A global polynomial must fit points far from the cutoff, so it can swing to reach them.

\(\downarrow\)

Polynomials are notoriously unstable at the boundaries of the data.

\(\downarrow\)

In RDD the boundary is all we care about.

\(\downarrow\)

So a wild boundary swing lands exactly on \(\lambda\) \(\rightarrow\) bias. Inside a narrow window, a straight line is safer.

Weight by closeness: the kernel

The bandwidth is blunt: a case just inside counts fully, a case just outside not at all.

Better to let importance fade with distance — cases nearer the cutoff embody the RDD logic most.

The triangular kernel is the default: full weight at the cutoff, linearly down to zero at the bandwidth edge. It prioritises the one thing we care about — the value at the line.

All in one: rdrobust()

rdrobust() implements the state of the art: local linear regression within an optimised bandwidth, using triangular-kernel weights.

mlda$all = all deaths per 100,000.

The jump: 9.6 more deaths per 100,000 (95 % CI [1.1, 18.3], robust \(p = 0.027\)), from the 12 cells in the optimal window.

Sharp RD estimates using local polynomial regression.
Number of Obs.                   48
BW type                       mserd
Kernel                   Triangular
                               Left        Right
Number of Obs.                   24           24
Eff. Number of Obs.               6            6
BW est. (h)                   0.493        0.493
=====================================================================
                   Point    Robust Inference
                Estimate         z     P>|z|      [ 95% C.I. ]       
---------------------------------------------------------------------
     RD Effect     9.595     2.205     0.027     [1.077 , 18.300]    
=====================================================================
RDD_all <- rdrobust(
  y = mlda$all,       # Outcome
  x = mlda$agecell,   # Running variable
  c = 21)             # Threshold
summary(RDD_all)

Was it the drinking, or just age?

If legal alcohol is the mechanism, the jump should be concentrated in alcohol-related deaths — above all motor-vehicle accidents.

mlda$mva = motor-vehicle deaths per 100,000.

The traffic jump is 4.9 — about 51 % of the all-cause jump. Roughly half of the extra deaths are on the road.

On this subgroup alone the robust interval is wide (p = 0.06) — a point estimate consistent with the alcohol story, not a second significance claim.

Sharp RD estimates using local polynomial regression.
Number of Obs.                   48
BW type                       mserd
Kernel                   Triangular
                               Left        Right
Number of Obs.                   24           24
Eff. Number of Obs.               6            6
BW est. (h)                   0.485        0.485
=====================================================================
                   Point    Robust Inference
                Estimate         z     P>|z|      [ 95% C.I. ]       
---------------------------------------------------------------------
     RD Effect     4.903     1.879     0.060    [-0.203 , 9.698]     
=====================================================================
RDD_drive <- rdrobust(
  y = mlda$mva,       # Motor-vehicle deaths
  x = mlda$agecell,
  c = 21)
summary(RDD_drive)

Research question answered

Making alcohol legal at 21 causes about 9.6 more deaths per 100,000.

Roughly half of them are extra traffic deaths.

Another rule: women and corruption

Pereira and Fernandez-Vazquez (2023) use a Spanish law: from 2007, municipalities above 5,000 residents had to fill ≥ 40 % of party lists with women.

  • Outcome: revealed corruption
  • Running variable: municipality population
  • Cutoff: 5,000

The quota jumps the share of women elected right at 5,000 — a textbook first stage.

% women elected jumps at the 5,000 threshold. Source: Pereira and Fernandez-Vazquez (2023).

Women and corruption: the effect

There is an effect — more women, less revealed corruption at the cutoff.

But why? A jump tells us that, not how.

Source: Pereira and Fernandez-Vazquez (2023).

A third rule: far-right mayors and discrimination

Schaeffer, Romarri, Rosenberg, and Krakowski (2025) exploit close mayoral elections in Italy: where a far-right candidate barely won vs. barely lost is as good as random.

  • Outcome: discrimination in access to healthcare (an audit study)
  • Running variable: far-right margin of victory
  • Cutoff: 0 %

Callers with a West-African accent get less favourable treatment than native-Italian callers. Source: Schaeffer, Romarri, Rosenberg et al. (2025).

Far-right mayors: the discontinuity

Municipalities where far-right candidates ran vs. won. Source: Romarri (2020).

Discrimination against West-African-accented callers jumps where the far-right barely won. Source: Schaeffer, Romarri, Rosenberg et al. (2025).

Your turn: exercise 2

Back to Uruguay’s PANES — does welfare buy loyalty? — but now with rdrobust(): a local-linear, optimal-bandwidth, triangular-kernel estimate. Then push the bandwidth by hand and watch the answer move.

Open exercise 2 in a new tab ↗

Today’s general lessons

  1. The RDD logic. A strict rule creates a natural experiment: at the cutoff, the only difference between treated and untreated is the rule, so assignment is as good as random. Sharp RDD is IV with perfect compliance — the jump is \(\lambda\).

  2. The bias–reliability trade-off. Wide bandwidth: more data, high precision, but risks comparing dissimilar people (bias). Narrow bandwidth: near-identical people (low bias), but little data (noise).

  3. Functional form matters. If the data curve and you fit a straight line, you mistake the bend for a jump. Allow curvature (polynomials — but mind the L12 overfitting trap) or stay local linear in a narrow window.

  4. Weighting — the kernel. Cases at the cutoff matter most. The triangular kernel gives them full weight and fades the rest to zero at the bandwidth edge.

Check yourself: today’s goals

  • Explain why “just below vs. barely above a threshold” is as good as random — and name the two assumptions it rests on.
  • Write a parametric RDD in OLS: centred running variable, treatment dummy, interaction — and say what \(\lambda\) measures and why we centre.
  • Run rdrobust(y, x, c) and read its output — knowing it is a local linear, triangular-kernel estimate of a LATE at the cutoff.

Shaky on any of these? That is what this week’s Absalon quiz and the Friday exercise class are for.

Today’s important functions

  • lm_robust(y ~ D * age0): the parametric RDD — treatment dummy, centred running variable, and their interaction (different slopes each side).
  • rdrobust::rdbwselect(y, x, c): choose the bandwidth that best trades reliability against bias.
  • rdrobust::rdrobust(y, x, c): the state-of-the-art estimate — local linear, optimal bandwidth, triangular-kernel weights.

References

Pereira, M. M. and P. Fernandez-Vazquez (2023). “Does Electing Women Reduce Corruption? A Regression Discontinuity Approach”. In: Legislative Studies Quarterly, pp. 731-763.

Romarri, A. (2020). Do Far-Right Mayors Increase the Probability of Hate Crimes? Evidence From Italy. SSRN Scholarly Paper ID 3506811. Rochester, NY: Social Science Research Network.

Schaeffer, M., A. Romarri, D. Rosenberg, et al. (2025). “When Politics Enters the Waiting Room: Far-Right Electoral Victories Exacerbate Discrimination in Access to Healthcare”. In: American Political Science Review. Forthcoming.