Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Regression Discontinuity Design (RDD) exploits a known threshold in a continuous assignment
variable ("running variable") that determines treatment. Units just above and just below the
cutoff are locally comparable — providing near-experimental variation for causal identification.
Conceptual Framework
Sharp RDD
Treatment is a deterministic function of the running variable X:
D_i = 1(X_i ≥ c)
The treatment effect τ_SRD is identified as:
τ_SRD = lim_{x↓c} E[Y|X=x] − lim_{x↑c} E[Y|X=x]
Identification requires:
Continuity: E[Y(0)|X=x] and E[Y(1)|X=x] are continuous at c.
No precise manipulation: agents cannot perfectly sort just above the cutoff.
Fuzzy RDD
When the probability of treatment changes discontinuously at c (but not from 0 to 1):
τ_FRD = jump in E[Y|X] at c / jump in E[D|X] at c
This is a local IV estimator; it recovers the LATE for compliers near the threshold.
Bandwidth Selection
The MSE-optimal bandwidth (Calonico, Cattaneo, Titiunik 2014):
h_MSE = C_n · n^{-1/5}
In practice rdrobust computes data-driven bandwidths via IK (Imbens-Kalyanaraman) or
CCT (Calonico-Cattaneo-Titiunik) selectors.
running_var: np.ndarray, cutoff: float,
n_bins: int = 30
dict
"""
McCrary (2008) density continuity test for sorting / manipulation.
H0: density of running variable is continuous at the cutoff.
Rejects if agents can precisely manipulate their score to just exceed c.
Method: estimate local linear density on each side of cutoff using
a bin count approximation; compare slopes.
Parameters
----------
running_var : 1-D array of running variable values
cutoff : threshold value c
n_bins : number of histogram bins per side (default 30)
Returns
-------
dict with t_statistic, p_value, interpretation, and density arrays
"""
# center at cutoff
# separate sides
0
0
# bin width: half the standard bandwidth rule
2
1.06
len
0.2
def
_local_density
side, sign
"""Fit local linear to binned density on one side."""
min
0
if
0
else
0
max
1
1
1
2
len
1
0
# local linear fit weighted by triangular kernel near 0
"""
Estimate sharp RDD treatment effect using local polynomial regression.
Parameters
----------
y : outcome variable
x : running variable
cutoff : threshold c
bandwidth : half-bandwidth h; if None, uses IK-style MSE-optimal selector
poly_order: polynomial order for local regression (1 = local linear)
kernel : 'triangular', 'uniform', or 'epanechnikov'
Returns
-------
dict with tau (LATE), se, t_stat, p_value, ci, bandwidth, and raw results
"""
"""
Fuzzy RDD: local IV using cutoff as instrument for treatment.
τ_FRDD = (jump in E[Y|X] at c) / (jump in E[D|X] at c)
Parameters
----------
y : outcome
x : running variable
z : actual treatment take-up (binary or continuous compliance)
cutoff : threshold
"""
float
float
float
if
is
None
# reduced form: effect of crossing on Y
# first stage: effect of crossing on treatment take-up
if
abs
"tau"
1e-10
raise
"First stage is essentially zero — instrument is weak."
"tau"
"tau"
# delta method SE
"se"
"tau"
2
"tau"
"se"
"tau"
2
2
2
1
abs
return
"tau_fuzzy"
"se"
"t_statistic"
"p_value"
"ci_95"
1.96
1.96
"first_stage_tau"
"tau"
"first_stage_F"
"t_statistic"
2
"reduced_form_tau"
"tau"
"bandwidth"
"interpretation"
f"Fuzzy RDD LATE = {tau_fuzzy:.4f} (SE={se_fuzzy:.4f}, p={p_fuzzy:.4f})"
"""
Test that predetermined covariates are balanced across the cutoff.
Run sharp RDD for each covariate as the outcome — reject H0 indicates imbalance.
Returns a DataFrame with tau, se, p_value, and pass/fail for each covariate.
"""
"""
Estimate sharp RDD across a grid of bandwidths and report stability.
A credible RDD should show τ stable across a reasonable bandwidth range.
Large swings suggest model sensitivity or violation of continuity assumption.
"""
"""
Simplified Imbens-Kalyanaraman (2012) bandwidth selector.
Uses a pilot bandwidth (Silverman) and regularises the optimal formula.
"""
len
# pilot bandwidth
1.84
1
5
0
0
if
sum
5
or
sum
5
return
# curvature estimates via second-order polynomial fits
def
_curvature
mask
if
sum
4
return
0.0
2
return
2
abs
0
# regularisation constants
4
4
3.4375
# triangular kernel constant
2
2
if
0
return
1
5
return
max
0.1
Example A — Education Policy at a Test Score Threshold
A school district assigns remedial tutoring to students scoring below 50 on a diagnostic
exam. We estimate the causal effect on year-end scores.
# example_a_education_rdd.py"""
Sharp RDD: effect of remedial tutoring assigned at test score < 50.
"""import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from rdd_design import (
test_manipulation,
run_sharp_rdd,
plot_rdd,
covariate_balance_rdd,
rdd_sensitivity_bandwidth,
)
rng = np.random.default_rng(1234)
n = 3000# running variable: baseline test score (0–100)
score = rng.beta(5, 5, n) * 100
cutoff = 50.0# treatment: tutoring if score < 50
treated = (score < cutoff).astype(float)
# covariates (pre-determined — should be balanced at cutoff)
age = 10 + rng.normal(0, 0.5, n)
female = rng.binomial(1, 0.5, n).astype(float)
ses = rng.normal(0, 1, n) # socioeconomic status index# outcome: end-of-year test score# true effect of tutoring: +6 points for compliers near cutoff
noise = rng.normal(0, 8, n)
outcome = (
40 + 0.5 * score
- 0.3 * (score - cutoff)**2 * 0.005
+ 6 * treated
- 2 * ses
+ noise
)
df = pd.DataFrame({
"outcome": outcome, "score": score, "treated": treated,
"age": age, "female": female, "ses": ses,
})
# ── 1. Manipulation test ──
manip = test_manipulation(df["score"].values, cutoff)
print("Manipulation test:", manip["interpretation"])
# ── 2. Main RDD estimate ──
result = run_sharp_rdd(df["outcome"].values, df["score"].values, cutoff)
print(f"\nSharp RDD τ = {result['tau']:.3f} SE = {result['se']:.3f} "f"p = {result['p_value']:.4f}")
print(f"95% CI: [{result['ci_95'][0]:.3f}, {result['ci_95'][1]:.3f}]")
print(f"Bandwidth: {result['bandwidth']:.2f} N (left/right): "f"{result['n_left']}/{result['n_right']}")
# ── 3. Covariate balance ──
balance = covariate_balance_rdd(df, "score", cutoff, ["age", "female", "ses"])
print("\nCovariate balance:")
print(balance.to_string(index=False))
# ── 4. Bandwidth sensitivity ──
sensitivity = rdd_sensitivity_bandwidth(
df["outcome"].values, df["score"].values, cutoff
)
print("\nBandwidth sensitivity (τ range):",
f"[{sensitivity['tau'].min():.3f}, {sensitivity['tau'].max():.3f}]")
# ── 5. RDD plot ──
fig = plot_rdd(
df["outcome"].values, df["score"].values, cutoff,
title="Effect of Remedial Tutoring on End-of-Year Score",
ylabel="End-of-year test score",
xlabel="Baseline test score",
)
# overlay bandwidth sensitivity as inset
ax_inset = fig.add_axes([0.62, 0.15, 0.28, 0.30])
ax_inset.fill_between(sensitivity["bandwidth"], sensitivity["ci_lower"],
sensitivity["ci_upper"], alpha=0.3, color="#2c7bb6")
ax_inset.plot(sensitivity["bandwidth"], sensitivity["tau"],
color="#2c7bb6", linewidth=2)
ax_inset.axhline(0, color="black", linestyle="--", linewidth=0.8)
ax_inset.set_xlabel("Bandwidth", fontsize=8)
ax_inset.set_ylabel("τ", fontsize=8)
ax_inset.set_title("BW sensitivity", fontsize=8)
fig.savefig("rdd_education.png", dpi=150, bbox_inches="tight")
print("\nSaved rdd_education.png")
Example B — Incumbency Advantage in Elections
Lee (2008) style: the running variable is the Democratic vote margin in election t;
the outcome is winning in election t+1. The cutoff is 0 (bare majority).
# example_b_incumbency_rdd.py"""
Fuzzy RDD: incumbency advantage.
Running variable: vote share margin = DEM% - 50.
Outcome: probability of winning next election.
Treatment: actually serving as incumbent (compliance < 1 due to death, resignation, etc.)
"""import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from rdd_design import (
test_manipulation,
run_sharp_rdd,
run_fuzzy_rdd,
plot_rdd,
rdd_sensitivity_bandwidth,
)
rng = np.random.default_rng(99)
n = 5000
cutoff = 0.0# margin of 0 = bare majority# running variable: Democratic vote margin (centred, in %)
margin = rng.normal(0, 15, n)
margin = np.clip(margin, -49, 49)
# fuzzy: incumbency take-up is 0.95 on winning side, 0 on losing side# (small non-compliance: some winners vacate seat before next election)
above = margin >= 0
prob_inc = np.where(above, 0.93, 0.0)
incumbent = rng.binomial(1, prob_inc, n).astype(float)
# true incumbency advantage: +15 pp in next election
noise = rng.normal(0, 20, n)
win_next = (
50 + 0.3 * margin + 15 * incumbent
+ noise
).clip(0, 100)
df = pd.DataFrame({
"win_next": win_next, "margin": margin, "incumbent": incumbent,
})
# ── Manipulation test ──
manip = test_manipulation(df["margin"].values, cutoff, n_bins=40)
print("Manipulation test:", manip["interpretation"])
# ── Sharp RDD (treating instrument as treatment) ──
sharp = run_sharp_rdd(df["win_next"].values, df["margin"].values, cutoff)
print(f"\nSharp (reduced-form) τ = {sharp['tau']:.3f} p = {sharp['p_value']:.4f}")
# ── Fuzzy RDD (LATE for compliers) ──
fuzzy = run_fuzzy_rdd(
df["win_next"].values, df["margin"].values,
df["incumbent"].values, cutoff
)
print(f"Fuzzy RDD LATE = {fuzzy['tau_fuzzy']:.3f} SE = {fuzzy['se']:.3f}"f" p = {fuzzy['p_value']:.4f}")
print(f"First-stage F = {fuzzy['first_stage_F']:.1f} "f"(rule of thumb: >10 for strong instrument)")
# ── Bandwidth sensitivity plot ──
sens = rdd_sensitivity_bandwidth(df["win_next"].values, df["margin"].values, cutoff)
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
# RDD scatterfrom rdd_design import plot_rdd as _plot_rdd
fig2 = plot_rdd(
df["win_next"].values, df["margin"].values, cutoff,
title="Incumbency Advantage (Sharp RDD)",
ylabel="Vote share in next election (%)",
xlabel="Vote margin in current election (%)",
)
fig2.savefig("rdd_incumbency.png", dpi=150, bbox_inches="tight")
# bandwidth sensitivity
axes[0].fill_between(sens["bandwidth"], sens["ci_lower"], sens["ci_upper"],
alpha=0.25, color="#2c7bb6", label="95% CI")
axes[0].plot(sens["bandwidth"], sens["tau"], "o-", color="#2c7bb6",
linewidth=2, markersize=4, label="τ estimate")
axes[0].axhline(0, color="black", linestyle="--", linewidth=0.8)
axes[0].set_xlabel("Bandwidth (vote margin %)")
axes[0].set_ylabel("Estimated τ")
axes[0].set_title("Bandwidth sensitivity")
axes[0].legend()
# sample size by bandwidth
axes[1].plot(sens["bandwidth"], sens["n_left"], "s--", color="#d7191c",
label="N (control, left)")
axes[1].plot(sens["bandwidth"], sens["n_right"], "o--", color="#2c7bb6",
label="N (treated, right)")
axes[1].set_xlabel("Bandwidth")
axes[1].set_ylabel("Observations in window")
axes[1].set_title("Sample size vs bandwidth")
axes[1].legend()
plt.tight_layout()
fig.savefig("rdd_sensitivity.png", dpi=150, bbox_inches="tight")
print("\nSaved rdd_incumbency.png and rdd_sensitivity.png")
# ── Summary ──print(f"""
=== INCUMBENCY ADVANTAGE: SUMMARY ===
Sharp reduced-form τ : {sharp['tau']:.2f} pp
Fuzzy LATE : {fuzzy['tau_fuzzy']:.2f} pp
First-stage F : {fuzzy['first_stage_F']:.1f}
Bandwidth used : {sharp['bandwidth']:.2f} pp
Manipulation test : {manip['interpretation']}
""")
Validity Checks Checklist
Check
Method
Pass criterion
No manipulation
McCrary density test
p ≥ 0.05
Covariate balance
RDD on predetermined X
All p ≥ 0.10
Bandwidth stability
τ across h ∈ [0.5h*, 2h*]
τ stable, does not cross 0
Placebo cutoffs
Run RDD at c ± δ
No significant effects
Donut hole
Exclude observations near c
Estimate unchanged
Polynomial order
Compare p=1,2,3
Consistent estimates
When RDD Is and Is Not Valid
RDD is valid when:
The running variable is continuous at the threshold
Agents cannot precisely manipulate their value just above/below c
Only treatment status changes discontinuously at c (no other discontinuous policies)
RDD is not valid when:
Bunching in the density at c (e.g., teachers round borderline exam scores)
Multiple simultaneous treatment discontinuities at the same cutoff
The bandwidth is so narrow that N is too small for reliable local estimates
Extrapolation: the LATE applies only near the cutoff, not in the full population
References
Lee, D. S., & Lemieux, T. (2010). Regression discontinuity designs in economics.
Journal of Economic Literature, 48(2), 281–355.
Calonico, S., Cattaneo, M. D., & Titiunik, R. (2014). Robust nonparametric
confidence intervals for regression-discontinuity designs. Econometrica, 82(6).
Imbens, G., & Kalyanaraman, K. (2012). Optimal bandwidth choice for the regression
discontinuity estimator. Review of Economic Studies, 79(3), 933–959.
McCrary, J. (2008). Manipulation of the running variable in the regression
discontinuity design: A density test. Journal of Econometrics, 142(2), 698–714.