Viechtbauer 2010 - Fitting Models with rma, Moderators and Knapp-Hartung
Summary
rma.uni()(aliasrma()) fits fixed, random and mixed-effects models from(yi, vi)or raw 2x2/continuous data, with moderators supplied as a matrix or an R formula. Wald tests use the normal/ distributions by default; the Knapp-Hartung adjustment (knha=TRUE) rescales the coefficient covariance for the uncertainty in and uses and reference distributions with degrees of freedom, bringing Type I error nearer nominal.
Overview
The call, with defaults from Sec. 3.3:
rma.uni(yi, vi, sei, ai, bi, ci, di, n1i, n2i, m1i, m2i, sd1i, sd2i, xi, mi, ri, ni, mods=NULL, data=NULL, intercept=TRUE, slab=NULL, subset=NULL, measure="GEN", add=1/2, to="only0", vtype="LS", method="REML", weighted=TRUE, level=95, digits=4, btt=NULL, tau2=NULL, knha=FALSE, control=list())
Main Content
Specifying data and model
- Data. Supply
yiwithvi(orsei) and keepmeasure="GEN", or pass theescalc()arguments and ameasure; effect sizes are then computed internally (Viechtbauer 2010 - Effect Size Measures and escalc). - Random effects:
rma(yi, vi, data=dat);methodpicks the estimator (default REML). - Fixed effects:
method="FE"; one must decide betweenweighted=TRUE(default) and unweighted least squares (Viechtbauer 2010 - Fixed, Random and Mixed-Effects Models). - Moderators:
modsaccepts a vector, a design matrix (cbind(mod1,mod2)) or a one-sided formula~ mod1 + mod2(interactions, polynomials,factor()); with a formula,interceptis ignored and- 1removes the intercept. subset,slab(study labels),tau2(fix ),control(algorithm).
Omnibus test and btt
Omnibus test of moderators
For a model with moderators, the default omnibus test is of (the intercept is excluded if present); its statistic is with the number of coefficients tested. Without an intercept the test includes all coefficients.
bttselects coefficient indices:btt=c(3,4)tests the third and fourth coefficients (with an intercept, the intercept is index 1).
Categorical moderators
Handled by dummy coding, manually or via factor(). Fitting separate random-effects models per subgroup (subset=) is possible but “not an ideal approach” because is then estimated within each level from fewer studies. A single mixed model with a dummy-coded factor is preferred; remove the intercept (~ factor(alloc) - 1) to read off one mean per level, and keep the intercept to test differences between levels (the omnibus is invariant to the reference level).
Knapp-Hartung adjustment
Knapp and Hartung (2003) adjustment
By default tests of individual coefficients use (normal), and the omnibus uses . With
knha=TRUEthe standard errors are adjusted to account for the uncertainty in ; coefficient tests and CIs use the -distribution with df and the omnibus test uses ( = number of coefficients including intercept). Only meant for random/mixed-effects models.Formula (Knapp and Hartung 2003; not printed in the metafor paper, but verified against its output below): with , and residuals ,
For the intercept-only case this is , the HKSJ variance (IntHout 2014 - HKSJ and DerSimonian-Laird Formal Specification).
Usually KH gives more conservative -values, though not guaranteed in a particular case; in general Type I error and CI coverage are closer to nominal.
Examples
Knapp-Hartung on BCG with allocation factor and latitude
Model
~ factor(alloc) + ablat, REML,knha=TRUE: (SE 0.1124), (), omnibus (); coefficients (intercept 0.2932, random -0.2675, systematic 0.0585, ablat -0.0273) have -based SEs 0.4188, 0.3624, 0.3925, 0.0095 and , on 9 df. Independent Python reproduction (using , which scales the covariance up, not down) gives identical numbers; see Viechtbauer 2010 - BCG Vaccine Worked Example.
import numpy as np
from scipy import optimize, stats
def meta_reg(y, v, X, knha=True):
k, p = X.shape
def nll(t): # REML negative log-likelihood
W = 1/(v+t); A = X.T@(W[:,None]*X); b = np.linalg.solve(A, X.T@(W*y)); r = y-X@b
return .5*(np.log(v+t).sum() + np.linalg.slogdet(A)[1] + (W*r**2).sum())
t = optimize.minimize_scalar(nll, bounds=(0,5), method="bounded").x
W = 1/(v+t); cov = np.linalg.inv(X.T@(W[:,None]*X)); b = cov@X.T@(W*y); r = y-X@b
if knha: cov = cov*(W*r**2).sum()/(k-p)
se = np.sqrt(np.diag(cov)); stat = b/se
pv = 2*stats.t.sf(abs(stat), k-p) if knha else 2*stats.norm.sf(abs(stat))
return t, b, se, pvConnections
- For marketing meta-analysis, moderators (channel, geography, funnel stage, budget share) with few experiments quickly exhaust degrees of freedom; KH’s df makes this explicit, and the rule “decide the model before looking” mirrors Researcher Degrees of Freedom.
- The same hierarchical logic with proper uncertainty in appears in Roever 2020 - Marginal Likelihood and Conditional Posteriors.