scm_balance_frontier: Balance Possibility Frontier for Staggered SCM

View source: R/scm_frontier.R

scm_balance_frontierR Documentation

Balance Possibility Frontier for Staggered SCM

Description

Traces how the two pre-treatment imbalances of partially pooled SCM trade off as the pooling parameter nu varies: q_pool, the imbalance of the average of the treated units, which the aggregate ATT rests on, and q_sep, the root mean square of the per-block imbalances, which the block-level effects rest on. Both are the cohort-level measures of Ben-Michael, Feller & Rothstein (2022, Appendix A.2), in which a block enters through the sum over its treated units, so a block of n_g units counts n_g times towards q_pool and n_g^2 times towards q_sep. Supplying weights to scm_fit() replaces the unit count with the mass it gives them, and the frontier is traced on that same objective.

Usage

scm_balance_frontier(fit, nu_grid = seq(0, 1, length.out = 11L))

Arguments

fit

A staggered coresynth object from scm_fit() with method = "scm".

nu_grid

Values of nu to trace.

Details

Ben-Michael, Feller & Rothstein (2022) call this plot "an important tool for using partially pooled SCM in practice" (S.4.2): the curve is strongly convex, so a little pooling usually buys a large reduction in one imbalance for a small rise in the other, and where that bargain stops being worth it is a judgement the data can inform but not settle. Their nu heuristic, which scm_fit() uses by default, is one point on this curve.

A fit made with cov_method = "balance" is traced on the objective that includes its covariate term, and the frontier reports that term's two measures in q_cov_sep and q_cov_pool. The plot still draws the outcome imbalances, which are then only part of what the fit trades off.

Value

A data.frame of class scm_frontier, one row per nu, with nu, q_sep, q_pool, their values normalised by the separate solution (q_sep_rel, q_pool_rel), estimate, and heuristic marking the row closest to the fit's own nu. A fit made with cov_method = "balance" trades off a covariate imbalance as well, so its frontier carries q_cov_sep and q_cov_pool too, and the two normalisation constants are then the combined measure of the paper's footnote 7 rather than the outcome imbalance alone – which is why q_sep_rel and q_pool_rel are not 1 at nu = 0 there. Plot it with plot().

References

Ben-Michael, E., Feller, A., & Rothstein, J. (2022). Synthetic controls with staggered adoption. JRSS-B, 84(2), 351-381.

Examples

set.seed(1)
panel <- expand.grid(unit = 1:15, year = 1:20)
adopt <- c(`1` = 12, `2` = 15, `3` = 17)
panel$treated <- as.integer(!is.na(adopt[as.character(panel$unit)]) &
  panel$year >= adopt[as.character(panel$unit)])
panel$treated[is.na(panel$treated)] <- 0L
panel$gdp <- panel$unit + 0.4 * panel$year +
  rnorm(nrow(panel)) + 2 * panel$treated
fit <- scm_fit(gdp ~ treated | unit + year, data = panel, method = "scm")
fr <- scm_balance_frontier(fit, nu_grid = c(0, 0.5, 1))
fr

coresynth documentation built on Oct. 11, 2026, 5:07 p.m.