Skip to contents

This is the human-readable diagnostic to call right after fit <- gllvmTMB(...). It combines the quick numerical screen (sanity_multi()), the rotation identifiability advisory, and key biological summaries (correlation diagonals, ICCs, communalities) into a single report with explicit next-step hints for any WARN signal. Use check_gllvmTMB() when you need the same fit-health checks as a stable table for scripts or reports.

Usage

gllvmTMB_diagnose(
  object,
  gradient_thresh = 0.01,
  se_thresh = 100,
  big_corr_thresh = 0.5,
  verbose = TRUE
)

Arguments

object

A fit returned by gllvmTMB().

gradient_thresh, se_thresh

Forwarded to sanity_multi().

big_corr_thresh

Threshold above which a Sigma_B correlation off-diagonal is flagged as worth highlighting. Default 0.5.

verbose

If TRUE (default), prints the report. Always returns the structured list invisibly.

Value

Invisibly a list with components: sanity (the sanity_multi() flags), rotation (rotation-advisory list), Sigma_B, Sigma_W, ICC_site, communality_B, communality_W, and hints (character vector of suggested actions).

Details

Scope: first-line convergence, Hessian, standard-error, restart, and rotation diagnostics. It reports risks and summaries but does not replace profile, bootstrap, or simulation calibration. Target-explicit validation will decide which interval warnings can be promoted to broader guarantees.

Examples

set.seed(1)
# Eight traits on 100 units, each unit measured twice. Odd-numbered
# traits share one latent axis, even-numbered traits share the other.
Lambda <- cbind(c(0.9, 0, 0.8, 0, 0.7, 0, 0.6, 0),
                c(0, 0.9, 0, 0.8, 0, 0.7, 0, 0.6))
sim <- simulate_unit_trait(n_units = 100, n_obs_per_unit = 2,
                           n_traits = 8, Lambda_B = Lambda,
                           psi_B = 0.3, sigma2_eps = 0.3)
fit <- gllvmTMB(
  value ~ 0 + trait + latent(0 + trait | unit, d = 2, unique = TRUE),
  data = sim$data, trait = "trait", unit = "unit"
)

# The printed report has four parts: optimiser checks, rotation,
# biological summaries (variances, strong trait correlations and
# communalities) and suggested next steps.
dx <- gllvmTMB_diagnose(fit)
#> 
#> ── 1. Optimiser & numerical sanity ──
#> 
#> Optimiser convergence (== 0):                PASS
#> Max |gradient| < 1.0e-02:                    PASS (max |gr| = 0.00115)
#> Hessian positive-definite:                   PASS
#> Max fixed-effect SE < 100:                   PASS (max SE = 0.112)
#> latent(unit, d=2) diag loadings non-zero:    PASS (min |Lambda_B diag| = 0.763)
#> ── 2. Rotational identifiability ──
#> 
#> ! `Lambda_B` (rank d = 2) is identified only up to rotation.
#> 
#> ── 3. Biological summaries ──
#> 
#> 
#>   Sigma_B (between-unit covariance) diagonal and correlations:
#>   0.952  1.071  1.087  0.812  0.818  0.794  0.666  0.535 
#>   12 trait pair(s) with |corr| > 0.50 (showing 8 of 12):
#>     trait_3 ~ trait_1 : +0.64
#>     trait_5 ~ trait_1 : +0.55
#>     trait_7 ~ trait_1 : +0.58
#>     trait_4 ~ trait_2 : +0.70
#>     trait_6 ~ trait_2 : +0.68
#>     trait_8 ~ trait_2 : +0.77
#>     trait_5 ~ trait_3 : +0.59
#>     trait_7 ~ trait_3 : +0.61
#> 
#>   Global communalities:
#>   0.612  0.825  0.703  0.591  0.506  0.56  0.55  0.712 
#> ── 4. Suggested next steps ──
#> 
#> • Lambda is identified only up to rotation. For a unique loading matrix, see
#>   `suggest_lambda_constraint()`. For interpretation, use `getLoadings(fit,
#>   rotate = "varimax")`. The implied Sigma matrices are rotation-invariant and
#>   need no constraint.

# The same results come back invisibly as a list, for use in scripts.
names(dx)
#> [1] "sanity"        "rotation"      "Sigma_B"       "Sigma_W"      
#> [5] "ICC_site"      "communality_B" "communality_W" "hints"        
dx$hints
#> [1] "Lambda is identified only up to rotation. For a unique loading matrix, see `suggest_lambda_constraint()`. For interpretation, use `getLoadings(fit, rotate = \"varimax\")`. The implied Sigma matrices are rotation-invariant and need no constraint."
round(dx$Sigma_B$R_B, 2)
#>         trait_1 trait_2 trait_3 trait_4 trait_5 trait_6 trait_7 trait_8
#> trait_1    1.00    0.00    0.64   -0.04    0.55    0.04    0.58   -0.03
#> trait_2    0.00    1.00    0.16    0.70    0.07    0.68    0.04    0.77
#> trait_3    0.64    0.16    1.00    0.09    0.59    0.17    0.61    0.12
#> trait_4   -0.04    0.70    0.09    1.00    0.02    0.57   -0.01    0.65
#> trait_5    0.55    0.07    0.59    0.02    1.00    0.09    0.53    0.04
#> trait_6    0.04    0.68    0.17    0.57    0.09    1.00    0.07    0.63
#> trait_7    0.58    0.04    0.61   -0.01    0.53    0.07    1.00    0.01
#> trait_8   -0.03    0.77    0.12    0.65    0.04    0.63    0.01    1.00
dx$communality_B
#>   trait_1   trait_2   trait_3   trait_4   trait_5   trait_6   trait_7   trait_8 
#> 0.6117654 0.8250612 0.7025898 0.5906170 0.5058653 0.5602779 0.5503689 0.7116746 

# verbose = FALSE skips the printed report; the numbers behind the
# optimiser checks are in $sanity.
quiet <- gllvmTMB_diagnose(fit, verbose = FALSE)
str(quiet$sanity)
#> List of 6
#>  $ converged       : logi TRUE
#>  $ max_gradient    : num 0.00115
#>  $ sdreport_ok     : logi TRUE
#>  $ pd_hessian      : logi TRUE
#>  $ max_se          : num 0.112
#>  $ rr_B_min_loading: num 0.763