Partial‑Identification Bounds for Sharp RDDs with Manipulation
bounds_sharp.Rd
Compute Manski–type lower/upper bounds on the causal effect at a Regression
Discontinuity Design (sharp treatment assignment) when the running
variable can be strategically manipulated to the right of the cutoff. The
implementation mirrors Algorithm 1 in Rosenman et al. (2025) and is the
foundation for the fuzzy variant bounds_fuzzy()
.
Arguments
- x
Numeric running variable.
- y
Numeric outcome variable.
- cutoff
Numeric threshold separating control (< cutoff) from treatment (>= cutoff).
- true_counts
Data.frame with columns
x
(support points >= cutoff) andn_true
(estimated number of non-manipulated observations at each support point).- weights
Numeric vector of observation weights (optional).
- poly_order
Integer polynomial order for local regression (default 1L).
- bounds
Character – which bound(s) to return; one of "both" (default), "lower", "upper".
- solver
Character – CVXR solver (default taken from
getOption("rdpartial.solver")
; package default is "ECOS").- ...
Further arguments passed on to
CVXR::solve()
. Use this to override CVXR tolerances or provide a different solver control list.
Value
A numeric vector of length 2 (lower
, upper
) when bounds = "both"
; otherwise a single numeric. The full CVXR
solution objects are
attached as attributes opt_lwr
/ opt_upr
for diagnostic purposes.
Required Inputs
x
– numeric running variable.y
– outcome measured under realised treatment status (because the design is sharp, no separatez
argument is needed).cutoff
– numeric threshold separating control (< cutoff) from treatment (≥ cutoff).true_counts
–data.frame
with columnsx
(support points ≥ cutoff) andn_true
(estimated number of non‑manipulated observations at each support point). Typically produced by.density_estimation()
.
Examples
set.seed(123)
n <- 4000
x <- rpois(n, lambda = 15)
y0 <- 0.1 * x + 0.002 * x^2 + rnorm(n)
y1 <- y0 + 1 # treatment adds 1
cutoff <- 16
y <- ifelse(x >= cutoff, y1, y0) # sharp assignment
# Assume 90 % of post‑cutoff mass is genuine
tc <- data.frame(
x = cutoff:max(x),
n_true = round(tabulate(x[x >= cutoff] + 1L) * 0.9)
)
bounds_sharp(x, y, cutoff, true_counts = tc)
#> lower upper
#> -2.057823 -2.057823
#> attr(,"opt_lwr")
#> ✔ Status: optimal
#> ✔ Objective value: -2.057823
#> ℹ Solver: ECOS
#> → <me>$getValue(x) returns value of variable/constraint x
#> attr(,"opt_upr")
#> ✔ Status: optimal
#> ✔ Objective value: -2.057823
#> ℹ Solver: ECOS
#> → <me>$getValue(x) returns value of variable/constraint x