Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Causal inference methods including DiD, IV, RDD, and synthetic control
.claude/skills/brycewang-stanford-causal-inference-guide/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -7% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 153% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 15% | 0% |
| case-08 | ✓→✓ | = Same ✓ | 52% | 0% |
A skill for applying quasi-experimental causal inference methods in observational research. Covers difference-in-differences, instrumental variables, regression discontinuity designs, and synthetic control methods with implementation code and diagnostic checks.
pythonimport numpy as np import pandas as pd import statsmodels.formula.api as smf def did_estimation(df: pd.DataFrame, outcome: str, treatment: str, post: str, covariates: list[str] = None) -> dict: """ Estimate a difference-in-differences model. Args: df: Panel DataFrame outcome: Name of outcome variable column treatment: Name of treatment group indicator (0/1) post: Name of post-treatment period indicator (0/1) covariates: Optional list of control variable names """ # Create interaction term df = df.copy() df['did'] = df[treatment] * df[post] # Build formula formula = f"{outcome} ~ {treatment} + {post} + did" if covariates: formula += ' + ' + ' + '.join(covariates) model = smf.ols(formula, data=df).fit(cov_type='cluster', cov_kwds={'groups': df.get('unit_id', df.index)}) return { 'did_estimate': model.params['did'], 'se': model.bse['did'], 'p_value': model.pvalues['did'], 'ci_95': (model.conf_int().loc['did', 0], model.conf_int().loc['did', 1]), 'r_squared': model.rsquared, 'n_obs': model.nobs, 'interpretation': ( f"The treatment effect is {model.params['did']:.3f} " f"(SE = {model.bse['did']:.3f}, p = {model.pvalues['did']:.4f}). " f"{'Statistically significant' if model.pvalues['did'] < 0.05 else 'Not significant'} " f"at the 5% level." ) }
The key identifying assumption. Test it with pre-treatment data:
pythondef test_parallel_trends(df: pd.DataFrame, outcome: str, treatment: str, time: str, treatment_period: int) -> dict: """ Test the parallel trends assumption using event study specification. """ df = df.copy() pre_periods = sorted(df[df[time] < treatment_period][time].unique()) # Create period dummies interacted with treatment for t in pre_periods: df[f'pre_{t}'] = ((df[time] == t) & (df[treatment] == 1)).astype(int) period_vars = [f'pre_{t}' for t in pre_periods[:-1]] # omit last pre-period (reference) formula = f"{outcome} ~ {' + '.join(period_vars)} + C({time}) + C(unit_id)" model = smf.ols(formula, data=df).fit() # Joint F-test: all pre-treatment interactions = 0 f_test = model.f_test(' = '.join([f'{v} = 0' for v in period_vars])) return { 'pre_period_coefficients': {v: model.params[v] for v in period_vars}, 'f_statistic': f_test.fvalue[0][0], 'f_pvalue': f_test.pvalue, 'parallel_trends_hold': f_test.pvalue > 0.05, 'interpretation': ( 'Parallel trends assumption supported (cannot reject joint null)' if f_test.pvalue > 0.05 else 'WARNING: Parallel trends assumption may be violated' ) }
pythonfrom linearmodels.iv import IV2SLS def iv_estimation(df: pd.DataFrame, outcome: str, endogenous: str, instrument: str, exogenous: list[str] = None) -> dict: """ Estimate an IV model using 2SLS. Args: outcome: Dependent variable endogenous: Endogenous regressor instrument: Instrumental variable exogenous: List of exogenous control variables """ exog_formula = '1' if exogenous: exog_formula += ' + ' + ' + '.join(exogenous) model = IV2SLS( dependent=df[outcome], exog=df[exogenous] if exogenous else None, endog=df[[endogenous]], instruments=df[[instrument]] ).fit(cov_type='robust') # First-stage F-statistic first_stage = smf.ols(f"{endogenous} ~ {instrument}", data=df).fit() f_stat = first_stage.fvalue return { 'iv_estimate': model.params[endogenous], 'se': model.std_errors[endogenous], 'p_value': model.pvalues[endogenous], 'first_stage_F': f_stat, 'weak_instrument': f_stat < 10, # Stock-Yogo rule of thumb 'interpretation': ( f"IV estimate: {model.params[endogenous]:.3f}. " f"First-stage F = {f_stat:.1f} " f"({'Strong' if f_stat >= 10 else 'WEAK'} instrument)." ) }
pythondef rdd_estimation(df: pd.DataFrame, outcome: str, running_var: str, cutoff: float, bandwidth: float = None) -> dict: """ Sharp regression discontinuity design estimation. """ df = df.copy() df['centered'] = df[running_var] - cutoff df['treated'] = (df[running_var] >= cutoff).astype(int) if bandwidth is None: bandwidth = df['centered'].std() # simple default # Restrict to bandwidth local = df[df['centered'].abs() <= bandwidth] # Local linear regression formula = f"{outcome} ~ treated * centered" model = smf.ols(formula, data=local).fit(cov_type='HC1') return { 'rdd_estimate': model.params['treated'], 'se': model.bse['treated'], 'p_value': model.pvalues['treated'], 'bandwidth': bandwidth, 'n_obs': len(local), 'n_treated': local['treated'].sum(), 'n_control': len(local) - local['treated'].sum() }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 34,279 | 44,171 | +29% | 1 | 1 | 0% | 7,433 | 6,912 | -7% | 0 | 0 | — |
case-02 | fail→fail | 40,143 | 18,940 | -53% | 1 | 1 | 0% | 8,279 | 5,901 | -29% | 0 | 0 | — |
case-03 | fail→fail | 19,818 | 12,279 | -38% | 1 | 1 | 0% | 3,955 | 4,530 | +15% | 0 | 0 | — |
case-04 | fail→fail | 14,992 | 15,503 | +3% | 1 | 1 | 0% | 2,801 | 5,087 | +82% | 0 | 0 | — |
case-21 | fail→pass | 5,887 | 2,835 | -52% | 1 | 1 | 0% | 882 | 2,235 | +153% | 0 | 0 | — |
case-05 | fail→pass | 17,165 | 12,970 | -24% | 1 | 1 | 0% | 3,017 | 4,102 | +36% | 0 | 0 | — |
case-06 | fail→fail | 36,923 | 13,688 | -63% | 1 | 1 | 0% | 3,260 | 4,382 | +34% | 0 | 0 | — |
case-07 | fail→fail | 16,020 | 15,406 | -4% | 1 | 1 | 0% | 2,764 | 4,365 | +58% | 0 | 0 | — |
case-08 | pass→pass | 11,148 | 5,658 | -49% | 1 | 1 | 0% | 1,834 | 2,796 | +52% | 0 | 0 | — |
case-09 | pass→pass | 11,391 | 12,644 | +11% | 1 | 1 | 0% | 2,097 | 4,178 | +99% | 0 | 0 | — |
case-10 | pass→pass | 12,088 | 9,615 | -20% | 1 | 1 | 0% | 2,133 | 3,576 | +68% | 0 | 0 | — |
case-11 | pass→pass | 10,586 | 12,924 | +22% | 1 | 1 | 0% | 1,847 | 4,113 | +123% | 0 | 0 | — |
case-12 | fail→pass | 11,668 | 2,772 | -76% | 1 | 1 | 0% | 2,081 | 2,400 | +15% | 0 | 0 | — |
case-13 | pass→pass | 15,516 | 16,086 | +4% | 1 | 1 | 0% | 2,565 | 4,648 | +81% | 0 | 0 | — |
case-14 | pass→pass | 10,344 | 9,801 | -5% | 1 | 1 | 0% | 1,778 | 3,693 | +108% | 0 | 0 | — |
case-15 | pass→pass | 11,899 | 11,433 | -4% | 1 | 1 | 0% | 1,848 | 3,698 | +100% | 0 | 0 | — |
case-16 | pass→pass | 8,273 | 2,309 | -72% | 1 | 1 | 0% | 1,316 | 2,258 | +72% | 0 | 0 | — |
case-17 | pass→pass | 11,321 | 11,922 | +5% | 1 | 1 | 0% | 1,851 | 3,738 | +102% | 0 | 0 | — |
case-18 | fail→fail | 6,735 | 3,015 | -55% | 1 | 1 | 0% | 1,033 | 2,300 | +123% | 0 | 0 | — |
case-19 | pass→pass | 8,383 | 4,809 | -43% | 1 | 1 | 0% | 1,507 | 2,694 | +79% | 0 | 0 | — |
case-20 | pass→pass | 6,756 | 2,786 | -59% | 1 | 1 | 0% | 1,105 | 2,385 | +116% | 0 | 0 | — |
case-22 | fail→fail | 26,628 | 16,944 | -36% | 1 | 1 | 0% | 4,873 | 5,157 | +6% | 0 | 0 | — |
case-23 | fail→fail | 25,597 | 12,951 | -49% | 1 | 1 | 0% | 4,284 | 4,372 | +2% | 0 | 0 | — |
case-24 | fail→fail | 20,834 | 16,981 | -18% | 1 | 1 | 0% | 4,229 | 5,177 | +22% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +17 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.