本文へ移動
cccskills
無料GitHub で公開

synthetic-control

Econometrics skill for Synthetic Control Method (SCM). Activates when the user asks about: "synthetic control", "SCM", "synthetic counterfactual", "donor pool", "placebo test", "in-space placebo", "in-time placebo", "MSPE ratio", "Abadie Diamond Hainmueller", "augmented synthetic control", "penalized SCM", "synthetic DID", "合成控制", "合成控制法", "捐助池", "安慰剂检验", "合成反事实", "合成DID", "政策评估"

インストール方法を見る

含まれるファイル(2)

  • SKILL.md16.4 KB
  • references/synthetic-control-reference.md4.9 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Synthetic Control Method (SCM) Skill

This skill guides complete synthetic control analyses: donor pool construction, weight optimization, gap estimation, placebo-based inference, and extensions including augmented SCM and synthetic DID. Designed for policy evaluation with few treated units.

When to Use Synthetic Control

SituationMethod
Single treated unit, many potential controlsClassic SCM
Few treated unitsMulti-unit SCM or Synthetic DID
Treatment at aggregate level (state, country)Classic SCM
Want DID-like inference with SCM weightingSynthetic DID (Arkhangelsky et al. 2021)

Key advantage over DID: SCM constructs a data-driven counterfactual rather than assuming parallel trends for all control units equally.

Core Logic

SCM constructs a weighted combination of untreated ("donor") units that best approximates the treated unit's pre-treatment characteristics and outcome trajectory.

Estimand: τ_t = Y₁ₜ − Ŷ₁ₜ^(SC) for post-treatment periods t > T₀

Key Assumptions:

  1. No interference between units (SUTVA)
  2. No anticipation of treatment
  3. Treated unit can be well-approximated by donor pool in pre-treatment period
  4. The data-generating process is stable across pre/post periods

SCM Workflow

  1. Define units and treatment: Identify treated unit, donor pool, treatment date
  2. Select predictors: Choose pre-treatment covariates and outcome lags
  3. Optimize weights: Minimize pre-treatment MSPE between treated and synthetic control
  4. Estimate effects: Gap = treated outcome − synthetic control outcome
  5. Inference: Run placebo tests (in-space and in-time)
  6. Report: Gap plot, placebo plot, MSPE ratios

Code Templates

R — Classic SCM (Synth package)

# R — Abadie-Diamond-Hainmueller SCM
library(Synth)

dataprep_out <- dataprep(
  foo = df,
  predictors = c("gdp_pc", "trade_share", "inflation"),
  predictors.op = "mean",
  time.predictors.prior = 1980:1999,
  special.predictors = list(
    list("outcome", 1995:1999, "mean"),   # pre-treatment outcome lags
    list("outcome", 1990, "mean"),
    list("outcome", 1985, "mean")
  ),
  dependent = "outcome",
  unit.variable = "unit_id",
  unit.names.variable = "unit_name",
  time.variable = "year",
  treatment.identifier = 1,              # treated unit ID
  controls.identifier = 2:20,            # donor pool IDs
  time.optimize.ssr = 1980:1999,         # pre-treatment period
  time.plot = 1980:2010                  # full plot range
)

synth_out <- synth(dataprep_out)

# View donor weights (non-zero weights = selected donors)
synth.tab <- synth.tab(synth_out, dataprep_out)
print(synth.tab$tab.w)   # unit weights
print(synth.tab$tab.v)   # predictor weights

# Gap plot: treated vs synthetic control
path.plot(synth_out, dataprep_out,
          Ylab = "Outcome", Xlab = "Year",
          Main = "Treated vs Synthetic Control")
abline(v = 2000, lty = 2, col = "red")

# Gap (treatment effect) plot
gaps.plot(synth_out, dataprep_out,
          Ylab = "Gap (Treated − Synthetic)", Xlab = "Year",
          Main = "Treatment Effect Over Time")
abline(v = 2000, lty = 2, col = "red")
abline(h = 0, lty = 3)

R — Augmented SCM (augsynth)

# R — Augmented SCM (Ridge-augmented, Ben-Michael et al. 2021)
library(augsynth)

asyn <- augsynth(outcome ~ treatment,
                 unit = unit_id, time = year,
                 data = df,
                 progfunc = "Ridge",    # augmentation method
                 scm = TRUE)            # include SCM weights
summary(asyn)
plot(asyn)

Ridge Augmentation (progfunc = "Ridge"): When the pre-treatment fit of standard SCM is imperfect, Ridge augmentation adds a bias-correction term estimated by a penalized (ridge) regression of the residuals on donor outcomes. This reduces sensitivity to poor pre-treatment balance by shrinking the bias correction toward zero when donor pool fit is already good. The result is an estimator that converges to the standard SCM when pre-treatment fit is excellent, but provides robustness when it is not. Other options include "gsyn" (matrix completion) and "None" (standard SCM only).

Python — SparseSC

# Python — SparseSC (penalized synthetic control)
import SparseSC
import numpy as np

# Reshape data to (N_units × T_periods) matrix
Y = df.pivot(index='unit_id', columns='year', values='outcome').values
T0 = 20  # number of pre-treatment periods

# Fit sparse synthetic control
sc = SparseSC.fit(
    features=Y[:, :T0],       # pre-treatment outcomes
    targets=Y[:, T0:],        # post-treatment outcomes
    treated_units=[0]          # index of treated unit
)

# Treatment effect estimate
treated_actual = Y[0, T0:]
synthetic_control = sc.predict(Y[0:1, :T0])[0]
effect = treated_actual - synthetic_control
print(f"Average post-treatment effect: {np.mean(effect):.4f}")

Stata — synth and synth_runner

* Stata — Classic SCM
ssc install synth
ssc install synth_runner

tsset unit_id year

synth outcome gdp_pc trade_share inflation ///
    outcome(1995) outcome(1990) outcome(1985), ///
    trunit(1) trperiod(2000) ///
    fig keep(synth_results) replace

* Plot results
twoway (line outcome year if unit_id == 1, lcolor(black) lwidth(medium)) ///
       (line _Y_synthetic year if unit_id == 1, lcolor(red) lpattern(dash)), ///
       xline(2000, lpattern(dash)) ///
       legend(label(1 "Treated") label(2 "Synthetic Control")) ///
       title("Synthetic Control Estimate")

Inference: Placebo Tests

SCM does not have standard errors in the traditional sense. Inference is based on placebo (permutation) tests.

In-Space Placebo Test

Iteratively apply SCM to every control unit as if it were treated. If the treated unit's effect is unusually large relative to placebos, the effect is credible.

# R — in-space placebo (Synth)
library(Synth)
placebo_gaps <- list()
all_units <- unique(df$unit_id)

for (u in all_units) {
  controls <- setdiff(all_units, u)
  dp <- dataprep(foo = df, predictors = c("gdp_pc", "trade_share"),
                 predictors.op = "mean", time.predictors.prior = 1980:1999,
                 special.predictors = list(list("outcome", 1995:1999, "mean")),
                 dependent = "outcome", unit.variable = "unit_id",
                 time.variable = "year", treatment.identifier = u,
                 controls.identifier = controls,
                 time.optimize.ssr = 1980:1999, time.plot = 1980:2010)
  so <- synth(dp, Sigf.ipop = 3)
  placebo_gaps[[as.character(u)]] <- dp$Y1plot - (dp$Y0plot %*% so$solution.w)
}

# Plot all gaps; treated unit should stand out
plot(1980:2010, placebo_gaps[["1"]], type = "l", lwd = 2, col = "black",
     ylim = range(unlist(placebo_gaps)), ylab = "Gap", xlab = "Year")
for (u in names(placebo_gaps)[-1]) {
  lines(1980:2010, placebo_gaps[[u]], col = "grey70")
}
abline(v = 2000, lty = 2); abline(h = 0, lty = 3)
legend("topleft", c("Treated", "Placebos"), col = c("black","grey70"), lwd = c(2,1))
* Stata — in-space placebo with synth_runner
synth_runner outcome gdp_pc trade_share inflation ///
    outcome(1995) outcome(1990) outcome(1985), ///
    trunit(1) trperiod(2000) gen_vars
effect_graphs
single_treatment_graphs

MSPE Ratios

Calculate post/pre MSPE ratio for each unit. The treated unit's ratio should rank highest.

# Rank by post/pre MSPE ratio
mspe_ratios <- sapply(names(placebo_gaps), function(u) {
  gap <- placebo_gaps[[u]]
  pre  <- gap[1:20]   # pre-treatment periods
  post <- gap[21:31]  # post-treatment periods
  sum(post^2) / sum(pre^2)
})

# p-value: fraction of placebos with ratio ≥ treated
p_value <- mean(mspe_ratios >= mspe_ratios["1"])
cat("MSPE ratio rank p-value:", p_value, "\n")
# p < 0.05 → significant effect

In-Time Placebo

Apply SCM with a fake treatment date in the pre-treatment period. Effect should be near zero.

# Use earlier fake treatment date
dp_placebo <- dataprep(foo = df, ...,
                       time.optimize.ssr = 1980:1989,  # shorter pre-period
                       time.plot = 1980:1999)           # only pre-treatment
so_placebo <- synth(dp_placebo)
# Gap should be ≈ 0 if model is well-specified

Donor Pool Construction

GuidelineRationale
Exclude units affected by similar treatmentAvoids contamination
Include only structurally similar unitsImproves fit quality
Use pre-treatment outcome lags as predictorsMost powerful predictors
Drop donors with zero weight and large pre-MSPEFocus on contributing donors
Leave-one-out: iteratively drop each donorCheck weight sensitivity

Covariate Balance Check

After constructing the synthetic control, verify that predictors are balanced between the treated unit and its synthetic counterpart:

# R — predictor balance check after synth()
synth_tab <- synth.tab(synth_out, dataprep_out)

# tab.pred: treated, synthetic control, and sample average for each predictor
print(synth_tab$tab.pred)
# Look for rows where treated and synthetic values are close (small gap)
# Large gaps in key predictors signal poor donor pool or predictor choice

# Compute RMSPE on predictor balance:
pred_balance <- synth_tab$tab.pred[, 1:2]  # treated vs synthetic
pred_gaps <- pred_balance[, 1] - pred_balance[, 2]
cat("Predictor balance (treated - synthetic):\n")
print(round(pred_gaps, 4))
# Python — predictor balance after SparseSC or manual SCM
import numpy as np
import pandas as pd

# Treated unit predictor values (pre-treatment means)
treated_pred  = X_treated.mean(axis=0)
synthetic_pred = (weights @ X_donors).flatten()  # weights × donor covariates

balance_df = pd.DataFrame({
    'Predictor':  predictor_names,
    'Treated':    treated_pred,
    'Synthetic':  synthetic_pred,
    'Gap':        treated_pred - synthetic_pred,
    'Pct_Gap':    100 * (treated_pred - synthetic_pred) / np.abs(treated_pred)
})
print(balance_df.to_string(index=False))
# Flag predictors where |Pct_Gap| > 5% — consider adjusting donor pool
* Stata — predictor balance displayed automatically after synth
synth outcome gdp_pc trade_share inflation ///
    outcome(1995) outcome(1990) outcome(1985), ///
    trunit(1) trperiod(2000)
* The output table "Predictor Balance" compares treated vs synthetic vs sample avg
* Verify that treated and synthetic columns are close for key predictors

Synthetic DID (Arkhangelsky et al. 2021)

Combines SCM weighting with DID estimation; works with multiple treated units.

# R — Synthetic DID
library(synthdid)

# Data must be a balanced panel in matrix form
setup <- panel.matrices(df, unit = "unit_id", time = "year",
                        outcome = "outcome", treatment = "treated")
sdid <- synthdid_estimate(setup$Y, setup$N0, setup$T0)
se <- sqrt(vcov(sdid, method = "placebo"))
cat("SDID estimate:", sdid, "SE:", se, "\n")
plot(sdid)
# Python — synthdid
# pip install synthdid
from synthdid.model import SynthDID
model = SynthDID(df, unit='unit_id', time='year',
                 outcome='outcome', treatment='treated')
model.fit()
print(f"SDID ATT: {model.att():.4f}")
model.plot()

Pre-Treatment Fit Diagnostics

Good pre-treatment fit is the foundation of SCM credibility. A poorly fitting synthetic control cannot be trusted as a counterfactual.

RMSPE benchmark: Pre-treatment RMSPE should generally be < 5% of the treated unit's pre-treatment outcome mean. Higher values suggest the synthetic control is unreliable; reconsider predictor selection or donor pool composition.

# R — compute and assess pre-treatment RMSPE
# After synth() and dataprep():
gaps <- dataprep_out$Y1plot - (dataprep_out$Y0plot %*% synth_out$solution.w)

pre_periods  <- which(as.numeric(rownames(gaps)) < treatment_year)
post_periods <- which(as.numeric(rownames(gaps)) >= treatment_year)

pre_rmspe  <- sqrt(mean(gaps[pre_periods]^2))
post_rmspe <- sqrt(mean(gaps[post_periods]^2))
outcome_mean <- mean(dataprep_out$Y1plot[pre_periods])

cat(sprintf("Pre-treatment RMSPE: %.4f (%.1f%% of outcome mean)\n",
            pre_rmspe, 100 * pre_rmspe / outcome_mean))
cat(sprintf("Post-treatment RMSPE: %.4f\n", post_rmspe))
cat(sprintf("Post/Pre MSPE ratio: %.2f\n", (post_rmspe/pre_rmspe)^2))

# Rule of thumb: if pre-RMSPE > 5% of outcome mean, reconsider the design
if (pre_rmspe / outcome_mean > 0.05) {
  warning("Pre-treatment RMSPE exceeds 5% of outcome mean — fit may be poor.")
}
# Python — pre-treatment fit visualization
import matplotlib.pyplot as plt
import numpy as np

years = np.array(all_years)
treated  = Y_actual          # actual treated unit outcomes
synthetic = Y_synthetic      # synthetic control outcomes
treatment_year = 2000

fig, axes = plt.subplots(1, 2, figsize=(12, 4))

# Panel A: level plot
axes[0].plot(years, treated,   'k-',  linewidth=2,   label='Treated')
axes[0].plot(years, synthetic, 'r--', linewidth=1.5, label='Synthetic Control')
axes[0].axvline(treatment_year, color='gray', linestyle=':', linewidth=1)
axes[0].set_title('Treated vs Synthetic Control'); axes[0].legend()

# Panel B: gap plot with pre-period RMSPE annotation
gap = treated - synthetic
axes[1].plot(years, gap, 'b-', linewidth=2, label='Gap (τ̂)')
axes[1].axvline(treatment_year, color='gray', linestyle=':', linewidth=1)
axes[1].axhline(0, color='black', linestyle='-', linewidth=0.5)

pre_mask = years < treatment_year
pre_rmspe = np.sqrt(np.mean(gap[pre_mask]**2))
axes[1].set_title(f'Treatment Effect (Pre-RMSPE = {pre_rmspe:.3f})')
axes[1].legend()

plt.tight_layout()
plt.savefig('synthetic_control_fit.pdf', bbox_inches='tight')
* Stata — pre-treatment fit assessment
* After synth estimation, compute RMSPE manually:
* (synth stores results; use stored matrices)
matrix gaps = e(Y_treated) - e(Y_synthetic)

* Pre-treatment RMSPE:
* (Stata code depends on synth version; synth_runner automates this)
synth_runner outcome gdp_pc trade_share outcome(1995) outcome(1990), ///
    trunit(1) trperiod(2000) gen_vars
* Outputs pre_rmspe and post_rmspe in stored results
pval2 using synth_results   // rank-based p-value from synth_runner

Reporting Standards

  1. Pre-treatment fit: Show treated vs synthetic control plot with clear pre-treatment match
  2. Gap plot: Show treatment effect over time with vertical treatment line
  3. Donor weights table: Report which units contribute to synthetic control and their weights
  4. Predictor balance table: Compare treated, synthetic, and sample average
  5. Placebo plot: In-space placebos with treated unit highlighted
  6. MSPE ratio: Report rank-based p-value (e.g., "treated unit ranks 1st of 20 units")
  7. Robustness: Leave-one-out donor test, in-time placebo, alternative predictor sets

Key sentence template:

"We construct a synthetic [unit] using a weighted combination of [N] donor [units] from the [donor pool description]. The synthetic [unit] closely tracks the treated [unit] in the pre-treatment period ([year range], pre-treatment MSPE = [value]). The estimated effect is [magnitude] ([% change]), with the treated unit ranking [1st/2nd] out of [N] units in post/pre-MSPE ratio (p = [value])."

Common Pitfalls

  • Poor pre-treatment fit: If pre-MSPE is large, the synthetic control is unreliable — reconsider predictor set or donor pool
  • Overfitting to noise: Using too many outcome lags can overfit; use 3–5 evenly spaced lags
  • Interpolation bias: SCM requires the treated unit to be within the convex hull of donors
  • Cherry-picking donors: Always report full donor pool; justify exclusions

See references/synthetic-control-reference.md for multi-unit SCM extensions, staggered adoption SCM, and sensitivity analysis.

Related Skills & Commands

  • did-analysis: DID is the most common alternative; consider SCM when few treated units exist
  • matching: Matching methods as complementary approach for causal inference
  • /robustness: Generate additional robustness checks for SCM results
  • /method: Write up the synthetic control methodology section
  • /plot: Create publication-quality SCM plots

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

a1

無料

VS-Enhanced Research Question Refiner - Prevents Mode Collapse and derives differentiated research questions Enhanced VS 3-Phase process: Modal question avoidance, alternatives presentation, differentiated RQ recommendation Use when: refining research ideas, formulating research questions, clarifying scope Triggers: research question, 연구 질문, PICO, SPIDER, research idea

日本語の概要は準備中です。原文の説明を表示しています。

brycewang-stanford/Auto-Empirical-Research-Skills4,5972026年10月12日 更新

a2

無料

VS-Enhanced Theoretical Framework Architect with Critique & Visualization Full VS 5-Phase process: Modal theory avoidance, Long-tail exploration, differentiated framework presentation Absorbed A3 (Devil's Advocate) critique and A6 (Conceptual Framework Visualizer) capabilities Use when: building theoretical foundations, designing conceptual models, deriving hypotheses, critiquing frameworks, visualizing models Triggers: theoretical framework, 이론적 프레임워크, conceptual model, 개념적 모형, hypothesis derivation, critique, devil's advocate, 반론, visualization, diagram

日本語の概要は準備中です。原文の説明を表示しています。

brycewang-stanford/Auto-Empirical-Research-Skills4,5972026年10月12日 更新

a5

無料

Agent A5 - Paradigm & Worldview Advisor - Philosophical foundations for research design. Covers ontology, epistemology, axiology, and methodology alignment. Use when: establishing philosophical foundations, justifying methodological choices, writing positionality statements Triggers: paradigm, 패러다임, ontology, epistemology, worldview, 세계관, philosophical foundations, 철학적 기초

日本語の概要は準備中です。原文の説明を表示しています。

brycewang-stanford/Auto-Empirical-Research-Skills4,5972026年10月12日 更新

Academic paper writing skill with 12-agent pipeline. v2.5: Style Calibration (learn author's writing voice from past papers) + Writing Quality Check (writing quality checklist for natural prose). Supports IMRaD, literature review, theoretical, case study, policy brief, and conference paper structures. APA 7.0 (default), Chicago, MLA, IEEE, Vancouver citation formats. Bilingual abstracts (zh-TW + EN). Multi-format output (LaTeX, DOCX, PDF, Markdown). Triggers on: write paper, academic paper, paper outline, write abstract, revise paper, check citations, convert to LaTeX, guide my paper, parse reviews, revision roadmap, 寫論文, 學術論文, 論文大綱, 寫摘要, 修改論文, 檢查引用, 引導我寫論文, 帶我規劃論文, 逐章規劃, 論文架構, 審查意見, 修訂路線圖.

日本語の概要は準備中です。原文の説明を表示しています。

brycewang-stanford/Auto-Empirical-Research-Skills4,5972026年10月12日 更新

Systematic writing framework for philosophy and interdisciplinary academic papers from optimized outline to submission-ready manuscript. Use when users want to: (1) write a paper from a detailed outline, (2) ensure quality control during writing, (3) maintain consistency across chapters, (4) prepare a submission-ready manuscript, or (5) systematically execute a planned paper. Triggered by phrases like 'write the paper from this outline,' 'compose the full manuscript,' 'execute the outline,' or when users have completed strategic planning (academic-paper-strategist skill) and are ready to write. Takes optimized outline as input; outputs complete manuscript with iterative quality checks.

日本語の概要は準備中です。原文の説明を表示しています。

brycewang-stanford/Auto-Empirical-Research-Skills4,5972026年10月12日 更新

Multi-perspective academic paper review with dynamic reviewer personas. Simulates 5 independent reviewers (EIC + 3 peer reviewers + Devil's Advocate) with field-specific expertise. Supports full review, re-review (verification), quick assessment, methodology focus, and Socratic guided modes. Triggers on: review paper, peer review, manuscript review, referee report, review my paper, critique paper, simulate review, editorial review.

日本語の概要は準備中です。原文の説明を表示しています。

brycewang-stanford/Auto-Empirical-Research-Skills4,5972026年10月12日 更新

brycewang-stanford のスキルをすべて見る

このスキルの問題を報告する