Skip to content

Repository files navigation

ppsynth — Partial pooling for synthetic control with multiple outcomes

Stata and Python implementations of the estimator in Grier (2026), "Correlated Weights: Partial Pooling for Synthetic Control with Multiple Outcomes" (working paper: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7149398; PDF: correlated_weights_wp.pdf in this repository).

A study that applies the synthetic control method to several outcomes faces a choice: fit each outcome its own donor weights, or impose one common set on all of them. ppsynth makes the middle available. Each outcome gets its own weights, their dispersion around a common centroid is penalized, no single penalty is ever selected (the estimator aggregates over a grid by leave-one-out exponential weighting), and the resulting mass profile reports how much pooling the data support. One command returns the per-outcome effects, the weights, the mass profile, and whole-pipeline placebo p-values.

Install (Stata)

  1. Download this repository (green Code button, Download ZIP) and unzip. Everything lives in one flat folder, which is exactly what Stata wants; keep it that way.
  2. In Stata, cd into that folder (or put it on your adopath).
  3. Run once: ppsynth_setup This checks the Stata-Python binding, installs the numerical dependencies, self-tests both engines, and warms the numba cache.
  4. Try it: do ppsynth_example.do (uses germany_panel.dta, included).

Requires Stata 16+ with Python integration configured (Python 3.10-3.13). ppsynth_setup diagnoses and repairs the common binding problems, including obsolete system-Python bindings, and prints exact instructions when it cannot repair automatically.

Install (Python only)

ppsynth_core.py (pure NumPy) and ppsynth_fast.py (numba, substantially faster and the one the Stata command uses) are importable directly. The two run one shared block of solver settings and return identical results:

from ppsynth_core import fit_ppsynth
fit = fit_ppsynth(Y_treated, Y_donors, T0=20, method="aggregate")

Quick start (Stata)

ppsynth outcome1 outcome2 outcome3, trunit(#) trperiod(#) ///
    id(id) time(year) inference

Output: per-outcome effects on the original scale, the aggregated weights, the mass profile over the pooling grid (the data's verdict on how much the outcomes share donor structure), and per-outcome + joint placebo p-values. help ppsynth documents every option, including method(cv) for the cross-validation companion, saving() for graphs, and the fittability guardrail.

If Stata appears frozen during inference

The placebo loop reruns the entire pipeline for every unit and blocks Stata's interface for its full duration (typically 1-5 minutes; macOS shows a spinning beachball, Windows shows "not responding" and offers to kill the program — on Windows, all output appears only when the run completes). This is normal. The computation is proceeding. Do not close Stata; the command returns on its own. Details: help ppsynth, Troubleshooting.

Versions

  • ppsynth 2.6.1 (2026-08-25): metadata-only. Moved the full version history out of the ado's starred header (which -which- prints in full) into CHANGELOG.md; -which ppsynth- now shows a two-line banner. Corrected the help file's stale internal version stamp. No numerical or behavioral change; engines identical to 2.6.0.

  • ppsynth 2.6.0 (2026-08-12): current. The default display now leads with realized weight pooling (percent reduction in weight dispersion relative to separate fitting) and the middle 50% of predictive mass on the pooling scale. The lambda-grid mass table moved behind the new massprofile option, with per-expert pooling percentages and a note that the pooling delivered by a given lambda is panel-specific. New returns: r(realized_pooling), r(pooling_q25), r(pooling_q75), r(pooling_grid). Reporting only; the estimator is unchanged.

  • ppsynth 2.5.3 (2026-08-01): Adds the donor-support check, which flags any pre-treatment period in which the treated unit falls outside the donor range (report-only, on by default, returned in r(support_warning)). The pure-NumPy engine's solver settings now match the numba engine's, so the two return bit-identical results.

  • ppsynth 2.5.1 (2026-07-18): Stata front end + numba engine with pure-NumPy fallback, exact corner solutions, aggregation default, whole-pipeline placebo inference, fittability guardrail.

  • Version history and design rationale: Appendix B of the paper.

Citing

If you use ppsynth, please cite the working paper (CITATION.cff has the metadata; a BibTeX entry is in the paper PDF's first-page footnote).

License

MIT. See LICENSE.

About

STATA / PYTHON software for partially pooling weights in SC models with multiple outcomes

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages