
Wald confidence intervals on augmented random-slope standard deviations
Source:R/slope-sd-ci.R
slope_sd_ci.RdPer-trait confidence intervals on the standard deviation of an augmented random-slope term. All intervals are delta-method Wald intervals built on the log-SD scale so the bounds stay positive by construction. Three underlying routes are covered, dispatched automatically from the fit's structure:
The ordinary augmented diagonal (Psi) route (
theta_diag_B_slope, fromlatent(0 + trait + (0 + trait):x | unit, d = K)orunique(...)).theta_diag_B_slopeis a genuine univariate log-SD (sd = exp(theta)), so the interval is read directly offsdreport()'scov.fixed– no Jacobian.The phylogenetic Cholesky augmented-slope route (
theta_dep_chol, fromphylo_dep(1 + x1 + ... + xs | species)/phylo_indep(1 + x | species)). The marginal slope SD is a nonlinear function of several packed Cholesky entries (Var(slope_t) = L21_t^2 + exp(diag_slope_t)^2for a single slope), so its standard error comes from anADREPORT()ed quantity in the C++ template (sd_b), lettingsdreport()run the multivariate delta method against the authoritative packing.The loadings-only augmented random-slope route (
theta_rr_B_slopewith no diagonal companion, e.g.latent(..., unique = FALSE)). The marginal slope variance is a quadratic form in several loading entries (Lambda_B_slope \%*\% t(Lambda_B_slope)), again handled via anADREPORT()ed quantity (sd_rr_B_slope).
Usage
slope_sd_ci(fit, level = 0.95, scale = c("sd", "variance"))Arguments
- fit
A multi-trait
gllvmTMB()fit with at least one augmented random-slope structure covered above.- level
Confidence level. Defaults to 0.95.
- scale
"sd"(default) returns the standard deviation scale;"variance"returns the variance scale (estimate^2, boundsexp(2 * (theta +/- z * se(theta)))). Both scales are computed from the identicaltheta/se_theta, so they agree by construction.total_sdis always reported on the SD scale regardless ofscale;total_lower/total_upperfollowscalelikelower/upper.
Value
A data frame of class gllvmTMB_slope_ci, one row per slope SD
(trait x slope term), with columns:
traitTrait factor level.
termThe slope covariate name.
estimate,lower,upperOn the requested
scale.lower/upperareNAwheneverstatus != "ok".component"unique_psi"when the diagonal route's fit also carries a shared loadings block, soestimateis only part of the marginal slope SD;"total"otherwise, meaningestimatealready is the total marginal slope SD.total_sdPoint estimate of the TOTAL marginal slope SD, on the SD scale regardless of
scale. Equal toestimatewhencomponent == "total"andscale = "sd".total_lower,total_upperInterval on
total_sd, on the requestedscale.NAwhenevertotal_status != "ok"or the supporting ADREPORT is unavailable.total_statusKill-switch status for the
total_sdinterval; see below."unavailable"when no supporting ADREPORT exists (only possible for the diagonal route on a stale cached fit).theta,se_thetaThe log-SD estimate and its standard error, for auditability. For the diagonal route these are the raw fixed-effect parameter and its
cov.fixedSE (unchanged from slice 1); for the phylogenetic and loadings-only routes these are derived from an ADREPORTed natural-scale quantity via the delta methodse(log(X)) = se(X) / X.method"wald_log_scale"for the diagonal route (direct parameter read),"wald_log_scale_adreport"for the phylogenetic and loadings-only routes (ADREPORT delta method).interval_statusAlways
"wald_uncalibrated".status"ok","no_pd_hessian","se_nonfinite","se_blowup", or"near_zero_relative"– see Kill-switch guard below.scaleThe requested scale,
"sd"or"variance".
The returned object also carries a hard-coded attribute
calibrated = FALSE (never an argument; only a future coverage
certificate can flip this in the source), and the requested level.
Scope boundary – what this interval IS and IS NOT
Every interval returned by this function is a transformed Wald
interval read off sdreport(), the same inferential instrument as
loading_ci() and confint_inspect(): it uses the asymptotic normal
approximation and propagates it through a monotone log/exp transform.
It is not a calibrated coverage statement – no repeated-sampling
coverage campaign has been run for any of these estimands, and
interval_status = "wald_uncalibrated" marks every row as such. The
calculation is tested; its repeated-sampling coverage is not.
When the fit's random-slope term carries BOTH a diagonal Psi companion
and a shared loadings component (theta_rr_B_slope, the default
alongside theta_diag_B_slope for ordinary latent()), estimate for
that route is the per-trait UNIQUE (Psi) component of slope variance
only – it excludes the shared loadings contribution to the marginal
slope variance, and can understate the total marginal slope SD
substantially (measured up to ~45% in a recovery simulation). This
restriction travels with the data, not only the print method: the
component column reads "unique_psi" in that case ("total"
otherwise – including for the phylogenetic and loadings-only routes,
where the reported quantity already IS the total marginal slope SD).
total_sd always reports the total marginal slope SD as a point
estimate. total_lower / total_upper report a genuine interval on
that total wherever the C++ template ADREPORT()s the combined
expression directly (both routes as of this version); total_status
names the same kill-switch statuses as status when that support is
present, or "unavailable" when it is not (e.g. a fit refit under an
older package version whose ADREPORT does not yet exist). A
cli::cli_warn() fires on the call itself whenever the shared loadings
block is present alongside the diagonal route, not only when the result
is printed.
Kill-switch guard – point estimates always, intervals never from a fit that cannot support one
Applied identically to every route. lower/upper (and, where
supported, total_lower/total_upper) are set to NA (with status
explaining why and a cli::cli_warn()) whenever any of:
fit$sd_report$pdHessisFALSE(status = "no_pd_hessian", applies to every row);the log-SD estimate or its standard error is non-finite (
status = "se_nonfinite") – for the ADREPORT-based routes this also covers a non-positive natural-scale estimate, which cannot be logged;se_theta > 10on the log scale – essentially information-free (an SE of 10 on the log scale means the interval would span \(e^{40}\));status = "se_blowup";sd_hatis at most 1% of the largestsd_hatamong this fit's OTHER slope coordinates on the SAME route (only evaluated when there is more than one to compare against);status = "near_zero_relative", mirroring the package's relative-to-siblings convention for a collapsed variance component (psi_rel_thresh/near_zero_psi_*).
The point estimate (estimate, theta) is always returned. A
non-positive-definite Hessian disqualifies standard errors, but not
point estimates.
See also
loading_ci() for the equivalent interval on Lambda entries;
extract_Sigma() with level = "unit_slope" or level = "phy" for
the full augmented covariance (point estimates only).
Examples
set.seed(1)
# Two traits (t1, t2) measured on five occasions for each of 60
# individuals. An environmental covariate x is recorded once per
# occasion and applies to both traits.
n_ind <- 60
dat <- expand.grid(occasion = 1:5, individual = factor(1:n_ind),
trait = factor(c("t1", "t2")))
dat$x <- rep(rnorm(5 * n_ind), times = 2)
# Each individual has its own intercept and its own slope on x for each
# trait, in the order (t1 intercept, t1 slope, t2 intercept, t2 slope).
# Two latent axes generate them: the first shifts both intercepts, the
# second shifts both slopes. The true slope SDs are 0.5 and 0.4.
Lambda <- cbind(c(0.8, 0, 0.6, 0), c(0, 0.5, 0, 0.4))
coefs <- matrix(rnorm(2 * n_ind), n_ind) %*% t(Lambda)
ind <- as.integer(dat$individual)
tr <- as.integer(dat$trait)
dat$value <- c(0.2, -0.1)[tr] + c(0.3, -0.2)[tr] * dat$x +
coefs[cbind(ind, 2 * tr - 1)] + coefs[cbind(ind, 2 * tr)] * dat$x +
rnorm(nrow(dat), sd = 0.3)
# Random intercepts and slopes for both traits. Their covariance is
# built from two latent axes; unique = FALSE adds no extra per-trait
# variance on top of the axes.
fit <- gllvmTMB(
value ~ 0 + trait + (0 + trait):x +
latent(0 + trait + (0 + trait):x | individual, d = 2,
unique = FALSE),
data = dat, trait = "trait", unit = "individual"
)
# 95% Wald intervals for how much individuals differ in their slopes,
# one row per trait, on the standard-deviation scale.
ci <- slope_sd_ci(fit)
ci[, c("trait", "term", "estimate", "lower", "upper")]
#> Wald (log-SD-scale) intervals on augmented random-slope standard
#> deviations. These are recovery-only, UNCALIBRATED intervals --
#> repeated-sampling coverage has not been measured for this estimand,
#> for any family. Consult the package's current limitations for
#> the applicable validation boundary.
#>
#> trait term estimate lower upper
#> 1 t1 x 0.5029887 0.4106722 0.6160573
#> 2 t2 x 0.3871676 0.3129357 0.4790081
# The same quantity as 90% intervals on the variance scale.
ci_var <- slope_sd_ci(fit, level = 0.90, scale = "variance")
ci_var[, c("trait", "term", "estimate", "lower", "upper")]
#> Wald (log-SD-scale) intervals on augmented random-slope standard
#> deviations. These are recovery-only, UNCALIBRATED intervals --
#> repeated-sampling coverage has not been measured for this estimand,
#> for any family. Consult the package's current limitations for
#> the applicable validation boundary.
#>
#> trait term estimate lower upper
#> 1 t1 x 0.2529976 0.1800143 0.3555706
#> 2 t2 x 0.1498987 0.1048662 0.2142696