metaGLMM_family() defines an R-level conditional
likelihood. Supply a name, a link accepted by
stats::make.link(), and a vectorized callback with
arguments (y, eta, vi, ni). The callback returns one finite
contribution for every value of eta. Optional
validate and initialize hooks can add
family-specific checks or deterministic starting values.
The example below defines a negative-binomial likelihood for
overdispersed study counts. Each response is an observed rate
y, ni is its exposure, and y * ni
recovers the count. The conditional count mean is
exp(eta) * ni. A fixed negative-binomial size is captured
by the callback; the between-study log-rate variance remains the
tau2 estimated by metaGLMM().
nb_size <- 8
negative_binomial_rate <- metaGLMM_family(
name = "negative-binomial-rate",
link = "log",
loglik = function(y, eta, vi, ni) {
count <- round(y * ni)
dnbinom(
x = count,
size = nb_size,
mu = exp(eta) * ni,
log = TRUE
)
},
validate = function(y, vi, ni) {
count <- y * ni
if (any(y < 0) || any(abs(count - round(count)) > 1e-7)) {
stop("y * ni must define non-negative integer counts.")
}
if (any(!is.finite(vi)) || any(vi <= 0)) {
stop("vi must contain finite positive display variances.")
}
TRUE
},
initialize = function(y, X, vi, ni) {
value <- rep(0, ncol(X))
intercept <- match("(Intercept)", colnames(X))
if (!is.na(intercept)) {
value[intercept] <- log(sum(y * ni) / sum(ni))
}
value
}
)
negative_binomial_rate
#>
#> Family: negative-binomial-rate
#> Link function: logThe loglik callback is vectorized in eta:
during QMC integration a single study count is evaluated at all
random-effect draws. The validation function checks the aggregate count
contract before optimization, and the initialization function supplies a
deterministic pooled log-rate start.
nb_dat <- data.frame(
study = paste0("Study ", 1:8),
events = c(8, 30, 5, 55, 18, 90, 9, 70),
exposure = c(20, 35, 18, 42, 30, 50, 25, 45)
)
nb_dat$rate <- nb_dat$events / nb_dat$exposure
nb_dat$vi <- 1 / pmax(0.5, nb_dat$events) + 1 / nb_size
nb_dat
#> study events exposure rate vi
#> 1 Study 1 8 20 0.4000000 0.2500000
#> 2 Study 2 30 35 0.8571429 0.1583333
#> 3 Study 3 5 18 0.2777778 0.3250000
#> 4 Study 4 55 42 1.3095238 0.1431818
#> 5 Study 5 18 30 0.6000000 0.1805556
#> 6 Study 6 90 50 1.8000000 0.1361111
#> 7 Study 7 9 25 0.3600000 0.2361111
#> 8 Study 8 70 45 1.5555556 0.1392857
qmc_points <- qnorm((seq_len(512) - 0.5) / 512)
nb_fit <- metaGLMM(
rate ~ 1,
data = nb_dat,
vi = nb_dat$vi,
ni = nb_dat$exposure,
tau2 = NA,
tau2_var = TRUE,
start_tau2 = 0.3,
family = negative_binomial_rate,
rstdnorm = qmc_points,
fast = FALSE
)
summary(nb_fit)
#> Aggregate-data generalized linear mixed-effects meta-analysis
#>
#> Call:
#> metaGLMM(formula = rate ~ 1, data = nb_dat, vi = nb_dat$vi, ni = nb_dat$exposure,
#> tau2 = NA, family = negative_binomial_rate, tau2_var = TRUE,
#> rstdnorm = qmc_points, fast = FALSE, start_tau2 = 0.3)
#>
#> Family:negative-binomial-rate(log)
#> Random-effect structure: row_intercept
#> Integration: qmc
#>
#> Fixed effects:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -0.2089 0.2377 -0.879 0.38
#>
#> Heterogeneity:
#> tau^2: 0.2522
#> tau: 0.5022
#>
#> Plug-in prediction interval for a future underlying true effect:
#> fit lower upper
#> -0.2089 -1.298 0.8802
#>
#> Fit diagnostics:
#> Optimizer: L-BFGS-B
#> Convergence code: 0
#> Boundary tau^2: no
#> Positive-definite Hessian: yes
exp(coef(nb_fit))
#> (Intercept)
#> 0.8114576
nb_ci <- list(
Wald = confint(nb_fit, method = "wald"),
Profile = confint(nb_fit, method = "profile"),
SBC = confint(nb_fit, method = "SBC")
)
do.call(rbind, nb_ci)
#> lower upper
#> (Intercept) -0.6748997 0.2570535
#> (Intercept) -0.7702812 0.2864727
#> (Intercept) -0.8377501 0.3449623
stopifnot(is.finite(nb_fit$tau), nb_fit$tau > 0)The exponentiated intercept is the pooled conditional rate. The
fitted tau2 describes additional between-study variation on
the log-rate scale, while nb_size controls
negative-binomial variation within the conditional count model. In this
interface nb_size is prespecified rather than estimated;
record its choice and consider sensitivity analyses when it is not
externally known.
Custom families support Wald, profile, and SBC intervals. The
simplified Bartlett correction uses vi and the
heterogeneity estimate under each profile constraint; for a custom
family, supply vi as an appropriate study-level sampling
variance on the link scale.
forest(
nb_fit,
labels = nb_dat$study,
type = "exp",
xlab = "Negative-binomial rate",
ci_methods = c("Wald", "profile", "SBC")
)The callback must return a finite numeric vector of the same length
as eta. Check response domains and variance conventions
before fitting; family validation errors are reported before numerical
optimization.
Custom families are supported through fast = FALSE in
this release. A request for fast = TRUE with a custom
family is rejected because compiled quadrature for arbitrary R callbacks
is not part of the interface.
try(
metaGLMM(
rate ~ 1,
data = nb_dat,
vi = nb_dat$vi,
ni = nb_dat$exposure,
tau2 = NA,
tau2_var = TRUE,
family = negative_binomial_rate,
fast = TRUE
)
)For production analyses, keep the callback definition, the QMC sequence, and the fitted object’s integration settings with the analysis record.