Skip to main content Skills Marketplace Descubra e explore skills de IA criadas pela comunidade.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Copiar promptMostrar detalhes do prompt Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
npx skills add https://github.com/xjtulyc/awesome-rosetta-skills --skill systematic-review-epiO comando permanece em uma só linha. Role horizontalmente para revisá-lo antes de copiar.
Prefere uma cópia local? Baixe os arquivos disponíveis atualmente no SkillsMP.
Baixar Zip Baixando... Mais deste repositório Astronomical data analysis with astropy and astroquery — FITS I/O, WCS transforms, catalog cross-matching, aperture photometry, and CMB power spectra.
Seismological data analysis with ObsPy — FDSN waveform download, response removal, phase picking, moment tensor inversion, and seismicity mapping.
Download and analyze oceanographic data from Copernicus Marine Service and Argo floats using copernicusmarine, gsw, and xarray.
Ocupações relacionadas SOC
Baseado na classificação ocupacional SOC
name systematic-review-epi description Use this Skill for systematic reviews: meta-regression, publication bias tests (Egger, funnel plot), GRADE evidence synthesis, and forest plots.
tags ["public-health","systematic-review","meta-analysis","evidence-synthesis","publication-bias"] version 1.0.0 authors [{"name":"Rosetta Skills Contributors","github":"@xjtulyc"}] license MIT platforms ["claude-code","codex","gemini-cli","cursor"] dependencies {"python":["numpy>=1.24","pandas>=2.0","scipy>=1.11","statsmodels>=0.14","matplotlib>=3.7"]} last_updated 2026-03-17 status stable
Systematic Review and Evidence Synthesis
One-line summary : Synthesize evidence from systematic reviews: compute pooled effects (OR, RR, RD, SMD), draw forest plots, test for heterogeneity (I², Q), detect publication bias (Egger test, funnel plot), and run meta-regression.
When to Use This Skill
When pooling effect sizes from multiple studies (meta-analysis)
When computing I² heterogeneity and Cochran's Q test
When drawing forest plots with random-effects pooled estimates
When testing for publication bias using Egger's test or Begg's test
When performing meta-regression on study-level covariates
When rating quality of evidence using GRADE framework
Trigger keywords : systematic review, meta-analysis, forest plot, random effects, fixed effects, DerSimonian-Laird, heterogeneity, I-squared, Cochran Q, publication bias, Egger test, funnel plot, meta-regression, pooled effect, odds ratio, risk ratio, SMD, Cohen's d, GRADE
Background & Key Concepts
Random-Effects Model (DerSimonian-Laird)
$$
\hat{\theta}_{RE} = \frac{\sum_i w_i^* \hat{\theta}_i}{\sum_i w_i^}, \quad w_i^ = \frac{1}{v_i + \hat{\tau}^2}
$$
where $\hat{\tau}^2$ is the between-study variance estimated from the Q statistic.
Heterogeneity
Cochran's Q: $Q = \sum_i w_i (\hat{\theta}i - \hat{\theta} {FE})^2 \sim \chi^2(k-1)$
$$
I^2 = \max\left(0, \frac{Q - (k-1)}{Q}\right) \times 100%
$$
$I^2 < 25%$: low, 25-75%: moderate, >75%: high heterogeneity.
Egger's Test for Publication Bias
Regress standard normal deviate ($\hat{\theta}/SE$) on precision ($1/SE$). Non-zero intercept indicates asymmetry.
Environment Setup
Install Dependencies
pip install numpy>=1.24 pandas>=2.0 scipy>=1.11 statsmodels>=0.14 matplotlib>=3.7
Verify Installation
import numpy as np
from scipy.stats import norm, chi2
log_ors = np.array([-0.1 , 0.3 , 0.5 , 0.2 ])
se_ors = np.array([0.15 , 0.20 , 0.25 , ])
weights = / se_ors**
pooled = np. (weights * log_ors) / np. (weights)
( )
0.12
1
2
sum
sum
print
f"Fixed-effects OR: {np.exp(pooled):.3 f} "
Core Workflow
Step 1: Data Extraction and Forest Plot import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.stats import chi2, norm
studies = [
{'study' : 'Aiello et al. 2008' , 'log_or' : -0.51 , 'se' : 0.14 , 'year' : 2008 , 'setting' : 'school' },
{'study' : 'Luby et al. 2005' , 'log_or' : -0.73 , 'se' : 0.18 , 'year' : 2005 , 'setting' : 'community' },
{'study' : 'Cairncross et al.' , 'log_or' : -0.45 , 'se' : 0.16 , 'year' : 2010 , 'setting' : 'community' },
{'study' : 'Jefferson et al.' , 'log_or' : -0.40 , 'se' : 0.20 , 'year' : 2009 , 'setting' : 'community' },
{'study' : 'Talaat et al. 2011' , 'log_or' : -0.35 , 'se' : 0.22 , 'year' : 2011 , 'setting' : 'school' },
{'study' : 'Grayson et al. 2009' , 'log_or' : -0.82 , 'se' : 0.25 , 'year' : 2009 , 'setting' : 'hospital' },
{'study' : 'Picheansanthian' , 'log_or' : -0.60 , 'se' : 0.30 , 'year' : 2004 , 'setting' : 'hospital' },
{'study' : 'Larson et al. 2012' , 'log_or' : -0.28 , 'se' : 0.17 , 'year' : 2012 , 'setting' : 'school' },
{'study' : 'Bowen et al. 2013' , 'log_or' : -0.55 , 'se' : 0.21 , 'year' : 2013 , 'setting' : 'community' },
{'study' : 'Hübner et al. 2013' , 'log_or' : -0.90 , 'se' : 0.35 , 'year' : 2013 , 'setting' : 'hospital' },
]
df = pd.DataFrame(studies)
k = len (df)
w_fe = 1 / df['se' ]**2
theta_fe = np.sum (w_fe * df['log_or' ]) / np.sum (w_fe)
se_fe = 1 / np.sqrt(np.sum (w_fe))
Q = np.sum (w_fe * (df['log_or' ] - theta_fe)**2 )
p_Q = 1 - chi2.cdf(Q, df=k-1 )
I2 = max (0 , (Q - (k-1 )) / Q) * 100
tau2 = max (0 , (Q - (k-1 )) / (np.sum (w_fe) - np.sum (w_fe**2 )/np.sum (w_fe)))
w_re = 1 / (df['se' ]**2 + tau2)
theta_re = np.sum (w_re * df['log_or' ]) / np.sum (w_re)
se_re = 1 / np.sqrt(np.sum (w_re))
ci_lo_re = theta_re - 1.96 * se_re
ci_hi_re = theta_re + 1.96 * se_re
print (f"=== Meta-Analysis Results ===" )
print (f"k = {k} studies" )
print (f"\nHeterogeneity:" )
print (f" Q = {Q:.3 f} (df={k-1 } , p={p_Q:.4 f} )" )
print (f" I² = {I2:.1 f} % ({'low' if I2<25 else 'moderate' if I2<75 else 'high' } )" )
print (f" τ² = {tau2:.4 f} (between-study variance)" )
print (f"\nFixed-effects: OR = {np.exp(theta_fe):.3 f} [{np.exp(theta_fe-1.96 *se_fe):.3 f} , {np.exp(theta_fe+1.96 *se_fe):.3 f} ]" )
print (f"Random-effects: OR = {np.exp(theta_re):.3 f} [{np.exp(ci_lo_re):.3 f} , {np.exp(ci_hi_re):.3 f} ]" )
fig, ax = plt.subplots(figsize=(12 , 8 ))
df_sorted = df.sort_values('log_or' ).reset_index(drop=True )
w_re_sorted = 1 / (df_sorted['se' ]**2 + tau2)
marker_sizes = w_re_sorted / w_re_sorted.max () * 200 + 20
for i, row in df_sorted.iterrows():
ci_lo = row['log_or' ] - 1.96 * row['se' ]
ci_hi = row['log_or' ] + 1.96 * row['se' ]
ax.errorbar(np.exp(row['log_or' ]), i, xerr=[[np.exp(row['log_or' ])-np.exp(ci_lo)],
[np.exp(ci_hi)-np.exp(row['log_or' ])]],
fmt='s' , markersize=np.sqrt(marker_sizes.iloc[i]),
color='steelblue' , ecolor='black' , capsize=3 , linewidth=1.5 )
ax.text(-0.02 , i, row['study' ], ha='right' , va='center' , fontsize=8 , transform=ax.get_yaxis_transform())
or_text = f"OR={np.exp(row['log_or' ]):.2 f} [{np.exp(ci_lo):.2 f} ,{np.exp(ci_hi):.2 f} ]"
ax.text(1.02 , i, or_text, ha='left' , va='center' , fontsize=7 , transform=ax.get_yaxis_transform())
y_pool = k + 0.5
or_lo_re = np.exp(ci_lo_re)
or_hi_re = np.exp(ci_hi_re)
or_re = np.exp(theta_re)
diamond_x = [or_lo_re, or_re, or_hi_re, or_re, or_lo_re]
diamond_y = [y_pool, y_pool + 0.4 , y_pool, y_pool - 0.4 , y_pool]
ax.fill(diamond_x, diamond_y, color='#e74c3c' , alpha=0.9 , zorder=5 )
ax.text(-0.02 , y_pool, "Pooled (RE)" , ha='right' , va='center' , fontsize=9 ,
fontweight='bold' , transform=ax.get_yaxis_transform())
ax.text(1.02 , y_pool, f"OR={or_re:.2 f} [{or_lo_re:.2 f} ,{or_hi_re:.2 f} ]" ,
ha='left' , va='center' , fontsize=9 , fontweight='bold' , transform=ax.get_yaxis_transform())
ax.axvline(1 , color='black' , linewidth=1 , linestyle='--' , alpha=0.7 )
ax.set_xscale('log' )
ax.set_xlim(0.1 , 3.0 )
ax.set_yticks([]); ax.set_xlabel("Odds Ratio (log scale)" )
ax.set_title(f"Forest Plot — Hand Hygiene vs. Infection Risk\n"
f"(k={k} , I²={I2:.0 f} %, RE-OR={or_re:.2 f} )" )
ax.text(0.5 , -0.08 , "← Favours intervention | Favours control →" ,
ha='center' , transform=ax.transAxes, fontsize=9 , style='italic' )
ax.grid(axis='x' , alpha=0.3 )
plt.tight_layout()
plt.savefig("forest_plot.png" , dpi=150 )
plt.show()
Step 2: Publication Bias Assessment import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
import statsmodels.api as sm
log_or = df['log_or' ].values
se = df['se' ].values
precision = 1 / se
standard_normal_deviate = log_or / se
X = sm.add_constant(precision)
egger_model = sm.OLS(standard_normal_deviate, X).fit()
intercept = egger_model.params[0 ]
intercept_se = egger_model.bse[0 ]
t_stat = intercept / intercept_se
p_egger = egger_model.pvalues[0 ]
print ("=== Egger's Publication Bias Test ===" )
print (f"Intercept = {intercept:.4 f} SE = {intercept_se:.4 f} " )
print (f"t = {t_stat:.3 f} , p = {p_egger:.4 f} " )
print (f"Interpretation: {'Evidence of asymmetry (p<0.10)' if p_egger < 0.10 else 'No significant asymmetry (p≥0.10)' } " )
rank_log_or = stats.rankdata(log_or)
rank_se = stats.rankdata(se)
tau, p_begg = stats.kendalltau(rank_log_or, rank_se)
print (f"\nBegg's test: τ = {tau:.4 f} , p = {p_begg:.4 f} " )
fig, axes = plt.subplots(1 , 2 , figsize=(13 , 6 ))
ax = axes[0 ]
for i in range (len (df)):
ax.scatter(np.exp(log_or[i]), se[i], s=60 , c='steelblue' ,
edgecolors='black' , linewidths=0.5 , zorder=5 )
ax.axvline(np.exp(theta_re), color='red' , linewidth=2 , linestyle='--' , label=f"RE OR={np.exp(theta_re):.2 f} " )
se_range = np.linspace(0 , se.max () * 1.1 , 100 )
ax.fill_betweenx(se_range,
np.exp(theta_re - 1.96 * se_range),
np.exp(theta_re + 1.96 * se_range),
alpha=0.1 , color='gray' , label='95% CI region' )
ax.invert_yaxis()
ax.set_xscale('log' )
ax.set_xlabel("Odds Ratio (log scale)" ); ax.set_ylabel("Standard Error" )
ax.set_title(f"Funnel Plot\n(Egger p={p_egger:.3 f} )" )
ax.legend(fontsize=8 ); ax.grid(True , alpha=0.3 )
ax2 = axes[1 ]
ax2.scatter(precision, standard_normal_deviate, s=60 , c='steelblue' ,
edgecolors='black' , linewidths=0.5 )
x_fit = np.linspace(precision.min (), precision.max (), 100 )
y_fit = egger_model.params[0 ] + egger_model.params[1 ] * x_fit
ax2.plot(x_fit, y_fit, 'r-' , linewidth=2 , label=f"Egger fit (intercept={intercept:.2 f} )" )
ax2.axhline(0 , color='gray' , linestyle='--' , linewidth=1 )
ax2.set_xlabel("Precision (1/SE)" ); ax2.set_ylabel("Standard Normal Deviate" )
ax2.set_title("Egger's Regression Plot" ); ax2.legend(fontsize=9 ); ax2.grid(True , alpha=0.3 )
plt.tight_layout()
plt.savefig("publication_bias.png" , dpi=150 )
plt.show()
Step 3: Meta-Regression import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
df['hospital' ] = (df['setting' ] == 'hospital' ).astype(float )
df['log_sample_size' ] = np.log(np.random.randint(100 , 5000 , len (df)))
w_re_array = 1 / (df['se' ]**2 + tau2)
X_meta = sm.add_constant(df[['hospital' , 'year' ]])
y_meta = df['log_or' ]
meta_reg = sm.WLS(y_meta, X_meta, weights=w_re_array).fit()
print ("=== Meta-Regression Results ===" )
print (meta_reg.summary())
fig, axes = plt.subplots(1 , 2 , figsize=(13 , 5 ))
for ax, (setting_val, label, color) in zip (
[axes[0 ], axes[0 ]],
[(0 , 'Community/School' , 'steelblue' ), (1 , 'Hospital' , '#e74c3c' )]
):
mask = df['hospital' ] == setting_val
ors = np.exp(df.loc[mask, 'log_or' ])
ci_los = np.exp(df.loc[mask, 'log_or' ] - 1.96 *df.loc[mask, 'se' ])
ci_his = np.exp(df.loc[mask, 'log_or' ] + 1.96 *df.loc[mask, 'se' ])
for or_val, ci_lo, ci_hi in zip (ors, ci_los, ci_his):
ax.errorbar(or_val, label, xerr=[[or_val-ci_lo],[ci_hi-or_val]],
fmt='o' , color=color, alpha=0.5 , capsize=3 )
axes[0 ].axvline(1 , color='black' , linewidth=1 , linestyle='--' )
axes[0 ].set_xscale('log' ); axes[0 ].set_xlabel("Odds Ratio" )
axes[0 ].set_title("Effect by Setting" ); axes[0 ].grid(True , alpha=0.3 )
axes[1 ].scatter(df['year' ], np.exp(df['log_or' ]), s=w_re_array/w_re_array.max ()*200 ,
c='steelblue' , edgecolors='black' , linewidths=0.5 , alpha=0.8 )
year_range = np.linspace(df['year' ].min (), df['year' ].max (), 50 )
X_pred = pd.DataFrame({'const' : 1 , 'hospital' : 0.5 , 'year' : year_range})
log_or_pred = meta_reg.predict(X_pred)
axes[1 ].plot(year_range, np.exp(log_or_pred), 'r-' , linewidth=2 , label='Meta-regression trend' )
axes[1 ].axhline(1 , color='gray' , linestyle='--' , linewidth=1 )
axes[1 ].set_xlabel("Publication year" ); axes[1 ].set_ylabel("Odds Ratio" )
axes[1 ].set_title("Meta-Regression: Year Trend" ); axes[1 ].legend(); axes[1 ].grid(True , alpha=0.3 )
plt.tight_layout()
plt.savefig("meta_regression.png" , dpi=150 )
plt.show()
Q_residual = np.sum (w_re_array * meta_reg.resid**2 )
p_Q_resid = 1 - chi2.cdf(Q_residual, df=k-2 -1 )
print (f"\nResidual Q (after meta-regression): {Q_residual:.3 f} , p={p_Q_resid:.4 f} " )
Advanced Usage
GRADE Evidence Quality Assessment import pandas as pd
grade_criteria = {
'Starting quality' : 'High (RCT)' ,
'Risk of bias' : {'assessment' : 'Moderate risk' , 'downgrade' : -1 },
'Inconsistency' : {'assessment' : f'I²={I2:.0 f} % (Moderate)' , 'downgrade' : -1 if I2 > 40 else 0 },
'Indirectness' : {'assessment' : 'Direct evidence' , 'downgrade' : 0 },
'Imprecision' : {'assessment' : 'Wide CI (upgrades rare)' , 'downgrade' : 0 },
'Publication bias' : {'assessment' : f"Egger p={p_egger:.3 f} " , 'downgrade' : -1 if p_egger < 0.10 else 0 },
}
quality_levels = {4 : 'High' , 3 : 'Moderate' , 2 : 'Low' , 1 : 'Very Low' }
starting = 4
total_downgrade = sum (v.get('downgrade' , 0 ) for k,v in grade_criteria.items() if isinstance (v, dict ))
final_quality = max (1 , starting + total_downgrade)
print ("=== GRADE Evidence Assessment ===" )
print (f"{'Domain' :<25 } {'Assessment' :<35 } {'Downgrade' } " )
print ("-" * 70 )
for domain, info in grade_criteria.items():
if isinstance (info, dict ):
print (f"{domain:<25 } {info['assessment' ]:<35 } {info['downgrade' ]:+d} " )
else :
print (f"{domain:<25 } {info} " )
print ("-" * 70 )
print (f"Total downgrade: {total_downgrade} " )
print (f"Final quality: {quality_levels[final_quality]} (score={final_quality} )" )
print (f"Recommendation: {'↑↑ Strong' if final_quality >= 3 else '↑ Conditional' } recommendation" )
Troubleshooting
Negative τ² in DerSimonian-Laird This can happen with Q < k-1. Truncate at 0:
tau2 = max (0 , (Q - (k-1 )) / (np.sum (w_fe) - np.sum (w_fe**2 )/np.sum (w_fe)))
Forest plot labels overflow figure fig, ax = plt.subplots(figsize=(16 , 10 ))
ax.set_xlim([0.05 , 5.0 ])
Meta-regression with few studies Meta-regression requires k >> number of predictors. Rule of thumb: ≥10 studies per predictor variable.
Version Compatibility Package Tested versions Notes statsmodels 0.14 WLS, OLS stable APIscipy 1.11, 1.12 kendalltau stablenumpy 1.24, 1.26 No known issues
External Resources
Official Documentation
Key Papers
DerSimonian, R. & Laird, N. (1986). Meta-analysis in clinical trials . Controlled Clinical Trials.
Higgins, J.P.T. & Thompson, S.G. (2002). Quantifying heterogeneity in a meta-analysis . Statistics in Medicine.
Egger, M. et al. (1997). Bias in meta-analysis detected by a simple, graphical test . BMJ.
Examples
Example 1: Dose-Response Meta-Analysis import numpy as np
import matplotlib.pyplot as plt
doses = np.array([0 , 1 , 2 , 3 , 4 , 5 ])
rrs = np.array([1.00 , 0.85 , 0.90 , 1.05 , 1.25 , 1.60 ])
ci_lo = np.array([1.00 , 0.78 , 0.83 , 0.97 , 1.14 , 1.40 ])
ci_hi = np.array([1.00 , 0.93 , 0.98 , 1.14 , 1.37 , 1.83 ])
fig, ax = plt.subplots(figsize=(8 , 5 ))
ax.fill_between(doses, ci_lo, ci_hi, alpha=0.2 , color='steelblue' )
ax.plot(doses, rrs, 'bo-' , linewidth=2 , markersize=8 )
ax.axhline(1 , color='black' , linewidth=1 , linestyle='--' )
ax.set_xlabel("Alcohol consumption (drinks/day)" )
ax.set_ylabel("Relative Risk (95% CI)" )
ax.set_title("Dose-Response: Alcohol and Cardiovascular Risk" )
ax.grid(True , alpha=0.3 )
plt.tight_layout(); plt.savefig("dose_response.png" , dpi=150 ); plt.show()
Example 2: Sensitivity Analysis (Influence Analysis) import numpy as np
import matplotlib.pyplot as plt
k = len (df)
lo_ors = []
for i in range (k):
mask = np.ones(k, dtype=bool ); mask[i] = False
lo_or = df['log_or' ].values[mask]
lo_se = df['se' ].values[mask]
w = 1 / (lo_se**2 + tau2)
pooled = np.sum (w * lo_or) / np.sum (w)
lo_ors.append(np.exp(pooled))
fig, ax = plt.subplots(figsize=(8 , 5 ))
ax.barh(df['study' ], lo_ors, color='steelblue' , edgecolor='black' , linewidth=0.6 , alpha=0.8 )
ax.axvline(np.exp(theta_re), color='red' , linewidth=2 , linestyle='--' , label=f"All studies: OR={np.exp(theta_re):.2 f} " )
ax.set_xlabel("Pooled OR (leave-one-out)" )
ax.set_title("Leave-One-Out Sensitivity Analysis" )
ax.legend(); ax.grid(axis='x' , alpha=0.3 )
plt.tight_layout(); plt.savefig("sensitivity_analysis.png" , dpi=150 ); plt.show()
Last updated: 2026-03-17 | Maintainer: @xjtulyc
Issues: GitHub Issues