Partial‑Identification Bounds for Fuzzy RDDs with Manipulation
bounds_fuzzy.Rd
Extension of bounds_sharp()
to fuzzy designs where treatment take‑up
jumps (up or down) at the cutoff but is not deterministic. The estimator
solves a linear‑fractional programme as described in Rosenman et al. (2025)
using CVXR
. Manipulation to the right of the cutoff is bounded by
true_counts
obtained from .density_estimation()
.
Arguments
- x
Numeric running variable.
- y
Numeric outcome variable.
- z
Numeric vector (0/1) – treatment take‑up.
- 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".
- treat_direction
Either "increase" (treatment prob. jumps up at the cutoff – standard case) or "decrease" (jumps down, e.g. donor deferral example). Determines the sign of the optimisation objective.
- solver
Character – CVXR solver (default taken from
getOption("rdpartial.solver")
; package default is "ECOS").- runVarPlots
Logical. If TRUE, generate running variable plots showing outcomes and treatment probabilities.
- ylab
Character. Label for the y-axis in plots.
- xlab
Character. Label for the x-axis in plots.
- ...
Further arguments passed on to
CVXR::solve()
. Use this to override CVXR tolerances or provide a different solver control list.
Value
Numeric vector of length 2 (lower
, upper
) when bounds = "both"
;
otherwise a single numeric. CVXR solutions attached as attributes
opt_lwr
/opt_upr
.
Required Inputs
x
– running variable.y
– outcome.z
– realised treatment indicator (0/1).cutoff
– threshold.true_counts
–data.frame(x, n_true)
of non‑manipulated counts.
Examples
set.seed(321)
n <- 6000
x <- rpois(n, 15)
cutoff <- 16
z <- rbinom(n, 1, prob = ifelse(x >= cutoff, 0.8, 0.2))
y0 <- 0.2 * x + rnorm(n)
y1 <- y0 + 2
y <- ifelse(z == 1, y1, y0)
freq <- table(factor(x[x >= cutoff], levels = cutoff:max(x)))
tc <- data.frame(
x = as.integer(names(freq)),
n_true = round(as.vector(freq) * 0.85)
)
bounds_fuzzy(x, y, z, cutoff, true_counts = tc)
#> lower upper
#> 1.480718 2.672821
#> attr(,"opt_lwr")
#> ✔ Status: optimal
#> ✔ Objective value: 1.480718
#> ℹ Solver: ECOS
#> → <me>$getValue(x) returns value of variable/constraint x
#> attr(,"opt_upr")
#> ✔ Status: optimal
#> ✔ Objective value: 2.672821
#> ℹ Solver: ECOS
#> → <me>$getValue(x) returns value of variable/constraint x