VanillaSC recovers the factor structure with many periods and many controls#
- Estimator:
Vanilla Synthetic Control (VanillaSC) –
mlsynth.VanillaSC(backend="outcome-only")- Source:
Ferman, B. (2021), “On the Properties of the Synthetic Control Estimator with Many Periods and Many Controls”, Journal of the American Statistical Association 116(536):1764-1772, doi:10.1080/01621459.2021.1965613.
- Replication type:
Path B – the paper’s Monte Carlo (Table 1), the SC-estimator columns.
- Status:
verified – mlsynth reproduces the Table 1 SC columns within Monte-Carlo error, and matches the paper’s own
solve.QPsolver value-for-value on a fixed draw.- Benchmark:
benchmarks/cases/ferman_manyperiods.py(source).
Why this case exists#
The other VanillaSC cases pin the estimator on empirical panels (Prop 99, the Iberian electricity market, the Swedish carbon tax). This one pins a theoretical property: what the synthetic control does as the panel gets large in both dimensions at once.
Abadie, Diamond and Hainmueller (2010) bounded the SC bias under a perfect pre-treatment fit as the number of pre-periods grows with a fixed donor pool. Ferman (2021) asks the harder question – what happens when the pre-treatment fit is imperfect and the number of donors \(J\) grows together with the number of pre-periods \(T_0\). His answer: if outcomes follow a linear factor model, the SC unit still recovers the treated unit’s factor structure, because weights diluted across a growing donor pool can reconstruct that structure even when no finite-donor combination fits the pre-period exactly. This is the regularity that justifies plain synthetic control in wide panels, and it is the behaviour this benchmark reproduces with mlsynth’s solver.
The setup#
Potential outcomes follow \(y_t = \boldsymbol{\mu} F_t + \varepsilon_t\) with \(K = 2\) Gaussian AR(1) factors (\(\rho = 0.5\)) and unit-variance idiosyncratic shocks. The treated unit and the first half of the donors load only on factor 1; the second half of the donors load only on factor 2. To reconstruct the treated unit’s factor structure the synthetic control must place (asymptotically) all its weight on the first-group donors. Treatment has no effect, so the estimated one-period-ahead effect is the bias, and \(\mathrm{se}(\widehat{\alpha})\) is its Monte-Carlo standard deviation.
Two summaries track the recovery: \(E[\widehat{\mu}_{01}]\), the total SC weight on the treated unit’s own factor group (heading to 1), and \(E[\widehat{\mu}_{02}]\), the weight on the other group (heading to 0).
Factor recovery#
Running mlsynth’s outcome-only solver over the paper’s grid (200 replications per cell, seed 47) reproduces the Table 1 SC columns. The weight on the treated unit’s own factor group, \(E[\widehat{\mu}_{01}]\), climbs toward 1 as \(J\) and \(T_0\) grow together (Panel B, \(T_0 = 2J\)):
\(J\) |
mlsynth |
Ferman Table 1 |
|---|---|---|
4 |
0.769 |
0.753 |
10 |
0.814 |
0.831 |
50 |
0.922 |
0.922 |
100 |
0.944 |
0.944 |
Panel A (\(T_0 = J + 5\), so donors and pre-periods stay roughly matched) tells the same story: \(E[\widehat{\mu}_{01}]\) runs 0.777 / 0.806 / 0.894 / 0.937 against the paper’s 0.760 / 0.817 / 0.905 / 0.929. Across both panels the recovery is strictly increasing in \(J\), and the misallocated weight \(E[\widehat{\mu}_{02}]\) falls to about 0.06 at \(J = 100\) – the paper’s Proposition 3.1 in the simulation.
Precision, and why the constraint matters#
The paper’s foil is unconstrained OLS, which also drives \(E[\widehat{\mu}_{01}] \to 1\) but pays for it in variance. As \(J\) grows with \(T_0\) (Panel A), the SC effect’s standard deviation shrinks (from \(\approx 1.27\) at \(J = 4\) to \(\approx 1.10\) at \(J = 100\)) while the OLS standard deviation grows about threefold (\(1.62 \to 4.98\)) – so SC’s is roughly a fifth of OLS’s at \(J = 100\). The simplex constraint (non-negative weights summing to one) is what buys synthetic control that precision; it is the practical reason to prefer SC over an unconstrained regression on the donors in a wide panel.
Same estimator as the paper#
Ferman’s simulation solves the SC weights with R’s solve.QP (the
Goldfarb-Idnani quadratic program in aux.R’s synth_control_est). On a
fixed draw (\(J = 10\), \(T_0 = 20\)) mlsynth’s outcome-only VanillaSC –
the solver mlsynth.VanillaSC dispatches to – returns those weights to
\(7 \times 10^{-11}\). mlsynth is not approximating the paper’s estimator; it
is the paper’s estimator.
Reproduce#
python benchmarks/run_benchmarks.py --case ferman_manyperiods
The case is self-contained: the factor-model DGP is ported from Ferman’s
main.R and runs mlsynth’s own solver – no external data. The pinned targets
are the published Table 1 numbers (reference bundle
benchmarks/reference/ferman_manyperiods/), independently cross-checked by
aggregating the authors’ supplementary results.csv (40,000 rows), which
reproduces every SC cell to \(\le 0.001\). A clean-room R reimplementation
regenerates the table from scratch:
# base R + quadprog (CRAN firewalled in CI; git clone the package):
# git clone --depth 1 https://github.com/cran/quadprog && R CMD INSTALL quadprog
Rscript benchmarks/R/ferman_manyperiods.R 5000