Synthetic Control Methods (SCM)

repository·master·Indexed 41 days ago

https://github.com/oscarengelbrektson/syntheticcontrolmethods

A Python package for causal inference that implements Synthetic Control Methods (SCM) to estimate the effect of interventions on panel data or time-series. It provides the `Synth` class for ordinary and penalized synthetic controls, and the `DiffSynth` class for differenced synthetic controls. Features include validity testing via in-time and in-space placebo studies, and visualization tools for original, pointwise, and cumulative effects.

Tokens
3.4K
Snippets
8
Records
13
Agent score
51%

What's inside SyntheticControlMethods

  1. Understand the SynthBase data structure

    master

    The SynthBase class acts as a central container for all variables, processed matrices, and results generated during the synthetic control procedure. It is used internally by Synth and DiffSynth to store state.

    Key Data Matrices

    • treated_outcome: (1 x pre-treatment periods) Outcome of the treated unit in the pre-treatment period.
    • control_outcome: (n_controls x pre-treatment periods) Outcomes of all control units in the pre-treatment period.
    • treated_covariates: (n_covariates x 1) Average value of each predictor for the treated unit in the pre-treatment period.
    • control_covariates: (n_covariates x n_controls) Average value of each predictor for each control unit in the pre-treatment period.
    • w: (1 x n_controls) The weights assigned to each control unit (must be non-negative and sum to 1).
    • v: (n_covariates x n_covariates) A diagonal, positive semi-definite matrix representing the relative importance of each covariate.
    • weight_df: A DataFrame containing the calculated unit weights.
    • comparison_df: A DataFrame containing the comparison between the treated and synthetic units.
  2. Use Penalized Synthetic Controls with pen="auto"

    master

    Penalized Synthetic Controls attempt to make control units similar to the treated unit both before and after they are mixed into the synthetic unit. This helps mitigate bias if the relationship between outcome and covariates is non-linear.

    By setting pen="auto", the penalization coefficient is optimized alongside the weights using pre-treatment data.

    # Fit penalized synthetic control with automatic optimization
    sc_pen = Synth(data, "gdp", "country", "year", 1990, "West Germany",
                   n_optim=30, pen="auto", exclude_columns=["code"])
  3. Data requirements for Synthetic Control Methods

    master

    To use Synth or DiffSynth, your input dataset must follow these requirements:

    1. Structure: A Pandas DataFrame where each row represents one observation for a unit at a specific time.
    2. Columns: Must include an ID column, a Time column, an Outcome column, and one or more Covariate columns.
    3. Sorting: The dataset must be sorted by ID, then by Time. This ensures that all observations for one unit are contiguous and ordered chronologically before moving to the next unit.
    4. Format:
      • ID: Unique identifier for the unit (e.g., string or integer).
      • Time: Integer indicating the time period.
      • outcome_var: The target variable being studied.
      • covariates: Predictor variables used to match the treated unit.
  4. Perform Validity Testing (Placebo Studies)

    master

    To evaluate the credibility of the synthetic control, perform placebo tests:

    1. In-time Placebo: Reassigns the treatment to a different time period in the data. If the model shows a large effect in a period where no treatment occurred, the results are suspect.

      • Method: sc.in_time_placebo(treatment_period, n_optim=...)
    2. In-space Placebo: Reassigns the treatment to different control units (the donor pool). A significant treatment effect is one where the treated unit's post-treatment RMSPE is unusually large compared to the distribution of placebo effects.

      • Method: sc.in_space_placebo(n_placebos)
    # Run in-time placebo for the year 1982
    sc.in_time_placebo(1982, n_optim=10)
    sc.plot(['in-time placebo'])
    
    # Run in-space placebo for 15 units
    sc.in_space_placebo(15)
    sc.plot(['rmspe ratio'])
  5. Prepare data for Synthetic Control Methods

    master

    To use Synth or DiffSynth, your dataset must adhere to these formatting requirements:

    1. Numerical Variables: All variables must be numerical, except for a single categorical unit identifier (e.g., country).
    2. Sorting: The dataset must be sorted by unit and then by time (all observations for a single unit must appear consecutively).
    3. Unit Identifier: Use exactly one unit identifier column. If your data has multiple (e.g., code and country), drop one or use the exclude_columns argument.
    4. Units: The dataset must contain one treated unit and multiple control units.
    5. Missing Values:
      • Outcome: No missing values are allowed in the outcome column. You must impute or drop missing outcome data before processing.
      • Covariates: Missing values are allowed in covariates. Synth() ignores them (using pre-treatment averages), while DiffSynth() automatically applies linear interpolation to impute them.
  6. Visualize Synthetic Control results

    master

    The .plot() method generates three types of visualizations to analyze the intervention's impact:

    1. "original": Shows the observed data for the treated unit alongside the counterfactual prediction (the synthetic control) for the post-treatment period.
    2. "pointwise": Shows the difference between the observed data and the counterfactual predictions. This represents the estimated pointwise causal effect.
    3. "cumulative": Shows the running sum of the pointwise contributions, representing the cumulative effect of the intervention over time.
    sc.plot(["original", "pointwise", "cumulative"], 
            treated_label="West Germany", 
            synth_label="Synthetic West Germany", 
            treatment_label="German Reunification")
  7. Use the Synth class for causal inference

    master

    The Synth class is the primary interface for estimating causal effects using the synthetic control method. It requires a pandas DataFrame containing your panel data.

    Parameters for Synth():

    • data: A pandas.DataFrame containing the observations.
    • outcome_col: The name of the column representing the outcome of interest.
    • unit_col: The name of the column representing the unit ID (e.g., 'country').
    • time_col: The name of the column representing the time period (e.g., 'year').
    • treatment_year: The year/period when the intervention occurred.
    • treated_unit: The identifier for the unit that received the treatment.
    • pen: Penalty parameter (default is 0).

    Methods:

    • plot(types, ...): Visualizes the results. types can be a list containing "original", "pointwise", and "cumulative".
    import pandas as pd
    from SyntheticControlMethods import Synth
    
    # Load your data
    data = pd.read_csv("your_data.csv")
    
    # Initialize and fit the model
    sc = Synth(data, "outcome_column", "unit_column", "time_column", 1990, "Treated Unit Name", pen=0)
    
    # Visualize the results
    sc.plot(["original", "pointwise", "cumulative"], 
            treated_label="Treated Unit", 
            synth_label="Synthetic Unit", 
            treatment_label="Intervention Name")
  8. Use DiffSynth() for Differenced Synthetic Controls

    master

    The DiffSynth() class implements Differenced Synthetic Controls. This method is useful if the ordinary Synth() fails (e.g., if the treated unit's outcome is more extreme than any control unit) because it focuses on matching the change in the outcome rather than the absolute level.

    Arguments:

    • All arguments from Synth().
    • not_diff_cols: A list of column names to omit from the first-differencing process. Use this for covariates with high missingness where differencing is not meaningful.

    Note on Interpretation: In the comparison_df attribute, DiffSynth displays the average change in covariate values instead of the absolute average values.

    from SyntheticControlMethods import DiffSynth
    
    # Fit differenced synthetic control
    # We exclude certain columns from differencing due to high missingness
    dsc = DiffSynth(
        data, 
        "gdp", 
        "country", 
        "year", 
        1990, 
        "West Germany", 
        not_diff_cols=["schooling", "invest60"],
        n_optim=100
    )
  9. Use the Synth class for Synthetic Control Methods

    master

    The Synth class implements the standard Synthetic Control Method (SCM). It estimates a synthetic version of a treated unit by assigning weights to control units to match the treated unit's pre-treatment characteristics.

    Initialization Parameters

    • dataset: A Pandas DataFrame containing the observations. The dataset must be sorted by ID and then by Time.
    • outcome_var: Name of the outcome column (e.g., 'gdp').
    • id_var: Name of the unit identifier column (e.g., 'country').
    • time_var: Name of the time period column (e.g., 'year').
    • treatment_period: The first time period after treatment occurred (the first period affected by the effect).
    • treated_unit: The ID of the unit that received the treatment.
    • n_optim: (int, default: 10) Number of different initialization values for optimization. Higher values increase the chance of finding a global optimum but increase runtime.
    • pen: (float, default: 0) Penalization coefficient. Higher values increase the importance of minimizing the pairwise difference of individual control units from the treated unit.
    from SyntheticControlMethods.main import Synth
    import pandas as pd
    
    # Example setup
    df = pd.read_csv('your_data.csv')
    synth = Synth(
        dataset=df,
        outcome_var='gdp',
        id_var='country',
        time_var='year',
        treatment_period=1990,
        treated_unit='West Germany',
        n_optim=20,
        pen=0.1
    )
  10. Use the DiffSynth class for Differenced Synthetic Control Methods

    master

    The DiffSynth class implements the Differenced Synthetic Control Method (DSC). This method is useful when working with first-differences of variables to account for non-stationarity.

    Initialization Parameters

    In addition to the standard Synth parameters, DiffSynth includes:

    • not_diff_cols: (list, default: []) A list of column names to omit from the first-differencing process. This is useful for columns where the first difference is not appropriate (e.g., if the proportion of missing values is high).

    Data Transformation

    DiffSynth automatically performs the following on the provided dataset:

    1. Linear Interpolation: Imputes missing values using linear interpolation to ensure consecutive periods are present (required for differencing).
    2. First Differencing: Computes the change from the previous time period for the outcome and specified covariates.
    3. Dropping Initial Periods: Removes the first time period for every unit since the change from the previous period is undefined.
    from SyntheticControlMethods.main import DiffSynth
    
    # Example setup for Differenced Synthetic Control
    diff_synth = DiffSynth(
        dataset=df,
        outcome_var='gdp',
        id_var='country',
        time_var='year',
        treatment_period=1990,
        treated_unit='West Germany',
        not_diff_cols=['some_stable_covariate']
    )
  11. Use the Synth() class for ordinary Synthetic Controls

    master

    The Synth() class implements ordinary Synthetic Control methods. It attempts to find a weighted average of control units that closely resembles the treated unit in the pre-treatment period.

    Arguments:

    • data: Pandas DataFrame (sorted by unit then time).
    • outcome_var: Name of the outcome column (str).
    • id_var: Name of the unit indicator column (str).
    • time_var: Name of the time column (str).
    • treatment_period: The first time point affected by treatment (int).
    • treated_unit: The identifier of the unit that received treatment (str).
    • n_optim: Number of initialization values for optimization (int, default: 10).
    • pen: Penalization coefficient. 0 is classic SC; `