Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Type: Package
Package: performance
Title: Assessment of Regression Models Performance
Version: 0.17.1
Version: 0.17.1.0001
Authors@R:
c(person(given = "Daniel",
family = "Lüdecke",
Expand Down
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# performance 0.17.xxx

## Bug fixes

* `check_collinearity()` now properly warns when the `vcov` matrix is rank
deficient (#922).


# performance 0.17.1

## Changes
Expand Down
55 changes: 38 additions & 17 deletions R/check_collinearity.R
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@
}


.check_collinearity <- function(x, component, ci = 0.95, verbose = TRUE) {

Check warning on line 450 in R/check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/check_collinearity.R,line=450,col=1,[cyclocomp_linter] Reduce the cyclomatic complexity of this expression from 49 to at most 40. Consider replacing high-complexity sections like loops and branches with helper functions.

Check warning on line 450 in R/check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/check_collinearity.R,line=450,col=1,[cyclocomp_linter] Reduce the cyclomatic complexity of this expression from 49 to at most 40. Consider replacing high-complexity sections like loops and branches with helper functions.
v <- .safe(insight::get_varcov(x, component = component, verbose = FALSE))

# fix class for fixest, which returns a "fixest_vcov" here
Expand Down Expand Up @@ -490,7 +490,7 @@
if (inherits(x, c("clm", "clmm"))) {
# names(x$beta) returns only non-singular (surviving) slopes
slope_names <- names(x$beta)
keep_idx <- which(colnames(v) %in% slope_names)
keep_idx <- colnames(v) %in% slope_names

# Rebuild term_assign by matching model matrix columns to surviving slopes
tryCatch(
Expand All @@ -507,34 +507,55 @@
},
error = function(e) NULL
)
} else if (insight::has_intercept(x)) {
# Standard behavior: drop the first column/row (the singular intercept)
keep_idx <- seq_len(ncol(v))[-1]
} else {
keep_idx <- seq_len(ncol(v))
if (isTRUE(verbose)) {
keep_idx <- rep(TRUE, ncol(v))
if (insight::has_intercept(x)) {
# Standard behavior: drop the first column/row (the singular intercept)
keep_idx[1] <- FALSE
} else if (isTRUE(verbose)) {
insight::format_alert("Model without intercept. VIFs may not be sensible.")
}
}

# we have rank-deficiency here. remove NA columns from assignment
if (isTRUE(attr(v, "rank_deficient")) || anyNA(v)) {
if (!is.null(attr(v, "na_columns_index"))) {
# If this attribute exists, then NA values were already removed from v
term_assign <- term_assign[-attr(v, "na_columns_index")]
na_cols <- names(attr(v, "na_columns_name"))
} else if (anyNA(v)) {
# If no attribute exists, then we need to identify NA columns manually
# fixme: this should be ficef in insight::get_varcov() to avoid this step
idx_na <- apply(is.na(m), 2, all)
Comment thread
mattansb marked this conversation as resolved.
Outdated
na_cols <- colnames(v)[idx_na]
keep_idx[idx_na] <- FALSE
}

if (isTRUE(verbose)) {
if (length(na_cols) > 0) {
insight::format_warning(
"Model matrix is rank deficient. VIFs may not be sensible.",
paste0(
"The following coefficients have VIF = Inf / tolerance = 0: ",
paste0(na_cols, collapse = ", ")

Check warning on line 540 in R/check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/check_collinearity.R,line=540,col=13,[paste_linter] Use paste(), not paste0(), to collapse a character vector when sep= is not used.

Check warning on line 540 in R/check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/check_collinearity.R,line=540,col=13,[paste_linter] toString(.) is more expressive than paste(., collapse = ", "). Note also glue::glue_collapse() and and::and() for constructing human-readable / translation-friendly lists

Check warning on line 540 in R/check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/check_collinearity.R,line=540,col=13,[paste_linter] Use paste(), not paste0(), to collapse a character vector when sep= is not used.

Check warning on line 540 in R/check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/check_collinearity.R,line=540,col=13,[paste_linter] toString(.) is more expressive than paste(., collapse = ", "). Note also glue::glue_collapse() and and::and() for constructing human-readable / translation-friendly lists
)
)
} else {
insight::format_warning(
"Model matrix is rank deficient. VIFs may not be sensible."
)
}
}
}

# Safely subset the matrix
if (length(keep_idx) < ncol(v)) {
if (any(!keep_idx) < ncol(v)) {

Check warning on line 552 in R/check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/check_collinearity.R,line=552,col=7,[outer_negation_linter] !all(x) is better than any(!x). The former applies negation only once after aggregation instead of many times for each element of x.

Check warning on line 552 in R/check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/check_collinearity.R,line=552,col=7,[outer_negation_linter] !all(x) is better than any(!x). The former applies negation only once after aggregation instead of many times for each element of x.
Comment thread
mattansb marked this conversation as resolved.
Outdated
if (!is.null(term_assign) && length(term_assign) == ncol(v)) {
term_assign <- term_assign[keep_idx]
}
v <- v[keep_idx, keep_idx, drop = FALSE]
}

# we have rank-deficiency here. remove NA columns from assignment
if (isTRUE(attributes(v)$rank_deficient) && !is.null(attributes(v)$na_columns_index)) {
term_assign <- term_assign[-attributes(v)$na_columns_index]
if (isTRUE(verbose)) {
insight::format_alert(
"Model matrix is rank deficient. VIFs may not be sensible."
)
}
}

f <- insight::find_formula(x, verbose = FALSE)

# hurdle or zeroinfl model can have no zero-inflation formula, in which case
Expand Down Expand Up @@ -571,7 +592,7 @@
return(NULL)
}

R <- stats::cov2cor(v)

Check warning on line 595 in R/check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/check_collinearity.R,line=595,col=3,[object_overwrite_linter] 'R' is an exported object from package 'tools'. Avoid re-using such symbols.

Check warning on line 595 in R/check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/check_collinearity.R,line=595,col=3,[object_overwrite_linter] 'R' is an exported object from package 'tools'. Avoid re-using such symbols.
detR <- det(R)

result <- vector("numeric")
Expand Down
21 changes: 21 additions & 0 deletions tests/testthat/test-check_collinearity.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# https://github.com/easystats/performance/pull/547
test_that("check_collinearity, correct order in print", {
data(mtcars)
m <- lm(mpg ~ wt + cyl + gear + disp, data = mtcars)
out <- capture.output(print(check_collinearity(m, verbose = FALSE)))
expect_identical(
Expand Down Expand Up @@ -410,3 +411,23 @@
expect_identical(out$Term, c("wt", "cyl"))
expect_false("disp" %in% out$Term)
})


test_that("check_collinearity, rank deficient.", {
data(mtcars)
mtcars$Z <- mtcars$wt + mtcars$cyl

m1 <- lm(mpg ~ wt + cyl + Z, data = mtcars)
m2 <- lm(mpg ~ wt + cyl, data = mtcars)
expect_warning(
out1 <- check_collinearity(m1),

Check warning on line 423 in tests/testthat/test-check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=tests/testthat/test-check_collinearity.R,line=423,col=5,[implicit_assignment_linter] Avoid implicit assignments in function calls. For example, instead of `if (x <- 1L) { ... }`, write `x <- 1L; if (x) { ... }`.
"Model matrix is rank deficient"
)
expect_warning(
out2 <- check_collinearity(m2),

Check warning on line 427 in tests/testthat/test-check_collinearity.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=tests/testthat/test-check_collinearity.R,line=427,col=5,[implicit_assignment_linter] Avoid implicit assignments in function calls. For example, instead of `if (x <- 1L) { ... }`, write `x <- 1L; if (x) { ... }`.
NA
)
expect_identical(out1$VIF, out2$VIF)
})

debugonce(performance:::.check_collinearity)
Comment thread
mattansb marked this conversation as resolved.
Outdated
Loading