Skip to content

Commit

Permalink
check_itemscale() accepts data frames (#664)
Browse files Browse the repository at this point in the history
  • Loading branch information
strengejacke committed Dec 19, 2023
1 parent fe1c449 commit e2bde2d
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 35 deletions.
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 @@ -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
}
)

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")
})

0 comments on commit e2bde2d

Please sign in to comment.