Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

check_itemscale() accepts data frames #664

Merged
merged 10 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all 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.10.8.7
Version: 0.10.8.8
Authors@R:
c(person(given = "Daniel",
family = "Lüdecke",
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ S3method(print,r2_nakagawa_by_group)
S3method(print,r2_pseudo)
S3method(print,test_likelihoodratio)
S3method(print,test_performance)
S3method(print_html,check_itemscale)
S3method(print_html,compare_performance)
S3method(print_html,test_performance)
S3method(print_md,check_itemscale)
Expand Down
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
* `r2()` for models of class `glmmTMB` without random effects now returns the
correct r-squared value for non-mixed models.

* `check_itemscale()` now also accepts data frames as input. In this case,
`factor_index` must be specified, which must be a numeric vector of same
length as number of columns in `x`, where each element is the index of the
factor to which the respective column in `x`.

* `check_itemscale()` gets a `print_html()` method.

## Bug fixes

* Fixed issue in `binned_residuals()` for models with binary outcome, where
Expand Down
78 changes: 68 additions & 10 deletions R/check_itemscale.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
#' @name check_itemscale
#'
#' @description Compute various measures of internal consistencies
#' applied to (sub)scales, which items were extracted using
#' `parameters::principal_components()`.
#' applied to (sub)scales, which items were extracted using
#' `parameters::principal_components()`.
#'
#' @param x An object of class `parameters_pca`, as returned by
#' [`parameters::principal_components()`].
#' [`parameters::principal_components()`], or a data frame.
#' @param factor_index If `x` is a data frame, `factor_index` must be specified.
#' It must be a numeric vector of same length as number of columns in `x`, where
#' each element is the index of the factor to which the respective column in `x`.
#'
#' @return A list of data frames, with related measures of internal
#' consistencies of each subscale.
Expand Down Expand Up @@ -48,21 +51,53 @@
#' X <- matrix(rnorm(1600), 100, 16)
#' Z <- X %*% C
#'
#' pca <- principal_components(as.data.frame(Z), rotation = "varimax", n = 3)
#' pca <- parameters::principal_components(
#' as.data.frame(Z),
#' rotation = "varimax",
#' n = 3
#' )
#' pca
#' check_itemscale(pca)
#'
#' # as data frame
#' check_itemscale(
#' as.data.frame(Z),
#' factor_index = parameters::closest_component(pca)
#' )
#' @export
check_itemscale <- function(x) {
if (!inherits(x, "parameters_pca")) {
check_itemscale <- function(x, factor_index = NULL) {
# check for valid input
if (!inherits(x, c("parameters_pca", "data.frame"))) {
insight::format_error(
"`x` must be an object of class `parameters_pca`, as returned by `parameters::principal_components()`."
"`x` must be an object of class `parameters_pca`, as returned by `parameters::principal_components()`, or a data frame." # nolint
)
}

insight::check_if_installed("parameters")
# if data frame, we need `factor_index`
if (inherits(x, "data.frame") && !inherits(x, "parameters_pca")) {
if (is.null(factor_index)) {
insight::format_error("If `x` is a data frame, `factor_index` must be specified.")
}
if (!is.numeric(factor_index)) {
insight::format_error("`factor_index` must be numeric.")
}
if (length(factor_index) != ncol(x)) {
insight::format_error(
"`factor_index` must be of same length as number of columns in `x`.",
"Each element of `factor_index` must be the index of the factor to which the respective column in `x` belongs to." # nolint
)
}
}

dataset <- attributes(x)$dataset
subscales <- parameters::closest_component(x)
# assign data and factor index
if (inherits(x, "parameters_pca")) {
insight::check_if_installed("parameters")
dataset <- attributes(x)$dataset
subscales <- parameters::closest_component(x)
} else {
dataset <- x
subscales <- factor_index
}

out <- lapply(sort(unique(subscales)), function(.subscale) {
columns <- names(subscales)[subscales == .subscale]
Expand Down Expand Up @@ -123,3 +158,26 @@ print.check_itemscale <- function(x, digits = 2, ...) {
zap_small = TRUE
))
}


#' @export
print_html.check_itemscale <- function(x, digits = 2, ...) {
x <- lapply(seq_along(x), function(i) {
out <- x[[i]]
attr(out, "table_caption") <- sprintf(
"Component %i: Mean inter-item-correlation = %.3f, Cronbach's alpha = %.3f",
i,
attributes(out)$item_intercorrelation,
attributes(out)$cronbachs_alpha
)
out
})
insight::export_table(
x,
caption = "Description of (Sub-)Scales",
digits = digits,
format = "html",
missing = "<NA>",
zap_small = TRUE
)
}
36 changes: 17 additions & 19 deletions R/performance_score.R
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
#' performance_score(model)
#' }
#' @export
performance_score <- function(model, verbose = TRUE, ...) {

Check warning on line 55 in R/performance_score.R

View workflow job for this annotation

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

file=R/performance_score.R,line=55,col=1,[cyclocomp_linter] Reduce the cyclomatic complexity of this function from 44 to at most 40.
# check special case
if (inherits(model, c("logitor", "logitmfx", "probitmfx", "negbinirr", "negbinmfx", "poissonirr", "poissonmfx"))) {
model <- model$fit
Expand Down Expand Up @@ -209,27 +209,25 @@
pred_zi <- NULL

tryCatch(
{
if (inherits(model, "MixMod")) {
pred <- stats::predict(model, type = "subject_specific")
pred_zi <- if (!is.null(model$gammas)) attr(pred, "zi_probs")
} else if (inherits(model, "glmmTMB")) {
pred <- stats::predict(model, type = "response")
pred_zi <- stats::predict(model, type = "zprob")
} else if (inherits(model, c("hurdle", "zeroinfl"))) {
pred <- stats::predict(model, type = "response")
pred_zi <- stats::predict(model, type = "zero")
} else if (inherits(model, c("clm", "clm2", "clmm"))) {
pred <- stats::predict(model)
} else if (all(inherits(model, c("stanreg", "lmerMod"), which = TRUE)) > 0) {
insight::check_if_installed("rstanarm")
pred <- colMeans(rstanarm::posterior_predict(model))
} else {
pred <- stats::predict(model, type = "response")
}
if (inherits(model, "MixMod")) {
pred <- stats::predict(model, type = "subject_specific")
pred_zi <- if (!is.null(model$gammas)) attr(pred, "zi_probs")
} else if (inherits(model, "glmmTMB")) {
pred <- stats::predict(model, type = "response")
pred_zi <- stats::predict(model, type = "zprob")
} else if (inherits(model, c("hurdle", "zeroinfl"))) {
pred <- stats::predict(model, type = "response")
pred_zi <- stats::predict(model, type = "zero")
} else if (inherits(model, c("clm", "clm2", "clmm"))) {
pred <- stats::predict(model)
} else if (all(inherits(model, c("stanreg", "lmerMod"), which = TRUE)) > 0) {
insight::check_if_installed("rstanarm")
pred <- colMeans(rstanarm::posterior_predict(model))
} else {
pred <- stats::predict(model, type = "response")
},
error = function(e) {
return(NULL)
NULL
}
)

Expand Down
20 changes: 17 additions & 3 deletions man/check_itemscale.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions tests/testthat/_snaps/windows/check_itemscale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# check_itemscale

Code
print(out)
Output
# Description of (Sub-)ScalesComponent 1

Item | Missings | Mean | SD | Skewness | Difficulty | Discrimination | alpha if deleted
-----------------------------------------------------------------------------------------
b | 0 | 5.02 | 0.79 | -0.04 | 0.84 | 0.06 | -0.55
e | 0 | 2.12 | 0.81 | -0.22 | 0.35 | -0.09 | -0.03
f | 0 | 2.00 | 0.82 | 0.00 | 0.33 | -0.16 | 0.17

Mean inter-item-correlation = -0.046 Cronbach's alpha = -0.159
Component 2

Item | Missings | Mean | SD | Skewness | Difficulty | Discrimination | alpha if deleted
-----------------------------------------------------------------------------------------
a | 0 | 5.02 | 0.83 | -0.04 | 0.84 | 0.21 | -0.18
c | 0 | 4.74 | 0.81 | 0.51 | 0.79 | -0.04 | 0.41
d | 0 | 2.07 | 0.79 | -0.13 | 0.34 | 0.13 | 0.04

Mean inter-item-correlation = 0.067 Cronbach's alpha = 0.178

29 changes: 28 additions & 1 deletion tests/testthat/test-check_itemscale.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
test_that("check_convergence", {
test_that("check_itemscale", {
skip_if_not_installed("parameters")

set.seed(123)
Expand All @@ -25,4 +25,31 @@ test_that("check_convergence", {
tolerance = 1e-4,
ignore_attr = TRUE
)
expect_snapshot(print(out), variant = "windows")
comp <- parameters::closest_component(pca)
out2 <- check_itemscale(d, comp)
expect_equal(
out[[1]]$Mean,
out2[[1]]$Mean,
tolerance = 1e-4,
ignore_attr = TRUE
)
expect_equal(
out[[1]]$Difficulty,
out2[[1]]$Difficulty,
tolerance = 1e-4,
ignore_attr = TRUE
)
expect_error(
check_itemscale(d),
regex = "If `x` is a data"
)
expect_error(
check_itemscale(d, factor_index = 1:8),
regex = "`factor_index` must be of same"
)
expect_error(
check_itemscale(d, factor_index = factor(comp)),
regex = "`factor_index` must be numeric."
)
})
2 changes: 1 addition & 1 deletion tests/testthat/test-glmmPQL.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ test_that("r2", {
random = ~ 1 | species,
family = "quasibinomial", data = example_dat
)
expect_message(performance_score(mn), regex = "Cant calculate")
expect_message(performance_score(mn), regex = "Can't calculate")
})
Loading