Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Conduct systematic meta-analyses with effect size pooling and heterogeneity
.claude/skills/brycewang-stanford-meta-analysis-guide/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✓→✓ | = Same ✓ | 59% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 72% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 145% | 0% |
| case-19 | ✓→✓ | = Same ✓ | 34% | 0% |
| case-07 | ✓→✓ | = Same ✓ | 101% | 0% |
A skill for conducting rigorous meta-analyses: computing and pooling effect sizes, assessing heterogeneity, evaluating publication bias, and generating forest plots. Follows Cochrane Handbook and PRISMA guidelines.
| Measure | Use Case | Formula | Interpretation | |---------|----------|---------|----------------| | Cohen's d | Mean difference (2 groups) | (M1 - M2) / S_pooled | 0.2 small, 0.5 medium, 0.8 large | | Hedges' g | d with small-sample correction | d J(df) | Preferred over d for small N | | Pearson r | Correlation | r | 0.1 small, 0.3 medium, 0.5 large | | Odds Ratio | Binary outcomes | (ad)/(bc) | 1 = no effect | | Risk Ratio | Binary outcomes | (a/(a+b))/(c/(c+d)) | 1 = no effect | | SMD | Standardized mean difference | Same as Hedges' g | When scales differ |
pythonimport numpy as np from dataclasses import dataclass @dataclass class EffectSize: estimate: float variance: float se: float ci_lower: float ci_upper: float measure: str def cohens_d(m1: float, m2: float, sd1: float, sd2: float, n1: int, n2: int) -> EffectSize: """ Compute Hedges' g (bias-corrected Cohen's d). """ # Pooled standard deviation sd_pooled = np.sqrt(((n1-1)*sd1**2 + (n2-1)*sd2**2) / (n1+n2-2)) # Cohen's d d = (m1 - m2) / sd_pooled # Small-sample correction (Hedges' g) df = n1 + n2 - 2 j = 1 - (3 / (4*df - 1)) g = d * j # Variance of g var_g = (n1+n2)/(n1*n2) + g**2 / (2*(n1+n2)) se_g = np.sqrt(var_g) return EffectSize( estimate=g, variance=var_g, se=se_g, ci_lower=g - 1.96*se_g, ci_upper=g + 1.96*se_g, measure='Hedges_g' ) def odds_ratio(a: int, b: int, c: int, d: int) -> EffectSize: """ Compute log odds ratio from a 2x2 table. a=treatment success, b=treatment failure, c=control success, d=control failure """ # Add 0.5 continuity correction if any cell is 0 if any(x == 0 for x in [a, b, c, d]): a, b, c, d = a+0.5, b+0.5, c+0.5, d+0.5 log_or = np.log((a*d) / (b*c)) var = 1/a + 1/b + 1/c + 1/d se = np.sqrt(var) return EffectSize( estimate=log_or, variance=var, se=se, ci_lower=log_or - 1.96*se, ci_upper=log_or + 1.96*se, measure='log_OR' )
pythondef random_effects_meta(effects: list[EffectSize]) -> dict: """ Random-effects meta-analysis using DerSimonian-Laird estimator. """ yi = np.array([e.estimate for e in effects]) vi = np.array([e.variance for e in effects]) wi = 1 / vi k = len(effects) # Fixed-effect estimate fe_estimate = np.sum(wi * yi) / np.sum(wi) # Q statistic for heterogeneity Q = np.sum(wi * (yi - fe_estimate)**2) df = k - 1 # DerSimonian-Laird tau-squared C = np.sum(wi) - np.sum(wi**2) / np.sum(wi) tau2 = max(0, (Q - df) / C) # Random-effects weights wi_re = 1 / (vi + tau2) re_estimate = np.sum(wi_re * yi) / np.sum(wi_re) re_se = np.sqrt(1 / np.sum(wi_re)) re_ci = (re_estimate - 1.96*re_se, re_estimate + 1.96*re_se) # Heterogeneity statistics I2 = max(0, (Q - df) / Q * 100) if Q > 0 else 0 H2 = Q / df if df > 0 else 1 return { 'pooled_effect': re_estimate, 'se': re_se, 'ci_95': re_ci, 'tau_squared': tau2, 'Q_statistic': Q, 'Q_df': df, 'Q_pvalue': 1 - stats.chi2.cdf(Q, df), 'I_squared': I2, 'H_squared': H2, 'interpretation': ( f"I-squared = {I2:.1f}%: " + ('low' if I2 < 25 else 'moderate' if I2 < 75 else 'high') + ' heterogeneity' ) }
pythonimport matplotlib.pyplot as plt def forest_plot(studies: list[dict], pooled: dict, title: str = 'Forest Plot') -> plt.Figure: """ Create a publication-quality forest plot. Args: studies: List of dicts with 'name', 'effect', 'ci_lower', 'ci_upper', 'weight' pooled: Dict with 'pooled_effect', 'ci_95' """ fig, ax = plt.subplots(figsize=(10, max(6, len(studies)*0.5))) k = len(studies) for i, study in enumerate(studies): y = k - i ax.plot([study['ci_lower'], study['ci_upper']], [y, y], 'b-', linewidth=1) size = study.get('weight', 5) * 2 ax.plot(study['effect'], y, 'bs', markersize=max(3, min(size, 15))) ax.text(-0.05, y, study['name'], ha='right', va='center', fontsize=9, transform=ax.get_yaxis_transform()) # Pooled estimate (diamond) pe = pooled['pooled_effect'] ci = pooled['ci_95'] ax.fill([ci[0], pe, ci[1], pe], [0.3, 0.6, 0.3, 0], 'r', alpha=0.7) ax.axvline(x=0, color='gray', linestyle='--', linewidth=0.5) ax.set_xlabel('Effect Size (Hedges g)') ax.set_title(title) ax.set_yticks([]) plt.tight_layout() return fig
Methods to assess and address publication bias:
Follow PRISMA 2020 guidelines for reporting:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 18,314 | 24,944 | +36% | 1 | 1 | 0% | 4,102 | 6,607 | +61% | 0 | 0 | — |
case-02 | fail→fail | 17,106 | 13,555 | -21% | 1 | 1 | 0% | 3,962 | 4,824 | +22% | 0 | 0 | — |
case-03 | fail→fail | 41,955 | 20,494 | -51% | 1 | 1 | 0% | 8,265 | 6,271 | -24% | 0 | 0 | — |
case-04 | pass→pass | 18,513 | 19,230 | +4% | 1 | 1 | 0% | 3,626 | 5,755 | +59% | 0 | 0 | — |
case-05 | pass→pass | 12,750 | 12,995 | +2% | 1 | 1 | 0% | 2,697 | 4,627 | +72% | 0 | 0 | — |
case-06 | pass→pass | 7,759 | 5,975 | -23% | 1 | 1 | 0% | 1,250 | 3,063 | +145% | 0 | 0 | — |
case-19 | pass→pass | 21,795 | 19,509 | -10% | 1 | 1 | 0% | 4,569 | 6,145 | +34% | 0 | 0 | — |
case-07 | pass→pass | 9,445 | 6,876 | -27% | 1 | 1 | 0% | 1,549 | 3,119 | +101% | 0 | 0 | — |
case-08 | pass→pass | 6,256 | 6,843 | +9% | 1 | 1 | 0% | 1,115 | 3,185 | +186% | 0 | 0 | — |
case-09 | fail→fail | 6,908 | 5,507 | -20% | 1 | 1 | 0% | 1,238 | 2,981 | +141% | 0 | 0 | — |
case-10 | pass→pass | 9,429 | 11,423 | +21% | 1 | 1 | 0% | 1,790 | 4,113 | +130% | 0 | 0 | — |
case-11 | pass→pass | 11,922 | 8,099 | -32% | 1 | 1 | 0% | 2,117 | 3,494 | +65% | 0 | 0 | — |
case-12 | pass→pass | 16,915 | 19,456 | +15% | 1 | 1 | 0% | 2,763 | 5,295 | +92% | 0 | 0 | — |
case-13 | pass→pass | 9,673 | 9,933 | +3% | 1 | 1 | 0% | 1,563 | 3,912 | +150% | 0 | 0 | — |
case-14 | pass→pass | 4,575 | 35,465 | +675% | 1 | 1 | 0% | 757 | 3,068 | +305% | 0 | 0 | — |
case-15 | pass→pass | 11,699 | 14,966 | +28% | 1 | 1 | 0% | 2,066 | 4,703 | +128% | 0 | 0 | — |
case-16 | pass→pass | 12,235 | 15,845 | +30% | 1 | 1 | 0% | 2,237 | 4,342 | +94% | 0 | 0 | — |
case-17 | pass→pass | 9,545 | 9,535 | -0% | 1 | 1 | 0% | 1,880 | 4,042 | +115% | 0 | 0 | — |
case-18 | fail→fail | 14,274 | 15,447 | +8% | 1 | 1 | 0% | 2,504 | 4,914 | +96% | 0 | 0 | — |
case-20 | pass→pass | 12,681 | 13,895 | +10% | 1 | 1 | 0% | 2,216 | 4,526 | +104% | 0 | 0 | — |
case-21 | pass→pass | 14,204 | 15,173 | +7% | 1 | 1 | 0% | 2,733 | 5,179 | +89% | 0 | 0 | — |
case-22 | pass→pass | 8,348 | 14,421 | +73% | 1 | 1 | 0% | 1,636 | 5,108 | +212% | 0 | 0 | — |
case-23 | pass→pass | 12,336 | 11,648 | -6% | 1 | 1 | 0% | 1,893 | 4,349 | +130% | 0 | 0 | — |
case-24 | pass→pass | 12,812 | 12,111 | -5% | 1 | 1 | 0% | 2,320 | 4,113 | +77% | 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 0 percentage points is the difference between those two pass rates over the 24 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Other measured skills in the registry, with their headline benchmark lift.