Walks through synthetic control analysis in 5 stages: setup, assumption checks, donor weighting, pre-treatment fit diagnostics, and placebo tests. Works in R or Python.
How this skill is triggered — by the user, by Claude, or both
Slash command
/everyday-causal-skills:causal-scThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You guide users through a complete synthetic control analysis following a 5-stage pattern.
You guide users through a complete synthetic control analysis following a 5-stage pattern.
references/lessons.md — known mistakes. Do not repeat them.references/assumptions/sc.md — the assumption checklist for synthetic control.references/method-registry.md → "Synthetic Control" section.docs/causal-plans/*/plan.md. If it does, read it for context.If a plan document from /causal-planner is provided: Extract the study design (treatment, population, outcome, data structure, language) directly from the plan. Do not re-ask questions the planner already answered. Acknowledge the plan and build on it.
If plan exists: Read it. Extract business objective, treated unit, donor pool, outcome, language, data structure. Confirm: "I've read your analysis plan. You're constructing a synthetic control for [treated unit] to estimate the effect of [treatment] on [outcome]. Does that sound right?"
If no plan: Ask:
Determine variant:
augsynth)gsynth)causal-did)scpi (Python) or gsynth (R)Read references/assumptions/sc.md. Walk through each assumption interactively:
For each assumption:
Key assumptions to walk through:
Pre-treatment fit quality: "Can a weighted combination of donor units reproduce the treated unit's pre-treatment trajectory? Poor fit means the synthetic control is unreliable."
Convex hull: "Does the treated unit's pre-treatment characteristics lie within the range spanned by the donor units? If the treated unit is an extreme outlier, no convex combination of donors can match it."
augsynth in R) which handles extrapolation by adding a ridge-regularized outcome model. This is the modern default for cases where the treated unit is an outlier relative to donors.No interference between units: "Could the treatment of [treated unit] have affected the donor units' outcomes? If donors are affected by the treatment, the synthetic counterfactual is contaminated."
Donor pool composition: "Are all donor units plausible counterfactuals? Including donors affected by their own shocks can bias the synthetic control."
No anticipation: "Did the treated unit's behavior change before the treatment actually started?"
After all assumptions, summarize with status indicators per assumption.
If fatal violations exist (especially poor pre-treatment fit or contaminated donor pool), warn clearly and suggest alternatives. If you cannot yet confirm the violation (because the user hasn't run diagnostic code), use the CONDITIONAL FATAL verdict format from Red Flags. Do not generate full analysis code before a fatal-level diagnostic has been resolved — require the user to report the diagnostic result first.
Generate complete analysis code. Read the appropriate template from templates/r/sc.md or templates/python/sc.md for code patterns.
Missing-package preflight: The template's Prerequisites block detects (never installs) missing packages. Follow references/preflight.md: report what's missing, then ask the user whether they want you to install it for them or do it themselves — install only on an explicit yes.
IMPORTANT — Template adherence: Copy the code pattern from the appropriate template (templates/r/sc.md or templates/python/sc.md) exactly, then adapt only variable names to match the user's data. Do not restructure the code, use alternative function APIs, or improvise accessor patterns. The templates have been tested; deviations introduce bugs.
R package preference: Use the Synth package (not tidysynth) for R implementations unless the user specifically requests tidysynth. The Synth package is more widely installed.
Always include:
Synthetic control (R — tidysynth):
library(tidysynth)
sc <- df %>%
synthetic_control(
outcome = outcome,
unit = unit_id,
time = time,
i_unit = "treated_unit_name",
i_time = treatment_time,
generate_placebos = TRUE
) %>%
generate_predictor(
time_window = pre_start:pre_end,
predictor1 = mean(predictor1),
predictor2 = mean(predictor2),
outcome_avg = mean(outcome)
) %>%
generate_weights(optimization_window = pre_start:pre_end) %>%
generate_control()
# Plot: treated vs synthetic
sc %>% plot_trends()
# Plot: treatment effect (gap)
sc %>% plot_differences()
# Donor weights
sc %>% grab_unit_weights() %>% arrange(desc(weight))
# Pre-treatment fit
sc %>% grab_significance() %>% filter(unit_name == "treated_unit_name")
Synthetic control (R — Synth):
library(Synth)
dataprep_out <- dataprep(
foo = df,
predictors = c("predictor1", "predictor2"),
predictors.op = "mean",
dependent = "outcome",
unit.variable = "unit_id",
time.variable = "time",
treatment.identifier = treated_id,
controls.identifier = donor_ids,
time.predictors.prior = pre_start:pre_end,
time.optimize.ssr = pre_start:pre_end,
time.plot = full_start:full_end
)
synth_out <- synth(dataprep_out)
path.plot(synth.res = synth_out, dataprep.res = dataprep_out)
gaps.plot(synth.res = synth_out, dataprep.res = dataprep_out)
Synthetic control (Python — scpi):
from scpi_pkg.scdata import scdata
from scpi_pkg.scest import scest
from scpi_pkg.scpi import scpi
from scpi_pkg.scplot import scplot
# Prepare data
scd = scdata(
df=df,
id_var="unit_id",
time_var="time",
outcome_var="outcome",
period_pre=list(range(pre_start, treatment_time)),
period_post=list(range(treatment_time, post_end + 1)),
unit_tr="treated_unit_name",
unit_co=donor_list
)
# Estimate
sc_est = scest(scd, w_constr={"name": "simplex"})
print(sc_est)
# Prediction intervals
sc_pred = scpi(scd, w_constr={"name": "simplex"})
print(sc_pred)
# Plot
scplot(sc_pred)
Augmented synthetic control (R — augsynth):
Use when pre-treatment fit is poor or the treated unit falls outside the donor pool's convex hull. ASCM adds a ridge-regularized outcome model on top of SCM weights, allowing controlled extrapolation.
library(augsynth)
# Augmented synthetic control
asyn <- augsynth(
outcome ~ treatment,
unit = unit_id,
time = time,
data = df,
progfunc = "Ridge", # bias correction via ridge regression
scm = TRUE # combine with SCM weights
)
summary(asyn)
plot(asyn)
# Compare with standard SCM
syn_only <- augsynth(
outcome ~ treatment,
unit = unit_id,
time = time,
data = df,
progfunc = "None", # no augmentation = standard SCM
scm = TRUE
)
# If augmented and standard diverge substantially,
# the treated unit was likely outside the convex hull
cat("Standard SCM ATT:", summary(syn_only)$att$Estimate, "\n")
cat("Augmented SCM ATT:", summary(asyn)$att$Estimate, "\n")
When to upgrade from SCM to ASCM:
Adapt code to the user's variable names and data structure.
Propose at least one check. Generate the code.
Options (offer the most relevant):
Before proceeding to interpretation, confirm ALL of the following from actual code output:
If any box is unchecked: Flag it to the user — explain which evidence is missing and why it matters. Offer to run the missing step before interpreting. If the user chooses to continue anyway, carry the gap forward as a caveat in the interpretation.
Watch for premature conclusions — phrases like "The results suggest..." or "Based on the analysis..." before the gate passes. These imply conclusions without evidence. Quote actual output instead.
Severity verdicts must appear BEFORE this gate. If a Fatal or Serious issue was identified during Stage 2 (Assumptions) or Stage 3 (Implementation), the severity verdict block must already be visible in the output above. Do not defer severity communication to after the user runs the code if the data or context already reveals the violation.
| Signal | Severity | Action |
|---|---|---|
| Pre-treatment RMSPE is large (poor fit) | 🚨 Fatal | Synthetic control is unreliable. Warn user; consider augmented SC or switching methods. |
| Treated unit outside donor convex hull | 🚨 Fatal | Extrapolation — weights cannot construct a valid counterfactual. Warn user before continuing. |
| One donor weight > 80% | ⚠️ Serious | Effectively a pairwise comparison. Flag and test robustness to removing that donor. |
| Fewer than 5 donors | ⚠️ Serious | Placebo inference has almost no power. State explicitly. |
🚨 Fatal = Emit this verdict block immediately after the diagnostic that reveals the violation:
FATAL: [violation name] [One sentence: what was found in the data.] This analysis should not proceed without addressing this issue. Results produced under this violation are not trustworthy. If you cannot yet confirm the violation (because the user hasn't run diagnostic code), use CONDITIONAL FATAL: [violation name] with the same format but replace the consequence line with: "If [specific diagnostic condition], this analysis should not proceed. Run the diagnostic above and report the result before continuing." If the user chooses to continue despite a Fatal verdict, repeat the verdict verbatim in Stage 5 interpretation.
⚠️ Serious = Emit this block:
SERIOUS: [limitation name] [One sentence: what was found.] Proceeding is possible, but the interpretation must prominently acknowledge this limitation and its consequences.
Use only FATAL and SERIOUS severity labels. Do not invent additional tiers (Critical, Yellow, Minor, etc.). When in doubt, round UP to the next severity level.
| Shortcut | Reality |
|---|---|
| "This is just an exploratory analysis" | If results will influence a decision, it's not exploratory. Apply full rigor. |
| "We don't need robustness checks -- the main result is strong" | Strong results without robustness checks are more suspicious, not less. |
| "The sample is too small for formal tests" | Small samples need more caution, not less. Flag the limitation explicitly. |
| "The pre-fit looks reasonable" | Report RMSPE. "Reasonable" needs a number. |
| "We have enough donors" | With fewer than ~10 donors, placebo inference has almost no power. Report it. |
| "The weights make sense" | If one donor dominates (>80%), the synthetic control is basically a comparison to one unit. Flag it. |
Help write a plain-language summary:
"Based on the synthetic control analysis:
Placebo inference:
Cumulative vs period effects:
Caveats:
Donor weights: "The synthetic control is a weighted mix of donor units. If one donor carries more than 60-70% of the weight, the analysis is essentially a comparison with that single unit — fragile and sensitive to anything idiosyncratic about that donor. A more balanced portfolio of donors is more credible."
Pre-treatment RMSPE: "RMSPE of [X] means the synthetic control's predictions were off by [X] on average in the pre-treatment period. Lower is better. If the synthetic version can't track the treated unit before treatment, its post-treatment projection is unreliable — like forecasting with a broken model."
Post/pre RMSPE ratio: "A ratio above 2 means the gap between the treated unit and its synthetic version roughly doubled after treatment — suggesting a real effect. A ratio near 1 means no detectable change. Compare this ratio to the placebo distribution."
Placebo rank: "The treated unit ranks [X] out of [N] in the placebo distribution. Only [X-1] donor units showed a larger effect when we pretended they were treated. Think of rank/N as a pseudo p-value: 1/20 = 0.05, 2/20 = 0.10. Ranks above 0.10 are not clearly distinguishable from noise."
Save alongside the plan (or create a new directory if standalone):
docs/causal-plans/YYYY-MM-DD-<project>/
├── plan.md # From planner (or created here if standalone)
├── implementation.md # This skill's stage-by-stage summary
└── analysis.[R|py] # Generated code
Use the Write tool. Tell the user where files are saved.
"Your synthetic control analysis is complete. Recommended next steps:
/causal-auditor to stress-test for threats to validity.Synth package (Abadie et al.), not tidysynth, for the canonical implementation. tidysynth has API differences that break standard diagnostics.Before this skill:
/causal-planner -- Identifies method and saves analysis plan (recommended)After this skill:
/causal-auditor -- Stress-test results for threats to validity (recommended)/causal-exercises -- Practice a similar analysis on simulated data (optional)If assumptions fail:
/causal-did -- If more treated units are available/causal-timeseries -- Single unit with no suitable donorsIf the user corrects you, append to references/lessons.md:
### SC: [Short description]
**Trigger**: [When this tends to happen]
**Mistake**: [What went wrong]
**Rule**: [What to do instead]
**Source**: User correction, [date]
npx claudepluginhub robsontigre/everyday-causal-skills --plugin everyday-causal-skillsGuides through complete difference-in-differences analysis: setup, parallel trends testing, staggered rollout handling, robustness checks, and plain-language interpretation.
Provides deep expertise in difference-in-differences and panel causal designs. Activates on terms like 'DID', 'event study', 'parallel trends', 'staggered treatment'.
Designs, runs, and critiques causal inference workflows in Stata for identification strategies, treatment effects, DiD, IV, event studies, RD, and assumption-sensitive empirical claims.