Skip to content

Add the cd model: Cao & Dowd synthetic control with spillovers - #1

Merged
cmg777 merged 4 commits into
mainfrom
feat/cao-dowd-replication
Oct 16, 2026
Merged

cmg777 merged 4 commits into
mainfrom
feat/cao-dowd-replication

Conversation

@HayaTaga

Copy link
Copy Markdown
Collaborator

Summary

Adds scspill's second model, method="cd": the synthetic control method with spillovers of Cao & Dowd (arXiv:1902.07343v4).

  • Estimator. An intercept-adjusted simplex SCM is fitted for every unit. A fixed exposure structure, given as affected_units or a labeled effect_matrix, then yields direct and spillover effects for each post-treatment period.
  • Inference. Andrews-type P tests of C α_t = d via test_hypothesis, pointwise intervals in effect_ci, and the κ_A specification test.
  • Data. load_california_cd(51) and load_california_cd(41) load the authors' 50-states-plus-DC Proposition 99 panel, from jcao0/synthetic-control-spillover at commit 86004020.
  • Defaults follow the authors' replication code, so a default fit reproduces the paper's Section 6. The paper's own Section 4 definitions can be selected instead (quantile_method="empirical", ci_method="inversion", reference_mode="leave_one_period_out"). The differences between the two are listed under Caveats in docs/models/cd.qmd.

sar behaves as before. It is still the default when method is omitted, and its tests are unchanged.

Commits

  1. Bundle the Cao-Dowd Proposition 99 panel. The data and the loader.
  2. Add the cd model. cd.py, the method dispatch, test_cd.py, and the reference fixtures from the authors.
  3. Document the cd model and its caveats. The model page, the catalogue, the README, llms.txt, and the CHANGELOG.
  4. Make scm_maxiter control the SLSQP iteration limit.

Where to look

  • scspill/utils/scspill_helpers/cd.py: the estimator and the tests (about 520 lines).
  • scspill/estimators/scspill.py: the method dispatch (about 20 lines).
  • scspill/tests/test_cd.py: checks against the paper and the authors' output.
  • docs/models/cd.qmd: usage, how to reproduce the paper, and the caveats.

About 2,500 of the added lines are data and fixtures copied as-is from the authors' repository: the CSVs and benchmarks/reference/cao_dowd/.

Validation

  • 51- and 41-unit fits against the authors' frozen output. P-values match exactly. Effects agree to within 0.01 packs, and the minimum eigenvalue of A'M̂A is 0.22 and 0.54 respectively.
  • Joint "no spillover" test. It rejects in 1990–1992 and 1994–2000, as in the paper's Figure 4.
  • Monte Carlo first replication. Six settings checked, plus leave-one-out and κ_A reference statistics, all compared against the authors' simulation code.
  • Local checks. make test gave 193 passed and 1 skipped; the slow tests, make lint and mypy also pass.

Caveats (documented, not bugs in this PR)

  1. Critical values. The paper defines the critical value as the left inverse of the empirical CDF, but the application code uses an interpolated quantile. With 19 pre-periods, this alone decides whether 1994–1995 reject.
  2. Interval direction. The published intervals add the signed error quantiles to the estimate. Theory says to subtract them, and the authors' own continuous-treatment code does subtract.
  3. Reference statistics. The paper recommends leave-one-out references, but the application uses full-sample ones.

HayaTaga and others added 4 commits October 14, 2026 16:04
load_california_cd(51) and load_california_cd(41) load the authors'
50-state-plus-DC cigarette-sales panel (1970-2000, treated from 1989),
copied from jcao0/synthetic-control-spillover at commit 86004020, with
the paper's exposure sets. .gitattributes keeps the authors' state list
byte-identical (BOM and CRLF).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
method="cd" fits an intercept-adjusted simplex SCM for every unit, a
fixed exposure structure (affected_units or a labeled effect_matrix),
period-specific direct and spillover effects, Andrews-type P tests for
C alpha_t = d, and the kappa_A specification test
(arXiv:1902.07343v4).

The defaults follow the authors' Proposition 99 replication code, so a
default fit reproduces the paper's Section 6 tables: interpolated
critical values (quantile_method="linear") and intervals that add the
signed pre-treatment error quantiles (ci_method="published", returned
by effect_ci). The paper's Section 4 definitions are available as
quantile_method="empirical", ci_method="inversion" and
reference_mode="leave_one_period_out".

test_cd.py checks the 51- and 41-unit fits against the authors' frozen
output, and Monte Carlo, leave-one-out and kappa_A statistics against
their simulation code; the fixtures are in benchmarks/reference/cao_dowd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds docs/models/cd.qmd: usage, inference options, how to reproduce
Tables S.6-S.8 and Figure 4, and the three places where the authors'
application code departs from the paper's Section 4 definitions. The
model catalogue, README, datasets page, API reference, llms.txt and
CHANGELOG now list cd as implemented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The solver applied max(3000, scm_maxiter), so the field (default 900)
had no effect. Default it to 3000 and pass it through unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HayaTaga
HayaTaga requested a review from cmg777 October 14, 2026 07:20

@cmg777 cmg777 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — validated locally (tests, slow test, lint, mypy, fixture checksums). Merging as-is; follow-up PRs will fold cd into the package's single-config / per-model-subpackage architecture, switch defaults to the paper's (v3) definitions with your application-code behaviour kept as switches, and extend it to mlsynth's cd feature set.

@cmg777
cmg777 merged commit 17fa959 into main Oct 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants