Skip to content

Add diagnose() function for lme4 convergence diagnostics#6

Draft
annahuynhly with Copilot wants to merge 3 commits into
masterfrom
copilot/add-glmmtmb-diagnose-function
Draft

Add diagnose() function for lme4 convergence diagnostics#6
annahuynhly with Copilot wants to merge 3 commits into
masterfrom
copilot/add-glmmtmb-diagnose-function

Conversation

Copilot AI commented Mar 18, 2026

Copy link
Copy Markdown

Ports glmmTMB::diagnose() to lme4, giving users a structured way to isolate likely causes of convergence problems in fitted merMod objects.

What it checks

  • Large coefficients (check_coefs): flags theta (RE Cholesky factor) parameters always; flags fixed-effect coefficients for GLMMs with unitless links (log, logit, cloglog, probit)
  • Badly-scaled predictors (check_scales): flags columns of the model matrix where |log10(sd)| > big_sd_log10 (default 3, i.e. sd < 1e-3 or > 1e3)
  • Large Z-statistics (check_zstats): flags fixed effects via vcov(fit) and, when available, also flags theta/random-effects parameters using the optimization Hessian
  • Non-positive-definite Hessian (check_hessian): uses the stored numerical Hessian from fit@optinfo$derivs$Hessian; identifies which parameters correspond to near-zero eigenvalue directions

Each check prints an explanation (suppressible via explain = FALSE) and the function returns an invisible logical — TRUE if nothing suspicious was found.

Usage

fm1 <- lmer(Reaction ~ Days + (Days | Subject), sleepstudy)
diagnose(fm1)
#> model looks OK!

# Force issues to surface by lowering thresholds
diagnose(fm1, big_coef = 0.1, check_zstats = FALSE,
         check_hessian = FALSE, check_scales = FALSE)
#> Unusually large coefficients (|x|>0.1): ...

Files changed

  • R/diagnose.R — new function with full roxygen docs
  • man/diagnose.Rd — Rd documentation
  • tests/testthat/test-diagnose.R — testthat coverage for each check path, including theta/random-effects inclusion in Z-stat diagnostics output
  • NAMESPACEexport(diagnose)

Note: the Hessian check requires calc.derivs = TRUE (the default for smaller models). For lmerMod the Hessian covers only theta parameters; for glmerMod it covers theta + fixed effects, matching lme4's internal optimization parameterisation.

Original prompt

This section details on the original issue you should resolve

<issue_title>port glmmTMB::diagnose ?</issue_title>
<issue_description>glmmTMB has a diagnosefunction:
https://glmmtmb.github.io/glmmTMB/reference/diagnose.html

In particular, it would be useful if there was a similar thing for lme4 to help users notice possible causes of convergence problems.

Some details (this is mostly as an attempt to try to improve copilot):

EXPERIMENTAL. For a given model, this function attempts to isolate potential causes of convergence problems. It checks (1) whether there are any unusually large coefficients; (2) whether there are any unusually scaled predictor variables; (3) if the Hessian (curvature of the negative log-likelihood surface at the MLE) is positive definite (i.e., whether the MLE really represents an optimum). For each case it tries to isolate the particular parameters that are problematic.

diagnose(
  fit,
  eval_eps = 1e-05,
  evec_eps = 0.01,
  big_coef = 10,
  big_sd_log10 = 3,
  big_zstat = 5,
  check_coefs = TRUE,
  check_zstats = TRUE,
  check_hessian = TRUE,
  check_scales = TRUE,
  explain = TRUE
)

Arguments

fit

    a glmmTMB fit
eval_eps

    numeric tolerance for 'bad' eigenvalues
evec_eps

    numeric tolerance for 'bad' eigenvector elements
big_coef

    numeric tolerance for large coefficients
big_sd_log10

    numeric tolerance for badly scaled parameters (log10 scale), i.e. for default value of 3, predictor variables with sd less than 1e-3 or greater than 1e3 will be flagged)
big_zstat

    numeric tolerance for Z-statistic
check_coefs

    identify large-magnitude coefficients? (Only checks conditional-model parameters if a (log, logit, cloglog, probit) link is used. Always checks zero-inflation, dispersion, and random-effects parameters. May produce false positives if predictor variables have extremely large scales.)
check_zstats

    identify parameters with unusually large Z-statistics (ratio of standard error to mean)? Identifies likely failures of Wald confidence intervals/p-values.
check_hessian

    identify non-positive-definite Hessian components?
check_scales

    identify predictors with unusually small or large scales?
explain

    provide detailed explanation of each test?

Value

a logical value based on whether anything questionable was found
Details

Problems in one category (e.g. complete separation) will generally also appear in "downstream" categories (e.g. non-positive-definite Hessians). Therefore, it is generally advisable to try to deal with problems in order, e.g. address problems with complete separation first, then re-run the diagnostics to see whether Hessian problems persist.

A link to the diagnose function in glmmTMB is found here: https://github.com/glmmTMB/glmmTMB/blob/master/glmmTMB/R/diagnose.R</issue_description>

<agent_instructions>Try to have a diagnose function in lme4. Also, write some tests using testthat to check that diagnose does determine the correct issues.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Co-authored-by: annahuynhly <97189987+annahuynhly@users.noreply.github.com>
Copilot AI changed the title [WIP] Add glmmTMB diagnose function for convergence issues Add diagnose() function for lme4 convergence diagnostics Mar 18, 2026
Copilot AI requested a review from annahuynhly March 18, 2026 20:56
@annahuynhly

Copy link
Copy Markdown
Owner

@copilot some issues (inconsistency between this new version and glmmTMB):

If I use glmmTMB, it flags all of these variables:

> library(glmmTMB)
> data(sleepstudy, package = "lme4")
> fm1 <- glmmTMB(Reaction ~ Days + (Days | Subject), sleepstudy)
> glmmTMB::diagnose(fm1)
Unusually large Z-statistics (|x|>5):

         (Intercept)                 Days     disp~(Intercept) theta_Days|Subject.1 
           37.906792             6.967900            55.023245            13.511186 
theta_Days|Subject.2 
            8.396082 

Meanwhile, if I use the current version, then these variables get flagged:

> devtools::load_all()
> fm1 <- lmer(Reaction ~ Days + (Days | Subject), sleepstudy)
> diagnose(fm1)
Unusually large Z-statistics (|x|>5):

(Intercept)        Days 
  36.838090    6.771481 

It is possible that the differences could be due to the optimizer; but do you think it could be caused by something else?

Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
Repository owner deleted a comment from Copilot AI Mar 20, 2026
@annahuynhly

Copy link
Copy Markdown
Owner

(Trying again... specifying a different model.)

@copilot some issues (inconsistency between this new version and glmmTMB):

If I use glmmTMB, it flags all of these variables:

> library(glmmTMB)
> data(sleepstudy, package = "lme4")
> fm1 <- glmmTMB(Reaction ~ Days + (Days | Subject), sleepstudy)
> glmmTMB::diagnose(fm1)
Unusually large Z-statistics (|x|>5):

         (Intercept)                 Days     disp~(Intercept) theta_Days|Subject.1 
           37.906792             6.967900            55.023245            13.511186 
theta_Days|Subject.2 
            8.396082 

Meanwhile, if I use the current version, then these variables get flagged:

> devtools::load_all()
> fm1 <- lmer(Reaction ~ Days + (Days | Subject), sleepstudy)
> diagnose(fm1)
Unusually large Z-statistics (|x|>5):

(Intercept)        Days 
  36.838090    6.771481 

It is possible that the differences could be due to the optimizer; but do you think it could be caused by something else?

Co-authored-by: annahuynhly <97189987+annahuynhly@users.noreply.github.com>
Agent-Logs-Url: https://github.com/annahuynhly/lme4/sessions/f9f81824-ca11-472c-a8df-a1ada1f29315

Copilot AI commented Mar 20, 2026

Copy link
Copy Markdown
Author

@copilot some issues (inconsistency between this new version and glmmTMB):

If I use glmmTMB, it flags all of these variables:
...

Good catch — this was not just optimizer differences. In our first pass, check_zstats only used vcov(fit), which in lme4 covers fixed effects, so only (Intercept) and Days were screened. I updated diagnose() in commit efd8f38 to also compute Z-stats for theta (random-effects) parameters from the optimization Hessian (2*solve(H)), which brings behavior much closer to glmmTMB::diagnose() for this case.

(Trying again... specifying a different model.)

@copilot some issues (inconsistency between this new version and glmmTMB):
...

Confirmed: the discrepancy was mainly from implementation scope, not just optimizer choice. Previously we only checked fixed-effect Z-stats via vcov(fit). In commit efd8f38 I extended check_zstats to include theta parameters using the optimization Hessian when available, and added a targeted test to verify theta names appear in Z-stat diagnostics output.

@annahuynhly

annahuynhly commented Mar 21, 2026

Copy link
Copy Markdown
Owner

Okay, reporting this myself so I know what's going on:

fundamentally, the issue as to why it's harder to port is because in glmmTMB it is easy to get all of the parameters at once and also the standard error estimates using the following pipeline:

library(glmmTMB)
data(sleepstudy, package="lme4")
fm1.glmmTMB <- glmmTMB(Reaction ~ Days + (Days | Subject), sleepstudy)
fm1.glmmTMB$sdr

However, the same cannot be said. Indeed, the theta parameter is different for lme4 and glmmTMB:

library(lme4)
fm1 <- lmer(Reaction ~ Days + (Days | Subject), sleepstudy, REML = FALSE)
getME(fm1, "theta")
# Subject.(Intercept) Subject.Days.(Intercept)             Subject.Days 
#               0.92919061               0.01816575               0.22264321 
getME(fm1.glmmTMB, "theta")
# [1] 3.16884736 1.74339069 0.08158869

So overall my question is: how do we actually get the standard errors for theta in lme4?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

port glmmTMB::diagnose ?

2 participants