Skip to content

Commit 9d7d968

Browse files
authored
improved check_singularity (#801)
1 parent f1ffa8c commit 9d7d968

File tree

6 files changed

+99
-22
lines changed

6 files changed

+99
-22
lines changed

DESCRIPTION

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Type: Package
22
Package: performance
33
Title: Assessment of Regression Models Performance
4-
Version: 0.13.0
4+
Version: 0.13.0.1
55
Authors@R:
66
c(person(given = "Daniel",
77
family = "Lüdecke",
@@ -137,6 +137,7 @@ Suggests:
137137
qqplotr (>= 0.0.6),
138138
randomForest,
139139
RcppEigen,
140+
reformulas,
140141
rempsyc,
141142
rmarkdown,
142143
rstanarm,

NEWS.md

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# performance (devel)
2+
3+
## Changes
4+
5+
* Singularity checks with `check_singularity()` are now more efficient and also
6+
include the random effects for the dispersion component (from package *glmmTMB*).
7+
Furthermore, a `check` argument allows to check for general singularity (for
8+
the full model), or can return singularity checks for each random effects term
9+
separately.
10+
111
# performance 0.13.0
212

313
## Breaking changes

R/check_singularity.R

+52-21
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
#'
66
#' @param x A mixed model.
77
#' @param tolerance Indicates up to which value the convergence result is
8-
#' accepted. The larger `tolerance` is, the stricter the test
9-
#' will be.
8+
#' accepted. The larger `tolerance` is, the stricter the test
9+
#' will be.
10+
#' @param check Indicates whether singularity check should be carried out for
11+
#' the full model (`"model"`, the default), or per random effects term (`"terms"`).
1012
#' @param ... Currently not used.
1113
#'
1214
#' @return `TRUE` if the model fit is singular.
@@ -95,7 +97,10 @@
9597
#' Reaction ~ Days + (1 | mygrp / mysubgrp) + (1 | Subject),
9698
#' data = sleepstudy
9799
#' )
100+
#' # any singular fits?
98101
#' check_singularity(model)
102+
#' # singular fit for which particular random effects terms?
103+
#' check_singularity(model, check = "terms")
99104
#'
100105
#' \dontrun{
101106
#' # Fixing singularity issues using priors in glmmTMB
@@ -129,35 +134,61 @@ check_singularity <- function(x, tolerance = 1e-5, ...) {
129134

130135

131136
#' @export
132-
check_singularity.merMod <- function(x, tolerance = 1e-5, ...) {
133-
insight::check_if_installed("lme4")
134-
135-
theta <- lme4::getME(x, "theta")
136-
# diagonal elements are identifiable because they are fitted
137-
# with a lower bound of zero ...
138-
diag.element <- lme4::getME(x, "lower") == 0
139-
any(abs(theta[diag.element]) < tolerance)
137+
check_singularity.merMod <- function(x, tolerance = 1e-5, check = "model", ...) {
138+
insight::check_if_installed(c("lme4", "reformulas"))
139+
140+
check <- insight::validate_argument(check, c("model", "terms"))
141+
result <- list()
142+
vv <- lme4::VarCorr(x)
143+
144+
re_names <- vapply(
145+
reformulas::findbars(stats::formula(x)),
146+
insight::safe_deparse,
147+
FUN.VALUE = character(1)
148+
)
149+
result <- vapply(
150+
vv,
151+
function(x) det(x) < tolerance,
152+
FUN.VALUE = logical(1)
153+
)
154+
155+
switch(check,
156+
model = any(unlist(result, use.names = FALSE)),
157+
insight::compact_list(result)
158+
)
140159
}
141160

142161
#' @export
143162
check_singularity.rlmerMod <- check_singularity.merMod
144163

145164

165+
#' @rdname check_singularity
146166
#' @export
147-
check_singularity.glmmTMB <- function(x, tolerance = 1e-5, ...) {
148-
insight::check_if_installed("lme4")
167+
check_singularity.glmmTMB <- function(x, tolerance = 1e-5, check = "model", ...) {
168+
insight::check_if_installed(c("lme4", "reformulas"))
149169

150-
eigen_values <- list()
170+
check <- insight::validate_argument(check, c("model", "terms"))
171+
result <- list()
151172
vv <- lme4::VarCorr(x)
152-
for (component in c("cond", "zi")) {
153-
for (i in seq_along(vv[[component]])) {
154-
eigen_values <- c(
155-
eigen_values,
156-
list(eigen(vv[[component]][[i]], only.values = TRUE)$values)
157-
)
158-
}
173+
174+
for (component in c("cond", "zi", "disp")) {
175+
re_names <- vapply(
176+
reformulas::findbars(stats::formula(x, component = component)),
177+
insight::safe_deparse,
178+
FUN.VALUE = character(1)
179+
)
180+
result[[component]] <- vapply(
181+
vv[[component]],
182+
function(x) det(x) < tolerance,
183+
FUN.VALUE = logical(1)
184+
)
185+
names(result[[component]]) <- re_names
159186
}
160-
any(vapply(eigen_values, min, numeric(1), na.rm = TRUE) < tolerance)
187+
188+
switch(check,
189+
model = any(unlist(result, use.names = FALSE)),
190+
insight::compact_list(result)
191+
)
161192
}
162193

163194

inst/WORDLIST

+1
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ brms
261261
brmsfit
262262
cauchy
263263
clusterable
264+
codecov
264265
confounder
265266
confounders
266267
concurvity

man/check_singularity.Rd

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-check_singularity.R

+25
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ test_that("check_singularity, lme4", {
1616
data = sleepstudy
1717
))
1818
expect_true(check_singularity(model))
19+
20+
# term-wise
21+
out <- check_singularity(model, check = "terms")
22+
expect_identical(
23+
out,
24+
c(`mysubgrp:mygrp` = TRUE, Subject = FALSE, mygrp = FALSE)
25+
)
1926
})
2027

2128

@@ -36,4 +43,22 @@ test_that("check_singularity", {
3643
m2 <- glmmTMB::glmmTMB(y ~ 1 + (x | f), data = dd, REML = FALSE)
3744
}))
3845
expect_true(check_singularity(m2))
46+
47+
data(Salamanders, package = "glmmTMB")
48+
m <- glmmTMB::glmmTMB(
49+
count ~ spp + mined + (1 | site),
50+
data = Salamanders[Salamanders$count > 0, , drop = FALSE],
51+
family = glmmTMB::truncated_nbinom2(),
52+
ziformula = ~ spp + (1 | site),
53+
dispformula = ~ spp + (1 | site)
54+
)
55+
out <- check_singularity(m, check = "terms")
56+
expect_identical(
57+
out,
58+
list(
59+
cond = c(`1 | site` = TRUE),
60+
zi = c(`1 | site` = TRUE),
61+
disp = c(`1 | site` = TRUE)
62+
)
63+
)
3964
})

0 commit comments

Comments
 (0)