Skip to content

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