From 4600b071d7425a50ce9765d78a510f1552094d17 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 5 Jan 2024 12:43:23 +0100 Subject: [PATCH] minor fixes, lintr, docs --- DESCRIPTION | 2 +- NEWS.md | 3 +++ R/compare_performance.R | 20 +++++++-------- R/performance_aicc.R | 37 ++++++++++++--------------- man/compare_performance.Rd | 3 ++- man/model_performance.merMod.Rd | 3 ++- man/model_performance.rma.Rd | 3 ++- man/performance_aicc.Rd | 6 ++++- tests/testthat/test-performance_aic.R | 3 +++ 9 files changed, 45 insertions(+), 35 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 81872ed89..d0bd9f334 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: performance Title: Assessment of Regression Models Performance -Version: 0.10.8.8 +Version: 0.10.8.9 Authors@R: c(person(given = "Daniel", family = "Lüdecke", diff --git a/NEWS.md b/NEWS.md index 6fe789f56..5475776cc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,6 +12,9 @@ * `check_itemscale()` gets a `print_html()` method. +* Clarification in the documentation of the `estimator` argument for + `performance_aic()`. + ## Bug fixes * Fixed issue in `binned_residuals()` for models with binary outcome, where diff --git a/R/compare_performance.R b/R/compare_performance.R index 76b0b329f..2318d5e63 100644 --- a/R/compare_performance.R +++ b/R/compare_performance.R @@ -86,21 +86,21 @@ #' @export compare_performance <- function(..., metrics = "all", rank = FALSE, estimator = "ML", verbose = TRUE) { # process input - objects <- insight::ellipsis_info(..., only_models = TRUE) + model_objects <- insight::ellipsis_info(..., only_models = TRUE) # ensure proper object names - objects <- .check_objectnames(objects, sapply(match.call(expand.dots = FALSE)$`...`, as.character)) + model_objects <- .check_objectnames(model_objects, sapply(match.call(expand.dots = FALSE)[["..."]], as.character)) # drop unsupport models - supported_models <- sapply(objects, function(i) insight::is_model_supported(i) | inherits(i, "lavaan")) - object_names <- names(objects) + supported_models <- sapply(model_objects, function(i) insight::is_model_supported(i) | inherits(i, "lavaan")) + object_names <- names(model_objects) if (!all(supported_models)) { insight::format_alert( "Following objects are not supported:", datawizard::text_concatenate(object_names[!supported_models], enclose = "`") ) - objects <- objects[supported_models] + model_objects <- model_objects[supported_models] object_names <- object_names[supported_models] } @@ -110,8 +110,8 @@ compare_performance <- function(..., metrics = "all", rank = FALSE, estimator = model_name <- gsub("\"", "", insight::safe_deparse(.y), fixed = TRUE) perf_df <- data.frame(Name = model_name, Model = class(.x)[1], dat, stringsAsFactors = FALSE) attributes(perf_df) <- c(attributes(perf_df), attributes(dat)[!names(attributes(dat)) %in% c("names", "row.names", "class")]) - return(perf_df) - }, objects, object_names, SIMPLIFY = FALSE) + perf_df + }, model_objects, object_names, SIMPLIFY = FALSE) attri <- lapply(m, function(x) { attri <- attributes(x) @@ -132,7 +132,7 @@ compare_performance <- function(..., metrics = "all", rank = FALSE, estimator = } # check if all models were fit from same data - if (!isTRUE(attributes(objects)$same_response) && verbose) { + if (!isTRUE(attributes(model_objects)$same_response) && verbose) { insight::format_alert( "When comparing models, please note that probably not all models were fit from same data." ) @@ -188,9 +188,9 @@ compare_performance <- function(..., metrics = "all", rank = FALSE, estimator = # only for IC comparison any(grepl("(AIC|BIC)", names(dfs))) && # only when mixed models are involved, others probably don't have problems with REML fit - any(sapply(objects, insight::is_mixed_model)) && + any(sapply(model_objects, insight::is_mixed_model)) && # only if not all models have same fixed effects (else, REML is ok) - !isTRUE(attributes(objects)$same_fixef)) { + !isTRUE(attributes(model_objects)$same_fixef)) { insight::format_alert( "Information criteria (like AIC) are based on REML fits (i.e. `estimator=\"REML\"`).", "Please note that information criteria are probably not directly comparable and that it is not recommended comparing models with different fixed effects in such cases." diff --git a/R/performance_aicc.R b/R/performance_aicc.R index 171a97ca2..e222dfc82 100644 --- a/R/performance_aicc.R +++ b/R/performance_aicc.R @@ -14,7 +14,8 @@ #' @param x A model object. #' @param estimator Only for linear models. Corresponds to the different #' estimators for the standard deviation of the errors. If `estimator = "ML"` -#' (default), the scaling is done by n (the biased ML estimator), which is +#' (default, except for `performance_aic()` when the model object is of class +#' `lmerMod`), the scaling is done by `n` (the biased ML estimator), which is #' then equivalent to using `AIC(logLik())`. Setting it to `"REML"` will give #' the same results as `AIC(logLik(..., REML = TRUE))`. #' @param verbose Toggle warnings. @@ -101,11 +102,16 @@ performance_aic.default <- function(x, estimator = "ML", verbose = TRUE, ...) { # mixed models ------------------------------------ +#' @rdname performance_aicc #' @export performance_aic.lmerMod <- function(x, estimator = "REML", verbose = TRUE, ...) { REML <- identical(estimator, "REML") if (isFALSE(list(...)$REML)) REML <- FALSE + if (isFALSE(as.logical(x@devcomp$dims[["REML"]])) && isTRUE(REML) && verbose) { + insight::format_alert("Model was not fitted with REML, however, `estimator = \"REML\"`. Set `estimator = \"ML\"` to obtain identical results as from `AIC()`.") # nolint + } + .safe( stats::AIC(insight::get_loglikelihood(x, check_response = TRUE, REML = REML, verbose = verbose)) ) @@ -275,26 +281,17 @@ performance_aicc.rma <- function(x, ...) { tryCatch( { trans <- insight::find_transformation(x) - - if (trans == "identity") { - .weighted_sum(log(insight::get_response(x)), w = model_weights) - } else if (trans == "log") { - .weighted_sum(log(1 / insight::get_response(x)), w = model_weights) - } else if (trans == "log1p") { - .weighted_sum(log(1 / (insight::get_response(x) + 1)), w = model_weights) - } else if (trans == "log2") { - .weighted_sum(log(1 / (insight::get_response(x) * log(2))), w = model_weights) - } else if (trans == "log10") { - .weighted_sum(log(1 / (insight::get_response(x) * log(10))), w = model_weights) - } else if (trans == "exp") { - .weighted_sum(insight::get_response(x), w = model_weights) - } else if (trans == "expm1") { - .weighted_sum((insight::get_response(x) - 1), w = model_weights) - } else if (trans == "sqrt") { - .weighted_sum(log(0.5 / sqrt(insight::get_response(x))), w = model_weights) - } else { + switch(trans, + identity = .weighted_sum(log(insight::get_response(x)), w = model_weights), + log = .weighted_sum(log(1 / insight::get_response(x)), w = model_weights), + log1p = .weighted_sum(log(1 / (insight::get_response(x) + 1)), w = model_weights), + log2 = .weighted_sum(log(1 / (insight::get_response(x) * log(2))), w = model_weights), + log10 = .weighted_sum(log(1 / (insight::get_response(x) * log(10))), w = model_weights), + exp = .weighted_sum(insight::get_response(x), w = model_weights), + expm1 = .weighted_sum((insight::get_response(x) - 1), w = model_weights), + sqrt = .weighted_sum(log(0.5 / sqrt(insight::get_response(x))), w = model_weights), .ll_jacobian_adjustment(x, model_weights) - } + ) }, error = function(e) { NULL diff --git a/man/compare_performance.Rd b/man/compare_performance.Rd index 30c324351..d6b17b9f1 100644 --- a/man/compare_performance.Rd +++ b/man/compare_performance.Rd @@ -25,7 +25,8 @@ overall model performance. See 'Details'.} \item{estimator}{Only for linear models. Corresponds to the different estimators for the standard deviation of the errors. If \code{estimator = "ML"} -(default), the scaling is done by n (the biased ML estimator), which is +(default, except for \code{performance_aic()} when the model object is of class +\code{lmerMod}), the scaling is done by \code{n} (the biased ML estimator), which is then equivalent to using \code{AIC(logLik())}. Setting it to \code{"REML"} will give the same results as \code{AIC(logLik(..., REML = TRUE))}.} diff --git a/man/model_performance.merMod.Rd b/man/model_performance.merMod.Rd index 519f1ee0a..2f02ea179 100644 --- a/man/model_performance.merMod.Rd +++ b/man/model_performance.merMod.Rd @@ -21,7 +21,8 @@ BIC, R2, ICC and RMSE.} \item{estimator}{Only for linear models. Corresponds to the different estimators for the standard deviation of the errors. If \code{estimator = "ML"} -(default), the scaling is done by n (the biased ML estimator), which is +(default, except for \code{performance_aic()} when the model object is of class +\code{lmerMod}), the scaling is done by \code{n} (the biased ML estimator), which is then equivalent to using \code{AIC(logLik())}. Setting it to \code{"REML"} will give the same results as \code{AIC(logLik(..., REML = TRUE))}.} diff --git a/man/model_performance.rma.Rd b/man/model_performance.rma.Rd index a035378ed..f6489005b 100644 --- a/man/model_performance.rma.Rd +++ b/man/model_performance.rma.Rd @@ -20,7 +20,8 @@ computed (some of \code{c("AIC", "BIC", "I2", "H2", "TAU2", "R2", "CochransQ", " \item{estimator}{Only for linear models. Corresponds to the different estimators for the standard deviation of the errors. If \code{estimator = "ML"} -(default), the scaling is done by n (the biased ML estimator), which is +(default, except for \code{performance_aic()} when the model object is of class +\code{lmerMod}), the scaling is done by \code{n} (the biased ML estimator), which is then equivalent to using \code{AIC(logLik())}. Setting it to \code{"REML"} will give the same results as \code{AIC(logLik(..., REML = TRUE))}.} diff --git a/man/performance_aicc.Rd b/man/performance_aicc.Rd index be3695527..0bf2120b7 100644 --- a/man/performance_aicc.Rd +++ b/man/performance_aicc.Rd @@ -4,6 +4,7 @@ \alias{performance_aicc} \alias{performance_aic} \alias{performance_aic.default} +\alias{performance_aic.lmerMod} \title{Compute the AIC or second-order AIC} \usage{ performance_aicc(x, ...) @@ -11,6 +12,8 @@ performance_aicc(x, ...) performance_aic(x, ...) \method{performance_aic}{default}(x, estimator = "ML", verbose = TRUE, ...) + +\method{performance_aic}{lmerMod}(x, estimator = "REML", verbose = TRUE, ...) } \arguments{ \item{x}{A model object.} @@ -19,7 +22,8 @@ performance_aic(x, ...) \item{estimator}{Only for linear models. Corresponds to the different estimators for the standard deviation of the errors. If \code{estimator = "ML"} -(default), the scaling is done by n (the biased ML estimator), which is +(default, except for \code{performance_aic()} when the model object is of class +\code{lmerMod}), the scaling is done by \code{n} (the biased ML estimator), which is then equivalent to using \code{AIC(logLik())}. Setting it to \code{"REML"} will give the same results as \code{AIC(logLik(..., REML = TRUE))}.} diff --git a/tests/testthat/test-performance_aic.R b/tests/testthat/test-performance_aic.R index ee255d0da..dedfb58b8 100644 --- a/tests/testthat/test-performance_aic.R +++ b/tests/testthat/test-performance_aic.R @@ -27,4 +27,7 @@ test_that("performance_aic lme4 default", { m1 <- lme4::lmer(Sepal.Length ~ Petal.Length + (1 | Species), data = iris) expect_equal(performance_aic(m1), AIC(m1), tolerance = 1e-2) expect_equal(performance_aic(m1, estimator = "ML"), 125.0043, tolerance = 1e-2) + m2 <- lme4::lmer(Sepal.Length ~ Petal.Length + (1 | Species), data = iris, REML = FALSE) + expect_equal(performance_aic(m2, estimator = "REML"), AIC(m2), tolerance = 1e-2) + expect_message(performance_aic(m2), regex = "was not fitted") })