Skip to content

Commit 3e8d506

Browse files
committed
allow residual type
1 parent 8f4fd19 commit 3e8d506

5 files changed

Lines changed: 106 additions & 40 deletions

File tree

R/check_model.R

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@
3434
#' `TRUE` for linear models or when `residual_type = "normal"`. Defaults to
3535
#' `FALSE` for QQ plots based on simulated residuals (i.e. when
3636
#' `residual_type = "simulated"`).
37-
#' @param residual_type Character, indicating the type of residuals to be used.
38-
#' For non-Gaussian models, the default is `"simulated"`, which uses simulated
39-
#' residuals. These are based on [`simulate_residuals()`] and thus uses the
40-
#' **DHARMa** package to return randomized quantile residuals. For Gaussian
41-
#' models, the default is `"normal"`, which uses the default residuals from
42-
#' the model. Setting `residual_type = "normal"` for non-Gaussian models will
43-
#' use a half-normal Q-Q plot of the absolute value of the standardized deviance
44-
#' residuals.
37+
#' @param residual_type Character, indicating the type of residuals to be used
38+
#' for QQ-plots and overdispersion tests. For non-Gaussian models, the default
39+
#' is `"simulated"`, which uses simulated residuals. These are based on
40+
#' [`simulate_residuals()`] and thus uses the **DHARMa** package to return
41+
#' randomized quantile residuals. For Gaussian models, the default is
42+
#' `"normal"`, which uses the default residuals from the model. Setting
43+
#' `residual_type = "normal"` for non-Gaussian models will use a half-normal Q-Q
44+
#' plot of the absolute value of the standardized deviance residuals.
4545
#' @param show_dots Logical, if `TRUE`, will show data points in the plot. Set
4646
#' to `FALSE` for models with many observations, if generating the plot is too
4747
#' time-consuming. By default, `show_dots = NULL`. In this case `check_model()`
@@ -269,6 +269,11 @@ check_model.default <- function(
269269

270270
minfo <- insight::model_info(model, verbose = FALSE)
271271

272+
# validate argument
273+
if (!is.null(residual_type)) {
274+
insight::validate_argument(residual_type, c("normal", "simulated"))
275+
}
276+
272277
# set default for residual_type
273278
if (is.null(residual_type)) {
274279
residual_type <- ifelse(minfo$is_linear && !minfo$is_gam, "normal", "simulated")
@@ -278,7 +283,11 @@ check_model.default <- function(
278283
# exceptions here as they appear, but for now, `check_model()` also
279284
# automatically falls back to normal Q-Q plot for all models not supported
280285
# by DHARMa
281-
if (minfo$family %in% c("quasipoisson", "quasibinomial")) {
286+
if (
287+
minfo$family %in%
288+
c("quasipoisson", "quasibinomial") ||
289+
!requireNamespace("DHARMa", quietly = TRUE)
290+
) {
282291
residual_type <- "normal"
283292
}
284293

@@ -732,7 +741,11 @@ check_model.DHARMa <- check_model.performance_simres
732741

733742
# misspecified dispersion and zero-inflation --------------
734743
if (isTRUE(model_info$is_count) && any(c("all", "overdispersion") %in% check)) {
735-
dat$OVERDISPERSION <- .model_diagnostic_overdispersion(model, ...)
744+
dat$OVERDISPERSION <- .model_diagnostic_overdispersion(
745+
model,
746+
residual_type = residual_type,
747+
...
748+
)
736749
}
737750

738751
dat <- insight::compact_list(dat)

R/check_model_diagnostics.R

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -298,16 +298,33 @@
298298
}
299299

300300

301-
.model_diagnostic_overdispersion <- function(model, ...) {
301+
.model_diagnostic_overdispersion <- function(model, residual_type = NULL, ...) {
302302
faminfo <- insight::model_info(model)
303303

304+
# validate argument
305+
if (!is.null(residual_type)) {
306+
insight::validate_argument(residual_type, c("normal", "simulated"))
307+
}
308+
304309
# For mixed models and glmmTMB models, use simulated residuals from DHARMa
305310
# for more accurate overdispersion visualization. Pearson residuals can be
306311
# misleading for these model types.
307-
use_simres <- (isTRUE(faminfo$is_mixed) ||
308-
inherits(model, "glmmTMB") ||
309-
faminfo$is_negbin) &&
310-
requireNamespace("DHARMa", quietly = TRUE)
312+
if (is.null(residual_type) || identical(residual_type, "simulated")) {
313+
use_simres <- (isTRUE(faminfo$is_mixed) ||
314+
inherits(model, "glmmTMB") ||
315+
faminfo$is_negbin) &&
316+
requireNamespace("DHARMa", quietly = TRUE)
317+
} else {
318+
use_simres <- FALSE
319+
}
320+
321+
# catch models/families not supported by DHARMa - we need to add more
322+
# exceptions here as they appear, but for now, `check_model()` also
323+
# automatically falls back to normal Q-Q plot for all models not supported
324+
# by DHARMa
325+
if (faminfo$family %in% c("quasipoisson", "quasibinomial")) {
326+
use_simres <- FALSE
327+
}
311328

312329
if (use_simres) {
313330
d <- .safe({

R/check_overdispersion.R

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
#'
77
#' @param x Fitted model of class `merMod`, `glmmTMB`, `glm`, or `glm.nb`
88
#' (package **MASS**), or an object returned by `simulate_residuals()`.
9+
#' @param residual_type Character, indicating the type of residuals to be used
10+
#' for overdispersion tests. For mixed models, the default is `"simulated"`,
11+
#' which uses simulated residuals. These are based on [`simulate_residuals()`],
12+
#' using the **DHARMa** package. For `glm`, the default is `"simulated"` for
13+
#' bernoulli, binomial and negative-binomial models. Set `residual_type = "normal"`
14+
#' to use
915
#'
1016
#' @inheritParams check_zeroinflation
1117
#'
@@ -102,8 +108,12 @@ plot.check_overdisp <- function(x, ...) {
102108
model <- .safe(get(obj_name, envir = globalenv()))
103109
}
104110
}
111+
112+
# detect residual type
113+
residual_type <- ifelse(isTRUE(attr(x, "simulated")), "simulated", NULL)
114+
105115
if (!is.null(model)) {
106-
x <- .model_diagnostic_overdispersion(model, ...)
116+
x <- .model_diagnostic_overdispersion(model, residual_type = residual_type, ...)
107117
class(x) <- c("see_check_overdisp", "data.frame")
108118
attr(x, "colors") <- list(...)$colors
109119
attr(x, "line_size") <- list(...)$size_line
@@ -171,17 +181,34 @@ print.check_overdisp <- function(x, digits = 3, ...) {
171181

172182
# Overdispersion for classical models -----------------------------
173183

184+
#' @rdname check_overdispersion
174185
#' @export
175-
check_overdispersion.glm <- function(x, verbose = TRUE, ...) {
186+
check_overdispersion.glm <- function(x, residual_type = NULL, verbose = TRUE, ...) {
176187
# model info
177188
info <- insight::model_info(x)
178189
obj_name <- insight::safe_deparse_symbol(substitute(x))
179190

180-
# for certain distributions, simulated residuals are more accurate
181-
use_simulated <- info$is_bernoulli ||
182-
info$is_binomial ||
183-
(!info$is_count && !info$is_binomial) ||
184-
info$is_negbin
191+
if (is.null(residual_type) || identical(residual_type, "simulated")) {
192+
# for certain distributions, simulated residuals are more accurate
193+
use_simulated <- info$is_bernoulli ||
194+
info$is_binomial ||
195+
(!info$is_count && !info$is_binomial) ||
196+
info$is_negbin
197+
} else {
198+
use_simulated <- FALSE
199+
}
200+
201+
# catch models/families not supported by DHARMa - we need to add more
202+
# exceptions here as they appear, but for now, `check_model()` also
203+
# automatically falls back to normal Q-Q plot for all models not supported
204+
# by DHARMa
205+
if (
206+
info$family %in%
207+
c("quasipoisson", "quasibinomial") ||
208+
!requireNamespace("DHARMa", quietly = TRUE)
209+
) {
210+
use_simulated <- FALSE
211+
}
185212

186213
# model classes not supported in DHARMa
187214
not_supported <- c("fixest", "glmx")
@@ -260,20 +287,18 @@ check_overdispersion.model_fit <- check_overdispersion.poissonmfx
260287
# Overdispersion for mixed models ---------------------------
261288

262289
#' @export
263-
check_overdispersion.merMod <- function(x, ...) {
290+
check_overdispersion.merMod <- function(x, residual_type = NULL, ...) {
264291
# for certain distributions, simulated residuals are more accurate
265292
info <- insight::model_info(x)
266293
obj_name <- insight::safe_deparse_symbol(substitute(x))
267294

268-
# for certain distributions, simulated residuals are more accurate
269-
use_simulated <- info$family == "genpois" ||
270-
info$is_zero_inflated ||
271-
info$is_bernoulli ||
272-
info$is_binomial ||
273-
(!info$is_count && !info$is_binomial) ||
274-
info$is_negbin # nolint
275-
276-
if (use_simulated) {
295+
# validate argument
296+
if (!is.null(residual_type)) {
297+
insight::validate_argument(residual_type, c("normal", "simulated"))
298+
}
299+
300+
# always use simulated residuals by default
301+
if (is.null(residual_type) || identical(residual_type, "simulated")) {
277302
return(check_overdispersion(simulate_residuals(x, ...), object_name = obj_name, ...))
278303
}
279304

@@ -341,6 +366,7 @@ check_overdispersion.performance_simres <- function(x, alternative = "two.sided"
341366

342367
class(out) <- c("check_overdisp", "see_check_overdisp")
343368
attr(out, "object_name") <- obj_name
369+
attr(out, "simulated") <- TRUE
344370

345371
out
346372
}

man/check_model.Rd

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/check_overdispersion.Rd

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)