Augmented Synthetic Control Method¶
The Augmented Synthetic Control Method is due to Ben-Michael, Feller & Rothstein [BMFR21] and adapts the Synthetic Control Method in an effort to adjust for poor pre-treatment fit.
The authors do this by adjusting the Synthetic Control Method estimate by adding a term that is an imbalance in a particular function of the pre-treatment outcomes. In the Ridge Augmented Synthetic Control Method this function is linear in the pre-treatment outcomes and fit by ridge regression of the control post-treatment outcomes against pre-treatment outcomes.
In particular, the method constructs a vector of weights \(w = (w_1, w_2, \dots, w_k)\) such that
where \(w_\mathrm{scm}\) are the weights obtained from the standard Synthetic Control Method and \(w_\mathrm{aug}\) are augmentations that are included when the treated unit lies outside the convex hull defined by the control units. The weights may be negative and larger than 1, the degree of extrapolation is controlled by a ridge parameter \(\lambda\).
In general, this method will obtain weights at least as good as the synthetic control method in terms of pre-treatment fit.
The AugSynth class¶
The AugSynth class implements the Ridge Augmented
Synthetic Control Method. The expected way to use the class is to first create a
Dataprep object that defines the study data and
then use it as input to a AugSynth object. See the
examples folder
of the repository for examples illustrating usage.
The implementation follows the same algorithm as the R
augsynth package with the option
progfunc="Ridge" and aims to produce results that can be reconciled with
that package, in particular:
The pre-treatment outcomes are centered at the control unit mean per time period. If covariates are included (
use_covariates=True, the default), they are centered at the control unit mean, scaled by the ratio of the standard deviation of the centered control outcomes to their own standard deviation, and concatenated with the pre-treatment outcomes into a single design matrix.The synthetic control weights \(w_\mathrm{scm}\) are obtained from the usual quadratic minimization problem (weights that sum to 1 and are non-negative) on this design matrix, so that the covariates are balanced jointly with the outcomes.
With
residualize=True(theresidualize = TRUEoption of the R package), the covariates are not balanced directly: the centered pre-treatment outcomes are regressed on the centered (unscaled) covariates with OLS on the control units and the residuals form the design matrix; after the ridge step the covariates are re-added to the weights exactly, so that the final weights balance the covariates exactly. The weights before the re-add are stored inno_cov_weights.The augmentation \(w_\mathrm{aug}\) is obtained by ridge regression of the imbalance of the synthetic control fit on the design matrix, with the ridge parameter \(\lambda\) controlling the degree of extrapolation.
When
lambda_is not supplied, it is selected by cross-validation over a grid of 21 log-spaced values generated from the largest singular value of the design matrix. In each fold a pre-treatment time period is held out and the weights are re-fit on the remaining time periods; the cross-validation error measures how well the augmented weights predict the treated unit’s held-out pre-treatment outcomes. The 1-standard-error rule is used to choose the final value.The pre-treatment periods are exactly the time periods before the first post-treatment period, i.e.
dataprep.time_optimize_ssrmust contain all of the time periods that occur before the treatment time.
In addition to the weights, the fit computes the ridge_mhat outcome model:
a ridge regression of the post-treatment control outcomes on the design
matrix, using the same \(\lambda\). The model is used to estimate the bias
of the augmented weights in each post-treatment period, with
bias_est = mhat[treated] - synw @ mhat[controls] (using the synthetic
control weights) and avg_bias the average over the post-treatment periods
- reproducing the Avg Estimated Bias diagnostic of the R package.
The fit also stores the pre-treatment fit diagnostics of the R package:
l2_imbalance, unif_l2_imbalance and scaled_l2_imbalance (computed
on the original outcome matrices, so that 1 - scaled_l2_imbalance is the
percentage improvement over uniform weights) and, when covariates are used,
covariate_l2_imbalance and scaled_covariate_l2_imbalance.
- class pysyncon.AugSynth¶
Implementation of the augmented synthetic control method due to Ben- Michael, Feller & Rothstein [BMFR21].
The implementation follows the augsynth R package with the option progfunc=”Ridge”. Both the direct-balance covariate option and the residualize=TRUE covariate option of the R package are supported.
In addition to the weights, the fit computes the ridge_mhat outcome model (a ridge regression of the post-treatment control outcomes on the design matrix), which is used for the bias_est/avg_bias diagnostics.
The fit also stores the pre-treatment fit diagnostics of the R package: l2_imbalance, unif_l2_imbalance and scaled_l2_imbalance (on the original outcome matrices) and, when covariates are used, covariate_l2_imbalance and scaled_covariate_l2_imbalance.
- att(time_period: Iterable | Series | dict, Z0: DataFrame | None = None, Z1: Series | None = None) dict[str, float]¶
Computes the average treatment effect on the treated unit (ATT) and the standard error to the value over the chosen time-period.
- Parameters:
time_period (Iterable | pandas.Series | dict, optional) – Time period to compute the ATT over.
Z0 (pandas.DataFrame, shape (n, c), optional) – The matrix of the time series of the outcome variable for the control units. If no dataprep is set, then this must be supplied along with Z1, by default None.
Z1 (pandas.Series, shape (n, 1), optional) – The matrix of the time series of the outcome variable for the treated unit. If no dataprep is set, then this must be supplied along with Z0, by default None.
- Returns:
A dictionary with the ATT value and the standard error to the ATT.
- Return type:
dict
- Raises:
ValueError – If there is no weight matrix available
ValueError – If there is no
Dataprepobject set or (Z0, Z1) is not supplied
- fit(dataprep: Dataprep, lambda_: float | None = None, use_covariates: bool = True, residualize: bool = False) None¶
Fit the model/calculate the weights.
- Parameters:
dataprep (Dataprep, optional) –
Dataprepobject containing data to model.lambda (float, optional) – Ridge parameter to use. If not supplied, then it is obtained by cross-validation, by default None
use_covariates (bool, optional) – Whether or not to include the covariates from the
Dataprepobject in the design matrix (concatenated with the pre-treatment outcomes, so that the covariates are balanced jointly with the outcomes), by default Trueresidualize (bool, optional) – Whether to residualize the covariates instead of balancing them directly (the residualize = TRUE option of the augsynth R package): the centered pre-treatment outcomes are regressed on the centered covariates (OLS, using the control units) and the residuals are used as the design matrix, with the covariates re-added to the weights exactly after the ridge step, by default False
- Raises:
ValueError – if dataprep.time_optimize_ssr is not exactly the pre-treatment time periods.
ValueError – if residualize=True but there are no covariates in the
Dataprepobject (use_covariates=False or dataprep.predictors is empty).
- gaps_plot(time_period: Iterable | Series | dict | None = None, treatment_time: int | None = None, grid: bool = True, Z0: DataFrame | None = None, Z1: Series | None = None) None¶
Plots the gap between the treated unit and the synthetic unit over time.
- Parameters:
time_period (Iterable | pandas.Series | dict, optional) – Time range to plot, if none is supplied then the time range used is the time period over which the optimisation happens, by default None
treatment_time (int, optional) – If supplied, plot a vertical line at the time period that the treatment time occurred, by default None
grid (bool, optional) – Whether or not to plot a grid, by default True
Z0 (pandas.DataFrame, shape (n, c), optional) – The matrix of the time series of the outcome variable for the control units. If no dataprep is set, then this must be supplied along with Z1, by default None.
Z1 (pandas.Series, shape (n, 1), optional) – The matrix of the time series of the outcome variable for the treated unit. If no dataprep is set, then this must be supplied along with Z0, by default None.
- Raises:
ValueError – If there is no weight matrix available
ValueError – If there is no
Dataprepobject set or (Z0, Z1) is not supplied
- mae(Z0: DataFrame | None = None, Z1: Series | None = None) float¶
Returns the mean absolute error in the fit of the synthetic control versus the treated unit over the optimization time-period.
- Parameters:
Z0 (pandas.DataFrame, shape (n, c), optional) – The matrix of the time series of the outcome variable for the control units. If no dataprep is set, then this must be supplied along with Z1, by default None.
Z1 (pandas.Series, shape (n, 1), optional) – The matrix of the time series of the outcome variable for the treated unit. If no dataprep is set, then this must be supplied along with Z0, by default None.
- Returns:
Mean absolute error
- Return type:
float
- Raises:
ValueError – If the fit method has not been run (no weights available.)
ValueError – If there is no
Dataprepobject set or (Z0, Z1) is not supplied
- mape(Z0: DataFrame | None = None, Z1: Series | None = None) float¶
Returns the mean absolute percentage error in the fit of the synthetic control versus the treated unit over the optimization time-period.
- Parameters:
Z0 (pandas.DataFrame, shape (n, c), optional) – The matrix of the time series of the outcome variable for the control units. If no dataprep is set, then this must be supplied along with Z1, by default None.
Z1 (pandas.Series, shape (n, 1), optional) – The matrix of the time series of the outcome variable for the treated unit. If no dataprep is set, then this must be supplied along with Z0, by default None.
- Returns:
Mean absolute percentage error
- Return type:
float
- Raises:
ValueError – If the fit method has not been run (no weights available.)
ValueError – If there is no
Dataprepobject set or (Z0, Z1) is not supplied
- mspe(Z0: DataFrame | None = None, Z1: Series | None = None) float¶
Returns the mean square prediction error in the fit of the synthetic control versus the treated unit over the optimization time-period.
- Parameters:
Z0 (pandas.DataFrame, shape (n, c), optional) – The matrix of the time series of the outcome variable for the control units. If no dataprep is set, then this must be supplied along with Z1, by default None.
Z1 (pandas.Series, shape (n, 1), optional) – The matrix of the time series of the outcome variable for the treated unit. If no dataprep is set, then this must be supplied along with Z0, by default None.
- Returns:
Mean square prediction Error
- Return type:
float
- Raises:
ValueError – If the fit method has not been run (no weights available.)
ValueError – If there is no
Dataprepobject set or (Z0, Z1) is not supplied
- path_plot(time_period: IsinArg_t | None = None, treatment_time: int | None = None, grid: bool = True, Z0: pd.DataFrame | None = None, Z1: pd.Series | None = None, plot_args: dict[str, Any] | None = None) tuple[plt.figure.Figure, plt.axes.Axes]¶
Plot the outcome variable over time for the treated unit and the synthetic control.
- Parameters:
time_period (Iterable | pandas.Series | dict, optional) – Time range to plot, if none is supplied then the time range used is the time period over which the optimisation happens, by default None
treatment_time (int, optional) – If supplied, plot a vertical line at the time period that the treatment time occurred, by default None
grid (bool, optional) – Whether or not to plot a grid, by default True
Z0 (pandas.DataFrame, shape (n, c), optional) – The matrix of the time series of the outcome variable for the control units. If no dataprep is set, then this must be supplied along with Z1, by default None.
Z1 (pandas.Series, shape (n, 1), optional) – The matrix of the time series of the outcome variable for the treated unit. If no dataprep is set, then this must be supplied along with Z0, by default None.
plot_args (dict[str, Any], optional) –
Argument to include axis and title for the plot. For instance can do: labels = {
”title”: “My Plot Title”, “xlabel”: “X-axis Label”, “ylabel”: “Y-axis Label”
}
- Returns:
Returns a tuple with the figure and axis if the user wishes to modify the resulting plot.
- Return type:
tuple[plt.Figure, plt.Axis]
- Raises:
ValueError – If there is no weight matrix available
ValueError – If there is no
Dataprepobject set or (Z0, Z1) is not supplied
- summary(round: int = 3, X0: DataFrame | None = None, X1: Series | None = None) DataFrame¶
Generates a
pandas.DataFramewith summary data. The first column will show the mean value of each predictor over the time periodtime_predictors_priorfor the treated unit and the second column the case of the synthetic unit and finally there will be a column ‘sample mean’ that shows the mean value of each predictor over the time periodtime_predictors_prioracross all the control units, i.e. this will be the same as a synthetic control where all the weights are equal.- Parameters:
round (int, optional) – Round the table values to the given number of places, by default 3
X0 (pd.DataFrame, shape (n_cov, n_controls), optional) – Matrix with each column corresponding to a control unit and each row is a covariate. If no dataprep is set, then this must be supplied along with X1, by default None.
X1 (pandas.Series, shape (n_cov, 1), optional) – Column vector giving the covariate values for the treated unit. If no dataprep is set, then this must be supplied along with Z1, by default None.
- Returns:
Summary data.
- Return type:
pandas.DataFrame
- Raises:
ValueError – If there is no weight matrix available
ValueError – If there is no
Dataprepobject set or (Z0, Z1) is not supplied
- weights(round: int = 3, threshold: float | None = None) Series¶
Return a
pandas.Seriesof the weights for each control unit.- Parameters:
round (int, optional) – Round the weights to given number of places, by default 3
threshold (float, optional) – If supplied, will only show weights above this value, by default None
- Returns:
The weights computed
- Return type:
pandas.Series
- Raises:
ValueError – If there is no weight matrix available