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
coresynthobject fromscm_fit()withmethod = "scm".- nu_grid
Values of
nuto trace.
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().
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.
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
#> === SCM balance possibility frontier ===
#> nu used by the fit: 0.5635
#> q_pool: imbalance of the treated average (drives the ATT)
#> q_sep : RMS of the per-block imbalances (drives block-level effects)
#>
#> nu q_sep q_pool estimate q_sep_rel q_pool_rel heuristic
#> 0.0 0.6310 0.3243 1.8017 1.0000 1.0000
#> 0.5 0.6833 0.2193 1.8730 1.0829 0.6762 <- nu_hat
#> 1.0 0.8579 0.1810 1.8449 1.3596 0.5582
