A Gaussian analysis uses one aggregate estimate and its sampling
variance per row. The sample size ni is retained in the
fitted object and can be used by downstream code, while the Gaussian
weighted likelihood is driven by vi.
gaussian_dat <- data.frame(
study = paste0("Study ", 1:8),
estimate = c(-0.80, -0.30, 0.10, 0.70, 1.10, -0.50, 0.40, 1.30),
moderator = c(0, 1, 0, 1, 0, 1, 0, 1),
vi = c(0.04, 0.05, 0.03, 0.06, 0.04, 0.05, 0.03, 0.05),
ni = c(100, 90, 120, 80, 110, 95, 130, 85)
)
gaussian_dat
#> study estimate moderator vi ni
#> 1 Study 1 -0.8 0 0.04 100
#> 2 Study 2 -0.3 1 0.05 90
#> 3 Study 3 0.1 0 0.03 120
#> 4 Study 4 0.7 1 0.06 80
#> 5 Study 5 1.1 0 0.04 110
#> 6 Study 6 -0.5 1 0.05 95
#> 7 Study 7 0.4 0 0.03 130
#> 8 Study 8 1.3 1 0.05 85For a single-group meta-analysis, fit an intercept-only model. Each
input row is already one study estimate, so
as_metafor_data() can pass the study estimates and sampling
variances directly to forest().
gaussian_fit <- metaGLMM(
estimate ~ 1,
data = gaussian_dat,
vi = gaussian_dat$vi,
ni = gaussian_dat$ni,
tau2 = NA,
family = gaussian(link = "identity"),
tau2_var = TRUE,
fast = TRUE
)
summary(gaussian_fit)
#> Aggregate-data generalized linear mixed-effects meta-analysis
#>
#> Call:
#> metaGLMM(formula = estimate ~ 1, data = gaussian_dat, vi = gaussian_dat$vi,
#> ni = gaussian_dat$ni, tau2 = NA, family = gaussian(link = "identity"),
#> tau2_var = TRUE, fast = TRUE)
#>
#> Family:gaussian(identity)
#> Random-effect structure: row_intercept
#> Integration: closed_form
#>
#> Fixed effects:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) 0.2484 0.2502 0.993 0.321
#>
#> Heterogeneity:
#> tau^2: 0.4572
#> tau: 0.6762
#>
#> Plug-in prediction interval for a future underlying true effect:
#> fit lower upper
#> 0.2484 -1.165 1.662
#>
#> Fit diagnostics:
#> Optimizer: L-BFGS-B
#> Convergence code: 0
#> Boundary tau^2: no
#> Positive-definite Hessian: yes
coef(gaussian_fit)
#> (Intercept)
#> 0.248429
confint(gaussian_fit, method = "wald")
#> lower upper
#> (Intercept) -0.2419502 0.7388083
stopifnot(is.finite(gaussian_fit$tau), gaussian_fit$tau > 0)The study rows show the supplied Gaussian estimates and their sampling intervals. The lower rows compare the three supported fixed-effect intervals and the plug-in prediction interval.
gaussian_studies <- as_metafor_data(
gaussian_fit, labels = gaussian_dat$study
)
head(gaussian_studies)
#> yi vi slab
#> 1 -0.8 0.04 1
#> 2 -0.3 0.05 2
#> 3 0.1 0.03 3
#> 4 0.7 0.06 4
#> 5 1.1 0.04 5
#> 6 -0.5 0.05 6
forest(
gaussian_fit,
labels = gaussian_dat$study,
xlab = "Effect estimate",
ci_methods = c("Wald", "profile", "SBC")
)Setting tau2_var = FALSE with tau2 = 0
requests the exact no-random-effect model. Its fixed-effect estimates
can be compared with the weighted least squares reference in base R.
fixed_fit <- metaGLMM(
estimate ~ moderator,
data = gaussian_dat,
vi = gaussian_dat$vi,
ni = gaussian_dat$ni,
tau2 = 0,
tau2_var = FALSE,
family = gaussian(link = "identity"),
fast = TRUE
)
X <- model.matrix(~ moderator, data = gaussian_dat)
W <- sweep(X, 1, gaussian_dat$vi, "/")
beta_wls <- solve(crossprod(X, W),
crossprod(X, gaussian_dat$estimate / gaussian_dat$vi))
names(beta_wls) <- colnames(X)
cbind(metaGLMM = coef(fixed_fit), weighted_least_squares = beta_wls)
#> metaGLMM
#> (Intercept) 0.20714286 0.20714286
#> moderator 0.07546584 0.07546584This no-random-effect fit is an analytical reference only. The next section uses the same study summaries while estimating a non-zero between-study component.
Set tau2 = NA and tau2_var = TRUE to
estimate the non-negative between-study variance. tau is
its square root.
random_fit <- metaGLMM(
estimate ~ moderator,
data = gaussian_dat,
vi = gaussian_dat$vi,
ni = gaussian_dat$ni,
tau2 = NA,
tau2_var = TRUE,
family = gaussian(link = "identity"),
fast = TRUE
)
summary(random_fit)
#> Aggregate-data generalized linear mixed-effects meta-analysis
#>
#> Call:
#> metaGLMM(formula = estimate ~ moderator, data = gaussian_dat,
#> vi = gaussian_dat$vi, ni = gaussian_dat$ni, tau2 = NA, family = gaussian(link = "identity"),
#> tau2_var = TRUE, fast = TRUE)
#>
#> Family:gaussian(identity)
#> Random-effect structure: row_intercept
#> Integration: closed_form
#>
#> Fixed effects:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) 0.20051 0.34997 0.573 0.567
#> moderator 0.09754 0.49933 0.195 0.845
#>
#> Heterogeneity:
#> tau^2: 0.455
#> tau: 0.6745
#>
#> Fit diagnostics:
#> Optimizer: L-BFGS-B
#> Convergence code: 0
#> Boundary tau^2: no
#> Positive-definite Hessian: yes
random_fit$tau2
#> [1] 0.4549558
random_fit$tau
#> [1] 0.6745041
logLik(random_fit)
#> 'log Lik.' -1.231958 (df=3)
nobs(random_fit)
#> [1] 8
stopifnot(is.finite(random_fit$tau), random_fit$tau > 0)The alternative tau2_param = "log_tau2" uses an
unconstrained optimization parameterization while reporting
tau2 on its original scale. Both parameterizations expose
the same fixed-effect API.
The model matrix is the source of truth for coefficient names. This remains true for no-intercept models, factors, and interactions.
gaussian_dat$design <- factor(c("A", "A", "B", "B", "A", "B", "A", "B"))
interaction_fit <- metaGLMM(
estimate ~ 0 + design + moderator:design,
data = gaussian_dat,
vi = gaussian_dat$vi,
ni = gaussian_dat$ni,
tau2 = 0,
tau2_var = FALSE,
family = gaussian(link = "identity"),
fast = TRUE
)
colnames(model.matrix(interaction_fit))
#> [1] "designA" "designB" "designA:moderator"
#> [4] "designB:moderator"
names(coef(interaction_fit))
#> [1] "designA" "designB" "designA:moderator"
#> [4] "designB:moderator"