diff --git a/DESCRIPTION b/DESCRIPTION index f34136fd9..1bfba562d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: parameters Title: Processing of Model Parameters -Version: 0.21.5.6 +Version: 0.21.5.7 Authors@R: c(person(given = "Daniel", family = "Lüdecke", @@ -182,7 +182,7 @@ Suggests: PMCMRplus, poorman, posterior, - PROreg, + PROreg (>= 1.3.0), pscl, psych, pvclust, @@ -199,7 +199,7 @@ Suggests: survival, testthat (>= 3.2.1), tidyselect, - tinytable, + tinytable (>= 0.1.0), TMB, truncreg, VGAM, diff --git a/NEWS.md b/NEWS.md index 970877c72..3e9bfe8f9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,13 @@ * `include_reference` can now directly be set to `TRUE` in `model_parameters()` and doesn't require a call to `print()` anymore. +* `compare_parameters()` gains a `include_reference` argument, to add the + reference category of categorical predictors to the parameters table. + +* `print_md()` for `compare_parameters()` now by default uses the *tinytable* + package to create markdown tables. This allows better control for column + heading spanning over multiple columns. + ## Bug fixes * Fixed issue with parameter names for `model_parameters()` and objects from diff --git a/R/compare_parameters.R b/R/compare_parameters.R index cf6285cb7..2c6fa7943 100644 --- a/R/compare_parameters.R +++ b/R/compare_parameters.R @@ -83,6 +83,8 @@ compare_parameters <- function(..., coefficient_names = NULL, keep = NULL, drop = NULL, + include_reference = FALSE, + groups = NULL, verbose = TRUE) { models <- list(...) @@ -186,6 +188,7 @@ compare_parameters <- function(..., keep = keep, drop = drop, wb_component = FALSE, + include_reference = include_reference, verbose = verbose ) } @@ -253,6 +256,7 @@ compare_parameters <- function(..., attr(all_models, "model_names") <- gsub("\"", "", unlist(lapply(model_names, insight::safe_deparse)), fixed = TRUE) attr(all_models, "output_style") <- select attr(all_models, "all_attributes") <- object_attributes + attr(all_models, "parameter_groups") <- groups class(all_models) <- c("compare_parameters", "see_compare_parameters", unique(class(all_models))) all_models diff --git a/R/display.R b/R/display.R index deb95ca05..ba45e6a66 100644 --- a/R/display.R +++ b/R/display.R @@ -53,7 +53,8 @@ #' experimental, thus, only a fixed layout-style is available at the moment #' (columns for estimates, confidence intervals and p-values). However, it #' is possible to include other model components, like zero-inflation, or random -#' effects in the table. See 'Examples'. +#' effects in the table. See 'Examples'. An alternative is to set `engine = "tt"` +#' in `print_html()` to use the _tinytable_ package for creating HTML tables. #' #' @seealso [print.parameters_model()] #' diff --git a/R/format.R b/R/format.R index c12e20a86..790489abf 100644 --- a/R/format.R +++ b/R/format.R @@ -253,6 +253,7 @@ format.compare_parameters <- function(x, zap_small = FALSE, format = NULL, groups = NULL, + engine = NULL, ...) { m_class <- attributes(x)$model_class x$Method <- NULL @@ -393,7 +394,7 @@ format.compare_parameters <- function(x, # group parameters - this function find those parameters that should be # grouped, reorders parameters into groups and indents lines that belong # to one group, adding a header for each group - if (!is.null(groups)) { + if (!is.null(groups) && !identical(engine, "tt")) { out <- .parameter_groups(out, groups) } indent_groups <- attributes(x)$indent_groups diff --git a/R/print.compare_parameters.R b/R/print.compare_parameters.R index 2bcdab704..8845d0701 100644 --- a/R/print.compare_parameters.R +++ b/R/print.compare_parameters.R @@ -31,6 +31,9 @@ print.compare_parameters <- function(x, if (missing(select)) { select <- attributes(x)$output_style } + if (missing(groups)) { + groups <- attributes(x)$parameter_groups + } formatted_table <- format( x, diff --git a/R/print.parameters_model.R b/R/print.parameters_model.R index b707ed422..422791258 100644 --- a/R/print.parameters_model.R +++ b/R/print.parameters_model.R @@ -137,6 +137,10 @@ #' default for the `select` argument. See argument's documentation for available #' options. #' +#' - `easystats_html_engine`: `options(easystats_html_engine = "gt")` will set +#' the default HTML engine for tables to `gt`, i.e. the _gt_ package is used to +#' create HTML tables. If set to `tt`, the _tinytable_ package is used. +#' #' @details `summary()` is a convenient shortcut for #' `print(object, select = "minimal", show_sigma = TRUE, show_formula = TRUE)`. #' @@ -308,10 +312,10 @@ print.parameters_model <- function(x, footer <- "" } if (!identical(footer, "")) { - if (!is.null(footer)) { - footer <- paste0("\n", footer, "\n", footer_stats) - } else { + if (is.null(footer)) { footer <- footer_stats + } else { + footer <- paste0("\n", footer, "\n", footer_stats) } } @@ -412,7 +416,7 @@ print.parameters_random <- function(x, digits = 2, ...) { show_formula = FALSE, format = "text") { # get attributes - sigma <- attributes(x)$sigma + model_sigma <- attributes(x)$sigma show_summary <- isTRUE(attributes(x)$show_summary) verbose <- .additional_arguments(x, "verbose", TRUE) @@ -423,7 +427,7 @@ print.parameters_random <- function(x, digits = 2, ...) { show_r2 <- .additional_arguments(x, "show_summary", FALSE) # set defaults, if necessary - if (is.null(sigma)) { + if (is.null(model_sigma)) { show_sigma <- FALSE } @@ -522,10 +526,10 @@ print.parameters_random <- function(x, digits = 2, ...) { random_params$Term[is.na(random_params$Term)] <- "" random_params$SD[is.na(random_params$SD)] <- "" - non_empty <- random_params$Term != "" & random_params$Type != "" + non_empty <- random_params$Term != "" & random_params$Type != "" # nolint random_params$Line[non_empty] <- sprintf("%s (%s)", random_params$Type[non_empty], random_params$Term[non_empty]) - non_empty <- random_params$Term != "" & random_params$Type == "" + non_empty <- random_params$Term != "" & random_params$Type == "" # nolint random_params$Line[non_empty] <- sprintf("%s", random_params$Term[non_empty]) # final fix, indentions @@ -567,11 +571,11 @@ print.parameters_random <- function(x, digits = 2, ...) { col_width <- rep(NA, length(shared_cols)) for (i in seq_along(shared_cols)) { col_width[i] <- max(unlist(lapply(formatted_table, function(j) { - col <- j[[shared_cols[i]]] - if (!is.null(col)) { - max(nchar(col)) - } else { + column <- j[[shared_cols[i]]] + if (is.null(column)) { NA + } else { + max(nchar(column)) } }))) } diff --git a/R/print_html.R b/R/print_html.R index 9b21d4180..d71aa194b 100644 --- a/R/print_html.R +++ b/R/print_html.R @@ -46,6 +46,9 @@ print_html.parameters_model <- function(x, if (missing(select) || is.null(select)) { select <- attributes(x)$output_style } + if (missing(groups)) { + groups <- attributes(x)$parameter_groups + } # we need glue-like syntax right now... if (!is.null(select)) { @@ -150,6 +153,7 @@ print_html.compare_parameters <- function(x, font_size = "100%", line_padding = 4, column_labels = NULL, + engine = "gt", ...) { # check if user supplied digits attributes if (missing(digits)) { @@ -169,6 +173,29 @@ print_html.compare_parameters <- function(x, select <- attributes(x)$output_style } + # markdown engine? + engine <- match.arg(getOption("easystats_html_engine", engine), c("gt", "default", "tt")) + + # for tiny table, we can just call print_md() + if (engine == "tt") { + return(print_md( + x, + digits = digits, + ci_digits = ci_digits, + p_digits = p_digits, + caption = caption, + subtitle = subtitle, + footer = footer, + select = select, + split_components = TRUE, + ci_brackets = ci_brackets, + zap_small = zap_small, + groups = groups, + engine = "tt", + outformat = "html" + )) + } + # we need glue-like syntax right now... select <- .convert_to_glue_syntax(style = select, "
") diff --git a/R/print_md.R b/R/print_md.R index 7d2237d3b..98a7adbec 100644 --- a/R/print_md.R +++ b/R/print_md.R @@ -91,10 +91,10 @@ print_md.parameters_model <- function(x, footer <- "" } if (!identical(footer, "")) { - if (!is.null(footer)) { - footer <- paste0("\n", footer, "\n", footer_stats) - } else { + if (is.null(footer)) { footer <- footer_stats + } else { + footer <- paste0("\n", footer, "\n", footer_stats) } } @@ -136,6 +136,7 @@ print_md.compare_parameters <- function(x, ci_brackets = c("(", ")"), zap_small = FALSE, groups = NULL, + engine = "tt", ...) { # check if user supplied digits attributes if (missing(digits)) { @@ -152,6 +153,12 @@ print_md.compare_parameters <- function(x, if (missing(select) || is.null(select)) { select <- attributes(x)$output_style } + if (missing(groups)) { + groups <- attributes(x)$parameter_groups + } + + # markdown engine? + engine <- match.arg(engine, c("tt", "default")) formatted_table <- format( x, @@ -164,16 +171,35 @@ print_md.compare_parameters <- function(x, ci_brackets = ci_brackets, format = "markdown", zap_small = zap_small, - groups = groups + groups = groups, + engine = engine ) - insight::export_table( - formatted_table, - format = "markdown", - caption = caption, - subtitle = subtitle, - footer = footer - ) + if (identical(engine, "tt")) { + # retrieve output format - print_md() may be called from print_html() + dots <- list(...) + if (identical(dots$outformat, "html")) { + outformat <- "html" + } else { + outformat <- "markdown" + } + .export_table_tt( + x, + formatted_table, + groups, + caption = caption, + footer = footer, + outformat = outformat + ) + } else { + insight::export_table( + formatted_table, + format = "markdown", + caption = caption, + subtitle = subtitle, + footer = footer + ) + } } @@ -262,14 +288,12 @@ print_md.equivalence_test_lm <- function(x, digits = 2, ci_brackets = c("(", ")" rule <- attributes(x)$rule rope <- attributes(x)$rope - if (!is.null(rule)) { - if (rule == "cet") { - table_caption <- "Conditional Equivalence Testing" - } else if (rule == "classic") { - table_caption <- "TOST-test for Practical Equivalence" - } else { - table_caption <- "Test for Practical Equivalence" - } + if (is.null(rule)) { + table_caption <- "Test for Practical Equivalence" + } else if (rule == "cet") { + table_caption <- "Conditional Equivalence Testing" + } else if (rule == "classic") { + table_caption <- "TOST-test for Practical Equivalence" } else { table_caption <- "Test for Practical Equivalence" } @@ -327,3 +351,100 @@ print_md.parameters_distribution <- function(x, digits = 2, ci_brackets = c("(", insight::export_table(formatted_table, format = "markdown", align = "firstleft", ...) } + + +# helper ----------------------- + +.export_table_tt <- function(x, formatted_table, groups, caption = NULL, footer = NULL, outformat = "markdown") { + insight::check_if_installed("tinytable", minimum_version = "0.1.0") + row_groups <- NULL + # check if we have a list of tables + if (!is.data.frame(formatted_table) && is.list(formatted_table) && length(formatted_table) > 1) { + # sanity check - cannot combine multiple tables when we have groups + if (!is.null(groups)) { + insight::format_error("Cannot combine multiple tables when groups are present.") + } + # add table caption as group variable, and bind tables + # we then extract row headers based on values in the group indices + formatted_table <- lapply(formatted_table, function(i) { + i$group <- attr(i, "table_caption") + i + }) + # bind tables + formatted_table <- do.call(rbind, formatted_table) + # find positions for sub headers + row_groups <- as.list(which(!duplicated(formatted_table$group))) + names(row_groups) <- formatted_table$group[unlist(row_groups)] + # remove no longer needed group variable + formatted_table$group <- NULL + } + # we need to find out which columns refer to which model, in order to + # add a column heading for each model + models <- attributes(x)$model_names + col_names <- gsub("(.*) \\((.*)\\)$", "\\2", colnames(formatted_table)) + col_groups <- sapply(models, function(i) which(i == col_names), simplify = FALSE) + # clean column names. These still contain the model name + colnames(formatted_table) <- gsub("(.*) \\((.*)\\)$", "\\1", colnames(formatted_table)) + # check if we have column spans at all? + if (all(lengths(col_groups) == 1)) { + col_groups <- NULL + } + # group rows? + if (!is.null(groups)) { + # make sure we have numeric indices for groups + groups <- lapply(groups, function(g) { + if (is.character(g)) { + # if groups were provided as parameter names, we find the row position + # by matching the parameter name + match(g, formatted_table$Parameter) + } else { + # else, we assume that the group is a row position + g + } + }) + # sanity check - do all rows match a parameter? + group_indices <- unlist(groups, use.names = FALSE) + if (anyNA(group_indices) || any(group_indices < 1) || any(group_indices > nrow(formatted_table))) { + insight::format_error("Some group indices do not match any parameter.") + } + # if row indices are not sorted, we need to resort the parameters data frame + if (is.unsorted(unlist(groups))) { + new_rows <- c(unlist(groups), setdiff(seq_len(nrow(formatted_table)), unlist(groups))) + formatted_table <- formatted_table[new_rows, ] + # we need to update indices in groups as well. Therefore, we need to convert + # list of row indices into a vector with row indices, then subtract the + # differences of old and new row positions, and then split that vector into + # a list again + groups <- stats::setNames(unlist(groups), rep(names(groups), lengths(groups))) + groups <- groups - (unlist(groups) - sort(unlist(groups))) + groups <- split(unname(groups), factor(names(groups), levels = unique(names(groups)))) + } + # find matching rows for groups + row_groups <- lapply(seq_along(groups), function(i) { + g <- groups[[i]] + if (is.character(g)) { + # if groups were provided as parameter names, we find the row position + # by matching the parameter name + g <- match(g, formatted_table$Parameter)[1] + } else { + # else, we assume that the group is a row position + g <- g[1] + } + g + }) + # set element names + names(row_groups) <- names(groups) + if (identical(outformat, "markdown")) { + # for markdown, format italic + names(row_groups) <- paste0("*", names(row_groups), "*") + } + } + # replace NA in formatted_table by "" + formatted_table[is.na(formatted_table)] <- "" + # create base table + out <- tinytable::tt(formatted_table, notes = footer, caption = caption) + # insert sub header rows and column spans + out <- tinytable::group_tt(out, i = row_groups, j = col_groups) + out@output <- outformat + out +} diff --git a/R/print_table.R b/R/print_table.R index 029f40c3a..d86399c09 100644 --- a/R/print_table.R +++ b/R/print_table.R @@ -154,20 +154,18 @@ print_table <- function(x, digits = 2, p_digits = 3, theme = "default", ...) { } # base table - out <- tinytable::tt(x, caption = NULL, notes = NULL, ...) + out <- tinytable::tt(as.data.frame(x), caption = NULL, notes = NULL, ...) # add subheaders, if any - if (!is.null(row_header_labels)) { + if (is.null(row_header_labels)) { + out <- tinytable::group_tt(out, j = col_groups) + } else { out <- tinytable::group_tt(out, i = row_header_labels, j = col_groups) out <- tinytable::style_tt(out, i = row_header_pos, italic = TRUE) - } else { - out <- tinytable::group_tt(out, j = col_groups) } # style table out <- insight::apply_table_theme(out, x, theme = theme, sub_header_positions = row_header_pos) - # workaround, to make sure HTML is default output - m <- attr(out, "tinytable_meta") - m$output <- "html" - attr(out, "tinytable_meta") <- m + # make sure HTML is default output + out@output <- "html" out } diff --git a/R/utils_format.R b/R/utils_format.R index 974e4da14..aba6a8381 100644 --- a/R/utils_format.R +++ b/R/utils_format.R @@ -53,7 +53,9 @@ # here we either have "
" or " " as line breaks, followed by empty "()" i <- gsub("
()", "", i, fixed = TRUE) i <- gsub(" ()", "", i, fixed = TRUE) + i <- gsub(" (, )", "", i, fixed = TRUE) i[i == "()"] <- "" + i[i == "(, )"] <- "" # remove other non-matched patterns i <- gsub("{stars}", "", i, fixed = TRUE) i <- gsub("{rhat}", "", i, fixed = TRUE) diff --git a/inst/WORDLIST b/inst/WORDLIST index d31a439cc..7637348ab 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -313,6 +313,7 @@ subscales systemfit th tidymodels +tinytable tseries unicode varEST diff --git a/man/compare_parameters.Rd b/man/compare_parameters.Rd index db1dbf328..0218c9bb0 100644 --- a/man/compare_parameters.Rd +++ b/man/compare_parameters.Rd @@ -20,6 +20,8 @@ compare_parameters( coefficient_names = NULL, keep = NULL, drop = NULL, + include_reference = FALSE, + groups = NULL, verbose = TRUE ) @@ -38,6 +40,8 @@ compare_models( coefficient_names = NULL, keep = NULL, drop = NULL, + include_reference = FALSE, + groups = NULL, verbose = TRUE ) } @@ -179,6 +183,22 @@ names.} \item{drop}{See \code{keep}.} +\item{include_reference}{Logical, if \code{TRUE}, the reference level of factors will +be added to the parameters table. This is only relevant for models with +categorical predictors. The coefficient for the reference level is always +\code{0} (except when \code{exponentiate = TRUE}, then the coefficient will be \code{1}), +so this is just for completeness.} + +\item{groups}{Named list, can be used to group parameters in the printed output. +List elements may either be character vectors that match the name of those +parameters that belong to one group, or list elements can be row numbers +of those parameter rows that should belong to one group. The names of the +list elements will be used as group names, which will be inserted as "header +row". A possible use case might be to emphasize focal predictors and control +variables, see 'Examples'. Parameters will be re-ordered according to the +order used in \code{groups}, while all non-matching parameters will be added +to the end.} + \item{verbose}{Toggle warnings and messages.} } \value{ diff --git a/man/display.parameters_model.Rd b/man/display.parameters_model.Rd index 1a36422f7..f351e0c63 100644 --- a/man/display.parameters_model.Rd +++ b/man/display.parameters_model.Rd @@ -305,7 +305,8 @@ which prints the output as a formatted HTML table. It is still somewhat experimental, thus, only a fixed layout-style is available at the moment (columns for estimates, confidence intervals and p-values). However, it is possible to include other model components, like zero-inflation, or random -effects in the table. See 'Examples'. +effects in the table. See 'Examples'. An alternative is to set \code{engine = "tt"} +in \code{print_html()} to use the \emph{tinytable} package for creating HTML tables. } \examples{ \dontshow{if (require("gt", quietly = TRUE)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} diff --git a/man/model_parameters.Rd b/man/model_parameters.Rd index 70e012556..d124d2cc9 100644 --- a/man/model_parameters.Rd +++ b/man/model_parameters.Rd @@ -374,6 +374,9 @@ will replace the interaction mark (by default, \code{*}) with the related charac \item \code{parameters_select}: \verb{options(parameters_select = )} will set the default for the \code{select} argument. See argument's documentation for available options. +\item \code{easystats_html_engine}: \code{options(easystats_html_engine = "gt")} will set +the default HTML engine for tables to \code{gt}, i.e. the \emph{gt} package is used to +create HTML tables. If set to \code{tt}, the \emph{tinytable} package is used. } } diff --git a/man/print.parameters_model.Rd b/man/print.parameters_model.Rd index 5b602b1d9..18bec59c7 100644 --- a/man/print.parameters_model.Rd +++ b/man/print.parameters_model.Rd @@ -185,6 +185,9 @@ will replace the interaction mark (by default, \code{*}) with the related charac \item \code{parameters_select}: \verb{options(parameters_select = )} will set the default for the \code{select} argument. See argument's documentation for available options. +\item \code{easystats_html_engine}: \code{options(easystats_html_engine = "gt")} will set +the default HTML engine for tables to \code{gt}, i.e. the \emph{gt} package is used to +create HTML tables. If set to \code{tt}, the \emph{tinytable} package is used. } } diff --git a/tests/testthat/_snaps/compare_parameters.md b/tests/testthat/_snaps/compare_parameters.md index dac4e1410..2e807d8ab 100644 --- a/tests/testthat/_snaps/compare_parameters.md +++ b/tests/testthat/_snaps/compare_parameters.md @@ -44,3 +44,127 @@ -------------------------------------------------------- SD (Intercept: persons) | | | 1.08 ( 0.49, 2.37) +# compare_parameters, print_md + + Code + print(out) + Output + +----------------+-----------------------+--------+----------------------+--------+ + | | lm1 | lm2 | + +----------------+-----------------------+--------+----------------------+--------+ + | Parameter | Estimate (ci) | p | Estimate (ci) | p | + +================+=======================+========+======================+========+ + | *Groups* | + +----------------+-----------------------+--------+----------------------+--------+ + | grp (2) | -4.31 (-15.95, 7.32) | 0.465 | 0.32 (-22.56, 23.20) | 0.978 | + +----------------+-----------------------+--------+----------------------+--------+ + | grp (3) | -1.31 (-13.47, 10.84) | 0.831 | 3.77 (-19.72, 27.26) | 0.752 | + +----------------+-----------------------+--------+----------------------+--------+ + | *Interactions* | + +----------------+-----------------------+--------+----------------------+--------+ + | Days * grp (2) | | | -1.01 (-5.35, 3.32) | 0.645 | + +----------------+-----------------------+--------+----------------------+--------+ + | Days * grp (3) | | | -1.11 (-5.53, 3.31) | 0.621 | + +----------------+-----------------------+--------+----------------------+--------+ + | *Controls* | + +----------------+-----------------------+--------+----------------------+--------+ + | Days | 10.44 (8.84, 12.03) | <0.001 | 11.23 (7.87, 14.60) | <0.001 | + +----------------+-----------------------+--------+----------------------+--------+ + | | | | | | + +----------------+-----------------------+--------+----------------------+--------+ + | Observations | 180 | | 180 | | + +----------------+-----------------------+--------+----------------------+--------+ + +--- + + Code + print_md(cp) + Output + +-------------------------+-----------------------+--------+----------------------+--------+ + | | lm1 | lm2 | + +-------------------------+-----------------------+--------+----------------------+--------+ + | Parameter | Estimate (ci) | p | Estimate (ci) | p | + +=========================+=======================+========+======================+========+ + | Fixed Effects | + +-------------------------+-----------------------+--------+----------------------+--------+ + | Days | 10.44 (8.84, 12.03) | <0.001 | 11.23 (7.87, 14.60) | <0.001 | + +-------------------------+-----------------------+--------+----------------------+--------+ + | grp (2) | -4.31 (-15.95, 7.32) | 0.465 | 0.32 (-22.56, 23.20) | 0.978 | + +-------------------------+-----------------------+--------+----------------------+--------+ + | grp (3) | -1.31 (-13.47, 10.84) | 0.831 | 3.77 (-19.72, 27.26) | 0.752 | + +-------------------------+-----------------------+--------+----------------------+--------+ + | Days * grp (2) | | | -1.01 (-5.35, 3.32) | 0.645 | + +-------------------------+-----------------------+--------+----------------------+--------+ + | Days * grp (3) | | | -1.11 (-5.53, 3.31) | 0.621 | + +-------------------------+-----------------------+--------+----------------------+--------+ + | Random Effects | + +-------------------------+-----------------------+--------+----------------------+--------+ + | SD (Intercept: Subject) | 37.06 (25.85, 53.13) | | 37.08 (25.85, 53.19) | | + +-------------------------+-----------------------+--------+----------------------+--------+ + | SD (Residual) | 31.13 (27.89, 34.75) | | 31.30 (28.02, 34.96) | | + +-------------------------+-----------------------+--------+----------------------+--------+ + +--- + + Code + print(out) + Output + +----------------+-----------------------+--------+----------------------+--------+ + | | lm1 | lm2 | + +----------------+-----------------------+--------+----------------------+--------+ + | Parameter | Estimate (ci) | p | Estimate (ci) | p | + +================+=======================+========+======================+========+ + | *Groups* | + +----------------+-----------------------+--------+----------------------+--------+ + | grp (1) | 0.00 | | 0.00 | | + +----------------+-----------------------+--------+----------------------+--------+ + | grp (2) | -4.31 (-15.95, 7.32) | 0.465 | 0.32 (-22.56, 23.20) | 0.978 | + +----------------+-----------------------+--------+----------------------+--------+ + | grp (3) | -1.31 (-13.47, 10.84) | 0.831 | 3.77 (-19.72, 27.26) | 0.752 | + +----------------+-----------------------+--------+----------------------+--------+ + | *Interactions* | + +----------------+-----------------------+--------+----------------------+--------+ + | Days * grp (2) | | | -1.01 (-5.35, 3.32) | 0.645 | + +----------------+-----------------------+--------+----------------------+--------+ + | Days * grp (3) | | | -1.11 (-5.53, 3.31) | 0.621 | + +----------------+-----------------------+--------+----------------------+--------+ + | *Controls* | + +----------------+-----------------------+--------+----------------------+--------+ + | Days | 10.44 (8.84, 12.03) | <0.001 | 11.23 (7.87, 14.60) | <0.001 | + +----------------+-----------------------+--------+----------------------+--------+ + | | | | | | + +----------------+-----------------------+--------+----------------------+--------+ + | Observations | 180 | | 180 | | + +----------------+-----------------------+--------+----------------------+--------+ + +--- + + Code + print(out) + Output + +----------------+-----------------------+----------------------+ + | Parameter | lm1 | lm2 | + +================+=======================+======================+ + | *Groups* | + +----------------+-----------------------+----------------------+ + | grp (1) | 0.00 | 0.00 | + +----------------+-----------------------+----------------------+ + | grp (2) | -4.31 (-15.95, 7.32) | 0.32 (-22.56, 23.20) | + +----------------+-----------------------+----------------------+ + | grp (3) | -1.31 (-13.47, 10.84) | 3.77 (-19.72, 27.26) | + +----------------+-----------------------+----------------------+ + | *Interactions* | + +----------------+-----------------------+----------------------+ + | Days * grp (2) | | -1.01 (-5.35, 3.32) | + +----------------+-----------------------+----------------------+ + | Days * grp (3) | | -1.11 (-5.53, 3.31) | + +----------------+-----------------------+----------------------+ + | *Controls* | + +----------------+-----------------------+----------------------+ + | Days | 10.44 (8.84, 12.03) | 11.23 (7.87, 14.60) | + +----------------+-----------------------+----------------------+ + | | | | + +----------------+-----------------------+----------------------+ + | Observations | 180 | 180 | + +----------------+-----------------------+----------------------+ + diff --git a/tests/testthat/test-compare_parameters.R b/tests/testthat/test-compare_parameters.R index 4c37549ff..fe261d8d9 100644 --- a/tests/testthat/test-compare_parameters.R +++ b/tests/testthat/test-compare_parameters.R @@ -1,117 +1,211 @@ +skip_if_not_installed("withr") + # make sure we have the correct interaction mark for tests -options(parameters_interaction = "*") - -data(iris) -m1 <- lm(Sepal.Length ~ Species, data = iris) -m2 <- lm(Sepal.Length ~ Species * Petal.Length, data = iris) -counts <- c(18, 17, 15, 20, 10, 20, 25, 13, 12) -outcome <- gl(3, 1, 9) -treatment <- gl(3, 3) -m3 <- glm(counts ~ outcome + treatment, family = poisson()) - -x <- compare_parameters(m1, m2, m3) -test_that("compare_parameters, default", { - expect_identical( - colnames(x), - c( - "Parameter", "Component", "Effects", "Coefficient.m1", "SE.m1", "CI.m1", - "CI_low.m1", "CI_high.m1", "t.m1", "df_error.m1", "p.m1", "Coefficient.m2", - "SE.m2", "CI.m2", "CI_low.m2", "CI_high.m2", "t.m2", "df_error.m2", - "p.m2", "Log-Mean.m3", "SE.m3", "CI.m3", "CI_low.m3", "CI_high.m3", - "z.m3", "df_error.m3", "p.m3" - ) - ) - out <- capture.output(x) - expect_length(out, 14) - out <- format(x, select = "ci") - expect_identical(colnames(out), c("Parameter", "m1", "m2", "m3")) - expect_identical( - out$Parameter, - c( - "(Intercept)", "Species (versicolor)", "Species (virginica)", - "Petal Length", "Species (versicolor) * Petal Length", - "Species (virginica) * Petal Length", "outcome (2)", "outcome (3)", - "treatment (2)", "treatment (3)", NA, "Observations" - ) - ) -}) - - -x <- compare_parameters(m1, m2, m3, select = "se_p2") -test_that("compare_parameters, se_p2", { - expect_identical( - colnames(x), - c( - "Parameter", "Component", "Effects", "Coefficient.m1", "SE.m1", "CI.m1", - "CI_low.m1", "CI_high.m1", "t.m1", "df_error.m1", "p.m1", "Coefficient.m2", - "SE.m2", "CI.m2", "CI_low.m2", "CI_high.m2", "t.m2", "df_error.m2", - "p.m2", "Log-Mean.m3", "SE.m3", "CI.m3", "CI_low.m3", "CI_high.m3", - "z.m3", "df_error.m3", "p.m3" - ) - ) - out <- capture.output(x) - expect_length(out, 14) - out <- format(x, select = "se_p2") - expect_identical( - colnames(out), - c( - "Parameter", "Estimate (SE) (m1)", "p (m1)", "Estimate (SE) (m2)", - "p (m2)", "Estimate (SE) (m3)", "p (m3)" - ) - ) - expect_identical( - out$Parameter, - c( - "(Intercept)", "Species (versicolor)", "Species (virginica)", - "Petal Length", "Species (versicolor) * Petal Length", - "Species (virginica) * Petal Length", "outcome (2)", "outcome (3)", - "treatment (2)", "treatment (3)", NA, "Observations" - ) - ) -}) - - -data(mtcars) -m1 <- lm(mpg ~ wt, data = mtcars) -m2 <- glm(vs ~ wt + cyl, data = mtcars, family = "binomial") - -test_that("compare_parameters, column name with escaping regex characters", { - out <- utils::capture.output(compare_parameters(m1, m2, column_names = c("linear model (m1)", "logistic reg. (m2)"))) - expect_identical(out[1], "Parameter | linear model (m1) | logistic reg. (m2)") -}) - - -data(mtcars) -m1 <- lm(mpg ~ hp, mtcars) -m2 <- lm(mpg ~ hp, mtcars) -test_that("compare_parameters, proper printing for CI=NULL #820", { - expect_snapshot(compare_parameters(m1, m2, ci = NULL)) -}) - - -skip_on_cran() - - -test_that("compare_parameters, correct random effects", { - skip_if_not_installed("glmmTMB") - skip_if_not(getRversion() >= "4.0.0") - - data("fish") - m0 <- glm(count ~ child + camper, data = fish, family = poisson()) - - m1 <- glmmTMB::glmmTMB( - count ~ child + camper + (1 | persons) + (1 | ID), - data = fish, - family = poisson() - ) - - m2 <- glmmTMB::glmmTMB( - count ~ child + camper + zg + (1 | ID), - ziformula = ~ child + (1 | persons), - data = fish, - family = glmmTMB::truncated_poisson() - ) - - cp <- compare_parameters(m0, m1, m2, effects = "all", component = "all") - expect_snapshot(cp) -}) +withr::with_options( + list(parameters_interaction = "*"), + { + data(iris) + m1 <- lm(Sepal.Length ~ Species, data = iris) + m2 <- lm(Sepal.Length ~ Species * Petal.Length, data = iris) + counts <- c(18, 17, 15, 20, 10, 20, 25, 13, 12) + outcome <- gl(3, 1, 9) + treatment <- gl(3, 3) + m3 <- glm(counts ~ outcome + treatment, family = poisson()) + + x <- compare_parameters(m1, m2, m3) + test_that("compare_parameters, default", { + expect_identical( + colnames(x), + c( + "Parameter", "Component", "Effects", "Coefficient.m1", "SE.m1", "CI.m1", + "CI_low.m1", "CI_high.m1", "t.m1", "df_error.m1", "p.m1", "Coefficient.m2", + "SE.m2", "CI.m2", "CI_low.m2", "CI_high.m2", "t.m2", "df_error.m2", + "p.m2", "Log-Mean.m3", "SE.m3", "CI.m3", "CI_low.m3", "CI_high.m3", + "z.m3", "df_error.m3", "p.m3" + ) + ) + out <- capture.output(x) + expect_length(out, 14) + out <- format(x, select = "ci") + expect_identical(colnames(out), c("Parameter", "m1", "m2", "m3")) + expect_identical( + out$Parameter, + c( + "(Intercept)", "Species (versicolor)", "Species (virginica)", + "Petal Length", "Species (versicolor) * Petal Length", + "Species (virginica) * Petal Length", "outcome (2)", "outcome (3)", + "treatment (2)", "treatment (3)", NA, "Observations" + ) + ) + }) + + + x <- compare_parameters(m1, m2, m3, select = "se_p2") + test_that("compare_parameters, se_p2", { + expect_identical( + colnames(x), + c( + "Parameter", "Component", "Effects", "Coefficient.m1", "SE.m1", "CI.m1", + "CI_low.m1", "CI_high.m1", "t.m1", "df_error.m1", "p.m1", "Coefficient.m2", + "SE.m2", "CI.m2", "CI_low.m2", "CI_high.m2", "t.m2", "df_error.m2", + "p.m2", "Log-Mean.m3", "SE.m3", "CI.m3", "CI_low.m3", "CI_high.m3", + "z.m3", "df_error.m3", "p.m3" + ) + ) + out <- capture.output(x) + expect_length(out, 14) + out <- format(x, select = "se_p2") + expect_identical( + colnames(out), + c( + "Parameter", "Estimate (SE) (m1)", "p (m1)", "Estimate (SE) (m2)", + "p (m2)", "Estimate (SE) (m3)", "p (m3)" + ) + ) + expect_identical( + out$Parameter, + c( + "(Intercept)", "Species (versicolor)", "Species (virginica)", + "Petal Length", "Species (versicolor) * Petal Length", + "Species (virginica) * Petal Length", "outcome (2)", "outcome (3)", + "treatment (2)", "treatment (3)", NA, "Observations" + ) + ) + }) + + + data(mtcars) + m1 <- lm(mpg ~ wt, data = mtcars) + m2 <- glm(vs ~ wt + cyl, data = mtcars, family = "binomial") + + test_that("compare_parameters, column name with escaping regex characters", { + out <- utils::capture.output(compare_parameters(m1, m2, column_names = c("linear model (m1)", "logistic reg. (m2)"))) + expect_identical(out[1], "Parameter | linear model (m1) | logistic reg. (m2)") + }) + + + data(mtcars) + m1 <- lm(mpg ~ hp, mtcars) + m2 <- lm(mpg ~ hp, mtcars) + test_that("compare_parameters, proper printing for CI=NULL #820", { + expect_snapshot(compare_parameters(m1, m2, ci = NULL)) + }) + + + skip_on_cran() + + + test_that("compare_parameters, correct random effects", { + skip_if_not_installed("glmmTMB") + skip_if_not(getRversion() >= "4.0.0") + + data("fish") + m0 <- glm(count ~ child + camper, data = fish, family = poisson()) + + m1 <- glmmTMB::glmmTMB( + count ~ child + camper + (1 | persons) + (1 | ID), + data = fish, + family = poisson() + ) + + m2 <- glmmTMB::glmmTMB( + count ~ child + camper + zg + (1 | ID), + ziformula = ~ child + (1 | persons), + data = fish, + family = glmmTMB::truncated_poisson() + ) + + cp <- compare_parameters(m0, m1, m2, effects = "all", component = "all") + expect_snapshot(cp) + }) + + + test_that("compare_parameters, print_md", { + skip_if_not_installed("lme4") + data(sleepstudy, package = "lme4") + set.seed(1234) + sleepstudy$grp <- as.factor(sample.int(3, nrow(sleepstudy), replace = TRUE)) + lm1 <- lme4::lmer(Reaction ~ Days + grp + (1 | Subject), data = sleepstudy) + lm2 <- lme4::lmer(Reaction ~ Days * grp + (1 | Subject), data = sleepstudy) + + cp <- compare_parameters(lm1, lm2, select = "{estimate} ({ci})|{p}", drop = "^\\(Intercept") + out <- print_md(cp, groups = list( + Groups = c("grp (2)", "grp (3)"), + Interactions = c("Days * grp (2)", "Days * grp (3)"), + Controls = "Days" + )) + expect_snapshot(print(out)) + + cp <- compare_parameters(lm1, lm2, select = "{estimate} ({ci})|{p}", drop = "^\\(Intercept", effects = "all") + expect_snapshot(print_md(cp)) + + # error + expect_error( + print_md(cp, groups = list( + Groups = c("grp (2)", "grp (3)"), + Interactions = c("Days * grp (2)", "Days * grp (3)"), + Controls = "Days" + )), + regex = "Cannot combine" + ) + + # with reference level + cp <- compare_parameters(lm1, lm2, select = "{estimate} ({ci})|{p}", drop = "^\\(Intercept", include_reference = TRUE) + out <- print_md(cp, groups = list( + Groups = 2:4, + Interactions = 5:6, + Controls = 1 + )) + expect_snapshot(print(out)) + + # with reference level + cp <- compare_parameters(lm1, lm2, drop = "^\\(Intercept", include_reference = TRUE) + out <- print_md(cp, groups = list( + Groups = 2:4, + Interactions = 5:6, + Controls = 1 + )) + expect_snapshot(print(out)) + + # error + cp <- compare_parameters(lm1, lm2, select = "{estimate} ({ci})|{p}", drop = "^\\(Intercept") + expect_error( + print_md(cp, groups = list( + Groups = c("grp (2)", "grp (3)"), + Interactions = c("Days * grp (2)", "Days * grp (3)"), + Controls = "XDays" + )), + regex = "Some group indices" + ) + expect_error( + print_md(cp, groups = list( + Groups = 1:2, + Interactions = 4:5, + Controls = 10 + )), + regex = "Some group indices" + ) + + # output identical for both calls + cp1 <- compare_parameters(lm1, lm2, select = "{estimate} ({ci})|{p}", drop = "^\\(Intercept") + out1 <- capture.output(print_md(cp1, groups = list( + Groups = c("grp (2)", "grp (3)"), + Interactions = c("Days * grp (2)", "Days * grp (3)"), + Controls = "Days" + ))) + cp2 <- compare_parameters( + lm1, + lm2, + select = "{estimate} ({ci})|{p}", + drop = "^\\(Intercept", + groups = list( + Groups = c("grp (2)", "grp (3)"), + Interactions = c("Days * grp (2)", "Days * grp (3)"), + Controls = "Days" + ) + ) + out2 <- capture.output(print_md(cp2)) + expect_identical(out1, out2) + }) + } +) diff --git a/tests/testthat/test-model_parameters_df.R b/tests/testthat/test-model_parameters_df.R index 5ad60ce95..da8b44508 100644 --- a/tests/testthat/test-model_parameters_df.R +++ b/tests/testthat/test-model_parameters_df.R @@ -28,7 +28,7 @@ test_that("model_parameters.glm", { test_that("model_parameters.BBmm", { - skip_if_not_installed("PROreg") + skip_if_not_installed("PROreg", minimum_version = "1.3.0") set.seed(1234) # defining the parameters @@ -63,17 +63,17 @@ test_that("model_parameters.BBmm", { })) params <- suppressWarnings(model_parameters(model)) expect_equal(params$df_error, c(96, 96), tolerance = 1e-3) - expect_equal(params$CI_low, c(0.26363, -1.46645), tolerance = 1e-3) - expect_equal(params$p, c(0.00811, 0), tolerance = 1e-3) + expect_equal(params$CI_low, c(0.26366, -1.46628), tolerance = 1e-3) + expect_equal(params$p, c(0.00814, 0), tolerance = 1e-3) params <- suppressWarnings(model_parameters(model, ci_method = "normal")) expect_equal(params$df_error, c(Inf, Inf), tolerance = 1e-3) - expect_equal(params$CI_low, c(0.27359, -1.46136), tolerance = 1e-3) - expect_equal(params$p, c(0.00811, 0), tolerance = 1e-3) + expect_equal(params$CI_low, c(0.27313, -1.46119), tolerance = 1e-3) + expect_equal(params$p, c(0.00814, 0), tolerance = 1e-3) }) test_that("model_parameters.BBreg", { - skip_if_not_installed("PROreg") + skip_if_not_installed("PROreg", minimum_version = "1.3.0") set.seed(18) # we simulate a covariate, fix the paramters of the beta-binomial # distribution and simulate a response variable.