| Title: | Staggered Regression Discontinuity with Network Interference |
|---|---|
| Description: | Implements a unified framework combining staggered difference-in-differences with regression discontinuity designs and network interference. Extends Callaway and Sant'Anna (2021) <doi:10.1016/j.jeconom.2020.12.001> to settings where treatment assignment is determined by a running variable crossing a cutoff, adoption timing is heterogeneous across units, and spillover effects operate through a known network structure. Provides group-time average treatment effects (direct and spillover), aggregation schemes, bandwidth selection, and pre-treatment falsification tests. |
| Authors: | Subir Hait [aut, cre] |
| Maintainer: | Subir Hait <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-06-05 06:23:21 UTC |
| Source: | https://github.com/causalfragility-lab/rdstagger |
Implements a unified framework combining staggered difference-in-differences with regression discontinuity designs and network interference. Extends Callaway and Sant'Anna (2021) to settings where:
Treatment assignment is determined by a running variable crossing a cutoff (RD)
Treatment adoption timing is heterogeneous across units (staggered DiD)
Spillover effects operate through a known network structure (interference)
sim_rdstaggerSimulate a staggered RD panel dataset with interference
rdstagger_bwOptimal bandwidth selection per cohort-time cell
rdstagger_attgtEstimate ATT(g,t) — direct and spillover effects
rdstagger_spilloverEstimate spillover effects at network distance d
rdstagger_aggAggregate ATT(g,t) into event-study or overall ATT
rdstagger_pretestPre-treatment parallel trends falsification tests
Callaway, B., & Sant'Anna, P. H. C. (2021). Difference-in-differences with multiple time periods. Journal of Econometrics, 225(2), 200-230.
Calonico, S., Cattaneo, M. D., & Titiunik, R. (2014). Robust nonparametric confidence intervals for regression-discontinuity designs. Econometrica, 82(6), 2295-2326.
Manski, C. F. (2013). Identification of treatment response with social interactions. The Econometrics Journal, 16(1), S1-S23.
Maintainer: Subir Hait [email protected]
Useful links:
Report bugs at https://github.com/causalfragility-lab/rdstagger/issues
Produces a ggplot2 event-study or aggregation plot from an
"rdstagger_agg" object.
## S3 method for class 'rdstagger_agg' plot(x, ...)## S3 method for class 'rdstagger_agg' plot(x, ...)
x |
An object of class |
... |
Additional arguments (currently unused). |
A ggplot2 object.
sim <- sim_rdstagger(n = 300, nperiods = 6, n_cohorts = 2, true_direct = 0.3, seed = 42) res <- rdstagger_attgt(data = sim$data, yname = "y", xname = "x", gname = "g", tname = "period", idname = "id", bw = 1.5, boot = FALSE) agg <- rdstagger_agg(res, type = "dynamic") plot(agg)sim <- sim_rdstagger(n = 300, nperiods = 6, n_cohorts = 2, true_direct = 0.3, seed = 42) res <- rdstagger_attgt(data = sim$data, yname = "y", xname = "x", gname = "g", tname = "period", idname = "id", bw = 1.5, boot = FALSE) agg <- rdstagger_agg(res, type = "dynamic") plot(agg)
Aggregates group-time average treatment effects ATT(g,t) from
rdstagger_attgt into summary estimands: event-study
(dynamic), cohort-level, calendar-time, or overall ATT.
rdstagger_agg( x, type = c("dynamic", "group", "calendar", "overall"), min_periods = 1L )rdstagger_agg( x, type = c("dynamic", "group", "calendar", "overall"), min_periods = 1L )
x |
An object of class |
type |
Character. Aggregation type: |
min_periods |
Integer. Minimum number of cohort-time cells required
to include an event-time bin. Default |
An object of class "rdstagger_agg", a list with:
aggData frame of aggregated estimates
typeAggregation type used
overall_attSimple overall ATT (post-treatment average)
attgtThe original ATT(g,t) data frame
sim <- sim_rdstagger(n = 300, nperiods = 6, n_cohorts = 2, true_direct = 0.3, seed = 42) res <- rdstagger_attgt(data = sim$data, yname = "y", xname = "x", gname = "g", tname = "period", idname = "id", bw = 1.5, boot = FALSE) # Event study agg_dyn <- rdstagger_agg(res, type = "dynamic") print(agg_dyn) plot(agg_dyn) # Overall ATT agg_ov <- rdstagger_agg(res, type = "overall") print(agg_ov)sim <- sim_rdstagger(n = 300, nperiods = 6, n_cohorts = 2, true_direct = 0.3, seed = 42) res <- rdstagger_attgt(data = sim$data, yname = "y", xname = "x", gname = "g", tname = "period", idname = "id", bw = 1.5, boot = FALSE) # Event study agg_dyn <- rdstagger_agg(res, type = "dynamic") print(agg_dyn) plot(agg_dyn) # Overall ATT agg_ov <- rdstagger_agg(res, type = "overall") print(agg_ov)
Main estimation function for the staggered RD framework with network
interference. Estimates ATT(g, t) — the average treatment effect for
cohort at time — separately for direct effects on
treated units and spillover effects on their network neighbors, within
an RD bandwidth around the cutoff.
rdstagger_attgt( data, yname, xname, cutoff = 0, gname, tname, idname, network = NULL, bw = "optimal", control_group = c("nevertreated", "notyetreated"), xformla = NULL, doubly_robust = TRUE, boot = TRUE, nboot = 999L, alpha = 0.05, kernel = c("triangular", "epanechnikov", "uniform") )rdstagger_attgt( data, yname, xname, cutoff = 0, gname, tname, idname, network = NULL, bw = "optimal", control_group = c("nevertreated", "notyetreated"), xformla = NULL, doubly_robust = TRUE, boot = TRUE, nboot = 999L, alpha = 0.05, kernel = c("triangular", "epanechnikov", "uniform") )
data |
A |
yname |
Character. Outcome variable name. |
xname |
Character. Running variable name. |
cutoff |
Numeric. RD cutoff. Default |
gname |
Character. Cohort variable name ( |
tname |
Character. Time period variable name. |
idname |
Character. Unit identifier variable name. |
network |
Matrix or |
bw |
Numeric or |
control_group |
Character. Which units form the control group.
|
xformla |
Formula or |
doubly_robust |
Logical. Use doubly-robust estimator. Default |
boot |
Logical. Compute bootstrap standard errors. Default |
nboot |
Integer. Number of bootstrap replications. Default |
alpha |
Numeric. Significance level for confidence intervals.
Default |
kernel |
Character. RD kernel. Default |
An object of class "rdstagger_attgt", a list with:
attgtData frame of ATT(g,t) estimates (direct effects)
spillgtData frame of spillover ATT(g,t) estimates
(if network supplied)
argsList of call arguments
bandwidthBandwidth used
sim <- sim_rdstagger(n = 300, nperiods = 6, n_cohorts = 2, true_direct = 0.3, true_spill = 0.1, seed = 42) res <- rdstagger_attgt( data = sim$data, yname = "y", xname = "x", cutoff = 0, gname = "g", tname = "period", idname = "id", network = sim$network, bw = 1.5, boot = FALSE ) print(res)sim <- sim_rdstagger(n = 300, nperiods = 6, n_cohorts = 2, true_direct = 0.3, true_spill = 0.1, seed = 42) res <- rdstagger_attgt( data = sim$data, yname = "y", xname = "x", cutoff = 0, gname = "g", tname = "period", idname = "id", network = sim$network, bw = 1.5, boot = FALSE ) print(res)
Computes an optimal bandwidth for each cohort-time cell using the
mean-squared-error-optimal bandwidth selector from rdrobust.
Separate bandwidths are estimated for pre-treatment and post-treatment
periods to ensure appropriate comparison groups.
rdstagger_bw( data, yname, xname, cutoff = 0, gname, tname, kernel = c("triangular", "epanechnikov", "uniform"), bw_common = FALSE )rdstagger_bw( data, yname, xname, cutoff = 0, gname, tname, kernel = c("triangular", "epanechnikov", "uniform"), bw_common = FALSE )
data |
A |
yname |
Character. Name of the outcome variable column. |
xname |
Character. Name of the running variable column. |
cutoff |
Numeric. The RD cutoff value. Default 0. |
gname |
Character. Name of the cohort variable column
( |
tname |
Character. Name of the time period column. |
kernel |
Character. Kernel for RD estimation. One of
|
bw_common |
Logical. If |
A list with elements:
bw_matrixA matrix of bandwidths with rows = cohorts, columns = time periods
bw_commonSingle common bandwidth (median across cells)
bw_summaryA data.frame summarising bandwidths
by cohort and period
sim <- sim_rdstagger(n = 400, nperiods = 6, n_cohorts = 2, seed = 42) bw <- rdstagger_bw(data = sim$data, yname = "y", xname = "x", cutoff = 0, gname = "g", tname = "period") bw$bw_common bw$bw_summarysim <- sim_rdstagger(n = 400, nperiods = 6, n_cohorts = 2, seed = 42) bw <- rdstagger_bw(data = sim$data, yname = "y", xname = "x", cutoff = 0, gname = "g", tname = "period") bw$bw_common bw$bw_summary
Tests the pre-treatment parallel trends assumption within the RD
bandwidth. Performs a joint test across all pre-treatment cohort-time
cells and individual cell tests, analogous to pretest in the
did package but adapted for the staggered RD setting.
rdstagger_pretest(x, method = c("joint", "individual", "both"))rdstagger_pretest(x, method = c("joint", "individual", "both"))
x |
An object of class |
method |
Character. Test method: |
A list with elements:
jointJoint test statistic, df, and p-value (if requested)
individualData frame of per-cell tests (if requested)
passesLogical. TRUE if joint test p-value > 0.05
sim <- sim_rdstagger(n = 400, nperiods = 8, n_cohorts = 2, true_direct = 0.3, seed = 42) res <- rdstagger_attgt(data = sim$data, yname = "y", xname = "x", gname = "g", tname = "period", idname = "id", bw = 1.5, boot = FALSE) pt <- rdstagger_pretest(res) print(pt)sim <- sim_rdstagger(n = 400, nperiods = 8, n_cohorts = 2, true_direct = 0.3, seed = 42) res <- rdstagger_attgt(data = sim$data, yname = "y", xname = "x", gname = "g", tname = "period", idname = "id", bw = 1.5, boot = FALSE) pt <- rdstagger_pretest(res) print(pt)
Estimates the spillover (indirect) treatment effects on network neighbors of treated units within the RD bandwidth. Spillover effects are estimated separately for each cohort-time cell.
rdstagger_spillover( data, yname, xname, cutoff = 0, gname, tname, idname, network, bw, kernel = c("triangular", "epanechnikov", "uniform"), boot = TRUE, nboot = 999L, alpha = 0.05 )rdstagger_spillover( data, yname, xname, cutoff = 0, gname, tname, idname, network, bw, kernel = c("triangular", "epanechnikov", "uniform"), boot = TRUE, nboot = 999L, alpha = 0.05 )
data |
A |
yname |
Character. Outcome variable name. |
xname |
Character. Running variable name. |
cutoff |
Numeric. RD cutoff. Default |
gname |
Character. Cohort variable name. |
tname |
Character. Time period variable name. |
idname |
Character. Unit identifier variable name. |
network |
Matrix. |
bw |
Numeric. Bandwidth around the cutoff. |
kernel |
Character. RD kernel. Default |
boot |
Logical. Bootstrap standard errors. Default |
nboot |
Integer. Bootstrap replications. Default |
alpha |
Numeric. Significance level. Default |
A data.frame with columns:
cohortTreatment cohort
periodTime period
spill_attSpillover ATT estimate
seStandard error
ci_lower, ci_upper
Confidence interval
pvalp-value
n_exposedNumber of exposed neighbors
sim <- sim_rdstagger(n = 300, nperiods = 6, n_cohorts = 2, true_direct = 0.3, true_spill = 0.15, seed = 42) sp <- rdstagger_spillover( data = sim$data, yname = "y", xname = "x", gname = "g", tname = "period", idname = "id", network = sim$network, bw = 1.5, boot = FALSE ) head(sp)sim <- sim_rdstagger(n = 300, nperiods = 6, n_cohorts = 2, true_direct = 0.3, true_spill = 0.15, seed = 42) sp <- rdstagger_spillover( data = sim$data, yname = "y", xname = "x", gname = "g", tname = "period", idname = "id", network = sim$network, bw = 1.5, boot = FALSE ) head(sp)
Generates synthetic panel data suitable for testing and demonstrating
the rdstagger estimators. The data generating process features
a running variable with a cutoff-based treatment assignment, staggered
adoption across cohorts, and network spillover effects.
sim_rdstagger( n = 500, nperiods = 8, n_cohorts = 3, cutoff = 0, bw = 1, network_density = 0.1, true_direct = 0.3, true_spill = 0.1, outcome_type = c("continuous", "binary", "count"), heterogeneous_te = FALSE, seed = NULL )sim_rdstagger( n = 500, nperiods = 8, n_cohorts = 3, cutoff = 0, bw = 1, network_density = 0.1, true_direct = 0.3, true_spill = 0.1, outcome_type = c("continuous", "binary", "count"), heterogeneous_te = FALSE, seed = NULL )
n |
Integer. Number of units. Default 500. |
nperiods |
Integer. Number of time periods. Default 8. |
n_cohorts |
Integer. Number of treatment cohorts. Default 3. |
cutoff |
Numeric. RD cutoff value on the running variable. Default 0. |
bw |
Numeric. True bandwidth around the cutoff. Default 1. |
network_density |
Numeric. Probability of a network tie between any two units (Erdos-Renyi model). Must be in (0, 1). Default 0.1. |
true_direct |
Numeric. True direct average treatment effect. Default 0.3. |
true_spill |
Numeric. True spillover effect on network neighbors. Default 0.1. |
outcome_type |
Character. One of |
heterogeneous_te |
Logical. If |
seed |
Integer. Random seed for reproducibility. Default |
A list with three elements:
dataA data.frame with columns:
id, period, y, x (running variable),
g (cohort, Inf for never-treated),
treated, neighbor_treated, spillover_share
networkAn adjacency matrix
true_paramsA list of the true parameter values used to generate the data
# Basic continuous outcome sim <- sim_rdstagger(n = 300, nperiods = 6, n_cohorts = 2, true_direct = 0.3, true_spill = 0.1, seed = 42) head(sim$data) sim$true_params # Binary outcome sim_bin <- sim_rdstagger(n = 500, nperiods = 8, n_cohorts = 3, outcome_type = "binary", seed = 123) table(sim_bin$data$y) # Count outcome sim_cnt <- sim_rdstagger(n = 400, nperiods = 6, n_cohorts = 2, outcome_type = "count", true_direct = 0.5, seed = 999)# Basic continuous outcome sim <- sim_rdstagger(n = 300, nperiods = 6, n_cohorts = 2, true_direct = 0.3, true_spill = 0.1, seed = 42) head(sim$data) sim$true_params # Binary outcome sim_bin <- sim_rdstagger(n = 500, nperiods = 8, n_cohorts = 3, outcome_type = "binary", seed = 123) table(sim_bin$data$y) # Count outcome sim_cnt <- sim_rdstagger(n = 400, nperiods = 6, n_cohorts = 2, outcome_type = "count", true_direct = 0.5, seed = 999)