Skip to content

Navigation Menu

Sign in
Sign up

check_singularity.lme(): errors on models with more than one level of nesting; misplaced parenthesis #937

Open

Description

Two small, independent things in check_singularity.lme(). performance 0.17.1
(= CRAN, = main @ 50552a0), nlme 3.1-170, R 4.6.1.

1. Errors instead of answering, for ~ 1 | A/B

nlme::getVarCov.lme() refuses more than one grouping level
(if (length(obj$groups) > 1) stop("not implemented for multiple levels of nesting")),
so check_singularity() errors out rather than returning TRUE/FALSE:

data(Pixel, package = "nlme")
fit <- nlme::lme(pixel ~ day, random = ~ 1 | Dog / Side, data = Pixel)
nlme::VarCorr(fit) # works fine
#> Dog = pdLogChol(1)
#> (Intercept) 647.2931 25.44196
#> Side = pdLogChol(1)
#> (Intercept) 218.3420 14.77640
#> Residual 233.3463 15.27568
performance::check_singularity(fit)
#> Error in getVarCov.lme(x) : not implemented for multiple levels of nesting

Single-level lme models are fine, and the lmer equivalent
(pixel ~ day + (1 | Dog/Side)) returns FALSE normally.

fit$modelStruct$reStruct looks like the natural replacement: it returns one
block per nesting level, and the blocks are already relative to sigma^2,
so they are dimensionless:

b <- as.matrix(fit$modelStruct$reStruct)
b
#> $Dog
#> (Intercept)
#> (Intercept) 2.77396
#>
#> $Side
#> (Intercept)
#> (Intercept) 0.9356995
lapply(b, function(v) as.matrix(v) * fit$sigma^2) # reproduces VarCorr exactly
#> $Dog 647.2931
#> $Side 218.342

Since this errors rather than misreports, any caller inherits the error unless
it shields itself: insight:::.compute_variances() does, wrapping the call in
.safe(); a direct caller does not.

2. abs() wraps the comparison rather than the variances

L228 of R/check_singularity.R, identical in CRAN 0.17.1 and at HEAD:

any(abs(stats::na.omit(as.numeric(diag(nlme::getVarCov(x)))) < tolerance))

The closing parenthesis of abs() sits after < tolerance, so abs() receives
the logical vector, not the variances:

v <- c(5.4150876, 0.0512695)
v < 1e-5 #> FALSE FALSE
abs(v < 1e-5) #> 0 0 <- abs() of a logical
any(abs(v < 1e-5)) #> FALSE
any(abs(v) < 1e-5) #> FALSE <- presumably intended

It is essentially harmless — abs() of a logical is a 0/1 numeric and any()
coerces back — and the two forms can only differ for a negative variance, which
getVarCov() does not return. But the intent was clearly abs(diag(...)) < tolerance,
as in the sibling methods on L206, L214 and L220. (bbolker made a related remark
about a redundant abs() in the old .merMod line in easystats/insight#878.)

The whole fix is moving that parenthesis: any(abs(...) < tolerance).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      AltStyle によって変換されたページ (->オリジナル) /