Summary Data Meta-analysis
Menu location: Analysis_Meta-Analysis_Summary.
This function provides a substitute for a proper meta-analysis when only summary statistics (odds ratio, relative risk or risk difference, with confidence intervals or standard errors) are known from a group of related studies.
Please do not use this function if the raw data from original studies are available, as the pooled estimates will be much more precise when using the original raw data. Better alternatives to this function are odds ratio, relative risk or risk difference meta-analysis based on counts from the included studies.
A generalised meta-analysis is calculated for the fixed effects model and the random effects model. Stratum weights are calculated as the inverse of the variance for the summary statistic (Y) supplied. If Y is specified as a ratio then calculations are performed on a natural log transformation of Y. The pooled estimate of Y is calculated as a weighted mean, i.e. the sum of weighted Y or ln(Y) for each stratum divided by the sum of the weights, transformed back to a natural scale if necessary.
The inconsistency of results across studies is summarised in the I² statistic, which is the percentage of variation across studies that is due to heterogeneity rather than chance – see the heterogeneity section for more information.
Please consult with a Statistician before using this method.
Example
Test workbook (Meta-analysis worksheet: Odds Ratio, LCI, UCI, Study).
The data are the odds ratio of death after heart attack, with its 95% confidence interval, from each of the seven trials of aspirin given by Fleiss (1993) and used in the other meta-analysis examples, i.e. the summary statistics that a published report would usually give when the counts are not available. They are provided in the test workbook in the columns marked "Odds Ratio", "LCI", "UCI" and "Study".
To analyse these data in StatsDirect open the test workbook using the file open function of the file menu. Then select Summary from the Meta-Analysis section of the Analysis menu. Choose Odds Ratio as the type of summary statistic and Confidence Interval as the input data type, then select the columns marked "Odds Ratio", "LCI", "UCI" and "Study" when prompted for the summary statistic, its lower and upper confidence limits and the study names.
For this example:
Summary meta-analysis
| Study | Odds Ratio | SE | Approximate 95% CI | ||
| 1 | 0.719714 | 0.207152 | 0.47831 | 1.077371 | MRC-1 |
| 2 | 0.68076 | 0.213469 | 0.446423 | 1.030758 | CDP |
| 3 | 0.80287 | 0.148339 | 0.599864 | 1.072965 | MRC-2 |
| 4 | 0.800739 | 0.270977 | 0.46972 | 1.358784 | GASP |
| 5 | 0.798143 | 0.19656 | 0.545041 | 1.177753 | PARIS |
| 6 | 1.132736 | 0.100512 | 0.930385 | 1.37967 | AMIS |
| 7 | 0.894969 | 0.039192 | 0.828783 | 0.966411 | ISIS-2 |
| Stratum | Standardized Effect | Standard Error | % Weights (fixed, random) | ||
| 1 | -0.328901 | 0.207152 | 2.647516 | 7.569774 | MRC-1 |
| 2 | -0.384545 | 0.213469 | 2.493139 | 7.198172 | CDP |
| 3 | -0.219562 | 0.148339 | 5.163037 | 12.748104 | MRC-2 |
| 4 | -0.22222 | 0.270977 | 1.547221 | 4.75221 | GASP |
| 5 | -0.225467 | 0.19656 | 2.940518 | 8.255604 | PARIS |
| 6 | 0.124636 | 0.100512 | 11.245456 | 20.878635 | AMIS |
| 7 | -0.110966 | 0.039192 | 73.963113 | 38.597502 | ISIS-2 |
Fixed effects (inverse variance)
Pooled odds ratio = 0.897845 (95% CI = 0.840448 to 0.959163)
Z (test Odds Ratio differs from 1) = -3.196974 P = 0.0014
Non-combinability of studies
Cochran Q = 9.278463 (df = 6) P = 0.1585
Moment-based estimate of between studies variance = 0.008558
I² (inconsistency) = 35.3% (95% CI = 0% to 75.6%)
Random effects (DerSimonian-Laird)
Pooled odds ratio = 0.881131 (95% CI = 0.779666 to 0.995799)
Z (test Odds Ratio) = -2.027405 P = 0.0426
Bias indicators
Begg-Mazumdar: Kendall's tau = -0.428571 P = 0.2389 (low power)
Egger: bias = -0.671627 (90% CI = -2.183157 to 0.839904) P = 0.4116
The standard error of each log odds ratio is recovered from the study's confidence limits, and the studies are then combined by inverse variance weighting on the log scale. Here the fixed effects pooled odds ratio, 0.90 with 95% confidence limits of 0.84 and 0.96, is close to the Peto odds ratio calculated from the counts of the same trials. The inconsistency between the studies is moderate (I² = 35%), and the random effects model, which allows for it, gives a wider interval whose upper limit approaches 1.
R code
This R code reproduces the example above. It needs no packages and was checked with R 4.6.1. Paste it into R, or save it as a script and run it.
# Summary data meta-analysis: the StatsDirect help example (the odds ratio of each of
# seven trials of aspirin after heart attack, Fleiss 1993, with its 95% confidence
# limits; the test workbook's Meta-analysis worksheet columns Odds Ratio, LCI, UCI and
# Study) in R
study <- c("MRC-1", "CDP", "MRC-2", "GASP", "PARIS", "AMIS", "ISIS-2")
or <- c(0.719714, 0.68076, 0.80287, 0.800739, 0.798143, 1.132736, 0.894969)
lower <- c(0.47831, 0.446423, 0.599864, 0.46972, 0.545041, 0.930385, 0.828783)
upper <- c(1.077371, 1.030758, 1.072965, 1.358784, 1.177753, 1.37967, 0.966411)
k <- length(or)
z <- qnorm(0.975)
six <- function(x) formatC(x, digits = 6, format = "f", drop0trailing = TRUE)
one <- function(x) formatC(x, digits = 1, format = "f", drop0trailing = TRUE)
pv <- function(p) {
if (p < 0.0001) "P < 0.0001" else
paste("P =", formatC(p, digits = 4, format = "f", drop0trailing = TRUE))
}
# Base R has no meta-analysis function, so the report is built from the formulae.
# A ratio is analysed on the log scale: each study's standard error of the log odds
# ratio is recovered from its limits as their log difference over twice the normal
# deviate (had standard errors been given instead, the limits would be recovered
# from them the other way round), and the inverse variance weights follow
y <- log(or)
se <- (log(upper) - log(lower)) / (2 * z)
w <- 1 / se^2
cat("Study Odds ratio SE Approximate 95% CI\n")
for (i in 1:k) {
cat(i, six(or[i]), six(se[i]), six(lower[i]), six(upper[i]), study[i], "\n")
}
# Fixed effects: the weighted mean of the log odds ratios, its standard error the
# root of the reciprocal of the total weight, transformed back to the ratio scale
pooled <- sum(w * y) / sum(w)
se_pooled <- 1 / sqrt(sum(w))
z_fixed <- pooled / se_pooled
# Cochran's Q about the pooled log odds ratio, then the DerSimonian-Laird moment
# estimate of the between studies variance, which inflates each study's variance
# for the random effects weights
q <- sum(w * (y - pooled)^2)
tau2 <- max(0, (q - (k - 1)) / (sum(w) - sum(w^2) / sum(w)))
w_dl <- 1 / (tau2 + se^2)
pooled_dl <- sum(w_dl * y) / sum(w_dl)
se_dl <- 1 / sqrt(sum(w_dl))
z_dl <- pooled_dl / se_dl
# The report's second table: each study's standardised effect, the log odds ratio on
# which the pooling is done, with its standard error on that scale and its share of
# the fixed and random weights
cat("Stratum Standardized effect Standard error % Weights (fixed, random)\n")
for (i in 1:k) {
cat(i, six(y[i]), six(se[i]), six(100 * w[i] / sum(w)),
six(100 * w_dl[i] / sum(w_dl)), study[i], "\n")
}
cat("Fixed effects (inverse variance)\n")
cat("Pooled odds ratio = ", six(exp(pooled)), " (95% CI = ",
six(exp(pooled - z * se_pooled)), " to ", six(exp(pooled + z * se_pooled)), ")\n",
sep = "")
cat("Z (test Odds Ratio differs from 1) =", six(z_fixed), " ",
pv(2 * pnorm(-abs(z_fixed))), "\n")
# I-squared = (Q - df) / Q, with the interval of the heterogeneity topic's default
# 'exact' option (the iterative method it credits to Hedges and Pigott, 2001): Q is
# treated as non-central chi-square and its distribution function at the observed Q
# is inverted for the non-centrality parameter lambda, the lower limit where that
# probability is 0.975 (0 when even the central distribution gives less) and the
# upper where it is 0.025; each limit becomes I-squared as lambda / (df + lambda)
cat("Non-combinability of studies\n")
dfree <- k - 1
cat("Cochran Q = ", six(q), " (df = ", dfree, ") ",
pv(pchisq(q, dfree, lower.tail = FALSE)), "\n", sep = "")
cat("Moment-based estimate of between studies variance =", six(tau2), "\n")
i2 <- max(0, 100 * (q - dfree) / q)
lambda <- function(p) {
if (pchisq(q, dfree) < p) 0 else
uniroot(function(l) pchisq(q, dfree, ncp = l) - p, c(0, 10 * q + 100),
tol = 1e-10)$root
}
lim <- c(lambda(0.975), lambda(0.025))
cat("I2 (inconsistency) = ", one(i2), "% (95% CI = ",
one(100 * lim[1] / (dfree + lim[1])), "% to ",
one(100 * lim[2] / (dfree + lim[2])), "%)\n", sep = "")
cat("Random effects (DerSimonian-Laird)\n")
cat("Pooled odds ratio = ", six(exp(pooled_dl)), " (95% CI = ",
six(exp(pooled_dl - z * se_dl)), " to ", six(exp(pooled_dl + z * se_dl)), ")\n",
sep = "")
cat("Z (test Odds Ratio) =", six(z_dl), " ", pv(2 * pnorm(-abs(z_dl))), "\n")
# Bias indicators. Begg and Mazumdar's test is Kendall's tau between each study's
# deviation from the pooled log odds ratio, standardised by its variance less the
# pooled variance, and that variance; with no ties among seven studies cor.test
# gives the exact two sided P (with tied standard errors it would give tau-b and a
# normal approximation, which the report corrects for continuity); the report marks
# the test as low in power when there are fewer than eleven studies
dev <- (y - pooled) / sqrt(se^2 - se_pooled^2)
kt <- cor.test(dev, se^2, method = "kendall")
cat("Bias indicators\n")
cat("Begg-Mazumdar: Kendall's tau =", six(kt$estimate), " ", pv(kt$p.value),
"(low power)\n")
# Egger's test regresses each study's standardised effect (log odds ratio over its
# standard error) on its precision (one over that standard error); the bias is the
# intercept, judged by a t test on k - 2 degrees of freedom; the interval is at
# twice the analysis's alpha, 90% for this 95% analysis
fit <- lm(I(y / se) ~ I(1 / se))
bias <- coef(fit)[1]
se_bias <- sqrt(vcov(fit)[1, 1])
cat("Egger: bias = ", six(bias), " (90% CI = ", six(bias - qt(0.95, k - 2) * se_bias),
" to ", six(bias + qt(0.95, k - 2) * se_bias), ") ",
pv(2 * pt(-abs(bias / se_bias), k - 2)), "\n", sep = "")
# The report's bias assessment (funnel) plot: each log odds ratio against its
# standard error, the axis reversed so that the 95% cone about the pooled log odds
# ratio opens downwards
se_top <- max(se) * 1.05
plot(y, se, ylim = c(se_top, 0), xlim = range(y, pooled + c(-1, 1) * z * se_top),
xlab = "Log(odds ratio)", ylab = "Standard error", main = "Bias assessment plot")
abline(v = pooled)
lines(pooled + c(-1, 0, 1) * z * se_top, c(se_top, 0, se_top))
# The two forest plots: each study's odds ratio (the symbol size growing with its
# weight) with its interval on a log scale, the line of no effect at 1 and the
# pooled estimate as a diamond at the foot
forest <- function(weights, est, se_est, title) {
yy <- rev(seq_len(k)) + 1
limits <- exp(est + c(-1, 1) * z * se_est)
par(mar = c(5, 8, 4, 8))
plot(or, yy, log = "x", xlim = range(lower, upper, limits), ylim = c(0.5, k + 1.5),
pch = 15, cex = 0.5 + 2 * sqrt(weights / max(weights)), yaxt = "n", ylab = "",
xlab = "odds ratio (95% confidence interval)", main = title)
segments(lower, yy, upper, yy)
polygon(c(limits[1], exp(est), limits[2], exp(est)), c(1, 1.3, 1, 0.7), col = "grey")
segments(exp(est), 1, exp(est), k + 1, lty = 3)
abline(v = 1)
axis(2, at = c(yy, 1), labels = c(study, "combined"), las = 1, tick = FALSE)
axis(4, at = c(yy, 1), las = 1, tick = FALSE,
labels = sprintf("%.2f (%.2f, %.2f)", c(or, exp(est)), c(lower, limits[1]),
c(upper, limits[2])))
}
forest(w, pooled, se_pooled, "Summary meta-analysis plot [fixed effects]")
forest(w_dl, pooled_dl, se_dl, "Summary meta-analysis plot [random effects]")