Skip to content

Commit

Permalink
checking diagonal elements of a covariance matrix is insufficient to …
Browse files Browse the repository at this point in the history
…diagnose singularity (#882)

* warnings/approximation of observation-level variance in .get_variance_distributional
Fixes #877

* fix test
  • Loading branch information
strengejacke authored Jun 12, 2024
1 parent cb70583 commit bd78d2c
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 72 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Type: Package
Package: insight
Title: Easy Access to Model Information for Various Model Objects
Version: 0.20.1
Version: 0.20.1.1
Authors@R:
c(person(given = "Daniel",
family = "Lüdecke",
Expand Down
9 changes: 6 additions & 3 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# insight 0.20.2

## General

* Improved accuracy of singularity-checks in `get_variance()`.

# insight 0.20.1

## Bug fixes
Expand All @@ -21,9 +27,6 @@

* Fixed errors in CRAN checks.

* Fixed issues in `compact_list()` for objects that contained variables of
class `vctrs`.

# insight 0.19.11

## General
Expand Down
5 changes: 4 additions & 1 deletion R/compute_variances.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
## Major revisions and adaption to more complex models and other packages
## by Daniel Lüdecke

# needed for singularity check
check_if_installed("performance", reason = "to check for singularity")

faminfo <- model_info(x, verbose = FALSE)

if (any(faminfo$family == "truncated_nbinom1")) {
Expand All @@ -36,7 +39,7 @@

# Test for non-zero random effects ((near) singularity)
no_random_variance <- FALSE
if (.is_singular(x, vals, tolerance = tolerance) && !(component %in% c("slope", "intercept"))) {
if (performance::check_singularity(x, tolerance = tolerance) && !(component %in% c("slope", "intercept"))) {
if (verbose) {
format_warning(
sprintf("Can't compute %s. Some variance components equal zero. Your model may suffer from singularity (see `?lme4::isSingular` and `?performance::check_singularity`).", name_full),

Check warning on line 45 in R/compute_variances.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/compute_variances.R,line=45,col=121,[line_length_linter] Lines should not be more than 120 characters. This line is 189 characters.
Expand Down
41 changes: 0 additions & 41 deletions R/helper_functions.R
Original file line number Diff line number Diff line change
Expand Up @@ -300,47 +300,6 @@




# checks if a mixed model fit is singular or not. Need own function,
# because lme4::isSingular() does not work with glmmTMB
.is_singular <- function(x, vals, tolerance = 1e-5) {
check_if_installed("lme4", reason = "to compute variances for mixed models")

tryCatch(
{
if (inherits(x, "glmmTMB")) {
eigen_values <- list()
for (component in c("cond", "zi")) {
for (i in seq_along(vals$vc[[component]])) {
eigen_values <- c(eigen_values, list(eigen(vals$vc[[component]][[i]], only.values = TRUE)$values))
}
}
is_si <- any(vapply(eigen_values, min, numeric(1), na.rm = TRUE) < tolerance)
} else if (inherits(x, c("clmm", "cpglmm"))) {
is_si <- any(sapply(vals$vc, function(.x) any(abs(diag(.x)) < tolerance)))
} else if (inherits(x, "merMod")) {
theta <- lme4::getME(x, "theta")
diag.element <- lme4::getME(x, "lower") == 0
is_si <- any(abs(theta[diag.element]) < tolerance)
} else if (inherits(x, "MixMod")) {
vc <- diag(x$D)
is_si <- any(sapply(vc, function(.x) any(abs(.x) < tolerance)))
} else if (inherits(x, "lme")) {
is_si <- any(abs(stats::na.omit(as.numeric(diag(vals$vc))) < tolerance))
} else {
is_si <- FALSE
}

is_si
},
error = function(x) {
FALSE
}
)
}



# Filter parameters from Stan-model fits
.filter_pars <- function(l, parameters = NULL, is_mv = NULL) {
if (!is.null(parameters)) {
Expand Down
52 changes: 26 additions & 26 deletions tests/testthat/test-rlmer.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ skip_if_not(getRversion() >= "4.1.0")
data(sleepstudy, package = "lme4")

set.seed(123)
sleepstudy$mygrp <- sample(1:5, size = 180, replace = TRUE)
sleepstudy$mygrp <- sample.int(5, size = 180, replace = TRUE)
sleepstudy$mysubgrp <- NA
for (i in 1:5) {
filter_group <- sleepstudy$mygrp == i
sleepstudy$mysubgrp[filter_group] <-
sample(1:30,
sample.int(30,
size = sum(filter_group),
replace = TRUE
)
Expand Down Expand Up @@ -121,18 +121,12 @@ test_that("link_inverse", {
})

test_that("get_data", {
expect_identical(colnames(get_data(m1)), c("Reaction", "Days", "Subject"))
expect_identical(colnames(get_data(m1, effects = "all")), c("Reaction", "Days", "Subject"))
expect_identical(colnames(get_data(m1, effects = "random")), "Subject")
expect_identical(
colnames(get_data(m2)),
c("Reaction", "Days", "mysubgrp", "mygrp", "Subject")
)
expect_identical(
colnames(get_data(m2, effects = "all")),
c("Reaction", "Days", "mysubgrp", "mygrp", "Subject")
)
expect_identical(colnames(get_data(m2, effects = "random")), c("mysubgrp", "mygrp", "Subject"))
expect_named(get_data(m1), c("Reaction", "Days", "Subject"))
expect_named(get_data(m1, effects = "all"), c("Reaction", "Days", "Subject"))
expect_named(get_data(m1, effects = "random"), "Subject")
expect_named(get_data(m2), c("Reaction", "Days", "mysubgrp", "mygrp", "Subject"))
expect_named(get_data(m2, effects = "all"), c("Reaction", "Days", "mysubgrp", "mygrp", "Subject"))
expect_named(get_data(m2, effects = "random"), c("mysubgrp", "mygrp", "Subject"))
})

test_that("find_formula", {
Expand Down Expand Up @@ -203,16 +197,13 @@ test_that("get_response", {
})

test_that("get_predictors", {
expect_identical(colnames(get_predictors(m1)), "Days")
expect_identical(colnames(get_predictors(m2)), "Days")
expect_named(get_predictors(m1), "Days")
expect_named(get_predictors(m2), "Days")
})

test_that("get_random", {
expect_identical(colnames(get_random(m1)), "Subject")
expect_identical(
colnames(get_random(m2)),
c("mysubgrp", "mygrp", "Subject")
)
expect_named(get_random(m1), "Subject")
expect_named(get_random(m2), c("mysubgrp", "mygrp", "Subject"))
})

test_that("clean_names", {
Expand Down Expand Up @@ -279,13 +270,22 @@ test_that("get_variance", {
tolerance = 1e-3
)

expect_warning(
{
out <- get_variance(m2)
},
regex = "Can't compute random effect"
)
expect_equal(
get_variance(m2),
out,
list(
var.fixed = 914.841369705921, var.random = 1406.78220075798,
var.residual = 809.318117542254, var.distribution = 809.318117542254,
var.dispersion = 0,
var.intercept = c(`mysubgrp:mygrp` = 0, Subject = 1390.66848960835, mygrp = 16.1137111496379)
var.fixed = 914.841369705924, var.residual = 809.318117542254,
var.distribution = 809.318117542254, var.dispersion = 0,
var.intercept = c(
`mysubgrp:mygrp` = 0,
Subject = 1390.66848960834,
mygrp = 16.1137111496375
)
),
tolerance = 1e-3
)
Expand Down

0 comments on commit bd78d2c

Please sign in to comment.