Synthetic control
polars_ts.causal.synthetic_control
Synthetic Control Method for causal inference.
Constructs a counterfactual for a treated unit by finding an optimal weighted combination of donor (control) units. The treatment effect is the gap between the observed treated series and the synthetic control.
Supports:
- Classic synthetic control (Abadie et al., 2010)
- Prediction intervals via scpi-style uncertainty quantification
(Cattaneo et al., 2025)
- Built-in placebo tests across all donor units
- Covariates with covariate_role guard to prevent post-treatment
bias (issue #185)
References
Abadie, Diamond & Hainmueller (2010). Synthetic Control Methods for Comparative Case Studies.
Xu (2017). Generalized Synthetic Control Method: Causal Inference with Interactive Fixed Effects Models.
Cattaneo, Feng, Palomba & Titiunik (2025). scpi: Uncertainty Quantification for Synthetic Control Methods. J. Stat. Soft.
SyntheticControlResult
dataclass
Result container for a synthetic control analysis.
Attributes
treated_id
Identifier of the treated unit.
weights
Donor weights, shape (n_donors,).
donor_ids
List of donor unit identifiers, aligned with weights.
counterfactual
Synthetic control series for the full time range.
counterfactual_lower
Lower prediction interval for the counterfactual.
counterfactual_upper
Upper prediction interval for the counterfactual.
point_effect
Pointwise treatment effect (observed - synthetic) for the
post-intervention period.
point_effect_lower
Lower bound of pointwise effect.
point_effect_upper
Upper bound of pointwise effect.
total_effect
Sum of pointwise effects.
total_effect_lower
Lower bound of total effect.
total_effect_upper
Upper bound of total effect.
observed_post
Observed values of the treated unit in the post period.
pre_rmse
Root mean squared error in the pre-intervention period.
covariate_balance
Pre-period covariate balance between treated and synthetic
control. Mapping from covariate name to
{"treated_mean", "synthetic_mean", "abs_diff"}.
None if no covariates were used.
SyntheticControl
Synthetic Control Method estimator.
Parameters
coverage Prediction interval coverage (e.g. 0.9 for 90%). covariates Column names of covariates to use in donor weight optimization. Covariates improve matching by considering additional variables beyond the target series. covariate_role Mapping from covariate name to role:
- ``"always"``: covariate is used in weight optimization and
contributes to the post-period counterfactual.
- ``"pre_only"``: covariate is used only for pre-period weight
optimization and excluded from post-period projection
(prevents post-treatment bias).
Covariates not listed default to ``"always"`` with a warning.
id_col Column identifying each unit. time_col Column with timestamps. target_col Column with observed values.
result
property
Access the fit result.
fit(df, treated_id, intervention_date, donor_ids=None)
Fit the synthetic control model.
Parameters
df
Panel DataFrame with all units (treated + donors), including
any columns listed in covariates.
treated_id
Identifier of the treated unit.
intervention_date
First date of the post-intervention period.
donor_ids
Specific donor unit IDs. If None, all units except
treated_id are used as donors.
Returns
SyntheticControl Self, for chaining.
to_frame()
Return pointwise post-period results as a DataFrame.
Columns: step, observed, counterfactual, counterfactual_lower, counterfactual_upper, point_effect, point_effect_lower, point_effect_upper.
placebo_test(df, intervention_date)
Run placebo tests treating each donor as the treated unit.
Fits the synthetic control for every donor unit (using remaining donors as the pool) and reports the empirical distribution of placebo effects. A credible treatment effect should be larger than the placebo distribution.
Parameters
df
Same panel DataFrame used in fit().
intervention_date
Same intervention date.
Returns
pl.DataFrame Columns: unit_id, is_treated, total_effect, pre_rmse.
_solve_sc_weights(y, D)
Solve for synthetic control weights using constrained least squares.
Minimizes ||y - D @ w||^2 subject to w >= 0, sum(w) = 1. Uses SLSQP from scipy.optimize.
synthetic_control(df, treated_id, intervention_date, donor_ids=None, coverage=0.9, covariates=None, covariate_role=None, id_col='unique_id', time_col='ds', target_col='y')
Estimate the causal effect using the synthetic control method.
Convenience function wrapping :class:SyntheticControl.
Parameters
df
Panel DataFrame with all units.
treated_id
Identifier of the treated unit.
intervention_date
First date of the post-intervention period.
donor_ids
Specific donor IDs. None uses all non-treated units.
coverage
Prediction interval coverage.
covariates
Column names of exogenous covariates.
covariate_role
Mapping from covariate name to "pre_only" or "always".
id_col, time_col, target_col
Column names.
Returns
SyntheticControlResult