Dependent effect sizes in meta-analysis

R
meta-analysis
effect size
heterogeneity
ecology tutorial
Most studies report several effect sizes, so they are not independent. A simulation prices the naive pooled fit, the three-level model and clustered errors.
Author

Tidy Ecology

Published

2026-08-04

A synthesis of warming experiments on plant growth: twenty published studies, open-top chambers or buried heating cables, one to five growing seasons, aboveground biomass in warmed plots against ambient controls. The effect metric is the log response ratio, and almost none of the studies reports one of them. A chamber experiment that measured four species reports four ratios. A study with two response variables at two sites reports four. Twenty studies, eighty effect sizes, and the spreadsheet that comes out of the literature search has eighty rows.

Fitting a random-effects model to those eighty rows is a single line of code and it is the default in every meta-analysis package. It also assumes that the eighty rows are eighty independent draws. They are not. Four ratios from one chamber experiment share a site, a climate, a soil, a warming magnitude, an observer, and often the same plants: whatever made that experiment’s response large or small applies to all four. The rows arrive in clumps and the model treats them as if they had arrived one at a time.

This is the point at which random-effects meta-analysis in R stops. That post builds the two-level model and says in its limits that the model assumes the studies are independent, and that multiple effects from one study break the assumption and call for a multilevel meta-analysis. This is that continuation. Heterogeneity in meta-analysis owns tau-squared, Cochran’s Q and I-squared, and works entirely inside the independent case; the last section here is about what happens to all three once the effect sizes come in clumps.

The error is the same shape as pseudoreplication one level down, and the difference is worth naming because it changes what the fix can be. In a primary dataset the fish inside a tank are raw measurements, and if you distrust the model you can fall back on the tank means and analyse eight numbers. In a meta-analysis the raw data are gone. What survives is an effect size and a sampling variance treated as known, computed from sample sizes and standard deviations printed in a table. The dependence lives in the effect sizes themselves, and the variances that describe their precision are inputs rather than things the fit estimates. That constrains every repair below.

Pappalardo, Song, Hungate and Osenberg re-examined ninety-six global-change meta-analyses in 2023 and found that sixty-six per cent acknowledged some form of non-independence, leaving about a third that never raised it, and that sixteen per cent went as far as modelling one of its sources. Most of the rest chose one effect per study or averaged them, which are both defensible, and one of them turns out below to be harder than it looks.

This post simulates that spreadsheet and measures five things: how far the naive interval falls short, how many independent effect sizes eighty clumped ones are actually worth, what each of the three standard repairs costs in width and buys in coverage, where the averaging repair breaks in a way its coverage does not reveal, and what tau-squared and I-squared mean once the variance is split across two levels.

library(ggplot2)

te_pal <- list(forest = "#275139", green = "#2f8f63", sage = "#93a87f",
               clay = "#b5534e", gold = "#cda23f", line = "#dad9ca",
               ink = "#16241d", paper = "#f5f4ee")

theme_te <- function() {
  theme_minimal(base_size = 12) +
    theme(panel.grid.minor = element_blank(),
          panel.grid.major = element_line(colour = "#e7e6dc"),
          plot.background = element_rect(fill = "#f5f4ee", colour = NA),
          panel.background = element_rect(fill = "#f5f4ee", colour = NA),
          plot.title = element_text(face = "bold", colour = te_pal$ink),
          axis.title = element_text(colour = "#2c3a31"),
          axis.text = element_text(colour = "#2c3a31"))
}

Two ways an effect size can be tied to its neighbours

Write \(y_{ij}\) for the \(j\)th effect size in study \(i\), \(\theta_{ij}\) for the true effect it estimates, and \(v_{ij}\) for its sampling variance, known. The generating model has three layers:

\[y_{ij} = \mu + u_i + w_{ij} + \varepsilon_{ij}, \qquad u_i \sim N(0, \tau^2_b), \quad w_{ij} \sim N(0, \tau^2_w)\]

\(u_i\) is what the study contributes to all of its effects: the site, the warming magnitude, the season length. \(w_{ij}\) is what separates one species or one response variable from another inside the same study. Together they say the true effects come in clumps, and their ratio \(\tau^2_b/(\tau^2_b + \tau^2_w)\) is the share of the heterogeneity that lives between studies rather than inside them.

The second tie is in the sampling error. If a study reports biomass and height for the same plants, the two effect sizes come from overlapping measurements and their sampling errors are correlated even though each variance is reported separately. Write \(\mathrm{cov}(\varepsilon_{ij}, \varepsilon_{il}) = r\sqrt{v_{ij}v_{il}}\) for \(j \neq l\), with \(r\) the sampling correlation, and zero across studies. Nothing in a published table tells you what \(r\) is, which is the whole reason the third repair below exists. What matters for everything that follows is the correlation between two observed effect sizes from the same study,

\[\rho = \frac{\tau^2_b + r\sqrt{v_{ij}v_{il}}} {\sqrt{(\tau^2_b + \tau^2_w + v_{ij})(\tau^2_b + \tau^2_w + v_{il})}}\]

which mixes both ties into one number. The simulation is tuned so that \(\rho\) averages about 0.6 across the eighty effects.

k_study <- 20
m_eff <- 4
n_es <- k_study * m_eff
mu_true <- 0.25
tau2_b_true <- 0.065
tau2_w_true <- 0.020
r_samp <- 0.25

sim_batch <- function(k, m, tb, tw, rs) {
  n_arm <- sample(5:30, k, replace = TRUE)          # replicate plots per arm
  cv_j <- runif(k * m, 0.30, 0.60)                  # coefficient of variation
  vv <- 2 * cv_j^2 / rep(n_arm, each = m)           # sampling variance of lnRR
  u_i <- rnorm(k, 0, sqrt(tb))                      # study level
  w_ij <- rnorm(k * m, 0, sqrt(tw))                 # effect within study
  a_i <- rnorm(k)                                   # shared sampling noise
  b_ij <- rnorm(k * m)                              # private sampling noise
  e_ij <- sqrt(vv) * (sqrt(rs) * rep(a_i, each = m) + sqrt(1 - rs) * b_ij)
  list(y = mu_true + rep(u_i, each = m) + w_ij + e_ij,
       vv = vv,
       theta = mu_true + rep(u_i, each = m) + w_ij,
       study = rep(seq_len(k), each = m))
}

set.seed(20260801)
one_set <- sim_batch(k_study, m_eff, tau2_b_true, tau2_w_true, r_samp)
rho_set <- mean((tau2_b_true + r_samp * one_set$vv) /
                  (tau2_b_true + tau2_w_true + one_set$vv))

print(round(c(studies = k_study, effects_per_study = m_eff, effect_sizes = n_es,
              true_mean = mu_true, tau2_between = tau2_b_true, tau2_within = tau2_w_true,
              sampling_correlation = r_samp, median_v = median(one_set$vv),
              mean_rho = rho_set), 4))
             studies    effects_per_study         effect_sizes 
             20.0000               4.0000              80.0000 
           true_mean         tau2_between          tau2_within 
              0.2500               0.0650               0.0200 
sampling_correlation             median_v             mean_rho 
              0.2500               0.0210               0.6482 

The dataset holds 80 effect sizes from 20 studies, 4 each, with a true pooled log response ratio of 0.25 (about a 28.4 per cent increase in biomass), a median sampling variance of 0.021, and an average within-study correlation of 0.648. Of the total heterogeneity, 76.5 per cent sits between studies and the rest inside them.

A dot and whisker chart with twenty rows, one per study, running down the vertical axis. Each row holds four dark green dots with horizontal whiskers, and within a row the four dots occupy a narrow band relative to the width of the chart. Row to row those bands sit at very different positions along the horizontal axis, from below minus zero point two to above zero point seven. A vertical dashed line at zero point two five marks the true pooled effect, and a short gold tick on each row marks that study's mean.
Figure 1: The eighty simulated effect sizes, four per study, plotted against their study. Each effect carries the interval implied by its own sampling variance. The four effects within a study sit close together and the study means scatter widely, which is what a within-study correlation of about 0.6 looks like on paper. The dashed line is the true pooled effect.

Writing both fits out

The three-level model has a marginal covariance that is block diagonal, with one block per study. For a study with \(m\) effects,

\[\Sigma_i = \tau^2_b \mathbf{1}\mathbf{1}^{\top} + D_i, \qquad D_i = \mathrm{diag}(\tau^2_w + v_{ij})\]

and the naive model is the same thing with \(\tau^2_b\) set to zero, which is why one function covers both. Inverting a block by brute force would be wasteful: \(\Sigma_i\) is a diagonal matrix plus a rank-one term, so the Sherman-Morrison identity gives the inverse in closed form,

\[\Sigma_i^{-1} = D_i^{-1} - \frac{\tau^2_b\, D_i^{-1}\mathbf{1}\mathbf{1}^{\top}D_i^{-1}} {1 + \tau^2_b\, \mathbf{1}^{\top}D_i^{-1}\mathbf{1}}\]

Because the only covariate is an intercept, every quantity the fit needs collapses to two sums per study. Write \(s_i = \sum_j 1/(\tau^2_w + v_{ij})\) and \(t_i = \sum_j y_{ij}/(\tau^2_w + v_{ij})\). Then

\[\mathbf{1}^{\top}\Sigma_i^{-1}\mathbf{1} = \frac{s_i}{1 + \tau^2_b s_i}, \qquad \mathbf{1}^{\top}\Sigma_i^{-1}y_i = \frac{t_i}{1 + \tau^2_b s_i}, \qquad \log|\Sigma_i| = \sum_j \log(\tau^2_w + v_{ij}) + \log(1 + \tau^2_b s_i)\]

so the generalised least squares mean and its variance are

\[\hat\mu = \frac{\sum_i t_i/(1 + \tau^2_b s_i)}{\sum_i s_i/(1 + \tau^2_b s_i)}, \qquad \mathrm{Var}(\hat\mu) = \Big(\sum_i \frac{s_i}{1 + \tau^2_b s_i}\Big)^{-1}\]

Both variance components come out of restricted maximum likelihood, which is what meta-analysis software reports by default and which corrects the downward bias that plain maximum likelihood puts on \(\tau^2\). The restricted log-likelihood is the usual one for a linear model with known error variances. Konstantopoulos worked through the fixed-effect and variance-component estimation for this model in 2011, and Cheung showed in 2014 that the same fit can be obtained as a structural equation model, which is why the three-level meta-analysis turns up under two different names in the applied literature.

blk <- function(x, m) colSums(matrix(x, nrow = m))

ll_reml <- function(tb, tw, y, vv, m) {
  dv <- tw + vv
  s_i <- blk(1 / dv, m)
  t_i <- blk(y / dv, m)
  den <- 1 + tb * s_i
  info <- sum(s_i / den)
  mu <- sum(t_i / den) / info
  ev <- y - mu
  te <- blk(ev / dv, m)
  quad <- sum(ev^2 / dv) - tb * sum(te^2 / den)
  list(ll = -0.5 * (sum(log(dv)) + sum(log(den)) + log(info) + quad),
       mu = mu, se = sqrt(1 / info))
}

fit_re <- function(y, vv) {                       # naive two-level fit
  op <- optimize(function(tw) -ll_reml(0, tw, y, vv, 1)$ll, c(0, 3), tol = 1e-9)
  rr <- ll_reml(0, op$minimum, y, vv, 1)
  c(mu = rr$mu, se = rr$se, tau2 = op$minimum)
}

fit_3l <- function(y, vv, m) {                    # effects within studies
  op <- optim(c(0.05, 0.02), function(p) -ll_reml(p[1], p[2], y, vv, m)$ll,
              method = "L-BFGS-B", lower = c(0, 0), upper = c(3, 3))
  rr <- ll_reml(op$par[1], op$par[2], y, vv, m)
  c(mu = rr$mu, se = rr$se, tau2_b = op$par[1], tau2_w = op$par[2])
}

naive_one <- fit_re(one_set$y, one_set$vv)
three_one <- fit_3l(one_set$y, one_set$vv, m_eff)

print(round(naive_one, 5))
     mu      se    tau2 
0.29514 0.03809 0.09110 
print(round(three_one, 5))
     mu      se  tau2_b  tau2_w 
0.29806 0.06660 0.07730 0.02064 

On this one dataset the naive fit returns a pooled effect of 0.2951 with a standard error of 0.0381, and the three-level fit returns 0.2981 with a standard error of 0.0666, a factor of 1.75 wider. The point estimates barely move, which is the first thing to hold on to: clustering is not a bias problem. Both fits are aiming at the same \(\mu\) and both hit it. What differs is the claim each one makes about how well it knows the answer.

The naive fit also puts the whole heterogeneity in one number, 0.0911, while the three-level fit splits it into 0.0773 between studies and 0.0206 inside them.

Coverage over two thousand replicates

One dataset settles nothing. The experiment below redraws studies, effects, sampling variances and both kinds of correlated noise two thousand times, and fits five analyses to each replicate: the naive random-effects model on all eighty rows, the study averages with their sampling variance computed the easy way and the correct way, the three-level model, and cluster-robust standard errors applied to the naive fit. The last three get a section each below; here they are only being scored.

fit_crve <- function(y, vv, m, tau2) {            # standard errors clustered by study
  wt <- 1 / (tau2 + vv)
  mu <- sum(wt * y) / sum(wt)
  g_i <- blk(wt * (y - mu), m)
  kk <- length(g_i)
  c(mu = mu, se = sqrt(sum(g_i^2) / sum(wt)^2 * kk / (kk - 1)))
}

agg_var <- function(vmat, rr) {                   # sampling variance of a study mean
  m <- nrow(vmat)
  (colSums(vmat) + rr * (colSums(sqrt(vmat))^2 - colSums(vmat))) / m^2
}

fit_agg <- function(y, vv, m, rr) {
  fit_re(colMeans(matrix(y, nrow = m)), agg_var(matrix(vv, nrow = m), rr))
}
set.seed(20260804)
n_rep <- 2000
res <- matrix(NA_real_, n_rep, 14)
for (r in seq_len(n_rep)) {
  d <- sim_batch(k_study, m_eff, tau2_b_true, tau2_w_true, r_samp)
  fn <- fit_re(d$y, d$vv)
  f3 <- fit_3l(d$y, d$vv, m_eff)
  fc <- fit_crve(d$y, d$vv, m_eff, fn["tau2"])
  fa <- fit_agg(d$y, d$vv, m_eff, r_samp)
  fw <- fit_agg(d$y, d$vv, m_eff, 0)
  res[r, ] <- c(fn, f3, fc, fa[1:2], fw[1:2],
                mean((tau2_b_true + r_samp * d$vv) /
                       (tau2_b_true + tau2_w_true + d$vv)))
}
colnames(res) <- c("mu_n", "se_n", "t2_n", "mu_3", "se_3", "tb_3", "tw_3",
                   "mu_c", "se_c", "mu_a", "se_a", "mu_w", "se_w", "rho")
res <- as.data.frame(res)

z95 <- qnorm(0.975)
t95 <- qt(0.975, k_study - 1)
cvg <- function(mu, se, crit) 100 * mean(abs(mu - mu_true) < crit * se)

rho_bar <- mean(res$rho)
cov_naive <- cvg(res$mu_n, res$se_n, z95)
cov_3l <- cvg(res$mu_3, res$se_3, z95)
cov_3l_t <- cvg(res$mu_3, res$se_3, t95)
cov_crve <- cvg(res$mu_c, res$se_c, t95)
cov_agg <- cvg(res$mu_a, res$se_a, t95)
cov_aggw <- cvg(res$mu_w, res$se_w, t95)
se_short <- 100 * (1 - mean(res$se_n) / sd(res$mu_n))
width_ratio <- mean(res$se_3) / mean(res$se_n)
mc_se <- 100 * sqrt(0.95 * 0.05 / n_rep)

print(round(c(mean_rho = rho_bar, replicates = n_rep, sd_of_estimate = sd(res$mu_n),
              bias_naive = mean(res$mu_n) - mu_true, bias_three = mean(res$mu_3) - mu_true,
              mean_se_naive = mean(res$se_n), mean_se_three = mean(res$se_3),
              se_shortfall_pct = se_short), 5))
        mean_rho       replicates   sd_of_estimate       bias_naive 
         0.63876       2000.00000          0.06451          0.00060 
      bias_three    mean_se_naive    mean_se_three se_shortfall_pct 
         0.00054          0.03668          0.06302         43.14081 
print(round(c(coverage_naive = cov_naive, coverage_three_z = cov_3l,
              coverage_three_t = cov_3l_t, coverage_clustered = cov_crve,
              coverage_average_correct = cov_agg, coverage_average_easy = cov_aggw,
              monte_carlo_se = mc_se), 3))
          coverage_naive         coverage_three_z         coverage_three_t 
                  73.250                   93.300                   94.800 
      coverage_clustered coverage_average_correct    coverage_average_easy 
                  94.450                   94.550                   94.850 
          monte_carlo_se 
                   0.487 

Over 2000 replicates the naive interval covers the true pooled effect 73.25 per cent of the time against a nominal 95, and the reason is in the line above it: the standard deviation of the naive estimate across replicates is 0.0645 while the standard error it reports averages 0.0367. The fit understates its own uncertainty by 43.1 per cent. Both estimators are essentially unbiased, the naive one by 0.00060 and the three-level one by 0.00054, so nothing is wrong with the number in the middle of the interval. Only the interval is wrong.

The three-level model brings coverage back to 93.3 per cent against a normal reference and 94.8 per cent against a \(t\) reference on 19 degrees of freedom, the fairer comparison once you accept that the pooled mean is an average over 20 studies rather than 80 effects. The Monte Carlo standard error on any of these percentages is 0.49, so the cluster-aware analyses are indistinguishable from nominal and from each other, and the naive one is 45 Monte Carlo standard errors away.

The price is the width. The three-level standard error averages 0.063 against the naive 0.0367, a factor of 1.718. The naive analysis reports an interval 41.8 per cent narrower than the evidence supports, so correcting it means publishing a synthesis that looks 1.72 times less decisive than the one the default code produced.

A horizontal bar chart with five rows on warm off-white paper. The top bar, labelled naive random-effects on eighty effects, is coloured red and stops near seventy-three per cent. Below it four dark green bars, labelled average within study the easy way, average within study with the covariance, three-level model and cluster-robust standard errors, all reach almost to a vertical dashed line drawn at ninety-five. The horizontal axis runs from sixty to one hundred per cent and each bar carries its value as a label at its right end.
Figure 2: Measured coverage of a nominal 95 per cent interval for the pooled effect under five analyses of the same simulated data, over two thousand replicates. The naive fit on all eighty rows falls far short. Averaging within studies, the three-level model and cluster-robust standard errors all land within Monte Carlo error of nominal, and the two averaging variants are indistinguishable from each other.

How many studies is eighty effect sizes worth

The naive fit is confident because it counts eighty pieces of evidence. The arithmetic that says how many there really are is the design effect, the same formula that governs cluster sampling. At equal weights and equal marginal variances \(\sigma^2\) the naive fit believes \(\mathrm{Var}(\hat\mu) = \sigma^2/km\), while the truth adds one covariance term for each of the \(m(m-1)\) ordered pairs inside each study:

\[\mathrm{Var}(\hat\mu) = \frac{1}{(km)^2}\Big(km\,\sigma^2 + km(m-1)\rho\,\sigma^2\Big) = \frac{\sigma^2}{km}\big(1 + (m-1)\rho\big)\]

The bracket is the design effect. Dividing the number of effect sizes by it gives the effective number of independent effect sizes: the count of one-effect studies that would deliver the same precision.

de_meas <- (sd(res$mu_n) / mean(res$se_n))^2
de_pred <- 1 + (m_eff - 1) * rho_bar
n_eff_meas <- n_es / de_meas
n_eff_pred <- n_es / de_pred
rho_grid <- seq(0, 1, by = 0.01)
n_eff_curve <- n_es / (1 + (m_eff - 1) * rho_grid)

print(round(c(design_effect_measured = de_meas, design_effect_predicted = de_pred,
              effective_effects_measured = n_eff_meas, effective_effects_predicted = n_eff_pred,
              floor_at_rho_one = n_es / m_eff, nominal_effects = n_es), 4))
     design_effect_measured     design_effect_predicted 
                     3.0931                      2.9163 
 effective_effects_measured effective_effects_predicted 
                    25.8637                     27.4321 
           floor_at_rho_one             nominal_effects 
                    20.0000                     80.0000 

The measured design effect is 3.093, against 2.916 from the formula at the average correlation of 0.639. The two differ because the fit uses inverse-variance weights rather than equal ones and the sampling variances are not equal, but the formula is close enough to use as a back of the envelope check on a real synthesis.

So the eighty effect sizes carry the information of about 25.9 independent ones. The lower bound is worth seeing: as \(\rho\) goes to one the design effect goes to \(m\), the effective count goes to \(km/m = k\), and eighty effect sizes collapse to 20, the number of studies. At 0.64 the count sits at 25.9, which is much nearer the twenty-study floor than the eighty-row claim on the spreadsheet. That is the sentence to carry into a synthesis: adding a fifth species to a study that already reports four adds very little, and adding a twenty-first study adds a great deal.

A curve on warm off-white paper falling from the top left to the bottom right. The vertical axis is the effective number of independent effect sizes and runs from zero to eighty; the horizontal axis is the within-study correlation and runs from zero to one. The curve starts at eighty, drops steeply over the first third of the axis and flattens onto a horizontal dashed line at twenty labelled floor: one per study. A gold dot sits just below the curve at a correlation of about zero point six four, with a short label giving the measured value near twenty six.
Figure 3: Effective number of independent effect sizes against the within-study correlation, for twenty studies reporting four effects each. The curve starts at eighty when the effects are independent and falls to twenty, the number of studies, when they are perfectly correlated. The measured point from the simulation sits close to the floor rather than close to the nominal count.

Averaging within a study is a covariance calculation

The oldest repair is to reduce each study to one number and pool the twenty. It is honest, it needs no new machinery, and it is what most of the meta-analyses in the Pappalardo survey did. The trap is in the second step, because collapsing a study to its mean means you now have to say what the sampling variance of that mean is, and the answer is not the mean of the sampling variances. Lajeunesse set out the algebra for response ratios in 2011, for the two designs that produce it most often in ecology: several outcomes measured on one set of plants, and several treatment arms compared against one shared control.

For a study with \(m\) effects and sampling covariance matrix \(V_i\),

\[\mathrm{Var}(\bar{y}_i) = \frac{1}{m^2}\mathbf{1}^{\top}V_i\mathbf{1} = \frac{1}{m^2}\Big(\sum_j v_{ij} + \sum_{j \neq l} r\sqrt{v_{ij}v_{il}}\Big)\]

The first sum is what you get by assuming the effects are independent. The second is the correction, it has \(m(m-1)\) terms against the first sum’s \(m\), and it is zero only if the sampling errors really are uncorrelated. With four effects of equal variance \(v\) and a sampling correlation of 0.25, the correct variance is \((4v + 3v)/16 = 0.4375v\) against the easy answer of \(0.25v\), larger by three quarters.

set.seed(70101)
n_rep_a <- 3000
hit_c <- hit_w <- 0
n_seen <- 0
ratio_acc <- 0
for (r in seq_len(n_rep_a)) {
  d <- sim_batch(k_study, m_eff, tau2_b_true, tau2_w_true, r_samp)
  vmat <- matrix(d$vv, nrow = m_eff)
  ybar <- colMeans(matrix(d$y, nrow = m_eff))
  tbar <- colMeans(matrix(d$theta, nrow = m_eff))
  v_ok <- agg_var(vmat, r_samp)
  v_easy <- agg_var(vmat, 0)
  hit_c <- hit_c + sum(abs(ybar - tbar) < z95 * sqrt(v_ok))
  hit_w <- hit_w + sum(abs(ybar - tbar) < z95 * sqrt(v_easy))
  ratio_acc <- ratio_acc + mean(sqrt(v_ok / v_easy))
  n_seen <- n_seen + k_study
}
cov_study_ok <- 100 * hit_c / n_seen
cov_study_easy <- 100 * hit_w / n_seen
se_ratio_agg <- ratio_acc / n_rep_a

print(round(c(study_intervals = n_seen, coverage_with_covariance = cov_study_ok,
              coverage_easy_way = cov_study_easy, se_ratio = se_ratio_agg,
              se_shortfall_pct = 100 * (1 - 1 / se_ratio_agg)), 4))
         study_intervals coverage_with_covariance        coverage_easy_way 
              60000.0000                  95.0517                  86.4417 
                se_ratio         se_shortfall_pct 
                  1.3126                  23.8125 

Scored on the quantity it actually claims, the easy answer fails. Across 60,000 study-level intervals from 3000 replicates, the interval built from the correct sampling variance covers the study’s own mean true effect 95.05 per cent of the time, and the interval built by summing the variances alone covers it 86.44 per cent of the time. The standard error is a factor of 1.313 too small, which is 23.8 per cent short. Those are the intervals a forest plot draws, and they are the weights a precision-weighted pool uses.

The pooled mean is a different story, and this went against what I expected before running it.

set.seed(70102)
n_rep_t <- 1500
acc_ok <- acc_easy <- 0
for (r in seq_len(n_rep_t)) {
  d <- sim_batch(k_study, m_eff, tau2_b_true, tau2_w_true, r_samp)
  acc_ok <- acc_ok + fit_agg(d$y, d$vv, m_eff, r_samp)["tau2"]
  acc_easy <- acc_easy + fit_agg(d$y, d$vv, m_eff, 0)["tau2"]
}
t2_ok <- unname(acc_ok / n_rep_t)
t2_easy <- unname(acc_easy / n_rep_t)
t2_target <- tau2_b_true + tau2_w_true / m_eff

print(round(c(tau2_of_study_means_true = t2_target, tau2_with_covariance = t2_ok,
              tau2_easy_way = t2_easy, inflation_pct = 100 * (t2_easy / t2_ok - 1),
              coverage_pooled_with_covariance = cov_agg,
              coverage_pooled_easy_way = cov_aggw), 5))
       tau2_of_study_means_true            tau2_with_covariance 
                        0.07000                         0.07066 
                  tau2_easy_way                   inflation_pct 
                        0.07598                         7.51989 
coverage_pooled_with_covariance        coverage_pooled_easy_way 
                       94.55000                        94.85000 

Coverage of the pooled mean is 94.55 per cent with the covariance term and 94.85 per cent without it, a gap of 0.3 points against a Monte Carlo standard error of 0.49. The mistake does not show up at all. The reason is that the random-effects fit on the twenty study means estimates \(\tau^2\) from the scatter of those means, and if you hand it sampling variances that are too small it simply calls the difference heterogeneity: \(\tau^2\) comes out at 0.076 instead of 0.0707, an inflation of 7.5 per cent, and the sum of the two that drives the interval barely moves. The true value for the study means is \(\tau^2_b + \tau^2_w/m =\) 0.07, which the correct calculation recovers and the easy one overshoots.

So the covariance derivation is not optional, but the thing it protects is not the headline interval. It protects the per-study intervals, the weights, the prediction interval for a new study, and every sentence you would write about how much the studies disagree. If the only output is a pooled mean and its interval, a synthesis that ignored the covariance got away with it, and it got away with it by silently relabelling sampling noise as ecology.

Twenty rows on warm off-white paper, one per study. Each row carries a short red horizontal bar drawn over a longer green horizontal bar, both centred on the same point, and a dark dot marking the study's true mean effect. In some rows the dot lies beyond one end of the red bar while remaining inside the green one. The horizontal axis is the log response ratio and runs from about minus zero point two to zero point seven.
Figure 4: Study-level intervals from one replicate, built two ways from the same four effect sizes per study. The narrow red interval assumes the sampling errors within a study are independent; the wider green one adds the covariance term. The dot marks the study’s own mean true effect. Across three thousand replicates it falls outside the narrow interval about one time in seven, and outside the wide one about one time in twenty, as it should.

Cluster-robust standard errors, and how few studies they survive

The third repair gives up on describing the correlation and estimates the variance of the pooled mean from the between-study scatter of the weighted residuals. Keep the naive fit and its weights \(w_{ij} = 1/(\hat\tau^2 + v_{ij})\), and replace its variance by the sandwich

\[V_R = \frac{k}{k-1}\, \frac{\sum_i \big(\sum_{j \in i} w_{ij} e_{ij}\big)^2} {\big(\sum_{ij} w_{ij}\big)^2}, \qquad e_{ij} = y_{ij} - \hat\mu\]

with the interval taken against a \(t\) distribution on \(k-1\) degrees of freedom. Nothing in that expression needs \(r\), or \(\tau^2_b\), or even a correct weighting scheme: it needs only that the studies are independent of each other and that there are enough of them for the outer sum to behave. Hedges, Tipton and Johnson introduced this for meta-regression with dependent effect sizes in 2010, and the small-print is the same as for every sandwich estimator, which is that the outer sum has \(k\) terms and \(k\) in ecology is often small.

set.seed(70104)
kk_grid <- c(8, 12, 20, 40)
n_rep_k <- 1000
tab_k <- data.frame(k = kk_grid, naive = NA_real_, clustered = NA_real_,
                    three_level = NA_real_)
for (j in seq_along(kk_grid)) {
  kk <- kk_grid[j]
  a_n <- a_c <- a_3 <- 0
  crit_k <- qt(0.975, kk - 1)
  for (r in seq_len(n_rep_k)) {
    d <- sim_batch(kk, m_eff, tau2_b_true, tau2_w_true, r_samp)
    fn <- fit_re(d$y, d$vv)
    fc <- fit_crve(d$y, d$vv, m_eff, fn["tau2"])
    f3 <- fit_3l(d$y, d$vv, m_eff)
    a_n <- a_n + (abs(fn[1] - mu_true) < z95 * fn[2])
    a_c <- a_c + (abs(fc[1] - mu_true) < crit_k * fc[2])
    a_3 <- a_3 + (abs(f3[1] - mu_true) < crit_k * f3[2])
  }
  tab_k[j, 2:4] <- 100 * c(a_n, a_c, a_3) / n_rep_k
}
print(tab_k)
   k naive clustered three_level
1  8  70.0      94.3        94.6
2 12  71.4      94.3        94.5
3 20  72.8      93.7        93.7
4 40  73.4      95.7        95.7

Across 1000 replicates at each study count, the clustered interval holds between 93.7 and 95.7 per cent while the naive one stays between 70 and 73.4 per cent and does not improve with more studies, because its failure is not a small-sample failure. The three-level model tracks the clustered one closely at every count, 94.6 against 94.3 per cent at 8 studies.

That agreement at eight studies is a property of the easiest possible case and should not be generalised. There is one covariate here, an intercept; every cluster has the same size; and the \(t\) reference on 7 degrees of freedom happens to be about right for it. Add a moderator whose values are unbalanced across studies and the effective degrees of freedom fall well below \(k-1\), which is the problem Tipton’s 2015 small-sample corrections exist to solve, and which is why the current advice is to fit the three-level model and put clustered standard errors on top of it rather than choosing between them.

A line chart on warm off-white paper with the number of studies spaced logarithmically along the horizontal axis at eight, twelve, twenty and forty, and coverage in per cent on the vertical axis from sixty five to one hundred. A horizontal dashed line marks ninety five. Two nearly overlapping lines, one dark green for the three-level model and one gold for cluster-robust standard errors, run flat just below the dashed line across the whole range. A red line for the naive fit rises only gently across the same range, from about seventy to about seventy-three per cent, and stays far below the others throughout.
Figure 5: Coverage of the nominal 95 per cent interval against the number of studies, holding four effect sizes per study. The clustered and three-level intervals sit on the nominal line from eight studies upwards. The naive interval sits far below it and does not climb as studies are added, because more clumped rows do not repair a variance that was computed for the wrong unit.

What tau-squared means once it is split

The naive fit reports one \(\tau^2\). The three-level fit reports two, and they are not the two halves of the first one in any simple sense. Start with what each is estimating. The naive \(\tau^2\) describes the marginal variance of a single effect size around the pooled mean, so it targets \(\tau^2_b + \tau^2_w\), the total heterogeneity, and it does so without ever seeing the clustering: the diagonal of the covariance matrix is right in the naive model even though the off-diagonal is missing. The three-level \(\tau^2_b\) describes how much study means differ and \(\tau^2_w\) how much effects differ inside a study.

The experiment below fits the three-level model twice, once to data where the sampling errors within a study are genuinely independent and once to the data used everywhere above, where they correlate at 0.25.

set.seed(70103)
n_rep_s <- 1200
split_indep <- split_corr <- matrix(NA_real_, n_rep_s, 2)
i2_naive <- i2_agg <- i2_between <- i2_within <- numeric(n_rep_s)

typical_v <- function(vv, n_unit) {
  (n_unit - 1) * sum(1 / vv) / (sum(1 / vv)^2 - sum(1 / vv^2))
}

for (r in seq_len(n_rep_s)) {
  d0 <- sim_batch(k_study, m_eff, tau2_b_true, tau2_w_true, 0)
  split_indep[r, ] <- fit_3l(d0$y, d0$vv, m_eff)[3:4]

  d1 <- sim_batch(k_study, m_eff, tau2_b_true, tau2_w_true, r_samp)
  f3 <- fit_3l(d1$y, d1$vv, m_eff)
  split_corr[r, ] <- f3[3:4]

  fn <- fit_re(d1$y, d1$vv)
  vt <- typical_v(d1$vv, n_es)
  i2_naive[r] <- 100 * fn["tau2"] / (fn["tau2"] + vt)
  i2_between[r] <- 100 * f3[3] / (f3[3] + f3[4] + vt)
  i2_within[r] <- 100 * f3[4] / (f3[3] + f3[4] + vt)

  v_a <- agg_var(matrix(d1$vv, nrow = m_eff), r_samp)
  fa <- fit_re(colMeans(matrix(d1$y, nrow = m_eff)), v_a)
  i2_agg[r] <- 100 * fa["tau2"] / (fa["tau2"] + typical_v(v_a, k_study))
}

print(round(c(true_tau2_between = tau2_b_true, true_tau2_within = tau2_w_true,
              indep_tau2_between = mean(split_indep[, 1]),
              indep_tau2_within = mean(split_indep[, 2]),
              corr_tau2_between = mean(split_corr[, 1]),
              corr_tau2_within = mean(split_corr[, 2]),
              naive_tau2_total = mean(res$t2_n),
              true_tau2_total = tau2_b_true + tau2_w_true), 5))
 true_tau2_between   true_tau2_within indep_tau2_between  indep_tau2_within 
           0.06500            0.02000            0.06542            0.02015 
 corr_tau2_between   corr_tau2_within   naive_tau2_total    true_tau2_total 
           0.07054            0.01504            0.08187            0.08500 
print(round(c(I2_naive_effect_level = mean(i2_naive), I2_study_level = mean(i2_agg),
              I2_between = mean(i2_between), I2_within = mean(i2_within),
              I2_three_level_total = mean(i2_between) + mean(i2_within)), 3))
I2_naive_effect_level        I2_study_level            I2_between 
               78.217                85.532                64.413 
            I2_within  I2_three_level_total 
               14.681                79.095 

With independent sampling errors the split is recovered: 0.0654 and 0.0201 against truths of 0.065 and 0.02. With sampling errors correlated at 0.25 the same fit returns 0.0705 and 0.015: variance has moved from the within level to the between level, by 0.0051 in one direction and 0.0051 in the other, and the total is almost unchanged. In absolute terms the two shifts match; in relative terms they do not, because the same displacement is 7.8 per cent of the between-study component and 25.4 per cent of the smaller within-study one. The mechanism is that a shared sampling error and a shared study effect have the same block structure. If every effect in a study has variance \(v\), then a sampling correlation of \(r\) contributes \(rv\) to every off-diagonal entry of that block, which is algebraically indistinguishable from adding \(rv\) to \(\tau^2_b\) and subtracting it from \(\tau^2_w\). The model cannot tell them apart, so it reports the sum. That is why the coverage was fine and the split was not, and why \(\tau^2_w\) from a three-level fit should be read as an upper bound on true within-study heterogeneity rather than a measurement of it.

I-squared is where the comparability breaks in public. Higgins and Thompson’s definition needs a typical within-study sampling variance, and the answer depends entirely on what a unit is. The same eighty numbers give 78.2 per cent when the unit is the effect size, 85.5 per cent when the unit is the study, and in the three-level fit a between-study share of 64.4 per cent and a within-study share of 14.7 per cent that add to 79.1. None of the three is wrong. They answer three different questions, and a review that lines up I-squared across syntheses which made different choices about the unit is comparing quantities that were never the same. Nakagawa and Santos set out the multilevel version for ecology in 2012, and the practical consequence of their formulation is that an I-squared from a multilevel fit has to be reported with its level attached or not reported at all.

Cochran’s Q inherits the same problem through its degrees of freedom. The naive Q is compared against a chi-squared distribution on 79 degrees of freedom, one per effect size, and the effective count established above is 25.9. A significant Q on eighty clumped effects is not evidence of the same strength as a significant Q on eighty independent ones.

Two panels side by side on warm off-white paper, one for the between-study variance component and one for the within-study component. Each panel holds two overlapping density curves, a dark green one for independent sampling errors and a red one for correlated sampling errors, with a vertical dashed line at the true value. In the left panel the two curves almost coincide, with the red one displaced a little to the right. In the right panel the red curve is taller and sits clearly to the left of both the green curve and the dashed line.
Figure 6: Three-level variance components over twelve hundred replicates, fitted to data whose sampling errors within a study are independent and to data whose sampling errors are correlated within a study. Dashed lines mark the true values. With correlated sampling errors the between-study component shifts up and the within-study component shifts down by almost the same absolute amount, which is small next to the spread of the between-study estimates and large next to the spread of the within-study ones.

Which one to use

All three repairs landed on nominal and all three cost about the same width. The three-level standard error averaged 0.063, the clustered one 0.063 and the averaged one 0.0633, a spread of 0.37 per cent between the widest and the narrowest. Averaging throws away all the within-study information and still costs almost nothing, which is not a coincidence: at a correlation of 0.64 there was little independent information inside a study to throw away. The choice cannot be made on precision, then, and should be made on what else you want the model to do.

Fit the three-level model when the within-study spread is itself of interest, when moderators vary inside a study (species identity, response variable, year), or when you need a prediction interval that separates the two sources. Use clustered standard errors when you cannot defend a correlation structure and the number of studies is comfortable, which here meant 8 and above for an intercept-only fit and would mean more with moderators. Average within studies when the synthesis genuinely has one question per study, and do the covariance calculation anyway, because the pooled interval survives without it but the forest plot and the heterogeneity statistics do not. The last two combine in practice: fit the three-level model, then cluster the standard errors on top of it, so the structure you can defend does the work and the sandwich covers the part you cannot.

The honest limit

The correlations here were known because I generated them. In a real synthesis \(r\) is not reported and is not estimable from the published summaries either: the covariance between two effect sizes computed on the same plants depends on the correlation between the two response variables in the raw data, which nobody prints. The usual practice is to assume a value, commonly a half, and to check whether the conclusion moves when it is set to zero and to four fifths. The measurements above say what that check will find: the pooled interval will hardly move, because \(\tau^2\) absorbs the difference, while the per-study intervals and the heterogeneity split will move a lot. A sensitivity analysis that reports only the pooled mean looks reassuring for the wrong reason.

The design was balanced, four effects in every study, and real syntheses are not: one study reports one species and another reports eleven. Unbalanced cluster sizes make the effective degrees of freedom for the clustered interval fall below \(k-1\), sometimes far below, and they give the naive fit an extra way to go wrong, because a study with eleven effects gets eleven times the weight of a study with one while contributing barely more independent information. Nothing here measures that, and the correction is Tipton’s, not the plain \(k-1\) used above.

The three-level structure was also the true structure. Real dependence is not always nested: effects can share a research group across studies, a study system, a control group shared by several treatment arms, or a phylogeny, and the last of those needs a correlation matrix rather than a nesting factor. Van den Noortgate and colleagues showed in 2013 that the three-level model handles several of these sources adequately even when it is not literally correct, which is the licence most ecological syntheses are operating under, but the licence runs out when the sharing is not nested at all.

Finally, everything above concerns the interval and not the estimate. Every fit was unbiased for \(\mu\) to within 0.00060. Dependence does not move the pooled effect; it moves the claim about how well you know it, which makes it a quiet error: the number quoted in the abstract is correct and the number that decides whether anyone believes it is not.

Where to go next

The natural next step is a moderator. Once the effects inside a study differ by species or by response variable, the interesting questions become within-study comparisons, and those are estimated more precisely under clustering rather than less, because the study effect cancels in the contrast. That reverses the intuition built above. The second direction is the correlation matrix version: when the dependence is phylogenetic or comes from shared control groups the block is not compound symmetric, the Sherman-Morrison shortcut does not apply, and only the generalised least squares step survives unchanged.

References

Van den Noortgate W, Lopez-Lopez JA, Marin-Martinez F, Sanchez-Meca J 2013 Behavior Research Methods 45(2):576-594 (10.3758/s13428-012-0261-6)

Hedges LV, Tipton E, Johnson MC 2010 Research Synthesis Methods 1(1):39-65 (10.1002/jrsm.5)

Nakagawa S, Santos ESA 2012 Evolutionary Ecology 26(5):1253-1274 (10.1007/s10682-012-9555-5)

Konstantopoulos S 2011 Research Synthesis Methods 2(1):61-76 (10.1002/jrsm.35)

Cheung MWL 2014 Psychological Methods 19(2):211-229 (10.1037/a0032968)

Tipton E 2015 Psychological Methods 20(3):375-393 (10.1037/met0000011)

Higgins JPT, Thompson SG 2002 Statistics in Medicine 21(11):1539-1558 (10.1002/sim.1186)

Lajeunesse MJ 2011 Ecology 92(11):2049-2055 (10.1890/11-0423.1)

Pappalardo P, Song C, Hungate BA, Osenberg CW 2023 PLOS ONE 18(10):e0292606 (10.1371/journal.pone.0292606)

Newsletter

Get new tutorials by email

New R and QGIS tutorials for ecologists, straight to your inbox. No spam; unsubscribe anytime.

By subscribing you agree to receive these emails and confirm your address once. See the privacy policy.