Correlation Meta-analysis

 

Menu location: Analysis_Meta-Analysis_Correlation.

 

This function enables you to calculate an overall correlation coefficient (r) from a set of correlations.

 

Two methods are used: The Hedges-Olkin method is based on a conventional summary meta-analysis with a Fisher Z transformation of the correlation coefficient (Hedges and Olkin, 1985). The Hunter-Schmidt method is effectively a weighted mean of the raw correlation coefficient (Hunter and Schmidt, 1990).

 

Neither of these methods is completely suitable for either a small number of studies (less than 30) or a heterogeneous set of studies (Field, 2001). The Hedges-Olkin method tends to over estimate the pooled effect whereas the Schmidt-Hunter method under-estimates it a little when the correlation is greater than 0.5. The least biased estimate of the true population correlation is provided by the Schmidt-Hunter method. The Hedges-Olkin method reduces the risk of type I error when compared with the Schmidt-Hunter method, but only when the studies are homogeneous. For heterogeneous studies you should consider consulting with a Statistician about an alternative multilevel modelling approach.

 

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.

 

DATA INPUT:

You enter the correlation coefficient and the sample size for each study. You may also enter a title for each study.

 

Example

The following data represent the relationship between drug misuse and delinquency. A small homogeneous set of studies is used for convenience here, and because the results are widely cited on-line across a variety of public code bases that implement the algorithms. Note that correlation studies are usually heterogeneous, in which case at least 30 studies should be used:

 

Correlation Sample size
0.51 131
0.48 129
0.30 155
0.21 121
0.60 111
0.46 119
0.22 112
0.25 145

 

To analyse these data in StatsDirect first prepare them in two workbook columns and label these columns appropriately. Alternatively, open the test workbook using the file open function of the file menu. Then select correlation from the meta-analysis section of the analysis menu, and then select the columns 'Correlation' and 'Sample size' as prompted.

 

For this example:

 

Study Size Correlation Approximate 95% CI
1 131 0.51 0.370922 0.626703
2 129 0.48 0.334935 0.602837
3 155 0.30 0.149418 0.436981
4 121 0.21 0.03273 0.37446
5 111 0.60 0.465688 0.707292
6 119 0.46 0.305281 0.591057
7 112 0.22 0.03591 0.389649
8 145 0.25 0.090686 0.396837

 

Stratum Standardized effect Variance % Weights (fixed, random, size)
1 0.56273 0.007813 12.812813 12.609861 12.805474
2 0.522984 0.007937 12.612613 12.559901 12.609971
3 0.30952 0.006579 15.215215 13.129365 15.151515
4 0.213171 0.008475 11.811812 12.347632 11.827957
5 0.693147 0.009259 10.810811 12.050626 10.85044
6 0.497311 0.008621 11.611612 12.291224 11.632454
7 0.223656 0.009174 10.910911 12.082088 10.948192
8 0.255413 0.007042 14.214214 12.929303 14.173998

 

Hedges-Olkin fixed effects

Pooled correlation = 0.383132 (95% CI = 0.329008 to 0.434748)

Z (test correlation differs from 0) = 12.760536  P < 0.0001

 

Non-combinability of studies

Cochran Q = 27.382264  (df = 7)  P = 0.0003

Moment-based estimate of between studies variance = 0.023363

I² (inconsistency) = 74.4% (95% CI = 46.2% to 86.5%)

 

Hedges-Olkin random effects

Pooled correlation = 0.387032 (95% CI = 0.277911 to 0.486292)

Z (test correlation differs from 0) = 6.512137  P < 0.0001

 

Bias indicators

Begg-Mazumdar: Kendall's tau = 0.071429  P = 0.9049 (low power)

Egger: bias = 10.348335 (90% CI = -13.777152 to 34.473823)  P = 0.4365

 

Schmidt-Hunter

Weighted mean correlation (95% CI): 0.374262 (0.278809 to 0.469714)

Z (test correlation differs from 0) = 7.684871 P < 0.0001

 

Observed variance across studies: 0.018974

Variance due to sampling error: 0.005828

Variance in the population correlations: 0.013146

95% Credibility interval for weighted mean correlation: 0.14954 to 0.598984

 

Indicators of homogeneity/heterogeneity:

  1. 1. Residual standard deviation (should be smaller than 1/4 WMC: 0.093565): 0.114656
  2. 2. Percent of observed variance accounted for by sampling error (should be at least 75%): 30.717078
  3. 3. Chi-square test of heterogeneity: 26.044144 P = 0.0005

 

Here we cannot make a confident statement about the pooled correlation coefficient because the set of studies is clearly not homogeneous and the number of studies is too small for standard correlation meta-analysis methods to be reliable.

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.

# Correlation meta-analysis: the StatsDirect help example (eight studies of the
# relationship between drug misuse and delinquency; the test workbook's meta worksheet
# columns Correlation and Sample size) in R
r <- c(0.51, 0.48, 0.30, 0.21, 0.60, 0.46, 0.22, 0.25)
n <- c(131, 129, 155, 121, 111, 119, 112, 145)

# Base R has no meta-analysis function, so the Hedges-Olkin method is computed as the
# report describes it: each correlation becomes Fisher's z (atanh), whose variance is
# 1 / (n - 3); the studies are pooled on the z scale with weights n - 3, and the pooled
# z and its limits are transformed back to correlations (tanh)
six <- function(x) formatC(x, digits = 6, 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))
}
k <- length(r)
z <- atanh(r)
v <- 1 / (n - 3)
cit <- qnorm(0.975)
lower <- tanh(z - cit * sqrt(v))
upper <- tanh(z + cit * sqrt(v))
w <- 1 / v
z_fixed <- sum(w * z) / sum(w)
se_fixed <- 1 / sqrt(sum(w))
fixed <- tanh(z_fixed + c(0, -1, 1) * cit * se_fixed)

# Cochran's Q on the z scale, and the DerSimonian-Laird moment estimate of the
# between-studies variance that the random effects weights add to each variance
Q <- sum(w * (z - z_fixed)^2)
df <- k - 1
tau2 <- max(0, (Q - df) / (sum(w) - sum(w^2) / sum(w)))
w_random <- 1 / (v + tau2)
z_random <- sum(w_random * z) / sum(w_random)
se_random <- 1 / sqrt(sum(w_random))
random <- tanh(z_random + c(0, -1, 1) * cit * se_random)

# The report's two tables: each correlation with its approximate 95% interval, then
# its Fisher z, variance and percentage weights (fixed effects, random effects, and by
# sample size as the Schmidt-Hunter method below weights)
print(data.frame(Study = 1:k, Size = n, Correlation = r, Lower = six(lower),
                 Upper = six(upper)), row.names = FALSE)
# (study 1's variance is exactly 1/128, printed 0.007812 here and 0.007813 by the
# program, which squares the standard error)
print(data.frame(Study = 1:k, Fisher_z = six(z), Variance = six(v),
                 Fixed = six(100 * w / sum(w)),
                 Random = six(100 * w_random / sum(w_random)),
                 Size = six(100 * n / sum(n))), row.names = FALSE)

cat("Hedges-Olkin fixed effects\n")
cat("Pooled correlation = ", six(fixed[1]), " (95% CI = ", six(fixed[2]), " to ",
    six(fixed[3]), ")\n", sep = "")
cat("Z (test correlation differs from 0) =", six(z_fixed / se_fixed), " ",
    pv(2 * pnorm(-abs(z_fixed / se_fixed))), "\n")

# I-squared is the percentage of Q beyond its degrees of freedom. Its confidence
# interval is the non-central chi-square method of Hedges and Pigott (2001) that the
# report uses: Q is treated as a non-central chi-square variable on df degrees of
# freedom, and each limit is the non-centrality parameter at which the observed Q sits
# at the 97.5th (lower limit) or the 2.5th (upper limit) percentile, found with uniroot
# and pchisq; a limit is 0 when the central distribution already puts Q below that
# percentile. A non-centrality lambda gives I-squared = lambda / (df + lambda)
cat("Non-combinability of studies\n")
cat("Cochran Q = ", six(Q), "  (df = ", df, ")  ",
    pv(pchisq(Q, df, lower.tail = FALSE)), "\n", sep = "")
cat("Moment-based estimate of between studies variance =", six(tau2), "\n")
one <- function(x) formatC(x, digits = 1, format = "f", drop0trailing = TRUE)
ncp_limit <- function(p) {
  if (pchisq(Q, df) <= p) return(0)
  # the bracket Q + 1000 is ample: the distribution function there is near 0
  uniroot(function(l) pchisq(Q, df, ncp = l) - p, c(0, Q + 1000), tol = 1e-10)$root
}
lambda <- c(ncp_limit(0.975), ncp_limit(0.025))
i2_ci <- 100 * lambda / (df + lambda)
cat("I-squared (inconsistency) = ", one(max(0, 100 * (Q - df) / Q)), "% (95% CI = ",
    one(i2_ci[1]), "% to ", one(i2_ci[2]), "%)\n", sep = "")

cat("Hedges-Olkin random effects\n")
cat("Pooled correlation = ", six(random[1]), " (95% CI = ", six(random[2]), " to ",
    six(random[3]), ")\n", sep = "")
cat("Z (test correlation differs from 0) =", six(z_random / se_random), " ",
    pv(2 * pnorm(-abs(z_random / se_random))), "\n")

# Begg-Mazumdar: Kendall's rank correlation between each study's standardised
# deviation from the fixed effects pooled z and its variance. With no tied variances
# cor.test gives the exact two sided P, as the report does; with tied sample sizes the
# report instead uses a continuity corrected normal approximation, which cor.test
# does not. The test has low power with fewer than 11 studies, as the report notes.
cat("Bias indicators\n")
deviation <- (z - z_fixed) / sqrt(v - 1 / sum(w))
bm <- cor.test(deviation, v, method = "kendall")
cat("Begg-Mazumdar: Kendall's tau =", six(bm$estimate), " ", pv(bm$p.value),
    if (k < 11) "(low power)\n" else "\n")

# Egger: each standardised effect (z / se) regressed on its precision (1 / se); the
# bias is the intercept, with a t test on k - 2 degrees of freedom and a 90% interval
egger <- lm(I(z / sqrt(v)) ~ I(1 / sqrt(v)))
egger_ci <- confint(egger, level = 0.9)[1, ]
cat("Egger: bias = ", six(coef(egger)[1]), " (90% CI = ", six(egger_ci[1]), " to ",
    six(egger_ci[2]), ")  ", pv(summary(egger)$coefficients[1, 4]), "\n", sep = "")

# Schmidt-Hunter: the mean correlation weighted by sample size, the variance of the
# correlations about it, the part of that variance expected from sampling error alone
# and the remainder, attributed to real differences between the populations studied
wmc <- sum(n * r) / sum(n)
var_r <- sum(n * (r - wmc)^2) / sum(n)
var_e <- (1 - wmc^2)^2 / (mean(n) - 1)
var_p <- max(0, var_r - var_e)
se_wmc <- sqrt(var_r / k)
sh <- wmc + c(0, -1, 1) * cit * se_wmc
cat("Schmidt-Hunter\n")
cat("Weighted mean correlation (95% CI): ", six(sh[1]), " (", six(sh[2]), " to ",
    six(sh[3]), ")\n", sep = "")
cat("Z (test correlation differs from 0) =", six(wmc / se_wmc),
    pv(2 * pnorm(-abs(wmc / se_wmc))), "\n")
cat("Observed variance across studies:", six(var_r), "\n")
cat("Variance due to sampling error:", six(var_e), "\n")
cat("Variance in the population correlations:", six(var_p), "\n")
# the credibility interval spreads the population correlations about the mean by
# their standard deviation, the square root of var_p (the report leaves it out when
# that is zero)
cat("95% Credibility interval for weighted mean correlation:",
    six(wmc - cit * sqrt(var_p)), "to", six(wmc + cit * sqrt(var_p)), "\n")
cat("Indicators of homogeneity/heterogeneity:\n")
cat("1. Residual standard deviation (should be smaller than 1/4 WMC: ", six(wmc / 4),
    "): ", six(sqrt(var_p)), "\n", sep = "")
cat("2. Percent of observed variance accounted for by sampling error",
    "(should be at least 75%):", six(100 * var_e / var_r), "\n")
het <- k * var_r / var_e
cat("3. Chi-square test of heterogeneity:", six(het),
    pv(pchisq(het, df, lower.tail = FALSE)), "\n")

# The report's charts: the bias assessment (funnel) plot of each study's Fisher z
# against its standard error, the most precise studies at the top, with the fixed
# effects pooled z and the limits within which 95% of studies of each precision would
# fall; then a forest plot for each of the three pooled correlations, the studies'
# squares scaled by the square root of their fixed effects weights
se <- sqrt(v)
top <- max(se) * 1.05
plot(z, se, ylim = c(top, 0), xlim = z_fixed + c(-1, 1) * cit * top,
     xlab = "Fisher Z(Correlation)", ylab = "Standard error",
     main = "Bias assessment plot", pch = 16)
abline(v = z_fixed)
lines(z_fixed + cit * c(0, top), c(0, top))
lines(z_fixed - cit * c(0, top), c(0, top))
forest <- function(pooled, title) {
  y <- rev(seq_len(k + 1))   # study 1 at the top, the pooled correlation at the foot
  par(mar = c(5, 8, 4, 2))
  # the axis runs from -1 when a study or limit is negative, as the program draws it
  left <- if (min(lower, pooled[2]) < 0) -1 else 0
  right <- if (max(upper, pooled[3]) <= 0) 0 else 1
  plot(c(r, pooled[1]), y, xlim = c(left, right), yaxt = "n", ylab = "",
       xlab = "Correlation (95% confidence interval)", main = title,
       pch = c(rep(15, k), 18), cex = c(2 * sqrt(w / max(w)), 3))
  segments(c(lower, pooled[2]), y, c(upper, pooled[3]), y)
  abline(v = pooled[1], lty = 2)
  axis(2, at = y, labels = c(paste("study", 1:k), "combined"), las = 1, tick = FALSE)
}
forest(fixed, "Correlation (Hedges-Olkin fixed effects) meta-analysis plot")
forest(random, "Correlation (Hedges-Olkin random effects) meta-analysis plot")
forest(sh, "Correlation (Schmidt-Hunter) meta-analysis plot")