| Title: | Specification Curve Analysis for Synthetic Control Methods |
|---|---|
| Description: | Estimates synthetic control models while exposing the implementation choices that define the synthetic counterfactual: how matching variables are constructed, how predictor weights are set, which constraints the donor weights satisfy (simplex, lasso, ridge, penalized), whether an intercept is included, and which outcome model corrects for bias (OLS, ridge, lasso, augmented synthetic control). Runs specification-curve analysis over a grid of these choices (Simonsohn, Simmons and Nelson (2020) <doi:10.1038/s41562-020-0912-z>), with placebo-in-space and bootstrap inference for single specifications and placebo inference for the curve as a whole, and uses 'xgboost' models with SHAP values (Lundberg and Lee (2017) <doi:10.48550/arXiv.1705.07874>) to show which choices drive the estimates. Methods follow Abadie, Diamond and Hainmueller (2010) <doi:10.1198/jasa.2009.ap08746>, Abadie (2021) <doi:10.1257/jel.20191450>, Ben-Michael, Feller and Rothstein (2021) <doi:10.1080/01621459.2021.1929245> and Chen and Guestrin (2016) <doi:10.1145/2939672.2939785>. |
| Authors: | Zubin Jelveh [aut, cre] |
| Maintainer: | Zubin Jelveh <[email protected]> |
| License: | GPL-3 |
| Version: | 0.1.1 |
| Built: | 2026-11-05 01:30:19 UTC |
| Source: | https://github.com/cran/SCMs |
Add fitted values, residuals, and other observation-level statistics to the original data used in synthetic control estimation.
## S3 method for class 'scest' augment(x, data = NULL, ...)## S3 method for class 'scest' augment(x, data = NULL, ...)
x |
An object of class "scest" from |
data |
Optional data.frame/data.table to augment. If NULL, attempts to reconstruct |
... |
Additional arguments (not currently used) |
A data.table with original data plus:
.fitted - Numeric. Fitted values from synthetic control
.resid - Numeric. Residuals (actual - fitted)
.period_type - Character. "pre" or "post" treatment
.is_treated - Logical. Whether observation is from treated unit
toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) sc_obj <- scdata( df = toy, id.var = "unit", time.var = "year", outcome.var = "y", period.pre = 1:4, period.post = 5:6, unit.tr = "treated", unit.co = c("donor1", "donor2"), covagg = list(list(var = "outcome_var", partition_periods = list(type = "by_period"))) ) fit <- scest(sc_obj, w.constr = list(name = "simplex")) generics::augment(fit)toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) sc_obj <- scdata( df = toy, id.var = "unit", time.var = "year", outcome.var = "y", period.pre = 1:4, period.post = 5:6, unit.tr = "treated", unit.co = c("donor1", "donor2"), covagg = list(list(var = "outcome_var", partition_periods = list(type = "by_period"))) ) fit <- scest(sc_obj, w.constr = list(name = "simplex")) generics::augment(fit)
Solve unconstrained quadratic programming problem using clarabel
b.est.clarabel.ols(A, Z, C = NULL, J, KM, w.constr, V)b.est.clarabel.ols(A, Z, C = NULL, J, KM, w.constr, V)
A |
Treated unit features matrix |
Z |
Donor features matrix |
C |
Optional constant-term matrix. |
J |
Number of donor units |
KM |
Number of additional variables |
w.constr |
Constraint specification |
V |
Feature weighting matrix |
Numeric vector of weights
Solve simplex-constrained QP using clarabel (sum to 1, non-negative)
b.est.clarabel.simplex(A, Z, C = NULL, J, KM, w.constr, V)b.est.clarabel.simplex(A, Z, C = NULL, J, KM, w.constr, V)
A |
Treated unit features matrix |
Z |
Donor features matrix |
C |
Optional constant-term matrix. |
J |
Number of donor units |
KM |
Number of additional variables |
w.constr |
Constraint specification |
V |
Feature weighting matrix |
Numeric vector of weights
Dispatch to appropriate clarabel constraint-specific function
b.est.clarabel.unified(A, Z, C = NULL, J, KM, w.constr, V)b.est.clarabel.unified(A, Z, C = NULL, J, KM, w.constr, V)
A |
Treated unit features matrix |
Z |
Donor features matrix |
C |
Optional constant-term matrix. |
J |
Number of donor units |
KM |
Number of additional variables |
w.constr |
Constraint specification |
V |
Feature weighting matrix |
Numeric vector of weights
Solve W optimization using CVXR (original implementation)
b.est.cvxr.fallback(A, Z, C = NULL, J, KM, w.constr, V, CVXR.solver)b.est.cvxr.fallback(A, Z, C = NULL, J, KM, w.constr, V, CVXR.solver)
A |
Treated unit features matrix |
Z |
Donor features matrix |
C |
Optional constant-term matrix. |
J |
Number of donor units |
KM |
Number of additional variables |
w.constr |
Constraint specification |
V |
Feature weighting matrix |
CVXR.solver |
CVXR solver to use |
Numeric vector of weights
Implements bootstrap inference by: 1) Enforcing null hypothesis by subtracting actual treatment effect from post-treatment values, 2) Bootstrap resampling at unit level, 3) Re-estimating SCM on bootstrapped samples, 4) Computing p-values from bootstrap distribution
bootstrap_null_inference( sc.pred, dataset, n_bootstrap = 1000, cores = 1, verbose = FALSE, seed = NULL )bootstrap_null_inference( sc.pred, dataset, n_bootstrap = 1000, cores = 1, verbose = FALSE, seed = NULL )
sc.pred |
Result from estimate_sc function |
dataset |
Original dataset used for estimation |
n_bootstrap |
Number of bootstrap replications (default: 1000) |
cores |
Number of cores for parallel processing (default: 1) |
verbose |
Logical for verbose output (default: FALSE) |
seed |
Random seed for reproducibility (default: NULL) |
Bootstrap Null Hypothesis Inference for Synthetic Control
List containing bootstrap results and p-values
Calculates significance measures following Abadie et al. (2010) German reunification paper.
Computes post/pre RMSPE ratios, treatment effects, normalized treatment effects, and their rank-based p-values:
one-sided in expected_direction, or two-sided (ranked by |statistic|) when expected_direction = "two_sided".
The RMSPE ratio is always one-sided.
calculate_abadie_significance( taus_result, rmse_result, rmspe_threshold = 0, expected_direction = "two_sided" )calculate_abadie_significance( taus_result, rmse_result, rmspe_threshold = 0, expected_direction = "two_sided" )
taus_result |
Data.table containing treatment effects for all units |
rmse_result |
Data.table containing RMSE values for all units |
rmspe_threshold |
Numeric. Threshold for filtering poor pre-treatment fits (default: 0) |
expected_direction |
Character. Expected direction of treatment effect: "negative", "positive", or "two_sided" (default: "two_sided") |
Calculate Abadie-style Significance Measures
List containing Abadie significance measures for all test statistics
Calculates empirical bootstrap p-values by determining where the actual treatment effect ranks within the bootstrap distribution. This is the standard approach for bootstrap hypothesis testing.
calculate_bootstrap_pvalues(bootstrap_effects, actual_effects, verbose = FALSE)calculate_bootstrap_pvalues(bootstrap_effects, actual_effects, verbose = FALSE)
bootstrap_effects |
List of bootstrap effect distributions |
actual_effects |
List of actual treatment effects |
verbose |
Logical for verbose output |
The p-values are calculated as follows:
One-tailed positive: P(bootstrap >= actual) = (n - rank) / n
One-tailed negative: P(bootstrap <= actual) = (rank + 1) / n
Two-tailed: 2 * min(positive, negative), capped at 1.0
Data.table with p-values and rank information
Calculates curve-level statistics and p-values on filtered specification-curve results using placebo-in-space comparison.
calculate_spec_curve_pvalues_filtered( filtered_results, curve_stat = c("abs_median_tau", "median_abs_tau", "consistency_ratio"), weighting = c("none", "pre_rmspe_percentile"), two_sided = TRUE, expected_direction = "two_sided", grid_policy = "strict", min_placebos = 1L, min_specs = 2L )calculate_spec_curve_pvalues_filtered( filtered_results, curve_stat = c("abs_median_tau", "median_abs_tau", "consistency_ratio"), weighting = c("none", "pre_rmspe_percentile"), two_sided = TRUE, expected_direction = "two_sided", grid_policy = "strict", min_placebos = 1L, min_specs = 2L )
filtered_results |
Data.table. Filtered results data (post-filtering by outcomes, RMSE, etc.). |
curve_stat |
Character vector. Curve-level statistics to compute:
|
weighting |
Character vector. Weighting mode(s) applied to each requested
curve-level statistic: |
two_sided |
Logical. Whether to compute two-sided p-values using absolute-value tails. |
expected_direction |
Character. Expected sign direction for one-sided tests
( |
grid_policy |
Character. Grid mismatch policy:
|
min_placebos |
Integer. Minimum number of placebo units required after applying
|
min_specs |
Integer. Minimum number of specifications required after applying
|
Calculate Specification Curve P-values on Filtered Data
For each unit and specification, this function uses the signed per-spec effect summary
tau_s. Two-sided p-values always compare absolute tails against placebo-unit
curve statistics:
One-sided p-values (two_sided = FALSE) require a signed statistic and count placebos
at least as extreme in expected_direction, e.g. for "negative"
The supported curve statistics are:
median_tau: (signed)
abs_median_tau:
median_abs_tau:
consistency_ratio:
For weighted variants, pre-RMSPE-percentile weights are defined within unit as
and applied directly to the
statistic computation.
List with:
treated_summary: Treated-unit curve statistics and p-values.
stats_by_unit: Curve statistics for treated and placebo units.
attrition_report: Unit/spec retention summary under grid_policy.
The coef method for synthetic control prediction fitted objects.
## S3 method for class 'scest' coef(object, ...)## S3 method for class 'scest' coef(object, ...)
object |
Class "scest" object, obtained by calling |
... |
Other arguments (eg. |
No return value, called to show scest constructed weights.
Matias Cattaneo, Princeton University. [email protected].
Yingjie Feng, Tsinghua University. [email protected].
Filippo Palomba, Princeton University (maintainer). [email protected].
Rocio Titiunik, Princeton University. [email protected].
scest for synthetic control prediction.
Supported methods: print.scest, summary.scest, coef.scest.
Creates standardized configuration objects for running specification curve analysis across multiple datasets.
create_analysis_config( analysis_type = c("homicide", "ois"), custom_params = list() )create_analysis_config( analysis_type = c("homicide", "ois"), custom_params = list() )
analysis_type |
Character. Type of analysis ("homicide" or "ois"). |
custom_params |
List. Custom parameters to override defaults. |
Create Analysis Configuration
List containing configuration parameters for the specified analysis type.
# Create homicide analysis configuration homicide_config <- create_analysis_config("homicide") # Create OIS analysis configuration with custom parameters ois_config <- create_analysis_config("ois", custom_params = list(treated_period = 2021))# Create homicide analysis configuration homicide_config <- create_analysis_config("homicide") # Create OIS analysis configuration with custom parameters ois_config <- create_analysis_config("ois", custom_params = list(treated_period = 2021))
Create Bootstrap Dataset by Unit-level Resampling
create_bootstrap_dataset( dataset_null, bootstrap_units, col_name_unit, treated_unit )create_bootstrap_dataset( dataset_null, bootstrap_units, col_name_unit, treated_unit )
dataset_null |
Original dataset with null enforced |
bootstrap_units |
Bootstrapped unit names |
col_name_unit |
Column name for unit identifier |
treated_unit |
Name of treated unit |
Bootstrap dataset
This function prepares a dataset for use with the Synthetic Control Method (SCM) by formatting and standardizing the input data.
create_scm_dataset( dataset, outcome, covagg, col_name_unit_name, name_treated_unit, col_name_period, treated_period, min_period, end_period, constant = FALSE )create_scm_dataset( dataset, outcome, covagg, col_name_unit_name, name_treated_unit, col_name_period, treated_period, min_period, end_period, constant = FALSE )
dataset |
Data frame or data.table containing the panel data. |
outcome |
Character. Name of the outcome variable column. |
covagg |
List of covariate-aggregation operations passed through to
|
col_name_unit_name |
Character. Name of the column containing unit names. |
name_treated_unit |
Character. Name of the treated unit. |
col_name_period |
Character. Name of the column containing time periods. |
treated_period |
Numeric. Time period when treatment starts. |
min_period |
Numeric or NULL. Earliest time period to include. If NULL, uses the minimum in the dataset. |
end_period |
Numeric or NULL. Latest time period to include. If NULL, uses the maximum in the dataset. |
constant |
Logical. Whether to include a constant term. Default is FALSE. |
A list containing the prepared data for SCM analysis.
toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) scm_data <- create_scm_dataset( dataset = toy, outcome = "y", covagg = list( list(var = "outcome_var", partition_periods = list(type = "by_period")) ), col_name_unit_name = "unit", name_treated_unit = "treated", col_name_period = "year", treated_period = 5, min_period = 1, end_period = 6 ) inherits(scm_data, "scdata")toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) scm_data <- create_scm_dataset( dataset = toy, outcome = "y", covagg = list( list(var = "outcome_var", partition_periods = list(type = "by_period")) ), col_name_unit_name = "unit", name_treated_unit = "treated", col_name_period = "year", treated_period = 5, min_period = 1, end_period = 6 ) inherits(scm_data, "scdata")
Creates configuration for XGBoost analysis that works directly with long format data from spec_curve().
create_xgboost_config( dataset_name, treated_unit_name, outcome_filter = NULL, spec_features = c("outcome_model", "const", "fw", "feat", "data_sample"), treated_unit_only = TRUE, tune_xgboost = NULL, xgboost_grid = NULL, xgboost_cv_folds = 5, xgboost_params = NULL )create_xgboost_config( dataset_name, treated_unit_name, outcome_filter = NULL, spec_features = c("outcome_model", "const", "fw", "feat", "data_sample"), treated_unit_only = TRUE, tune_xgboost = NULL, xgboost_grid = NULL, xgboost_cv_folds = 5, xgboost_params = NULL )
dataset_name |
Character. Name of the dataset for identification. |
treated_unit_name |
Character. Name of the treated unit. |
outcome_filter |
Character. Outcome variable to filter on (optional). |
spec_features |
Character vector. Specification features to use as predictors. |
treated_unit_only |
Logical. If TRUE, only analyze the treated unit. |
tune_xgboost |
Logical or NULL. If TRUE, tune XGBoost hyperparameters via k-fold CV on the treated unit before SHAP/LOO. If NULL, defaults to TRUE when xgboost_params is NULL and FALSE otherwise. |
xgboost_grid |
Data.frame, data.table, or list. Grid of hyperparameters for tuning. Must include max_depth, eta, nrounds, subsample, colsample_bytree. If NULL, uses an internal default grid. |
xgboost_cv_folds |
Integer. Number of CV folds for tuning. Default is 5. |
xgboost_params |
List. Fixed parameters for XGBoost training. If NULL, defaults are used. If tune_xgboost is TRUE, xgboost_params must be NULL. |
List containing configuration parameters.
xgb_cfg <- create_xgboost_config( dataset_name = "toy", treated_unit_name = "treated", outcome_filter = "outcome", spec_features = c("feat", "outcome_model", "const", "fw", "data_sample") )xgb_cfg <- create_xgboost_config( dataset_name = "toy", treated_unit_name = "treated", outcome_filter = "outcome", spec_features = c("feat", "outcome_model", "const", "fw", "data_sample") )
Enforce Null Hypothesis by Subtracting Treatment Effects
enforce_null_hypothesis(dataset, sc.pred, actual_effects, verbose = FALSE)enforce_null_hypothesis(dataset, sc.pred, actual_effects, verbose = FALSE)
dataset |
Original dataset |
sc.pred |
SC estimation results |
actual_effects |
List of actual treatment effects by outcome model |
verbose |
Logical for verbose output |
Dataset with null hypothesis enforced
This function processes and estimates a Synthetic Control Model (SCM) based on the provided dataset and parameters.
estimate_sc( dataset, outcome, covagg, col_name_unit_name, name_treated_unit, col_name_period, treated_period, min_period, end_period, outcome_models = c("none", "ols", "ridge", "lasso", "augsynth"), feature_weights = "uniform", w.constr = NULL, V = "separate", V.mat = NULL, solver = "ECOS", constant = FALSE )estimate_sc( dataset, outcome, covagg, col_name_unit_name, name_treated_unit, col_name_period, treated_period, min_period, end_period, outcome_models = c("none", "ols", "ridge", "lasso", "augsynth"), feature_weights = "uniform", w.constr = NULL, V = "separate", V.mat = NULL, solver = "ECOS", constant = FALSE )
dataset |
A data frame containing the panel data. |
outcome |
Character. Name of the outcome variable (column in dataset). |
covagg |
List of covariate-aggregation operations passed to
|
col_name_unit_name |
Character. Column name in dataset containing unit names (e.g., state names). |
name_treated_unit |
Character. Name of the treated unit (e.g., the state that received treatment). |
col_name_period |
Character. Column name in dataset containing time periods. |
treated_period |
Numeric. Time period when treatment starts for the treated unit. |
min_period |
Numeric. Earliest time period in the dataset. |
end_period |
Numeric. Latest time period in the dataset. |
outcome_models |
Character vector. Outcome models to fit. Default is c("none", "ols", "ridge", "lasso", "augsynth"). |
feature_weights |
Character. Method for assigning weights to predictors ("uniform" or "optimize"). Default is "uniform". |
w.constr |
Optional. Constraints on the weights. |
V |
Character. Covariance matrix estimation method. Default is "separate". |
V.mat |
Optional. Pre-computed covariance matrix. |
solver |
Character. Solver to use for the optimization problem. Default is "ECOS". |
constant |
Logical. Whether to include a constant term in synthetic control weights. Default is FALSE. |
A list of class "scm_estimate" containing estimated SCM results
and input parameters.
toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) fit <- estimate_sc( dataset = toy, outcome = "y", covagg = list( list(var = "outcome_var", partition_periods = list(type = "by_period")) ), col_name_unit_name = "unit", name_treated_unit = "treated", col_name_period = "year", treated_period = 5, min_period = 1, end_period = 6, outcome_models = "none" ) names(fit)toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) fit <- estimate_sc( dataset = toy, outcome = "y", covagg = list( list(var = "outcome_var", partition_periods = list(type = "by_period")) ), col_name_unit_name = "unit", name_treated_unit = "treated", col_name_period = "year", treated_period = 5, min_period = 1, end_period = 6, outcome_models = "none" ) names(fit)
Provides a formula interface for synthetic control methods, similar to lm() and other standard R modeling functions. This makes the package more accessible to users familiar with standard R model syntax while maintaining compatibility with data.table workflows.
Formula Interface for Synthetic Control Methods
Extract model-level fit statistics and metadata from synthetic control estimation results in a single-row data.table.
## S3 method for class 'scest' glance(x, ...)## S3 method for class 'scest' glance(x, ...)
x |
An object of class "scest" from |
... |
Additional arguments (not currently used) |
A single-row data.table with columns:
n_donors - Integer. Number of donor units
n_pre_periods - Integer. Number of pre-treatment periods
n_post_periods - Integer. Number of post-treatment periods
rmse_pre - Numeric. Root mean squared error in pre-treatment fit
rmse_post - Numeric. Root mean squared error in post-treatment prediction
constraint_type - Character. Type of weight constraint used
feature_weighting - Character. Feature weighting method used
n_active_donors - Integer. Number of donors with meaningful weights
max_weight - Numeric. Largest donor weight
herfindahl_index - Numeric. Concentration measure of weights
has_constant - Logical. Whether constant term was included
toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) sc_obj <- scdata( df = toy, id.var = "unit", time.var = "year", outcome.var = "y", period.pre = 1:4, period.post = 5:6, unit.tr = "treated", unit.co = c("donor1", "donor2"), covagg = list(list(var = "outcome_var", partition_periods = list(type = "by_period"))) ) fit <- scest(sc_obj, w.constr = list(name = "simplex")) generics::glance(fit)toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) sc_obj <- scdata( df = toy, id.var = "unit", time.var = "year", outcome.var = "y", period.pre = 1:4, period.post = 5:6, unit.tr = "treated", unit.co = c("donor1", "donor2"), covagg = list(list(var = "outcome_var", partition_periods = list(type = "by_period"))) ) fit <- scest(sc_obj, w.constr = list(name = "simplex")) generics::glance(fit)
Performs placebo inference by estimating synthetic control for each control unit
and computing three test statistics: post/pre RMSE ratio, treatment effect, and normalized treatment effect.
P-values are one-sided in expected_direction, or two-sided (ranked by |statistic|) when
expected_direction = "two_sided". The RMSPE ratio is always one-sided.
inference_placebo( sc.pred, dataset, cores, verbose, expected_direction = "two_sided" )inference_placebo( sc.pred, dataset, cores, verbose, expected_direction = "two_sided" )
sc.pred |
List. Results from synthetic control estimation |
dataset |
Data.frame. Original dataset used for estimation |
cores |
Numeric. Number of cores for parallel processing |
verbose |
Logical. Whether to print progress information |
expected_direction |
Character. Expected direction of treatment effect: "negative", "positive", or "two_sided" (default: "two_sided") |
Placebo Inference for Synthetic Control Method
List containing:
taus: Treatment effects for all units
rmse: RMSE values for all units
abadie_significance: List with three elements:
rmse_ratio: Post/pre RMSE ratio test statistic and p-values
treatment_effect: Treatment effect test statistic and p-values
normalized_te: Normalized treatment effect test statistic and p-values
Wrapper around inference_placebo for rank-based
placebo inference.
inference_sc( sc.pred, dataset, cores = 1, verbose = TRUE, expected_direction = "two_sided" )inference_sc( sc.pred, dataset, cores = 1, verbose = TRUE, expected_direction = "two_sided" )
sc.pred |
List. Synthetic-control estimation result, typically from
|
dataset |
Data frame. Original panel dataset. |
cores |
Integer. Number of cores for placebo runs. Default is 1. |
verbose |
Logical. Whether to print progress information. Default is TRUE. |
expected_direction |
Character. Expected direction of treatment effect:
|
A list with data, est.results, inference.results,
rmse, and abadie_significance.
set.seed(1) toy <- expand.grid(unit = c("treated", "donor1", "donor2", "donor3"), year = 1:10, stringsAsFactors = FALSE) toy$y <- 50 + 0.8 * toy$year + ifelse(toy$unit == "treated" & toy$year >= 7, 3, 0) + rnorm(nrow(toy), 0, 0.5) sc_fit <- estimate_sc( dataset = toy, outcome = "y", covagg = list( list(var = "outcome_var", partition_periods = list(type = "by_period")) ), col_name_unit_name = "unit", name_treated_unit = "treated", col_name_period = "year", treated_period = 7, min_period = 1, end_period = 10, outcome_models = "none" ) inf <- inference_sc(sc_fit, toy, cores = 1, verbose = FALSE) names(inf)set.seed(1) toy <- expand.grid(unit = c("treated", "donor1", "donor2", "donor3"), year = 1:10, stringsAsFactors = FALSE) toy$y <- 50 + 0.8 * toy$year + ifelse(toy$unit == "treated" & toy$year >= 7, 3, 0) + rnorm(nrow(toy), 0, 0.5) sc_fit <- estimate_sc( dataset = toy, outcome = "y", covagg = list( list(var = "outcome_var", partition_periods = list(type = "by_period")) ), col_name_unit_name = "unit", name_treated_unit = "treated", col_name_period = "year", treated_period = 7, min_period = 1, end_period = 10, outcome_models = "none" ) inf <- inference_sc(sc_fit, toy, cores = 1, verbose = FALSE) names(inf)
Perform Bootstrap Inference
perform_bootstrap_inference( dataset_null, sc.pred, actual_effects, n_bootstrap, cores, verbose )perform_bootstrap_inference( dataset_null, sc.pred, actual_effects, n_bootstrap, cores, verbose )
dataset_null |
Dataset with null hypothesis enforced |
sc.pred |
Original SC estimation results |
actual_effects |
List of actual treatment effects |
n_bootstrap |
Number of bootstrap replications |
cores |
Number of cores for parallel processing |
verbose |
Logical for verbose output |
List with bootstrap effects and estimates
Creates specification curve plots directly from long format data with integrated SHAP analysis and flexible filtering capabilities. Key features:
Internal SHAP computation using XGBoost (automatic when show_shap=TRUE)
Specification filtering BEFORE SHAP computation and p-value calculation
Perfect alignment between SHAP values and specifications via full_spec_id
FAIL HARD error handling with specific guidance when requirements aren't met
plot_spec_curve( long_data, name_treated_unit, outcomes = NULL, normalize_outcomes = "none", rmse_threshold = Inf, shap_values = NULL, file_path_save = NA, width = 6, height = 10, show_pvalues = FALSE, p_threshold = 0.05, prefer_bootstrap_pvalues = FALSE, curve_stat = "abs_median_tau", weighting = c("none", "pre_rmspe_percentile"), two_sided = TRUE, grid_policy = "strict", min_placebos = 1L, min_specs = 2L, test_statistic = "rmse_ratio", null_distribution = "placebo", crop_outliers = "none", sort_by = "tau", filter_specs = NULL, show_shap = TRUE, shap_label_type = "absolute", show_predictions = FALSE, predictions = NULL, xgboost_params = NULL, tune_xgboost = NULL, xgboost_grid = NULL, xgboost_cv_folds = 5 )plot_spec_curve( long_data, name_treated_unit, outcomes = NULL, normalize_outcomes = "none", rmse_threshold = Inf, shap_values = NULL, file_path_save = NA, width = 6, height = 10, show_pvalues = FALSE, p_threshold = 0.05, prefer_bootstrap_pvalues = FALSE, curve_stat = "abs_median_tau", weighting = c("none", "pre_rmspe_percentile"), two_sided = TRUE, grid_policy = "strict", min_placebos = 1L, min_specs = 2L, test_statistic = "rmse_ratio", null_distribution = "placebo", crop_outliers = "none", sort_by = "tau", filter_specs = NULL, show_shap = TRUE, shap_label_type = "absolute", show_predictions = FALSE, predictions = NULL, xgboost_params = NULL, tune_xgboost = NULL, xgboost_grid = NULL, xgboost_cv_folds = 5 )
long_data |
Data.table or List. Long format data from spec_curve(). Can be either the results data.table directly, or the full structured results (with results, abadie_inference, bootstrap_inference components). |
name_treated_unit |
Character. Name of the treated unit. |
outcomes |
Character vector or NULL. Names of the outcome variables to plot. If NULL, all outcomes in the data will be plotted. |
normalize_outcomes |
Character. Method for normalizing treatment effects for comparability. Options: "none" (default), "percent_of_mean", "percent_of_preperiod", "standardized", "rmspe_ratio".
|
rmse_threshold |
Numeric. Threshold for root mean square error to filter results. Default is Inf. |
shap_values |
Data.table or NULL. External SHAP values from run_xgboost_shap_analysis(). Should contain columns: unit, full_spec_id, feature_group, feature, shapley_value. If NULL and show_shap=TRUE, SHAP values will be computed internally. Default is NULL. |
file_path_save |
Character or NA. File path to save the plot. If NA, plot is not saved. Default is NA. |
width |
Numeric. Width of the saved plot in inches. Default is 6. |
height |
Numeric. Height of the saved plot in inches. Default is 10. |
show_pvalues |
Logical. Whether to include significance coloring for treatment effects. Uses Abadie p-values by default, or bootstrap p-values if available. Default is TRUE. |
p_threshold |
Numeric. P-value threshold for significance coloring. Default is 0.05. |
prefer_bootstrap_pvalues |
Logical. Whether to prefer bootstrap p-values over Abadie p-values when both are available. Default is FALSE (prefer Abadie). |
curve_stat |
Character vector. Curve-level statistics to compute for placebo-in-space inference. Options:
Default is |
weighting |
Character vector. Weighting modes to evaluate for curve-level statistics.
Options: |
two_sided |
Logical. If |
grid_policy |
Character. How to handle incomplete placebo specification grids when computing curve-level p-values. Options:
|
min_placebos |
Integer. Minimum number of placebo units required after applying
|
min_specs |
Integer. Minimum number of specifications required after applying
|
test_statistic |
Character. Which test statistic p-values to use for coloring. Options: "rmse_ratio" (default), "treatment_effect", "normalized_te". Only applies to Abadie placebo inference which provides multiple test statistics. |
null_distribution |
Character. The distribution to plot as the null/comparison. Options: "placebo" (default) or "bootstrap".
|
crop_outliers |
Character or Numeric. Method for cropping outliers in Panel A. Options: "none" (default), "percentile", "iqr", "mad", or numeric vector c(ymin, ymax).
|
sort_by |
Character. Method for sorting specifications on x-axis. Options: "tau" (default), "pvalue", "rmspe_ratio".
|
filter_specs |
Named list or NULL. Filters to apply to specifications BEFORE SHAP computation and p-value calculation. Each element should be named by the feature group column and contain allowed values. Available filters: "constant" (TRUE/FALSE), "const" (simplex/lasso/ridge/pensynth), "outcome_model" (none/augsynth/lasso/ridge/ols), "fw" (uniform/optimize), "feat" (covariate aggregation labels), "data_sample" (all/most_similar). Examples: list(constant = "TRUE"), list(const = c("simplex", "lasso")), list(constant = "TRUE", outcome_model = c("none", "augsynth")). Default is NULL (no filtering). |
show_shap |
Logical. Whether to compute and display SHAP values. When TRUE:
|
shap_label_type |
Character. How to display SHAP values in y-axis labels: "absolute" or "signed".
|
show_predictions |
Logical. Whether to display predicted treatment effects from XGBoost models alongside actual treatment effects in Panel A. Uses leave-one-out cross-validation predictions for robust evaluation. Default is FALSE. |
predictions |
Data.table or NULL. External LOO predictions from run_xgboost_shap_analysis(). Should contain columns: unit, full_spec_id, predicted_loo, actual. If NULL and show_predictions=TRUE, predictions will be computed internally. Default is NULL. |
xgboost_params |
List. Custom parameters for XGBoost model training. If NULL, uses defaults: list(objective='reg:squarederror', max_depth=10, eta=0.05, nrounds=500, subsample=0.8, colsample_bytree=0.8, nthread=1, seed=42, verbose=0). Common parameters to modify: nrounds (more = better fit but slower), max_depth (complexity), eta (learning rate). Default is NULL. If tune_xgboost is TRUE, xgboost_params must be NULL. |
tune_xgboost |
Logical or NULL. If TRUE, tune XGBoost hyperparameters via k-fold CV on the treated unit before SHAP/LOO. If NULL, defaults to TRUE when xgboost_params is NULL and FALSE otherwise. Default is NULL. |
xgboost_grid |
Data.frame, data.table, or list. Optional grid for tuning. Must include max_depth, eta, nrounds, subsample, colsample_bytree. Default is NULL. |
xgboost_cv_folds |
Integer. Number of CV folds for tuning. Default is 5. |
Plot Specification Curve Using Direct Long Format Data
Panel B labels are rich text. To save final_plot as SVG, use the Cairo
device (grDevices::svg); svglite misaligns the labels.
List containing:
final_plot: ggplot object representing the complete specification curve with Panel A (treatment effects) and Panel B (feature groups/SHAP)
panel_a: ggplot object for Panel A only (treatment effects)
panel_b: ggplot object for Panel B only (feature groups/SHAP)
plot_data_p1: data.table with Panel A plotting data including columns: Unit Name, Estimate, RMSE, Specification, unit_type, p_value (if available)
plot_data_p2: data.table with Panel B plotting data including columns: Specification, feature_group, feature, shapley_value (if SHAP computed)
computed_shap: Complete results from internal SHAP computation (NULL if external shap_values provided or show_shap=FALSE). Contains: results (feature importance), shapley (SHAP values), predictions (model predictions), models (trained XGBoost models), config (SHAP configuration)
spec_curve_pvals: List with specification curve-level p-values calculated on filtered data:
treated_summary (treated-unit curve estimates and p-values) and
stats_by_unit (curve statistics for treated and placebo units), plus
attrition_report summarizing unit/spec retention under grid_policy.
NULL if no inference data available.
filtered_specs: Integer count of specifications remaining after filtering (before filtering if filter_specs=NULL)
feature_groups_displayed: Character vector of feature groups shown in Panel B (only groups with variation in filtered data)
specs <- data.table::data.table( full_spec_id = paste0("s", 1:4), outcome_model = c("none", "ridge", "none", "ridge"), const = c("simplex", "lasso", "simplex", "lasso"), fw = c("uniform", "uniform", "optimize", "optimize"), feat = c("f1", "f1", "f2", "f2") ) long_data <- data.table::CJ( unit_name = c("treated", "donor1"), full_spec_id = as.character(specs[["full_spec_id"]]), post_period = c(FALSE, TRUE), sorted = FALSE ) long_data[, unit_type := ifelse(unit_name == "treated", "treated", "control")] long_data <- merge(long_data, specs, by = "full_spec_id", all.x = TRUE) long_data[, outcome := "y"] long_data[, rmse := 0.2 + as.numeric(factor(full_spec_id)) * 0.01] long_data[, tau := ifelse( unit_name == "treated", as.numeric(factor(full_spec_id)) * 0.5 + ifelse(post_period, 0.2, 0), as.numeric(factor(full_spec_id)) * 0.1 + ifelse(post_period, 0.05, 0) )] out <- plot_spec_curve( long_data = long_data, name_treated_unit = "treated", show_shap = FALSE, show_pvalues = FALSE, curve_stat = "abs_median_tau", weighting = "none" ) out$panel_aspecs <- data.table::data.table( full_spec_id = paste0("s", 1:4), outcome_model = c("none", "ridge", "none", "ridge"), const = c("simplex", "lasso", "simplex", "lasso"), fw = c("uniform", "uniform", "optimize", "optimize"), feat = c("f1", "f1", "f2", "f2") ) long_data <- data.table::CJ( unit_name = c("treated", "donor1"), full_spec_id = as.character(specs[["full_spec_id"]]), post_period = c(FALSE, TRUE), sorted = FALSE ) long_data[, unit_type := ifelse(unit_name == "treated", "treated", "control")] long_data <- merge(long_data, specs, by = "full_spec_id", all.x = TRUE) long_data[, outcome := "y"] long_data[, rmse := 0.2 + as.numeric(factor(full_spec_id)) * 0.01] long_data[, tau := ifelse( unit_name == "treated", as.numeric(factor(full_spec_id)) * 0.5 + ifelse(post_period, 0.2, 0), as.numeric(factor(full_spec_id)) * 0.1 + ifelse(post_period, 0.05, 0) )] out <- plot_spec_curve( long_data = long_data, name_treated_unit = "treated", show_shap = FALSE, show_pvalues = FALSE, curve_stat = "abs_median_tau", weighting = "none" ) out$panel_a
Plot method that creates a specification curve visualization by calling the main plotting function with appropriate defaults.
## S3 method for class 'spec_curve' plot(x, ...)## S3 method for class 'spec_curve' plot(x, ...)
x |
An object of class "spec_curve" from |
... |
Additional arguments passed to |
This is a convenience method that calls plot_spec_curve with the
specification curve data. All arguments supported by plot_spec_curve
can be passed through the ... parameter.
A ggplot object containing the specification curve plot
plot_spec_curve for detailed plotting options
specs <- data.table::data.table( full_spec_id = paste0("s", 1:4), outcome_model = c("none", "ridge", "none", "ridge"), const = c("simplex", "lasso", "simplex", "lasso"), fw = c("uniform", "uniform", "optimize", "optimize"), feat = c("f1", "f1", "f2", "f2") ) dt <- data.table::CJ( unit_name = c("treated", "donor1"), full_spec_id = as.character(specs[["full_spec_id"]]), post_period = c(FALSE, TRUE), sorted = FALSE ) dt[, unit_type := ifelse(unit_name == "treated", "treated", "control")] dt <- merge(dt, specs, by = "full_spec_id", all.x = TRUE) dt[, outcome := "y"] dt[, rmse := 0.2 + as.numeric(factor(full_spec_id)) * 0.01] dt[, tau := ifelse( unit_name == "treated", as.numeric(factor(full_spec_id)) * 0.5 + ifelse(post_period, 0.2, 0), as.numeric(factor(full_spec_id)) * 0.1 + ifelse(post_period, 0.05, 0) )] spec_results <- list(results = dt) class(spec_results) <- "spec_curve" plot(spec_results, show_shap = FALSE, show_pvalues = FALSE)specs <- data.table::data.table( full_spec_id = paste0("s", 1:4), outcome_model = c("none", "ridge", "none", "ridge"), const = c("simplex", "lasso", "simplex", "lasso"), fw = c("uniform", "uniform", "optimize", "optimize"), feat = c("f1", "f1", "f2", "f2") ) dt <- data.table::CJ( unit_name = c("treated", "donor1"), full_spec_id = as.character(specs[["full_spec_id"]]), post_period = c(FALSE, TRUE), sorted = FALSE ) dt[, unit_type := ifelse(unit_name == "treated", "treated", "control")] dt <- merge(dt, specs, by = "full_spec_id", all.x = TRUE) dt[, outcome := "y"] dt[, rmse := 0.2 + as.numeric(factor(full_spec_id)) * 0.01] dt[, tau := ifelse( unit_name == "treated", as.numeric(factor(full_spec_id)) * 0.5 + ifelse(post_period, 0.2, 0), as.numeric(factor(full_spec_id)) * 0.1 + ifelse(post_period, 0.05, 0) )] spec_results <- list(results = dt) class(spec_results) <- "spec_curve" plot(spec_results, show_shap = FALSE, show_pvalues = FALSE)
The print method for synthetic control data objects.
## S3 method for class 'scdata' print(x, ...)## S3 method for class 'scdata' print(x, ...)
x |
Class "scdata" object, obtained by calling |
... |
Other arguments. |
No return value, called to print scdata results.
Matias Cattaneo, Princeton University. [email protected].
Yingjie Feng, Tsinghua University. [email protected].
Filippo Palomba, Princeton University (maintainer). [email protected].
Rocio Titiunik, Princeton University. [email protected].
scdata for synthetic control data preparation.
Supported methods: print.scdata, summary.scdata.
The print method for synthetic control prediction fitted objects.
## S3 method for class 'scest' print(x, ...)## S3 method for class 'scest' print(x, ...)
x |
Class "scest" object, obtained by calling |
... |
Other arguments. |
No return value, called to print scest results.
Matias Cattaneo, Princeton University. [email protected].
Yingjie Feng, Tsinghua University. [email protected].
Filippo Palomba, Princeton University (maintainer). [email protected].
Rocio Titiunik, Princeton University. [email protected].
scest for synthetic control prediction.
Supported methods: print.scest, summary.scest, coef.scest.
Print method for scest_formula objects
## S3 method for class 'scest_formula' print(x, ...)## S3 method for class 'scest_formula' print(x, ...)
x |
An scest_formula object |
... |
Additional arguments |
Performs specification curve analysis across multiple modeling choices and configurations, providing a systematic exploration of researcher degrees of freedom.
run_spec_curve_analysis(dataset, params, cores = 1, output_dir = NULL)run_spec_curve_analysis(dataset, params, cores = 1, output_dir = NULL)
dataset |
Data frame containing the panel data for analysis. |
params |
List containing ALL analysis parameters including:
|
cores |
Integer. Number of cores for parallel processing (applies to both bootstrap and placebo inference unless overridden in params$inference_config). |
output_dir |
Character. Directory to save results. If NULL, results are not saved. |
Run Comprehensive Specification Curve Analysis
List structure containing:
results - Data.table in long format with all specification results
abadie_inference - Abadie placebo inference results (if inference_type includes "placebo")
bootstrap_inference - Bootstrap inference results (if inference_type includes "bootstrap")
set.seed(1) toy <- expand.grid(unit = c("treated", "donor1", "donor2", "donor3"), year = 1:10, stringsAsFactors = FALSE) toy$y <- 50 + 0.8 * toy$year + ifelse(toy$unit == "treated" & toy$year >= 7, 3, 0) + rnorm(nrow(toy), 0, 0.5) params <- list( outcomes = "y", col_name_unit_name = "unit", name_treated_unit = "treated", covagg = list( baseline = list( label = "baseline", operations = list(list(var = "outcome_var", partition_periods = list(type = "by_period"))) ) ), treated_period = 7, min_period = 1, end_period = 10, col_name_period = "year", feature_weights = "uniform", donor_sample = "all", outcome_models = "none", constraints = list(list(name = "simplex")), inference_type = "placebo", inference_config = list(verbose = FALSE, placebo_cores = 1) ) results <- run_spec_curve_analysis(dataset = toy, params = params, cores = 1) names(results)set.seed(1) toy <- expand.grid(unit = c("treated", "donor1", "donor2", "donor3"), year = 1:10, stringsAsFactors = FALSE) toy$y <- 50 + 0.8 * toy$year + ifelse(toy$unit == "treated" & toy$year >= 7, 3, 0) + rnorm(nrow(toy), 0, 0.5) params <- list( outcomes = "y", col_name_unit_name = "unit", name_treated_unit = "treated", covagg = list( baseline = list( label = "baseline", operations = list(list(var = "outcome_var", partition_periods = list(type = "by_period"))) ) ), treated_period = 7, min_period = 1, end_period = 10, col_name_period = "year", feature_weights = "uniform", donor_sample = "all", outcome_models = "none", constraints = list(list(name = "simplex")), inference_type = "placebo", inference_config = list(verbose = FALSE, placebo_cores = 1) ) results <- run_spec_curve_analysis(dataset = toy, params = params, cores = 1) names(results)
Performs XGBoost modeling and SHAP analysis directly on long format
data produced by spec_curve() or run_spec_curve_analysis().
run_xgboost_shap_analysis(long_data, config, compute_loo = TRUE)run_xgboost_shap_analysis(long_data, config, compute_loo = TRUE)
long_data |
Data.table. Long format data (typically |
config |
List. Configuration object from |
compute_loo |
Logical. If TRUE (default), compute leave-one-out predictions. |
Run XGBoost SHAP Analysis on Long Format Data
List containing:
results - Feature importance results
shapley - SHAP values with proper spec_number alignment
predictions - Model predictions
config - Original configuration object
if (requireNamespace("xgboost", quietly = TRUE)) { long_data <- data.table::data.table( unit_name = rep("treated", 6), full_spec_id = paste0("s", 1:6), spec_number = 1:6, post_period = TRUE, tau = c(0.4, 0.6, 0.2, 0.8, 0.3, 0.7), outcome_model = c("none", "ridge", "none", "ridge", "none", "ridge"), const = c("simplex", "lasso", "simplex", "lasso", "simplex", "lasso"), fw = c("uniform", "uniform", "optimize", "optimize", "uniform", "optimize"), feat = c("f1", "f1", "f2", "f2", "f3", "f3") ) xgb_cfg <- create_xgboost_config( dataset_name = "toy", treated_unit_name = "treated", spec_features = c("outcome_model", "const", "fw", "feat"), tune_xgboost = FALSE, xgboost_params = list( objective = "reg:squarederror", max_depth = 2, eta = 0.1, nrounds = 20, subsample = 1, colsample_bytree = 1, nthread = 1, seed = 42, verbose = 0 ) ) shap_results <- run_xgboost_shap_analysis( long_data = long_data, config = xgb_cfg, compute_loo = FALSE ) names(shap_results) }if (requireNamespace("xgboost", quietly = TRUE)) { long_data <- data.table::data.table( unit_name = rep("treated", 6), full_spec_id = paste0("s", 1:6), spec_number = 1:6, post_period = TRUE, tau = c(0.4, 0.6, 0.2, 0.8, 0.3, 0.7), outcome_model = c("none", "ridge", "none", "ridge", "none", "ridge"), const = c("simplex", "lasso", "simplex", "lasso", "simplex", "lasso"), fw = c("uniform", "uniform", "optimize", "optimize", "uniform", "optimize"), feat = c("f1", "f1", "f2", "f2", "f3", "f3") ) xgb_cfg <- create_xgboost_config( dataset_name = "toy", treated_unit_name = "treated", spec_features = c("outcome_model", "const", "fw", "feat"), tune_xgboost = FALSE, xgboost_params = list( objective = "reg:squarederror", max_depth = 2, eta = 0.1, nrounds = 20, subsample = 1, colsample_bytree = 1, nthread = 1, seed = 42, verbose = 0 ) ) shap_results <- run_xgboost_shap_analysis( long_data = long_data, config = xgb_cfg, compute_loo = FALSE ) names(shap_results) }
This function prepares a data structure for use with Synthetic Control Methods (SCM) by formatting and organizing the input data. It validates inputs, structures the data appropriately, and creates matrices needed for synthetic control estimation.
scdata( df, id.var, time.var, outcome.var, period.pre, period.post, unit.tr, unit.co, anticipation = 0, constant = FALSE, verbose = TRUE, covagg = list(), cointegrated.data = FALSE )scdata( df, id.var, time.var, outcome.var, period.pre, period.post, unit.tr, unit.co, anticipation = 0, constant = FALSE, verbose = TRUE, covagg = list(), cointegrated.data = FALSE )
df |
Data frame containing the panel data in long format. |
id.var |
Character. Name of the column containing unit identifiers (e.g., state names, country codes). |
time.var |
Character. Name of the column containing time periods (e.g., year, quarter). |
outcome.var |
Character. Name of the column containing the outcome variable of interest. |
period.pre |
Numeric vector. Pre-treatment time periods used for fitting the synthetic control. |
period.post |
Numeric vector. Post-treatment time periods for evaluating treatment effects. |
unit.tr |
Character. Name or identifier of the treated unit. |
unit.co |
Character vector. Names or identifiers of the potential control units (donor pool). |
anticipation |
Numeric. Number of pre-treatment periods to exclude due to anticipation effects. Default is 0. |
constant |
Logical. Whether to include a global constant (intercept) term in synthetic control estimation. When TRUE, adds a constant feature that allows the synthetic control to have a level shift relative to donors. Requires multiple features (more than one covariate specification). The constant term is estimated alongside donor weights and can be positive or negative. Default is FALSE. |
verbose |
Logical. Whether to print diagnostic messages and warnings. Default is TRUE. |
covagg |
List of covariate-aggregation operations. Each entry must be a list with:
If |
cointegrated.data |
Logical. Whether to treat the data as cointegrated (affects some computations). Default is FALSE. |
This function performs several key operations:
Validates that all required variables exist in the data
Checks for balanced panel structure
Creates outcome matrices for treated and control units
Handles missing data appropriately
Prepares data structures required by estimation functions
The function expects panel data in long format where each row represents one unit-time observation.
A list of class 'scdata' containing:
Y.pre: Matrix of pre-treatment outcomes (controls × time periods)
Y.post: Matrix of post-treatment outcomes
Y.donors: Full outcome matrix for donor units
Y.treated: Outcome vector for treated unit
specs: List containing specification details
Additional metadata for estimation and plotting
toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) scm_data <- scdata( df = toy, id.var = "unit", time.var = "year", outcome.var = "y", period.pre = 1:4, period.post = 5:6, unit.tr = "treated", unit.co = c("donor1", "donor2"), covagg = list( list(var = "outcome_var", partition_periods = list(type = "by_period")) ) ) class(scm_data)toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) scm_data <- scdata( df = toy, id.var = "unit", time.var = "year", outcome.var = "y", period.pre = 1:4, period.post = 5:6, unit.tr = "treated", unit.co = c("donor1", "donor2"), covagg = list( list(var = "outcome_var", partition_periods = list(type = "by_period")) ) ) class(scm_data)
This function estimates synthetic control weights for a single treated unit using various constraint specifications and optimization methods. It implements the core synthetic control methodology with flexible constraint options and feature weighting schemes.
scest( data, w.constr = NULL, feature_weights = "uniform", V = "separate", V.mat = NULL, solver = "ECOS", save.data = NULL, margin.ipop = 0.05, sigf.ipop = 5, bound.ipop = 10 )scest( data, w.constr = NULL, feature_weights = "uniform", V = "separate", V.mat = NULL, solver = "ECOS", save.data = NULL, margin.ipop = 0.05, sigf.ipop = 5, bound.ipop = 10 )
data |
A list of class "scdata" containing the prepared data from |
w.constr |
List specifying constraints on the synthetic control weights. Options include:
Default is NULL (no constraints). |
feature_weights |
Character. Method for weighting pre-treatment periods/features:
|
V |
Character or matrix specifying the feature weighting approach:
|
V.mat |
Matrix. Custom V (feature weighting) matrix. If provided, overrides V parameter. |
solver |
Character. CVXR solver to use ("ECOS", "OSQP", "GUROBI", etc.). Must be installed on system. Default is "ECOS". |
save.data |
Logical. Whether to save input data in output object (for debugging). Default is NULL. |
margin.ipop |
Numeric. Convergence margin passed to the inner interior-point
quadratic program when |
sigf.ipop |
Numeric. Significant digits required of the inner solver. Default 5. |
bound.ipop |
Numeric. Bound passed to the inner solver. Default 10. |
The function implements the synthetic control method by solving the optimization problem:
where is the treated unit's pre-treatment characteristics, is the control units'
characteristics matrix, are the synthetic control weights, and is the feature weighting matrix.
Different constraint types impose different penalty functions:
Simplex:
Lasso:
Ridge:
OLS: No constraints
A list of class "scest" containing:
est.results$w - Vector of synthetic control weights for donor units
est.results$constant_term - Constant (intercept) coefficient if constant=TRUE was used in scdata, NULL otherwise
est.results$b - Full coefficient vector (donor weights + constant term)
est.results$Y.pre.fit - Fitted pre-treatment outcomes
est.results$Y.post.fit - Fitted post-treatment outcomes
est.results$V - Feature weighting matrix used
est.results$w.constr - Weight constraint specification used
data - Original scdata object with estimation matrices
Additional estimation metadata and diagnostics
toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) scm_data <- scdata( df = toy, id.var = "unit", time.var = "year", outcome.var = "y", period.pre = 1:4, period.post = 5:6, unit.tr = "treated", unit.co = c("donor1", "donor2"), covagg = list( list(var = "outcome_var", partition_periods = list(type = "by_period")) ) ) result <- scest(data = scm_data, w.constr = list(name = "simplex")) names(result$est.results)toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) scm_data <- scdata( df = toy, id.var = "unit", time.var = "year", outcome.var = "y", period.pre = 1:4, period.post = 5:6, unit.tr = "treated", unit.co = c("donor1", "donor2"), covagg = list( list(var = "outcome_var", partition_periods = list(type = "by_period")) ) ) result <- scest(data = scm_data, w.constr = list(name = "simplex")) names(result$est.results)
Plot treated and synthetic time series from a fitted
scest object.
Companion Stata and Python packages are described in Cattaneo, Feng, Palomba, and Titiunik (2022).
Companion commands are scdata for data preparation, scest for point estimation, and inference_sc for inference procedures.
For an introduction to synthetic control methods, see Abadie (2021) and references therein.
scplot( result, fig.path = NULL, fig.name = NULL, fig.format = "png", col.treated = "black", col.synth = "mediumblue", label.xy = NULL, plot.range = NULL, x.ticks = NULL, event.label = NULL, plot.specs = NULL, save.data = NULL )scplot( result, fig.path = NULL, fig.name = NULL, fig.format = "png", col.treated = "black", col.synth = "mediumblue", label.xy = NULL, plot.range = NULL, x.ticks = NULL, event.label = NULL, plot.specs = NULL, save.data = NULL )
result |
A class |
fig.path |
a string indicating the path where the plot(s) should be saved. |
fig.name |
a string indicating the name of the plot(s). If multiple plots will be saved the command automatically generates a numeric suffix to avoid overwriting them. |
fig.format |
a string indicating the format in which the plot(s) should be saved. |
col.treated |
a string specifying the color for the treated unit series. Find the full list at https://sape.inf.usi.ch/quick-reference/ggplot2/colour. |
col.synth |
a string specifying the color for the synthetic unit series. Find the full list at https://sape.inf.usi.ch/quick-reference/ggplot2/colour. |
label.xy |
a character list with two elements indicating the name of the axes (eg. label.xy = list(x.lab = "Year", y.lab = "GDP growth (%)")). |
plot.range |
a numeric array indicating the time range of the plot(s). |
x.ticks |
a numeric list containing the location of the ticks on the x axis. |
event.label |
a list containing a character object ('lab') indicating the label of the event and a numeric object indicating the height of the label in the plot. |
plot.specs |
a list containing some specifics to be passed to ggsave (eg. img.width, img.height, dpi) |
save.data |
a character specifying the name and the path of the saved dataframe containing the processed data used to produce the plot. |
plots |
a list containing standard ggplot object(s) that can be used for further customization. |
Matias Cattaneo, Princeton University. [email protected].
Yingjie Feng, Tsinghua University. [email protected].
Filippo Palomba, Princeton University (maintainer). [email protected].
Rocio Titiunik, Princeton University. [email protected].
Abadie, A. (2021). Using synthetic controls: Feasibility, data requirements, and methodological aspects. Journal of Economic Literature, 59(2), 391-425.
# Create example data df <- data.frame( unit = rep(c("treated", "control1", "control2"), each = 20), time = rep(1:20, 3), outcome = c(rnorm(20, mean = 1:20), rnorm(20, mean = 1:20), rnorm(20, mean = 1:20)) ) # Prepare data for SCM scm_data <- scdata(df = df, id.var = "unit", time.var = "time", outcome.var = "outcome", period.pre = 1:10, period.post = 11:20, unit.tr = "treated", unit.co = c("control1", "control2")) # Estimate synthetic control result <- scest(scm_data, w.constr = list(name = "simplex")) # Plot results scplot(result)# Create example data df <- data.frame( unit = rep(c("treated", "control1", "control2"), each = 20), time = rep(1:20, 3), outcome = c(rnorm(20, mean = 1:20), rnorm(20, mean = 1:20), rnorm(20, mean = 1:20)) ) # Prepare data for SCM scm_data <- scdata(df = df, id.var = "unit", time.var = "time", outcome.var = "outcome", period.pre = 1:10, period.post = 11:20, unit.tr = "treated", unit.co = c("control1", "control2")) # Estimate synthetic control result <- scest(scm_data, w.constr = list(name = "simplex")) # Plot results scplot(result)
Solve quadratic programming problem using clarabel solver
solve_qp_clarabel(c, H, A, b, l, u)solve_qp_clarabel(c, H, A, b, l, u)
c |
Linear objective coefficients |
H |
Quadratic objective matrix (Hessian) |
A |
Equality constraint matrix |
b |
Equality constraint RHS |
l |
Lower bounds |
u |
Upper bounds |
Solution vector as matrix
INTERNAL COMPUTATIONAL CORE - This function performs the core computation
for specification curve analysis by systematically varying modeling choices and estimating
treatment effects across all combinations. Called internally by run_spec_curve_analysis().
For user-facing specification curve analysis, use run_spec_curve_analysis() instead.
spec_curve( dataset, outcomes, col_name_unit_name, name_treated_unit, covagg = list(), treated_period, min_period, end_period, col_name_period, feature_weights = c("uniform", "optimize"), outcome_models = c("none", "augsynth", "ridge", "lasso", "ols"), donor_sample = c("all", "most_similar"), sim_function = most_similar, constraints = list(list(name = "ols"), list(name = "simplex"), list(name = "lasso"), list(name = "ridge"), list(name = "L1-L2")), constants = c(FALSE, TRUE), cores = 1, verbose = TRUE, inference_type = "placebo", inference_config = list(), expected_direction = "two_sided" )spec_curve( dataset, outcomes, col_name_unit_name, name_treated_unit, covagg = list(), treated_period, min_period, end_period, col_name_period, feature_weights = c("uniform", "optimize"), outcome_models = c("none", "augsynth", "ridge", "lasso", "ols"), donor_sample = c("all", "most_similar"), sim_function = most_similar, constraints = list(list(name = "ols"), list(name = "simplex"), list(name = "lasso"), list(name = "ridge"), list(name = "L1-L2")), constants = c(FALSE, TRUE), cores = 1, verbose = TRUE, inference_type = "placebo", inference_config = list(), expected_direction = "two_sided" )
dataset |
Data frame containing panel data in long format with units, time periods, and outcomes. |
outcomes |
Character vector of outcome variable names to analyze. Multiple outcomes will be processed separately. |
col_name_unit_name |
Character. Name of column containing unit identifiers (e.g., "state", "country"). |
name_treated_unit |
Character. Identifier of the treated unit as it appears in the data. |
covagg |
List of covariate specifications for pre-treatment matching.
Each top-level entry must be a named specification with optional |
treated_period |
Numeric. First time period when treatment is active for the treated unit. |
min_period |
Numeric. Earliest time period available in the dataset. |
end_period |
Numeric. Latest time period available in the dataset. |
col_name_period |
Character. Name of column containing time period identifiers. |
feature_weights |
Character vector of feature weighting methods:
Default is |
outcome_models |
Character vector of outcome modeling approaches:
Default is |
donor_sample |
Character vector specifying donor pool selection:
Default is |
sim_function |
Function used when |
constraints |
List of constraint specifications for weight optimization.
Each element should be a list with constraint parameters (e.g.,
|
constants |
Logical vector indicating whether to include a constant term in synthetic control estimation for each specification. |
cores |
Integer. Number of CPU cores for parallel processing. Default is 1 (sequential). Higher values increase speed at higher memory cost. |
verbose |
Logical. Whether to display progress messages and warnings. Default is TRUE. |
inference_type |
Character. Type of inference to perform:
|
inference_config |
List. Configuration settings for inference methods:
|
expected_direction |
Character scalar used by direction-aware inference
helpers: |
Generate Specification Curve (Core Computational Engine)
The function creates a Cartesian product of all specification choices and estimates synthetic controls for each combination. This implementation includes:
Parallel Processing: Distributes computations across multiple cores.
Error Handling: Captures failed estimations and returns diagnostics.
Progress Tracking: Provides estimation progress updates.
Memory Management: Handles large specification spaces.
The resulting specification curve can reveal:
Sensitivity of treatment-effect estimates to modeling choices.
Distribution of effect sizes across specifications.
Which modeling choices are associated with estimate shifts.
Overall robustness of findings.
A list structure containing:
results: data.table in long format with all specification results.
abadie_inference: list containing placebo-in-space inference
outputs, including p-values and post/pre RMSPE ratios.
bootstrap_inference: list containing bootstrap inference outputs.
The results data.table contains treatment effects (tau), unit information, and specification
metadata for all units (treated, control placebo, bootstrap iterations) across all specifications.
Inference results are separated to avoid duplication and provide cleaner access to statistical tests.
Simonsohn, U., Simmons, J. P., and Nelson, L. D. (2020). Specification curve analysis. Nature Human Behaviour, 4(11), 1208-1214.
Huntington-Klein, N. et al. (2021). The influence of hidden researcher decisions in applied microeconomics. Economic Inquiry, 59(3), 944-960.
set.seed(1) toy <- expand.grid(unit = c("treated", "donor1", "donor2", "donor3"), year = 1:10, stringsAsFactors = FALSE) toy$y <- 50 + 0.8 * toy$year + ifelse(toy$unit == "treated" & toy$year >= 7, 3, 0) + rnorm(nrow(toy), 0, 0.5) results <- spec_curve( dataset = toy, outcomes = "y", col_name_unit_name = "unit", name_treated_unit = "treated", covagg = list( baseline = list( label = "baseline", operations = list(list(var = "outcome_var", partition_periods = list(type = "by_period"))) ) ), treated_period = 7, min_period = 1, end_period = 10, col_name_period = "year", feature_weights = "uniform", outcome_models = "none", donor_sample = "all", constraints = list(list(name = "simplex")), constants = FALSE, cores = 1, inference_type = "placebo", inference_config = list(verbose = FALSE, placebo_cores = 1) ) names(results)set.seed(1) toy <- expand.grid(unit = c("treated", "donor1", "donor2", "donor3"), year = 1:10, stringsAsFactors = FALSE) toy$y <- 50 + 0.8 * toy$year + ifelse(toy$unit == "treated" & toy$year >= 7, 3, 0) + rnorm(nrow(toy), 0, 0.5) results <- spec_curve( dataset = toy, outcomes = "y", col_name_unit_name = "unit", name_treated_unit = "treated", covagg = list( baseline = list( label = "baseline", operations = list(list(var = "outcome_var", partition_periods = list(type = "by_period"))) ) ), treated_period = 7, min_period = 1, end_period = 10, col_name_period = "year", feature_weights = "uniform", outcome_models = "none", donor_sample = "all", constraints = list(list(name = "simplex")), constants = FALSE, cores = 1, inference_type = "placebo", inference_config = list(verbose = FALSE, placebo_cores = 1) ) names(results)
This file contains S3 methods for objects of class "spec_curve" created by
the spec_curve function. These methods provide a professional
interface for examining and summarizing specification curve analysis results.
Print method for specification curve objects that provides a concise overview of the analysis results including number of specifications, treatment effect distribution, and available inference results.
## S3 method for class 'spec_curve' print(x, ...)## S3 method for class 'spec_curve' print(x, ...)
x |
An object of class "spec_curve" from |
... |
Additional arguments (currently unused) |
The print method displays:
Number of specifications estimated
Number of outcomes analyzed
Treatment effect summary statistics
Available inference methods and significance rates
Specification dimension summary
No return value, called for side effects (printing)
specs <- data.table::data.table( full_spec_id = paste0("s", 1:4), outcome_model = c("none", "ridge", "none", "ridge"), const = c("simplex", "lasso", "simplex", "lasso"), fw = c("uniform", "uniform", "optimize", "optimize"), feat = c("f1", "f1", "f2", "f2") ) dt <- data.table::CJ( unit_name = c("treated", "donor1"), full_spec_id = as.character(specs[["full_spec_id"]]), post_period = c(FALSE, TRUE), sorted = FALSE ) dt[, unit_type := ifelse(unit_name == "treated", "treated", "control")] dt <- merge(dt, specs, by = "full_spec_id", all.x = TRUE) dt[, outcome := "y"] dt[, rmse := 0.2 + as.numeric(factor(full_spec_id)) * 0.01] dt[, tau := ifelse( unit_name == "treated", as.numeric(factor(full_spec_id)) * 0.5 + ifelse(post_period, 0.2, 0), as.numeric(factor(full_spec_id)) * 0.1 + ifelse(post_period, 0.05, 0) )] spec_results <- list(results = dt) class(spec_results) <- "spec_curve" print(spec_results)specs <- data.table::data.table( full_spec_id = paste0("s", 1:4), outcome_model = c("none", "ridge", "none", "ridge"), const = c("simplex", "lasso", "simplex", "lasso"), fw = c("uniform", "uniform", "optimize", "optimize"), feat = c("f1", "f1", "f2", "f2") ) dt <- data.table::CJ( unit_name = c("treated", "donor1"), full_spec_id = as.character(specs[["full_spec_id"]]), post_period = c(FALSE, TRUE), sorted = FALSE ) dt[, unit_type := ifelse(unit_name == "treated", "treated", "control")] dt <- merge(dt, specs, by = "full_spec_id", all.x = TRUE) dt[, outcome := "y"] dt[, rmse := 0.2 + as.numeric(factor(full_spec_id)) * 0.01] dt[, tau := ifelse( unit_name == "treated", as.numeric(factor(full_spec_id)) * 0.5 + ifelse(post_period, 0.2, 0), as.numeric(factor(full_spec_id)) * 0.1 + ifelse(post_period, 0.05, 0) )] spec_results <- list(results = dt) class(spec_results) <- "spec_curve" print(spec_results)
R6 class for managing large specification curve results with disk-based storage, compression, and memory-efficient retrieval patterns.
storage_dirDirectory for storing specification results
compression_levelCompression level (0-9, higher = more compression)
max_memory_mbMaximum memory usage in MB before offloading to disk
current_memory_usageCurrent memory usage tracking
SpecResultsManager$new()Initialize storage manager
SpecResultsManager$new( storage_dir = tempdir(), max_memory_mb = 1000, compression_level = 6 )
storage_dirCharacter, directory path for storage
max_memory_mbNumeric, maximum memory in MB
compression_levelInteger 0-9, compression level
SpecResultsManager$store_results()Store specification results efficiently
SpecResultsManager$store_results(spec_results, spec_id, metadata = list())
spec_resultsList or data.table with specification results
spec_idCharacter, unique identifier for this specification set
metadataList with additional metadata
SpecResultsManager$retrieve_results()Retrieve specification results
SpecResultsManager$retrieve_results(spec_id, lazy = TRUE)
spec_idCharacter, specification set identifier
lazyLogical, whether to use lazy loading for large results
SpecResultsManager$get_filtered_results()Get filtered subset of results without loading all data
SpecResultsManager$get_filtered_results( spec_id, filter_function = NULL, select_cols = NULL )
spec_idCharacter, specification set identifier
filter_functionFunction to apply for filtering
select_colsCharacter vector of columns to select
SpecResultsManager$update_metadata()Update metadata for stored results
SpecResultsManager$update_metadata( spec_id, file_path, spec_results, additional_metadata = list() )
spec_idCharacter, specification identifier
file_pathCharacter, path to results file
spec_resultsThe results object
additional_metadataList with additional metadata
SpecResultsManager$get_metadata()Get metadata for all stored results
SpecResultsManager$get_metadata()
SpecResultsManager$check_memory_usage()Check current memory usage and trigger cleanup if needed
SpecResultsManager$check_memory_usage()
SpecResultsManager$cleanup_memory()Clean up memory by removing old cached objects
SpecResultsManager$cleanup_memory()
SpecResultsManager$get_storage_summary()Get summary statistics about stored specifications
SpecResultsManager$get_storage_summary()
SpecResultsManager$cleanup_storage()Clean up storage directory
SpecResultsManager$cleanup_storage(older_than_days = 30)
older_than_daysRemove results older than this many days
SpecResultsManager$merge_specification_sets()Merge multiple specification result sets efficiently
SpecResultsManager$merge_specification_sets(spec_ids, output_spec_id)
spec_idsCharacter vector of specification IDs to merge
output_spec_idCharacter, ID for merged results
SpecResultsManager$clone()The objects of this class are cloneable with this method.
SpecResultsManager$clone(deep = FALSE)
deepWhether to make a deep clone.
The summary method for synthetic control prediction objects.
## S3 method for class 'scdata' summary(object, ...)## S3 method for class 'scdata' summary(object, ...)
object |
Class "scest" object, obtained by calling |
... |
Additional arguments |
No return value, called to summarize scdata results.
Matias Cattaneo, Princeton University. [email protected].
Yingjie Feng, Tsinghua University. [email protected].
Filippo Palomba, Princeton University (maintainer). [email protected].
Rocio Titiunik, Princeton University. [email protected].
Supported methods: print.scdata, summary.scdata.
The summary method for synthetic control prediction fitted objects.
## S3 method for class 'scest' summary(object, ...)## S3 method for class 'scest' summary(object, ...)
object |
Class "scest" object, obtained by calling |
... |
Additional arguments |
No return value, called to summarize scest results.
Matias Cattaneo, Princeton University. [email protected].
Yingjie Feng, Tsinghua University. [email protected].
Filippo Palomba, Princeton University (maintainer). [email protected].
Rocio Titiunik, Princeton University. [email protected].
Supported methods: print.scest, summary.scest, coef.scest.
Summary method for scest_formula objects
## S3 method for class 'scest_formula' summary(object, ...)## S3 method for class 'scest_formula' summary(object, ...)
object |
An scest_formula object |
... |
Additional arguments |
Detailed summary method for specification curve objects that provides comprehensive information about the analysis results, including breakdown by specification dimensions and detailed inference results.
## S3 method for class 'spec_curve' summary(object, ...)## S3 method for class 'spec_curve' summary(object, ...)
object |
An object of class "spec_curve" from |
... |
Additional arguments (currently unused) |
The summary method provides detailed information including:
Treatment effect statistics by specification dimension
Inference results breakdown
Model performance statistics (RMSE, fit)
Specification robustness indicators
No return value, called for side effects (printing detailed summary)
specs <- data.table::data.table( full_spec_id = paste0("s", 1:4), outcome_model = c("none", "ridge", "none", "ridge"), const = c("simplex", "lasso", "simplex", "lasso"), fw = c("uniform", "uniform", "optimize", "optimize"), feat = c("f1", "f1", "f2", "f2") ) dt <- data.table::CJ( unit_name = c("treated", "donor1"), full_spec_id = as.character(specs[["full_spec_id"]]), post_period = c(FALSE, TRUE), sorted = FALSE ) dt[, unit_type := ifelse(unit_name == "treated", "treated", "control")] dt <- merge(dt, specs, by = "full_spec_id", all.x = TRUE) dt[, outcome := "y"] dt[, rmse := 0.2 + as.numeric(factor(full_spec_id)) * 0.01] dt[, tau := ifelse( unit_name == "treated", as.numeric(factor(full_spec_id)) * 0.5 + ifelse(post_period, 0.2, 0), as.numeric(factor(full_spec_id)) * 0.1 + ifelse(post_period, 0.05, 0) )] spec_results <- list(results = dt) class(spec_results) <- "spec_curve" summary(spec_results)specs <- data.table::data.table( full_spec_id = paste0("s", 1:4), outcome_model = c("none", "ridge", "none", "ridge"), const = c("simplex", "lasso", "simplex", "lasso"), fw = c("uniform", "uniform", "optimize", "optimize"), feat = c("f1", "f1", "f2", "f2") ) dt <- data.table::CJ( unit_name = c("treated", "donor1"), full_spec_id = as.character(specs[["full_spec_id"]]), post_period = c(FALSE, TRUE), sorted = FALSE ) dt[, unit_type := ifelse(unit_name == "treated", "treated", "control")] dt <- merge(dt, specs, by = "full_spec_id", all.x = TRUE) dt[, outcome := "y"] dt[, rmse := 0.2 + as.numeric(factor(full_spec_id)) * 0.01] dt[, tau := ifelse( unit_name == "treated", as.numeric(factor(full_spec_id)) * 0.5 + ifelse(post_period, 0.2, 0), as.numeric(factor(full_spec_id)) * 0.1 + ifelse(post_period, 0.05, 0) )] spec_results <- list(results = dt) class(spec_results) <- "spec_curve" summary(spec_results)
Estimate synthetic control using standard R formula syntax. This function provides a convenient interface that automatically handles data preparation and estimation based on formula specification.
synth( formula, data, time.var, id.var, treated.period, pre.period, post.period, constraint = "simplex", feature.weights = "uniform", covariate.matching = "average", ... )synth( formula, data, time.var, id.var, treated.period, pre.period, post.period, constraint = "simplex", feature.weights = "uniform", covariate.matching = "average", ... )
formula |
A formula object specifying the model. Format: |
data |
A data.frame or data.table containing the panel data |
time.var |
Character. Name of time variable column |
id.var |
Character. Name of unit identifier column |
treated.period |
Numeric. First period when treatment begins |
pre.period |
Numeric vector. Pre-treatment periods to use for matching |
post.period |
Numeric vector. Post-treatment periods for outcome prediction |
constraint |
Character or list. Weight constraint specification:
|
feature.weights |
Character. Feature weighting method ("uniform" or "optimize") |
covariate.matching |
Character. How to aggregate covariates:
|
... |
Additional arguments passed to |
The formula interface translates standard R formula syntax into the appropriate scdata/scest calls:
Formula Components:
Left side: Outcome variable
Right side (before |): Covariates for matching
Right side (after |): Treated unit identifier
Special Formula Syntax:
I(log(var)) - Transform variables
var1 + var2 - Include multiple covariates
. - Include all available covariates
-var - Exclude specific variables from matching
An object of class "scest_formula" inheriting from "scest" with additional formula-related metadata
panel_data <- data.frame( state = rep(c("California", "Texas", "Florida"), each = 6), year = rep(1:6, 3), gdp = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15), population = c(1, 1.1, 1.2, 1.3, 1.35, 1.4, 0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.33, 1.36, 1.4), investment = c(2, 2.1, 2.2, 2.4, 2.5, 2.6, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.0, 2.1, 2.2, 2.25, 2.3, 2.35) ) result <- synth( gdp ~ population + investment | California, data = panel_data, time.var = "year", id.var = "state", treated.period = 5, pre.period = 1:4, post.period = 5:6, constraint = "simplex" ) class(result)panel_data <- data.frame( state = rep(c("California", "Texas", "Florida"), each = 6), year = rep(1:6, 3), gdp = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15), population = c(1, 1.1, 1.2, 1.3, 1.35, 1.4, 0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.33, 1.36, 1.4), investment = c(2, 2.1, 2.2, 2.4, 2.5, 2.6, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.0, 2.1, 2.2, 2.25, 2.3, 2.35) ) result <- synth( gdp ~ population + investment | California, data = panel_data, time.var = "year", id.var = "state", treated.period = 5, pre.period = 1:4, post.period = 5:6, constraint = "simplex" ) class(result)
These methods provide broom-compatible interfaces for extracting information from synthetic control estimation results in tidy data.table format. They follow the broom convention of tidy(), glance(), and augment() methods while returning data.table objects for efficiency.
Tidy Methods for SCMs Objects
Extract donor unit weights and related information from synthetic control estimation results in a tidy data.table format compatible with broom workflows.
## S3 method for class 'scest' tidy(x, conf.int = FALSE, conf.level = 0.95, ...)## S3 method for class 'scest' tidy(x, conf.int = FALSE, conf.level = 0.95, ...)
x |
An object of class "scest" from |
conf.int |
Logical. Whether to include confidence intervals (if available) |
conf.level |
Numeric. Confidence level for intervals (default 0.95) |
... |
Additional arguments (not currently used) |
A data.table with columns:
term - Character. Donor unit names
estimate - Numeric. Synthetic control weights
donor_rank - Integer. Rank of donor by weight (1 = highest)
weight_percentage - Numeric. Weight as percentage of total
is_active - Logical. Whether weight is meaningfully non-zero
constraint_type - Character. Type of constraint used
std.error - Numeric. Standard error (if conf.int=TRUE and available)
conf.low - Numeric. Lower confidence bound (if conf.int=TRUE)
conf.high - Numeric. Upper confidence bound (if conf.int=TRUE)
toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) sc_obj <- scdata( df = toy, id.var = "unit", time.var = "year", outcome.var = "y", period.pre = 1:4, period.post = 5:6, unit.tr = "treated", unit.co = c("donor1", "donor2"), covagg = list(list(var = "outcome_var", partition_periods = list(type = "by_period"))) ) fit <- scest(sc_obj, w.constr = list(name = "simplex")) generics::tidy(fit)toy <- data.frame( unit = rep(c("treated", "donor1", "donor2"), each = 6), year = rep(1:6, 3), y = c(10, 11, 12, 14, 15, 16, 9, 10, 11, 12, 13, 14, 11, 12, 13, 13, 14, 15) ) sc_obj <- scdata( df = toy, id.var = "unit", time.var = "year", outcome.var = "y", period.pre = 1:4, period.post = 5:6, unit.tr = "treated", unit.co = c("donor1", "donor2"), covagg = list(list(var = "outcome_var", partition_periods = list(type = "by_period"))) ) fit <- scest(sc_obj, w.constr = list(name = "simplex")) generics::tidy(fit)
Extract treatment effect estimates and metadata from specification curve analysis results in tidy data.table format.
## S3 method for class 'spec_curve' tidy(x, conf.int = FALSE, conf.level = 0.95, ...)## S3 method for class 'spec_curve' tidy(x, conf.int = FALSE, conf.level = 0.95, ...)
x |
An object of class "spec_curve" from |
conf.int |
Logical. Whether to include confidence intervals from inference |
conf.level |
Numeric. Confidence level for intervals (default 0.95) |
... |
Additional arguments (not currently used) |
A data.table with columns:
spec_id - Character. Specification identifier
estimate - Numeric. Treatment effect estimate
outcome - Character. Outcome variable name
unit_name - Character. Treated unit name
rmse - Numeric. Root mean squared error
outcome_model - Character. Outcome modeling approach
constraint_type - Character. Weight constraint type
feature_weights - Character. Feature weighting method
p.value - Numeric. P-value from inference (if available)
statistic - Numeric. Test statistic (if available)
method - Character. Inference method used
significant - Logical. Whether p < 0.05
spec_results <- list( results = data.table::data.table( full_spec_id = c("s1", "s2"), tau = c(1.2, 0.8), outcome = "y", unit_name = "treated", unit_type = "treated", post_period = TRUE, rmse = c(0.2, 0.3) ) ) class(spec_results) <- "spec_curve" generics::tidy(spec_results)spec_results <- list( results = data.table::data.table( full_spec_id = c("s1", "s2"), tau = c(1.2, 0.8), outcome = "y", unit_name = "treated", unit_type = "treated", post_period = TRUE, rmse = c(0.2, 0.3) ) ) class(spec_results) <- "spec_curve" generics::tidy(spec_results)