Skip to content

Commit 3886b63

Browse files
authored
R2 incorrect for methods that use update(model, ~1) and have missing data (#804)
1 parent 9d7d968 commit 3886b63

8 files changed

+59
-29
lines changed

DESCRIPTION

+3-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.1
4+
Version: 0.13.0.2
55
Authors@R:
66
c(person(given = "Daniel",
77
family = "Lüdecke",
@@ -126,6 +126,7 @@ Suggests:
126126
multimode,
127127
nestedLogit,
128128
nlme,
129+
nnet,
129130
nonnest2,
130131
ordinal,
131132
parallel,
@@ -161,3 +162,4 @@ Config/Needs/website:
161162
r-lib/pkgdown,
162163
easystats/easystatstemplate
163164
Config/rcmdcheck/ignore-inconsequential-notes: true
165+
Remotes: easystats/insight

NEWS.md

+8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
the full model), or can return singularity checks for each random effects term
99
separately.
1010

11+
## Bug fixes
12+
13+
* Fixed issue with wrong computation of pseudo-R2 for some models where the
14+
base-model (null model) was updated using the original data, which could
15+
include missing values. Now the model frame is used, ensuring the correct
16+
number of observations in the returned base-model, thus calculating the correct
17+
log-likelihood and returning the correct pseudo-R2.
18+
1119
# performance 0.13.0
1220

1321
## Breaking changes

R/r2_coxsnell.R

+5-10
Original file line numberDiff line numberDiff line change
@@ -218,25 +218,20 @@ r2_coxsnell.svycoxph <- function(model, ...) {
218218

219219
#' @export
220220
r2_coxsnell.multinom <- function(model, ...) {
221-
l_base <- insight::get_loglikelihood(stats::update(model, ~1, trace = FALSE))
221+
l_base <- insight::get_loglikelihood(insight::null_model(model))
222222
.r2_coxsnell(model, l_base)
223223
}
224224

225225
#' @export
226-
r2_coxsnell.clm2 <- function(model, ...) {
227-
l_base <- insight::get_loglikelihood(stats::update(model, location = ~1, scale = ~1))
228-
.r2_coxsnell(model, l_base)
229-
}
226+
r2_coxsnell.clm2 <- r2_coxsnell.multinom
230227

231228
#' @export
232-
r2_coxsnell.bayesx <- function(model, ...) {
233-
junk <- utils::capture.output(l_base <- insight::get_loglikelihood(stats::update(model, ~1))) # nolint
234-
.r2_coxsnell(model, l_base)
235-
}
229+
r2_coxsnell.bayesx <- r2_coxsnell.multinom
236230

237231
#' @export
238232
r2_coxsnell.clm <- function(model, ...) {
239-
l_base <- insight::get_loglikelihood(stats::update(model, ~1))
233+
l_base <- insight::get_loglikelihood(insight::null_model(model))
234+
240235
# if no loglik, return NA
241236
if (length(as.numeric(l_base)) == 0) {
242237
return(NULL)

R/r2_mcfadden.R

+4-7
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ r2_mcfadden.glm <- function(model, verbose = TRUE, ...) {
7373
return(NULL)
7474
}
7575

76-
l_null <- insight::get_loglikelihood(stats::update(model, ~1))
76+
l_null <- insight::get_loglikelihood(insight::null_model(model))
7777
.r2_mcfadden(model, l_null)
7878
}
7979

@@ -162,23 +162,20 @@ r2_mcfadden.vglm <- function(model, ...) {
162162
insight::format_error("Can't get log-likelihood when `summ` is not zero.")
163163
}
164164

165-
l_null <- insight::get_loglikelihood(stats::update(model, ~1))
165+
l_null <- insight::get_loglikelihood(insight::null_model(model))
166166
.r2_mcfadden(model, l_null)
167167
}
168168

169169

170170
#' @export
171171
r2_mcfadden.clm2 <- function(model, ...) {
172-
l_null <- insight::get_loglikelihood(stats::update(model, location = ~1, scale = ~1))
172+
l_null <- insight::get_loglikelihood(insight::null_model(model))
173173
.r2_mcfadden(model, l_null)
174174
}
175175

176176

177177
#' @export
178-
r2_mcfadden.multinom <- function(model, ...) {
179-
l_null <- insight::get_loglikelihood(stats::update(model, ~1, trace = FALSE))
180-
.r2_mcfadden(model, l_null)
181-
}
178+
r2_mcfadden.multinom <- r2_mcfadden.clm2
182179

183180

184181
#' @export

R/r2_mckelvey.R

+1-6
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,8 @@ r2_mckelvey.default <- function(model) {
7474
}
7575

7676

77-
.null_model <- function(model) {
78-
stats::update(model, ~1)
79-
}
80-
81-
8277
.get_poisson_variance <- function(model) {
83-
mu <- exp(stats::coef(.null_model(model)))
78+
mu <- exp(stats::coef(insight::null_model(model)))
8479
if (is.na(mu)) {
8580
return(0)
8681
}

R/r2_nagelkerke.R

+3-3
Original file line numberDiff line numberDiff line change
@@ -170,19 +170,19 @@ r2_nagelkerke.negbinmfx <- r2_nagelkerke.logitmfx
170170

171171
#' @export
172172
r2_nagelkerke.multinom <- function(model, ...) {
173-
l_base <- insight::get_loglikelihood(stats::update(model, ~1, trace = FALSE))
173+
l_base <- insight::get_loglikelihood(insight::null_model(model))
174174
.r2_nagelkerke(model, l_base)
175175
}
176176

177177
#' @export
178178
r2_nagelkerke.clm2 <- function(model, ...) {
179-
l_base <- insight::get_loglikelihood(stats::update(model, location = ~1, scale = ~1))
179+
l_base <- insight::get_loglikelihood(insight::null_model(model))
180180
.r2_nagelkerke(model, l_base)
181181
}
182182

183183
#' @export
184184
r2_nagelkerke.clm <- function(model, ...) {
185-
l_base <- insight::get_loglikelihood(stats::update(model, ~1))
185+
l_base <- insight::get_loglikelihood(insight::null_model(model))
186186
# if no loglik, return NA
187187
if (length(as.numeric(l_base)) == 0) {
188188
return(NULL)

tests/testthat/test-check_singularity.R

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ test_that("check_singularity", {
4545
expect_true(check_singularity(m2))
4646

4747
data(Salamanders, package = "glmmTMB")
48-
m <- glmmTMB::glmmTMB(
48+
m <- suppressWarnings(glmmTMB::glmmTMB(
4949
count ~ spp + mined + (1 | site),
5050
data = Salamanders[Salamanders$count > 0, , drop = FALSE],
5151
family = glmmTMB::truncated_nbinom2(),
5252
ziformula = ~ spp + (1 | site),
5353
dispformula = ~ spp + (1 | site)
54-
)
54+
))
5555
out <- check_singularity(m, check = "terms")
5656
expect_identical(
5757
out,

tests/testthat/test-r2_nagelkerke.R

+33
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,36 @@ test_that("r2_nagelkerke", {
1717
}
1818
)
1919
})
20+
21+
test_that("r2_nagelkerke, multinom, correct base-model with NA", {
22+
skip_on_cran()
23+
skip_if_not_installed("nnet")
24+
25+
n_obs <- 1000
26+
softmax <- function(x) {
27+
exp(x - max(x)) / sum(exp(x - max(x)))
28+
}
29+
sample_y <- function(x) {
30+
sample(1:3, size = 1, prob = softmax(c(0.25 * x, -0.1 * x, 0 * x)))
31+
}
32+
set.seed(123)
33+
sim_df <- data.frame(x = rnorm(n_obs, 0, 1), y = NA)
34+
35+
for (i in 1:nrow(sim_df)) {
36+
sim_df$y[i] <- sample_y(sim_df$x[i])
37+
}
38+
39+
sim_df$x[1:500] <- NA
40+
sim_df2 <- sim_df[!is.na(sim_df$x), ]
41+
42+
m1 <- nnet::multinom(y ~ x, data = sim_df, trace = FALSE)
43+
m2 <- nnet::multinom(y ~ x, data = sim_df2, trace = FALSE)
44+
45+
out1 <- r2_nagelkerke(m1)
46+
out2 <- r2_nagelkerke(m2)
47+
expect_equal(out1, out2, tolerance = 1e-4, ignore_attr = TRUE)
48+
49+
out1 <- r2_mcfadden(m1)
50+
out2 <- r2_mcfadden(m2)
51+
expect_equal(out1$R2, out2$R2, tolerance = 1e-4, ignore_attr = TRUE)
52+
})

0 commit comments

Comments
 (0)