| name | meta-analysis |
| description | Pairwise meta-analysis in R, including fixed and random effects, heterogeneity, bias checks, and forest plots. |
Meta-Analysis Methods in R
Overview
Comprehensive pairwise meta-analysis methods covering effect size calculation, fixed and random effects models, heterogeneity assessment, publication bias detection, subgroup analysis, meta-regression, and sensitivity analyses for synthesizing evidence across studies.
Effect Size Calculation
Continuous Outcomes
library(metafor)
dat <- escalc(
measure = "SMD",
m1i = mean_treatment,
sd1i = sd_treatment,
n1i = n_treatment,
m2i = mean_control,
sd2i = sd_control,
n2i = n_control,
data = studies
)
dat_md <- escalc(
measure = "MD",
m1i = mean_treatment, m2i = mean_control,
sd1i = sd_treatment, sd2i = sd_control,
n1i = n_treatment, n2i = n_control,
data = studies
)
dat_pre <- escalc(
measure = "SMD",
yi = effect_size,
sei = standard_error,
data = studies
)
Binary Outcomes
library(metafor)
dat_or <- escalc(
measure = "OR",
ai = events_treatment,
bi = n_treatment - events_treatment,
ci = events_control,
di = n_control - events_control,
data = studies
)
dat_rr <- escalc(
measure = "RR",
ai = events_treatment, bi = n_treatment - events_treatment,
ci = events_control, di = n_control - events_control,
data = studies
)
dat_rd <- escalc(
measure = "RD",
ai = events_treatment, bi = n_treatment - events_treatment,
ci = events_control, di = n_control - events_control,
data = studies
)
dat_as <- escalc(
measure = "AS",
ai = events_treatment, bi = n_treatment - events_treatment,
ci = events_control, di = n_control - events_control,
data = studies
)
Correlation Coefficients
library(metafor)
dat_cor <- escalc(
measure = "ZCOR",
ri = correlation,
ni = sample_size,
data = studies
)
dat_cor_raw <- escalc(
measure = "COR",
ri = correlation,
ni = sample_size,
data = studies
)
Hazard Ratios
library(metafor)
dat_hr <- escalc(
measure = "GEN",
yi = log_hr,
sei = se_log_hr,
data = studies
)
studies <- studies |>
mutate(
log_hr = log(hr),
se_log_hr = (log(hr_upper) - log(hr_lower)) / (2 * 1.96)
)
Fixed and Random Effects Models
Using meta Package
library(meta)
m_cont <- metacont(
n.e = n_treatment,
mean.e = mean_treatment,
sd.e = sd_treatment,
n.c = n_control,
mean.c = mean_control,
sd.c = sd_control,
studlab = study,
data = studies,
sm = "SMD",
method.smd = "Hedges",
fixed = TRUE,
random = TRUE,
method.tau = "REML"
)
m_bin <- metabin(
event.e = events_treatment,
n.e = n_treatment,
event.c = events_control,
n.c = n_control,
studlab = study,
data = studies,
sm = "OR",
method = "MH",
fixed = TRUE,
random = TRUE,
method.tau = "DL"
)
m_gen <- metagen(
TE = yi,
seTE = sqrt(vi),
studlab = study,
data = dat,
sm = "SMD",
fixed = TRUE,
random = TRUE
)
summary(m_cont)
Using metafor Package
library(metafor)
res <- rma(yi, vi, data = dat, method = "REML")
res_fe <- rma(yi, vi, data = dat, method = "FE")
res_dl <- rma(yi, vi, data = dat, method = "DL")
res_pm <- rma(yi, vi, data = dat, method = "PM")
res_ml <- rma(yi, vi, data = dat, method = "ML")
res_eb <- rma(yi, vi, data = dat, method = "EB")
res_sj <- rma(yi, vi, data = dat, method = "SJ")
summary(res)
confint(res)
Heterogeneity Assessment
Heterogeneity Statistics
library(metafor)
res <- rma(yi, vi, data = dat)
res$QE
res$QEp
res$I2
res$H2
res$tau2
ci <- confint(res)
ci$random
predict(res)
Interpretation Guidelines
het_summary <- data.frame(
Statistic = c("Q", "df", "p-value", "I²", "τ²", "τ"),
Value = c(
round(res$QE, 2),
res$k - 1,
format.pval(res$QEp, digits = 3),
paste0(round(res$I2, 1), "%"),
round(res$tau2, 4),
round(sqrt(res$tau2), 4)
)
)
Forest Plots
Basic Forest Plot
library(meta)
forest(m_cont,
sortvar = TE,
prediction = TRUE,
print.tau2 = TRUE,
print.I2 = TRUE,
print.pval.Q = TRUE,
leftlabs = c("Study", "N", "Mean", "SD", "N", "Mean", "SD"),
lab.e = "Treatment",
lab.c = "Control"
)
Customized Forest Plot
library(meta)
forest(m_bin,
sortvar = TE,
prediction = TRUE,
print.tau2 = TRUE,
leftcols = c("studlab", "event.e", "n.e", "event.c", "n.c"),
leftlabs = c("Study", "Events", "Total", "Events", "Total"),
rightcols = c("effect", "ci", "w.random"),
rightlabs = c("OR", "95% CI", "Weight"),
smlab = "Odds Ratio",
weight.study = "random",
squaresize = 0.5,
col.square = "navy",
col.diamond = "maroon",
col.predict = "black",
fontsize = 10,
spacing = 1.1,
colgap.forest.left = unit(15, "mm")
)
Forest Plot with metafor
library(metafor)
forest(res,
header = TRUE,
xlim = c(-2, 3),
alim = c(-1, 2),
slab = dat$study,
ilab = cbind(dat$n_treatment, dat$n_control),
ilab.xpos = c(-1.5, -1.2),
cex = 0.8
)
text(c(-1.5, -1.2), res$k + 2, c("Treatment N", "Control N"), cex = 0.8, font = 2)
Funnel Plots and Publication Bias
Funnel Plots
library(meta)
funnel(m_cont,
xlab = "Effect Size (SMD)",
studlab = TRUE)
funnel(m_cont,
xlim = c(-2, 2),
contour = c(0.9, 0.95, 0.99),
col.contour = c("darkgray", "gray", "lightgray"),
studlab = TRUE)
library(metafor)
funnel(res, main = "Funnel Plot")
tf <- trimfill(res)
funnel(tf)
Statistical Tests for Bias
library(metafor)
regtest(res, model = "lm")
ranktest(res)
regtest(res, model = "lm", predictor = "ni")
library(meta)
metabias(m_cont, method.bias = "Egger")
metabias(m_bin, method.bias = "Peters")
Trim-and-Fill Method
library(metafor)
tf <- trimfill(res)
summary(tf)
tf$k0
funnel(tf, legend = TRUE)
Fail-Safe N
library(metafor)
fsn(yi, vi, data = dat, type = "Rosenthal")
fsn(yi, vi, data = dat, type = "Orwin", target = 0.1)
fsn(yi, vi, data = dat, type = "Rosenberg")
Selection Models
library(metafor)
sel <- selmodel(res, type = "stepfun", steps = c(0.025, 0.5))
summary(sel)
sel3 <- selmodel(res, type = "beta")
summary(sel3)
Subgroup Analysis
Categorical Moderators
library(meta)
m_sub <- update(m_cont, subgroup = risk_of_bias, tau.common = FALSE)
forest(m_sub)
library(metafor)
res_sub <- rma(yi, vi, mods = ~ factor(risk_of_bias), data = dat)
summary(res_sub)
res_sub$QM
res_sub$QMp
Forest Plot by Subgroup
library(meta)
forest(m_sub,
sortvar = TE,
prediction = TRUE,
print.subgroup.labels = TRUE,
subgroup.hetstat = TRUE,
test.subgroup = TRUE
)
Meta-Regression
Continuous Moderators
library(metafor)
res_reg <- rma(yi, vi, mods = ~ year, data = dat)
summary(res_reg)
res_reg2 <- rma(yi, vi, mods = ~ year + sample_size + mean_age, data = dat)
summary(res_reg2)
regplot(res_reg,
xlab = "Publication Year",
label = TRUE,
labsize = 0.8)
Mixed Moderators
library(metafor)
res_mixed <- rma(yi, vi,
mods = ~ factor(study_design) + mean_age + follow_up,
data = dat)
summary(res_mixed)
anova(res, res_mixed)
Multimodel Inference
library(metafor)
library(MuMIn)
res_full <- rma(yi, vi, mods = ~ year + sample_size + risk_of_bias, data = dat)
dredge(res_full)
Sensitivity Analysis
Leave-One-Out Analysis
library(metafor)
loo <- leave1out(res)
print(loo)
forest(loo$estimate, sei = loo$se,
slab = paste0("Omitting ", dat$study),
header = "Leave-One-Out Analysis",
refline = coef(res))
library(meta)
metainf(m_cont, pooled = "random")
Influence Diagnostics
library(metafor)
inf <- influence(res)
plot(inf)
inf$inf
inf$dfbs
inf$cook.d
which(inf$is.infl)
baujat(res)
Cumulative Meta-Analysis
library(metafor)
cum <- cumul(res, order = dat$year)
forest(cum, header = "Cumulative Meta-Analysis")
library(meta)
metacum(m_cont, sortvar = year)
Bayesian Meta-Analysis
library(brms)
fit_bayes <- brm(
yi | se(sqrt(vi)) ~ 1 + (1 | study),
data = dat,
prior = c(
prior(normal(0, 1), class = Intercept),
prior(cauchy(0, 0.5), class = sd)
),
iter = 4000,
chains = 4,
cores = 4
)
summary(fit_bayes)
pp_check(fit_bayes)
library(tidybayes)
fit_bayes |>
spread_draws(b_Intercept, r_study[study,]) |>
mutate(study_effect = b_Intercept + r_study) |>
ggplot(aes(y = study, x = study_effect)) +
stat_halfeye()
Diagnostic Meta-Analysis
library(mada)
fit_diag <- reitsma(
data = diag_data,
formula = cbind(TP, FN, FP, TN) ~ 1
)
summary(fit_diag)
plot(fit_diag, sroclwd = 2)
summary_sens <- fit_diag$coefficients["tsens..Intercept."]
summary_spec <- fit_diag$coefficients["tfpr..Intercept."]
Reporting Meta-Analysis Results
Summary Table
library(meta)
summary_table <- data.frame(
Model = c("Fixed Effect", "Random Effects"),
Estimate = c(m_cont$TE.fixed, m_cont$TE.random),
CI_Lower = c(m_cont$lower.fixed, m_cont$lower.random),
CI_Upper = c(m_cont$upper.fixed, m_cont$upper.random),
p_value = c(m_cont$pval.fixed, m_cont$pval.random)
)
het_table <- data.frame(
Q = m_cont$Q,
df = m_cont$df.Q,
p_value = m_cont$pval.Q,
I2 = m_cont$I2,
tau2 = m_cont$tau2
)
PRISMA Flow Diagram
library(PRISMAstatement)
prisma_flow <- prisma(
found = 1000,
found_other = 50,
no_dupes = 800,
screened = 800,
screen_exclusions = 600,
full_text = 200,
full_text_exclusions = 150,
qualitative = 50,
quantitative = 45
)
prisma_flow
Key Packages Summary
| Package | Purpose |
|---|
| meta | User-friendly meta-analysis with forest plots |
| metafor | Comprehensive meta-analysis and meta-regression |
| dmetar | Meta-analysis helpers and additional functions |
| metasens | Sensitivity analysis and bias assessment |
| robumeta | Robust variance estimation for dependent effects |
| clubSandwich | Cluster-robust standard errors |
| mada | Diagnostic test accuracy meta-analysis |
| brms | Bayesian meta-analysis |
| PRISMAstatement | PRISMA flow diagrams |
Best Practices
- Pre-register protocol: Define inclusion criteria and analysis plan a priori
- Use appropriate effect measure: Match to outcome type and clinical meaning
- Assess heterogeneity: Report Q, I², tau² and interpret in context
- Investigate sources: Use subgroup analysis and meta-regression
- Evaluate bias: Use multiple methods (funnel, Egger, trim-fill)
- Sensitivity analysis: Leave-one-out, influence diagnostics, different estimators
- Report transparently: Follow PRISMA guidelines
- Consider prediction intervals: More relevant for clinical application than CI