diff --git a/DESCRIPTION b/DESCRIPTION index a0189267c..81872ed89 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -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", diff --git a/NAMESPACE b/NAMESPACE index 0bb599f64..970895f14 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -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) diff --git a/NEWS.md b/NEWS.md index f3bb5fdd3..6fe789f56 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/R/check_itemscale.R b/R/check_itemscale.R index 5aa704618..fda758d24 100644 --- a/R/check_itemscale.R +++ b/R/check_itemscale.R @@ -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. @@ -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] @@ -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 = "", + zap_small = TRUE + ) +} diff --git a/R/performance_score.R b/R/performance_score.R index e65ae820a..5ca3fdc84 100644 --- a/R/performance_score.R +++ b/R/performance_score.R @@ -209,27 +209,25 @@ print.performance_score <- function(x, ...) { 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 } ) diff --git a/man/check_itemscale.Rd b/man/check_itemscale.Rd index 7f790b1d2..a5ada3875 100644 --- a/man/check_itemscale.Rd +++ b/man/check_itemscale.Rd @@ -4,11 +4,15 @@ \alias{check_itemscale} \title{Describe Properties of Item Scales} \usage{ -check_itemscale(x) +check_itemscale(x, factor_index = NULL) } \arguments{ \item{x}{An object of class \code{parameters_pca}, as returned by -\code{\link[parameters:principal_components]{parameters::principal_components()}}.} +\code{\link[parameters:principal_components]{parameters::principal_components()}}, or a data frame.} + +\item{factor_index}{If \code{x} is a data frame, \code{factor_index} must be specified. +It must be a numeric vector of same length as number of columns in \code{x}, where +each element is the index of the factor to which the respective column in \code{x}.} } \value{ A list of data frames, with related measures of internal @@ -51,9 +55,19 @@ set.seed(17) 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) +) \dontshow{\}) # examplesIf} } \references{ diff --git a/tests/testthat/_snaps/windows/check_itemscale.md b/tests/testthat/_snaps/windows/check_itemscale.md new file mode 100644 index 000000000..48e0bd482 --- /dev/null +++ b/tests/testthat/_snaps/windows/check_itemscale.md @@ -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 + diff --git a/tests/testthat/test-check_itemscale.R b/tests/testthat/test-check_itemscale.R index 119283d37..95ab4e169 100644 --- a/tests/testthat/test-check_itemscale.R +++ b/tests/testthat/test-check_itemscale.R @@ -1,4 +1,4 @@ -test_that("check_convergence", { +test_that("check_itemscale", { skip_if_not_installed("parameters") set.seed(123) @@ -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." + ) }) diff --git a/tests/testthat/test-glmmPQL.R b/tests/testthat/test-glmmPQL.R index 64caaccea..6c1713cc8 100644 --- a/tests/testthat/test-glmmPQL.R +++ b/tests/testthat/test-glmmPQL.R @@ -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") })