Bayesian Synthetic Control of Martinez & Vives-i-Bastida (MVBBSC)#
When to use MVBBSC – and when not to#
MVBBSC is the Bayesian synthetic control of Martinez and Vives-i-Bastida (2024). It keeps the standard synthetic-control model – the untreated outcome of the treated unit is a simplex-weighted average of the donor pool – but replaces the single optimized weight vector with a posterior over weights, so every quantity it reports (the counterfactual, the per-period effect, the ATT) arrives with a credible band rather than a point. Reach for it when you want the interpretable, non-extrapolating fit of a classical synthetic control and honest uncertainty on the effect, drawn from one coherent Bayesian model. It is a good choice when:
You want donor weights that are non-negative and sum to one – the same convex-combination the classical method reports – but with a full posterior, not a single solve.
You want a principled interval. Under the paper’s conditions the Bayesian credible interval is also a valid frequentist confidence interval (a Bernstein-von Mises result), so the band is usable inference, not just a visual aid.
The treated unit lies inside (or near) the donors’ convex hull, so a weighted average is the right model and a good pre-period fit is attainable.
Do not use MVBBSC when:
The treated unit is outside the donor hull. A hard simplex cannot reach it and the pre-period fit will be poor; a factor model (Bayesian Factor Synthetic Control (BFSC), Factor Model Approach (FMA)) or an unconstrained-weight method (Bayesian Synthetic Control Methods (BSCM)) is the better model there.
You need a dependency-free estimate. MVBBSC draws its posterior with NUTS (NumPyro), so it needs the
[bayes]optional dependency (pip install 'mlsynth[bayes]'). For a dependency-free Bayesian SC use Bayesian Synthetic Control Methods (BSCM) (a pure-numpy Gibbs sampler).
The Bayesian SC family#
mlsynth carries four Bayesian synthetic-control estimators; they differ in what they place a prior on and whether the weights are constrained to the simplex.
Bayesian Synthetic Control Methods (BSCM) (Kim, Lee and Gupta) – shrinkage (horseshoe or spike-and-slab) on unconstrained donor weights; a pure-numpy Gibbs sampler.
Bayesian Synthetic Control with a Soft Simplex Constraint (BVS-SS) (Xu and Zhou) – spike-and-slab donor selection on a soft simplex whose tightness is learned; reports inclusion probabilities.
Bayesian Factor Synthetic Control (BFSC) (Pinkney) – a Bayesian latent-factor model, not a donor weighting; reports a counterfactual band and no donor weights.
Bayesian Synthetic Control of Martinez & Vives-i-Bastida (MVBBSC) (Martinez and Vives-i-Bastida) – a uniform prior over the hard simplex, standardized internally, with the frequentist-inference (Bernstein-von Mises) guarantee.
Reach for MVBBSC when you want the classical convex-combination model with a principled interval; reach for Bayesian Synthetic Control with a Soft Simplex Constraint (BVS-SS) when you additionally want the model to select which donors enter; reach for Bayesian Synthetic Control Methods (BSCM) when you are willing to drop the simplex for shrinkage on unconstrained weights; reach for Bayesian Factor Synthetic Control (BFSC) when a shared factor structure, not a weighted average, is the right model.
Notation#
We use the synthetic-control canon. Let \(j = 1\) denote the treated unit and \(\mathcal{N}_0 \coloneqq \{2, \ldots, J+1\}\) the donor pool; time is \(t \in \mathcal{T} \coloneqq \{1, \ldots, T\}\) with the intervention after \(T_0\), pre-period \(\mathcal{T}_1\) and post-period \(\mathcal{T}_2\). Write \(y_{1t}\) for the treated outcome and \(\mathbf{y}_{0t} = (y_{2t}, \ldots, y_{J+1,t})^{\top}\) for the donor outcomes at \(t\). MVBBSC models the no-intervention outcome as a simplex-weighted average of the donors,
where \(\Delta^{J}\) is the unit simplex (\(w_j \ge 0,\ \sum_j w_j = 1\)).
Prior. The paper’s characterization of when the risk minimizer is a synthetic control motivates placing the weights in the simplex; MVBBSC does so with a uniform Dirichlet prior and a weakly-informative scale,
Standardization. Before fitting, the treated series and every donor series are centered on their pre-period mean and scaled by their pre-period standard deviation; the counterfactual is transformed back to the outcome scale afterwards. No post-period information enters the scaling, so it cannot bias the treated estimate, and because the fit happens on standardized data the estimate is invariant to the units of the outcome.
Counterfactual. The posterior is drawn with NUTS. The counterfactual is the posterior-predictive draw of the untreated outcome, \(\widehat{y}_{1t}^{N,(m)} = \mathbf{y}_{0t}^{\top}\mathbf{w}^{(m)} + \eta_t^{(m)}\) with \(\eta_t^{(m)} \sim \mathcal{N}(0, \sigma^{(m)2})\) for each posterior draw \(m\), so its pointwise quantiles are a credible band. The per-period effect is \(\tau_t \coloneqq y_{1t} - \widehat{y}_{1t}^N\) and the ATT is \(\widehat{\tau} \coloneqq T_2^{-1}\sum_{t\in\mathcal{T}_2}\tau_t\), each with a full posterior credible band.
Assumptions#
Single treated unit, balanced panel. One unit is treated after \(T_0\); every unit is observed at every period.
Remark. The setup boundary (
mlsynth.utils.mvbbsc_helpers.setup) enforces both throughdataprep(), translating a violation (including a second treated cohort) toMlsynthDataError.The treated unit is a convex combination of the donors. There exist simplex weights under which the donors reproduce the treated unit’s pre-period path.
Remark. This is the classical synthetic-control design assumption. When it fails – the treated unit outside the donors’ hull – the hard simplex cannot track the pre-period, the fit is poor, and (as the paper stresses) the estimate is likely biased. A poor pre-period fit is the diagnostic; read it before trusting the effect.
Gaussian idiosyncratic noise. \(\varepsilon_t \sim \mathcal{N}(0, \sigma^2)\) with a single \(\sigma\).
Remark. The Gaussian likelihood is what the paper’s Bernstein-von Mises argument uses to link the Bayesian posterior predictive to the frequentist sampling distribution as \(T_0, J \to \infty\).
Inference and diagnostics#
MVBBSC is inferential by construction. res.inference.ci_lower /
res.inference.ci_upper give the ATT credible interval, and the counterfactual
band is on res.inference_detail (counterfactual_lower /
counterfactual_upper). Under the paper’s conditions this credible interval is
also a valid confidence interval, so it can be read as frequentist inference.
NUTS diagnostics are surfaced on res.weights.summary_stats –
nuts_accept_prob, nuts_divergences, max_rhat – alongside
sigma_post_mean. The posterior-mean simplex weights are on
res.weights.donor_weights. Read the pre-period fit
(res.fit_diagnostics.rmse_pre) first: a large value means the treated unit is
not in the donors’ hull and the effect should not be trusted.
Example#
import pandas as pd
from mlsynth import MVBBSC # requires: pip install 'mlsynth[bayes]'
df = pd.read_csv(
"https://raw.githubusercontent.com/jgreathouse9/mlsynth/main/"
"basedata/german_reunification.csv")
df["treat"] = df["Reunification"].astype(int)
res = MVBBSC({
"df": df, "outcome": "gdp", "treat": "treat",
"unitid": "country", "time": "year",
"seed": 0, "display_graphs": False,
}).fit()
print(f"pre-period RMSE : {res.fit_diagnostics.rmse_pre:.1f}")
print(f"mean post ATT : {res.att:+.0f}")
print(f"95% credible : [{res.inference.ci_lower:+.0f}, {res.inference.ci_upper:+.0f}]")
print(f"NUTS max r-hat : {res.weights.summary_stats['max_rhat']:.3f}")
On West Germany this fits the pre-1990 path to an RMSE near \(62\) (in PPP-USD per capita) and estimates a mean post-1990 effect near \(-2080\) – about a \(7.5\%\) decline in per-capita GDP – with a 95% credible band of roughly \([-2600, -1600]\) that widens through the post-period as the counterfactual extrapolates. The posterior weight is spread across Norway, Italy, Austria, and the USA.
Verification#
The MVBBSC model is cross-validated against the reference bsynth R package
(Martinez and Vives-i-Bastida’s own implementation). On the German reunification
panel a fresh NumPyro fit matches the bsynth posterior to Monte-Carlo error:
pre-period RMSE \(62.2\) vs \(62.2\), mean post-ATT \(-2078\) vs
\(-2075\), and the 95% credible band agrees year-by-year to within a few
percent (ratios \(0.95\)–\(1.07\)), with in-sample coverage of the
observed series at \(96.7\%\) against the nominal \(95\%\). See the
replication page MVBBSC – Bayesian Synthetic Control (Martinez & Vives-i-Bastida 2024) and the durable case
benchmarks/cases/mvbbsc_germany.py. The estimator, config validation, the
missing-dependency guard, effect recovery, scale invariance, and the result
contract are unit-tested (mlsynth/tests/test_mvbbsc.py).
Core API#
Bayesian synthetic control of Martinez & Vives-i-Bastida (MVBBSC).
Implements:
Martinez, I. & Vives-i-Bastida, J. (2024). “Bayesian and Frequentist Inference for Synthetic Controls.” arXiv:2206.01779.
MVBBSC models the treated unit’s no-intervention outcome as a simplex-weighted
average of the donor pool with a uniform Dirichlet prior on the weights, a
HalfNormal idiosyncratic scale, and a Gaussian likelihood on the
pre-treatment window. Both the treated and donor series are standardized by
their pre-period moments before fitting and the counterfactual is transformed
back to the outcome scale, so the fit is invariant to the units of the outcome.
The posterior is drawn with NUTS (NumPyro); the counterfactual is the
posterior-predictive draw of the untreated outcome, giving a full credible band
and an ATT credible interval. MVBBSC therefore needs the [bayes] optional
dependency.
See mlsynth.utils.mvbbsc_helpers for the algorithmic pieces.
- class mlsynth.estimators.mvbbsc.MVBBSC(config: MVBBSCConfig | dict)#
Bases:
objectBayesian synthetic control of Martinez & Vives-i-Bastida (2024).
- Parameters:
config (MVBBSCConfig or dict) – Configuration object. See
mlsynth.config_models.MVBBSCConfig.- Returns:
MVBBSCResults – Posterior counterfactual with a credible band, the ATT posterior mean + credible interval, the posterior-mean simplex donor weights, and NUTS diagnostics. Requires
mlsynth[bayes].
- fit() MVBBSCResults#
Run the MVBBSC NUTS sampler and assemble results.
Configuration#
- class mlsynth.config_models.MVBBSCConfig(*, df: ~pandas.DataFrame, outcome: str, treat: str, unitid: str, time: str, display_graphs: bool = False, save: bool | str = False, counterfactual_color: ~typing.List[str] = <factory>, treated_color: str = 'black', plot: ~mlsynth.config_models.PlotConfig = <factory>, n_warmup: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)] = 1000, n_samples: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)] = 1000, n_chains: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)] = 4, target_accept: ~typing.Annotated[float, ~annotated_types.Gt(gt=0.0), ~annotated_types.Lt(lt=1.0)] = 0.8, ci_alpha: ~typing.Annotated[float, ~annotated_types.Gt(gt=0.0), ~annotated_types.Lt(lt=1.0)] = 0.05, seed: int = 0, verbose: bool = False)#
Configuration for the Martinez–Vives-i-Bastida Bayesian synthetic control.
Implements the outcome-only Bayesian synthetic control of
Martinez, I. & Vives-i-Bastida, J. (2024). “Bayesian and Frequentist Inference for Synthetic Controls.” arXiv:2206.01779.
The no-intervention outcome of the treated unit is modelled as a simplex-weighted average of the donor pool with a uniform Dirichlet prior on the weights (
w ~ Dir(1)), aHalfNormalidiosyncratic scale (sigma), and a Gaussian likelihood on the pre-treatment window. Both the treated and donor series are standardized by their pre-period mean and standard deviation before fitting and the counterfactual is transformed back to the outcome scale, so the fit is invariant to the units of the outcome. The posterior is drawn with NUTS (NumPyro) and the counterfactual is the posterior-predictive draw of the treated outcome in absence of treatment, giving a full credible band and an ATT credible interval. This estimator therefore requires the[bayes]optional dependency (pip install mlsynth[bayes]).- Parameters:
n_warmup (int) – NUTS warm-up (adaptation) iterations per chain.
n_samples (int) – Retained NUTS samples per chain.
n_chains (int) – Number of NUTS chains.
target_accept (float) – NUTS target acceptance probability.
ci_alpha (float) – Two-sided level for the credible interval (0.05 -> 95% band).
seed (int) – PRNG seed (makes a fit reproducible, including the posterior-predictive draw used for the bands).
display_graphs (bool) – Plot the observed-vs-counterfactual path with the credible band.
verbose (bool) – Show the NUTS progress bar.
- model_config: ClassVar[ConfigDict] = {'arbitrary_types_allowed': True, 'extra': 'forbid'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].