diff --git a/DESCRIPTION b/DESCRIPTION index a6505e2a4..40a31d936 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: insight Title: Easy Access to Model Information for Various Model Objects -Version: 1.4.2.4 +Version: 1.4.2.5 Authors@R: c(person(given = "Daniel", family = "Lüdecke", diff --git a/NEWS.md b/NEWS.md index 35c4629b3..1ffc23147 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,12 @@ * Better formatting for p-values-columns from equivalence tests from other packages. +* `format_value()`, `format_number()`, and `export_table()` gain a `big_mark` + argument to format numbers with thousands separators (e.g., `1,234,567.89` or + `1 234 567.89`). This makes large numbers more readable in tables and formatted + output. When `big_mark` is provided, scientific notation is suppressed for + large numbers to show the full value with separators. + ## Bug fixes * Fix CRAN check failures. diff --git a/R/export_table.R b/R/export_table.R index 089d3a16e..466d50dd4 100644 --- a/R/export_table.R +++ b/R/export_table.R @@ -207,6 +207,7 @@ export_table <- function(x, align = NULL, by = NULL, zap_small = FALSE, + big_mark = NULL, table_width = "auto", remove_duplicates = FALSE, row_groups = NULL, @@ -267,6 +268,7 @@ export_table <- function(x, align = align, group_by = by, zap_small = zap_small, + big_mark = big_mark, empty_line = empty_line, indent_groups = indent_groups, row_groups = row_groups, @@ -511,6 +513,7 @@ print.insight_table <- function(x, ...) { align = NULL, group_by = NULL, zap_small = FALSE, + big_mark = NULL, empty_line = NULL, indent_groups = NULL, row_groups = NULL, @@ -549,7 +552,8 @@ print.insight_table <- function(x, ...) { if (is.numeric(i)) { out <- format_value(i, digits = digits, protect_integers = protect_integers, - missing = missing, width = col_width, zap_small = zap_small + missing = missing, width = col_width, zap_small = zap_small, + big_mark = big_mark ) } else { out <- i @@ -1386,15 +1390,16 @@ print.insight_table <- function(x, ...) { # -------------------------------------------------------- .format_html_table <- function( - final, - caption = NULL, - subtitle = NULL, - footer = NULL, - align = "center", - group_by = NULL, - row_groups = NULL, - column_groups = NULL, - ...) { + final, + caption = NULL, + subtitle = NULL, + footer = NULL, + align = "center", + group_by = NULL, + row_groups = NULL, + column_groups = NULL, + ... +) { check_if_installed("gt") if (is.null(align)) { diff --git a/R/format_value.R b/R/format_value.R index 985107fbb..eaa385e15 100644 --- a/R/format_value.R +++ b/R/format_value.R @@ -36,6 +36,9 @@ #' `"parens"` to wrap the number in parentheses. #' @param decimal_point Character string containing a single character that #' is used as decimal point in output conversions. +#' @param big_mark Character used as thousands separator. If `NULL` (default), +#' no thousands separator is used. Use `","` for comma separator or `" "` +#' for space separator. #' @param ... Arguments passed to or from other methods. #' #' @@ -52,6 +55,8 @@ #' format_value(c(0.0045, 0.12, 0.34), digits = "scientific2") #' format_value(c(0.045, 0.12, 0.34), lead_zero = FALSE) #' format_value(c(0.0045, 0.12, 0.34), decimal_point = ",") +#' format_value(c(1234567.89, 1234.56), big_mark = ",") +#' format_value(c(1234567.89, 1234.56), big_mark = " ") #' #' # default #' format_value(c(0.0045, 0.123, 0.345)) @@ -73,18 +78,21 @@ format_value <- function(x, ...) { #' @rdname format_value #' @export -format_value.data.frame <- function(x, - digits = 2, - protect_integers = FALSE, - missing = "", - width = NULL, - as_percent = FALSE, - zap_small = FALSE, - lead_zero = TRUE, - style_positive = "none", - style_negative = "hyphen", - decimal_point = getOption("OutDec"), - ...) { +format_value.data.frame <- function( + x, + digits = 2, + protect_integers = FALSE, + missing = "", + width = NULL, + as_percent = FALSE, + zap_small = FALSE, + lead_zero = TRUE, + style_positive = "none", + style_negative = "hyphen", + decimal_point = getOption("OutDec"), + big_mark = NULL, + ... +) { as.data.frame(sapply( x, format_value, @@ -98,6 +106,7 @@ format_value.data.frame <- function(x, style_positive = style_positive, style_negative = style_negative, decimal_point = decimal_point, + big_mark = big_mark, simplify = FALSE )) } @@ -105,18 +114,21 @@ format_value.data.frame <- function(x, #' @rdname format_value #' @export -format_value.numeric <- function(x, - digits = 2, - protect_integers = FALSE, - missing = "", - width = NULL, - as_percent = FALSE, - zap_small = FALSE, - lead_zero = TRUE, - style_positive = "none", - style_negative = "hyphen", - decimal_point = getOption("OutDec"), - ...) { +format_value.numeric <- function( + x, + digits = 2, + protect_integers = FALSE, + missing = "", + width = NULL, + as_percent = FALSE, + zap_small = FALSE, + lead_zero = TRUE, + style_positive = "none", + style_negative = "hyphen", + decimal_point = getOption("OutDec"), + big_mark = NULL, + ... +) { # check input style_positive <- validate_argument(style_positive, c("none", "plus", "space")) style_negative <- validate_argument(style_negative, c("hyphen", "minus", "parens")) @@ -129,6 +141,7 @@ format_value.numeric <- function(x, .width = width, .as_percent = as_percent, .zap_small = zap_small, + .big_mark = big_mark, ... ) } else { @@ -139,6 +152,7 @@ format_value.numeric <- function(x, .width = width, .as_percent = as_percent, .zap_small = zap_small, + .big_mark = big_mark, ... ) } @@ -205,12 +219,16 @@ format_percent <- function(x, ...) { } -.format_value_unless_integer <- function(x, - digits = 2, - .missing = "", - .width = NULL, - .as_percent = FALSE, - .zap_small = FALSE, ...) { +.format_value_unless_integer <- function( + x, + digits = 2, + .missing = "", + .width = NULL, + .as_percent = FALSE, + .zap_small = FALSE, + .big_mark = NULL, + ... +) { x_nonmiss <- x[!is.na(x)] if (is.numeric(x) && !all(.is_integer(x_nonmiss))) { .format_value( @@ -219,41 +237,66 @@ format_percent <- function(x, ...) { .missing = .missing, .width = .width, .as_percent = .as_percent, - .zap_small = .zap_small + .zap_small = .zap_small, + .big_mark = .big_mark ) } else if (anyNA(x)) { .convert_missing(x, .missing) } else if (is.numeric(x) && all(.is_integer(x_nonmiss)) && !is.null(.width)) { format(x, justify = "right", width = .width) } else { - as.character(x) + # For integers, apply big_mark if requested + out <- as.character(x) + if (!is.null(.big_mark) && !identical(.big_mark, "")) { + needs_big_mark <- !is.na(out) & out != .missing + if (any(needs_big_mark)) { + out[needs_big_mark] <- prettyNum( + as.numeric(out[needs_big_mark]), + big.mark = .big_mark, + preserve.width = "none", + scientific = FALSE + ) + } + } + out } } -.format_value <- function(x, - digits = 2, - .missing = "", - .width = NULL, - .as_percent = FALSE, - .zap_small = FALSE, - ...) { +.format_value <- function( + x, + digits = 2, + .missing = "", + .width = NULL, + .as_percent = FALSE, + .zap_small = FALSE, + .big_mark = NULL, + ... +) { # proper character NA - if (is.na(.missing)) .missing <- NA_character_ + if (is.na(.missing)) { + .missing <- NA_character_ + } # sometimes, digits can be `NULL` - sanity check if (is.null(digits)) { digits <- 2 } + use_big_mark <- !is.null(.big_mark) && !identical(.big_mark, "") + if (is.numeric(x)) { if (isTRUE(.as_percent)) { need_sci <- (abs(100 * x) >= 1e+5 | (log10(abs(100 * x)) < -digits)) & x != 0 - if (.zap_small) { + # When big_mark is provided, suppress scientific notation for percentages too + if (.zap_small || use_big_mark) { x <- ifelse(is.na(x), .missing, sprintf("%.*f%%", digits, 100 * x)) } else { - x <- ifelse(is.na(x), .missing, - ifelse(need_sci, # nolint + x <- ifelse( + is.na(x), + .missing, + ifelse( + need_sci, sprintf("%.*e%%", digits, 100 * x), sprintf("%.*f%%", digits, 100 * x) ) @@ -266,7 +309,9 @@ format_percent <- function(x, ...) { 5 } ) - if (is.na(digits)) digits <- 5 + if (is.na(digits)) { + digits <- 5 + } x <- sprintf("%.*e", digits, x) } else if (is.character(digits) && grepl("signif", digits, fixed = TRUE)) { digits <- tryCatch( @@ -275,21 +320,58 @@ format_percent <- function(x, ...) { NA } ) - if (is.na(digits)) digits <- 3 + if (is.na(digits)) { + digits <- 3 + } x <- as.character(signif(x, digits)) } else { + # When big_mark is provided, suppress scientific notation for large numbers + # since the user wants to see the full number with thousands separators need_sci <- (abs(x) >= 1e+5 | (log10(abs(x)) < -digits)) & x != 0 - if (.zap_small) { + if (.zap_small || use_big_mark) { x <- ifelse(is.na(x), .missing, sprintf("%.*f", digits, x)) } else { - x <- ifelse(is.na(x), .missing, - ifelse(need_sci, # nolint + x <- ifelse( + is.na(x), + .missing, + ifelse( + need_sci, # nolint sprintf("%.*e", digits, x), sprintf("%.*f", digits, x) ) ) } } + + # Apply thousands separator if requested + # Only apply to non-scientific notation values + if (!is.null(.big_mark) && !identical(.big_mark, "") && is.character(x)) { + # Don't apply to scientific notation (contains 'e') + needs_big_mark <- !grepl("e", x, fixed = TRUE) & !is.na(x) & x != .missing + if (any(needs_big_mark)) { + # For percentages, we need to handle them specially + has_percent <- grepl("%", x, fixed = TRUE) + + # Apply big_mark to non-percentage values directly + if (any(needs_big_mark & !has_percent)) { + x[needs_big_mark & !has_percent] <- prettyNum( + x[needs_big_mark & !has_percent], + big.mark = .big_mark, + preserve.width = "none" + ) + } + + # For percentages, extract the number, apply big_mark, then add % back + if (any(needs_big_mark & has_percent)) { + selector <- needs_big_mark & has_percent + num_parts <- gsub("%", "", x[selector], fixed = TRUE) + x[selector] <- paste0( + prettyNum(num_parts, big.mark = .big_mark, preserve.width = "none"), + "%" + ) + } + } + } } else if (anyNA(x)) { x <- .convert_missing(x, .missing) } diff --git a/R/get_predicted_bayesian.R b/R/get_predicted_bayesian.R index 7f4476788..201d13ce0 100644 --- a/R/get_predicted_bayesian.R +++ b/R/get_predicted_bayesian.R @@ -4,16 +4,17 @@ #' @rdname get_predicted #' @export get_predicted.stanreg <- function( - x, - data = NULL, - predict = "expectation", - iterations = NULL, - ci = NULL, - ci_method = NULL, - include_random = "default", - include_smooth = TRUE, - verbose = TRUE, - ...) { + x, + data = NULL, + predict = "expectation", + iterations = NULL, + ci = NULL, + ci_method = NULL, + include_random = "default", + include_smooth = TRUE, + verbose = TRUE, + ... +) { check_if_installed("rstantools") if (is.null(ci_method)) { diff --git a/R/n_obs.R b/R/n_obs.R index cbe4d4236..f56aaff24 100644 --- a/R/n_obs.R +++ b/R/n_obs.R @@ -48,7 +48,8 @@ n_obs.default <- function(x, ...) { }, error = function(e) { FALSE - }) + } + ) if (isTRUE(is_binomial)) { return(n_obs.glm(x, ...)) @@ -68,7 +69,8 @@ n_obs.glm <- function(x, disaggregate = FALSE, ...) { }, error = function(e) { FALSE - }) + } + ) .nobs <- stats::nobs(x) diff --git a/man/export_table.Rd b/man/export_table.Rd index 3c0f97b33..c5426465a 100644 --- a/man/export_table.Rd +++ b/man/export_table.Rd @@ -23,6 +23,7 @@ export_table( align = NULL, by = NULL, zap_small = FALSE, + big_mark = NULL, table_width = "auto", remove_duplicates = FALSE, row_groups = NULL, @@ -117,6 +118,10 @@ frames. See also \code{row_groups} to group rows in the printed output.} \code{digits} decimal places. If \code{FALSE}, values with more decimal places than \code{digits} are printed in scientific notation.} +\item{big_mark}{Character used as thousands separator. If \code{NULL} (default), +no thousands separator is used. Use \code{","} for comma separator or \code{" "} +for space separator.} + \item{table_width}{Numeric,\code{"auto"}, \code{NULL} or \code{Inf}, indicating the width of the complete table. \itemize{ diff --git a/man/format_value.Rd b/man/format_value.Rd index bb06bdc59..5cff6abc6 100644 --- a/man/format_value.Rd +++ b/man/format_value.Rd @@ -21,6 +21,7 @@ format_value(x, ...) style_positive = "none", style_negative = "hyphen", decimal_point = getOption("OutDec"), + big_mark = NULL, ... ) @@ -36,6 +37,7 @@ format_value(x, ...) style_positive = "none", style_negative = "hyphen", decimal_point = getOption("OutDec"), + big_mark = NULL, ... ) @@ -84,6 +86,10 @@ May be \code{"hyphen"} (default), \code{"minus"} for a proper Unicode minus symb \item{decimal_point}{Character string containing a single character that is used as decimal point in output conversions.} + +\item{big_mark}{Character used as thousands separator. If \code{NULL} (default), +no thousands separator is used. Use \code{","} for comma separator or \code{" "} +for space separator.} } \value{ A formatted string. @@ -104,6 +110,8 @@ format_value(c(0.0045, 0.12, 0.34), digits = "scientific") format_value(c(0.0045, 0.12, 0.34), digits = "scientific2") format_value(c(0.045, 0.12, 0.34), lead_zero = FALSE) format_value(c(0.0045, 0.12, 0.34), decimal_point = ",") +format_value(c(1234567.89, 1234.56), big_mark = ",") +format_value(c(1234567.89, 1234.56), big_mark = " ") # default format_value(c(0.0045, 0.123, 0.345)) diff --git a/tests/testthat/test-betareg.R b/tests/testthat/test-betareg.R index 0c7fe9239..584e4f0ff 100644 --- a/tests/testthat/test-betareg.R +++ b/tests/testthat/test-betareg.R @@ -1,4 +1,5 @@ skip_if_not_installed("betareg") +skip_if_not_installed("statmod") data("GasolineYield", package = "betareg") data("FoodExpenditure", package = "betareg") diff --git a/tests/testthat/test-export_table.R b/tests/testthat/test-export_table.R index 4a5ceb179..46f15cc41 100644 --- a/tests/testthat/test-export_table.R +++ b/tests/testthat/test-export_table.R @@ -13,15 +13,21 @@ test_that("export_table", { test_that("export_table", { out <- export_table(d, format = "md") - expect_equal(out, structure( - c( - "| a| b|", "|------:|-----:|", - "| 1.30| ab|", "| 2.00| cd|", - "| 543.00| abcde|" + expect_equal( + out, + structure( + c( + "| a| b|", + "|------:|-----:|", + "| 1.30| ab|", + "| 2.00| cd|", + "| 543.00| abcde|" + ), + format = "pipe", + class = c("knitr_kable", "character") ), - format = "pipe", - class = c("knitr_kable", "character") - ), ignore_attr = TRUE) + ignore_attr = TRUE + ) }) d <- data.frame(a = c(1.3, 2, 543), b = c("ab", "cd", "abcde"), stringsAsFactors = FALSE) @@ -33,8 +39,13 @@ test_that("export_table", { out, structure( c( - "Table: Table Title", "", "| a| b|", "|------:|-----:|", - "| 1.30| ab|", "| 2.00| cd|", "| 543.00| abcde|" + "Table: Table Title", + "", + "| a| b|", + "|------:|-----:|", + "| 1.30| ab|", + "| 2.00| cd|", + "| 543.00| abcde|" ), format = "pipe", class = c("knitr_kable", "character") @@ -52,8 +63,13 @@ test_that("export_table", { out, structure( c( - "Table: Table Title", "", "| a| b|", "|------:|-----:|", - "| 1.30| ab|", "| 2.00| cd|", "| 543.00| abcde|" + "Table: Table Title", + "", + "| a| b|", + "|------:|-----:|", + "| 1.30| ab|", + "| 2.00| cd|", + "| 543.00| abcde|" ), format = "pipe", class = c("knitr_kable", "character") @@ -70,8 +86,13 @@ test_that("export_table", { out, structure( c( - "Table: Table Title", "", "| a| b|", "|------:|-----:|", - "| 1.30| ab|", "| 2.00| cd|", "| 543.00| abcde|" + "Table: Table Title", + "", + "| a| b|", + "|------:|-----:|", + "| 1.30| ab|", + "| 2.00| cd|", + "| 543.00| abcde|" ), format = "pipe", class = c("knitr_kable", "character") @@ -90,9 +111,16 @@ test_that("export_table", { out, structure( c( - "Table: Table Title", "", "| a| b|", "|------:|-----:|", - "| 1.30| ab|", "| 2.00| cd|", "| 543.00| abcde|", - "first", "second", "third" + "Table: Table Title", + "", + "| a| b|", + "|------:|-----:|", + "| 1.30| ab|", + "| 2.00| cd|", + "| 543.00| abcde|", + "first", + "second", + "third" ), format = "pipe", class = c("knitr_kable", "character") @@ -122,7 +150,10 @@ test_that("export_table, table_width", { lm1 <- lm(Sepal.Length ~ Species, data = iris) lm2 <- lm(Sepal.Length ~ Species + Petal.Length, data = iris) lm3 <- lm(Sepal.Length ~ Species * Petal.Length, data = iris) - lm6 <- lm5 <- lm4 <- lm(Sepal.Length ~ Species * Petal.Length + Petal.Width, data = iris) + lm6 <- lm5 <- lm4 <- lm( + Sepal.Length ~ Species * Petal.Length + Petal.Width, + data = iris + ) tab <- parameters::compare_parameters(lm1, lm2, lm3, lm4, lm5, lm6) expect_snapshot(print(tab, ci_digits = 2, table_width = 80), variant = "windows") @@ -149,7 +180,10 @@ test_that("export_table, table_width, no split", { lm1 <- lm(Sepal.Length ~ Species, data = iris) lm2 <- lm(Sepal.Length ~ Species + Petal.Length, data = iris) lm3 <- lm(Sepal.Length ~ Species * Petal.Length, data = iris) - lm6 <- lm5 <- lm4 <- lm(Sepal.Length ~ Species * Petal.Length + Petal.Width, data = iris) + lm6 <- lm5 <- lm4 <- lm( + Sepal.Length ~ Species * Petal.Length + Petal.Width, + data = iris + ) tab <- parameters::compare_parameters(lm1, lm2, lm3, lm4, lm5, lm6) expect_snapshot(print(tab, table_width = NULL), variant = "windows") @@ -162,24 +196,82 @@ test_that("export_table, table_width, remove duplicated empty lines", { out <- datawizard::data_codebook(efc) out$.row_id <- NULL expect_snapshot(print(export_table(out, table_width = 60, remove_duplicates = FALSE))) - expect_snapshot(print(export_table(out, table_width = 60, empty_line = "-", remove_duplicates = FALSE))) - expect_snapshot(print(export_table(out, table_width = 60, empty_line = "-", sep = " | ", remove_duplicates = FALSE))) - expect_snapshot(print(export_table(out, table_width = 60, empty_line = "-", cross = "+", remove_duplicates = FALSE))) + expect_snapshot(print(export_table( + out, + table_width = 60, + empty_line = "-", + remove_duplicates = FALSE + ))) + expect_snapshot(print(export_table( + out, + table_width = 60, + empty_line = "-", + sep = " | ", + remove_duplicates = FALSE + ))) + expect_snapshot(print(export_table( + out, + table_width = 60, + empty_line = "-", + cross = "+", + remove_duplicates = FALSE + ))) # don't remove duplicates expect_snapshot(print(export_table(out, table_width = 60, remove_duplicates = TRUE))) - expect_snapshot(print(export_table(out, table_width = 60, empty_line = "-", remove_duplicates = TRUE))) - expect_snapshot(print(export_table(out, table_width = 60, empty_line = "-", sep = " | ", remove_duplicates = TRUE))) - expect_snapshot(print(export_table(out, table_width = 60, empty_line = "-", cross = "+", remove_duplicates = TRUE))) + expect_snapshot(print(export_table( + out, + table_width = 60, + empty_line = "-", + remove_duplicates = TRUE + ))) + expect_snapshot(print(export_table( + out, + table_width = 60, + empty_line = "-", + sep = " | ", + remove_duplicates = TRUE + ))) + expect_snapshot(print(export_table( + out, + table_width = 60, + empty_line = "-", + cross = "+", + remove_duplicates = TRUE + ))) data(efc_insight, package = "insight") out <- datawizard::data_codebook(efc_insight[, 1:4]) out$.row_id <- NULL - expect_snapshot(print(export_table(out, table_width = 60, remove_duplicates = TRUE, empty_line = "-", cross = "+"))) - expect_snapshot(print(export_table(out, table_width = 60, remove_duplicates = FALSE, empty_line = "-", cross = "+"))) + expect_snapshot(print(export_table( + out, + table_width = 60, + remove_duplicates = TRUE, + empty_line = "-", + cross = "+" + ))) + expect_snapshot(print(export_table( + out, + table_width = 60, + remove_duplicates = FALSE, + empty_line = "-", + cross = "+" + ))) out <- datawizard::data_codebook(efc_insight[, 1:3]) out$.row_id <- NULL - expect_snapshot(print(export_table(out, table_width = 60, remove_duplicates = TRUE, empty_line = "-", cross = "+"))) - expect_snapshot(print(export_table(out, table_width = 60, remove_duplicates = FALSE, empty_line = "-", cross = "+"))) + expect_snapshot(print(export_table( + out, + table_width = 60, + remove_duplicates = TRUE, + empty_line = "-", + cross = "+" + ))) + expect_snapshot(print(export_table( + out, + table_width = 60, + remove_duplicates = FALSE, + empty_line = "-", + cross = "+" + ))) }) @@ -202,7 +294,11 @@ test_that("export_table, overlengthy lines", { test_that("export_table, gt, simple", { skip_if_not_installed("gt") skip_on_cran() - d <- data.frame(a = c(1.3, 2, 543), b = c("ab", "cd", "abcde"), stringsAsFactors = FALSE) + d <- data.frame( + a = c(1.3, 2, 543), + b = c("ab", "cd", "abcde"), + stringsAsFactors = FALSE + ) attr(d, "table_caption") <- "Table Title" set.seed(123) out <- gt::as_raw_html(export_table(d, format = "html")) @@ -238,7 +334,8 @@ test_that("export_table, gt, complex with group indention", { cp <- parameters::compare_parameters(lm1, lm2, drop = "^\\(Intercept") set.seed(123) - out <- gt::as_raw_html(print_html(cp, + out <- gt::as_raw_html(print_html( + cp, select = "{estimate}{stars}|({se})", groups = list( Species = c( @@ -260,12 +357,12 @@ test_that("export_table, new column names", { x <- as.data.frame(iris[1:5, ]) out <- export_table(x, column_names = letters[1:5]) expect_identical( - strsplit(out, "\n")[[1]][1], + strsplit(out, "\n", fixed = TRUE)[[1]][1], " a | b | c | d | e" ) out <- export_table(x, column_names = c(Species = "a")) expect_identical( - strsplit(out, "\n")[[1]][1], + strsplit(out, "\n", fixed = TRUE)[[1]][1], "Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | a" ) @@ -360,6 +457,7 @@ test_that("export_table, tinytable with indented rows", { expect_snapshot(export_table(mp, format = "tt", table_width = Inf)) mp <- as.data.frame(format(parameters::model_parameters(model, drop = "^\\(Intercept"))) + # fmt: skip mp$groups <- c( "Engine", "Controls", "Controls", "Engine", "Engine", "Engine", "Controls", "Interactions", "Interactions" @@ -389,3 +487,30 @@ test_that("export_table, removing captions work", { out <- gt::as_raw_html(print_html(means, footer = "", caption = "")) expect_snapshot(as.character(out)) }) + +test_that("export_table with big_mark", { + # Test with comma separator + d <- data.frame( + x = c(1234.56, 9876543.21, 12.34), + y = c("a", "b", "c"), + stringsAsFactors = FALSE + ) + out <- export_table(d, big_mark = ",", format = "text") + expect_true(any(grepl("1,234.56", out, fixed = TRUE))) + expect_true(any(grepl("9,876,543.21", out, fixed = TRUE))) + + # Test with space separator + out <- export_table(d, big_mark = " ", format = "text") + expect_true(any(grepl("1 234.56", out, fixed = TRUE))) + expect_true(any(grepl("9 876 543.21", out, fixed = TRUE))) + + # Test with markdown format + out <- export_table(d, big_mark = ",", format = "md") + expect_true(any(grepl("1,234.56", out, fixed = TRUE))) + expect_true(any(grepl("9,876,543.21", out, fixed = TRUE))) + + # Test backward compatibility - no big_mark + out <- export_table(d, format = "text") + expect_true(any(grepl("1234.56", out, fixed = TRUE))) + expect_true(any(grepl("9.88e+06", out, fixed = TRUE))) +}) diff --git a/tests/testthat/test-format.R b/tests/testthat/test-format.R index ab6c6b717..4009d7f34 100644 --- a/tests/testthat/test-format.R +++ b/tests/testthat/test-format.R @@ -6,22 +6,45 @@ test_that("format_value", { expect_identical(format_value(0), "0.00") expect_identical(format_value(0, decimal_point = ","), "0,00") expect_identical(format_value(1234565789101112), "1.23e+15") - expect_identical(format_value(1234565789101112, protect_integers = TRUE), "1234565789101112") + expect_identical( + format_value(1234565789101112, protect_integers = TRUE), + "1234565789101112" + ) expect_identical(format_value(0.0000000123), "1.23e-08") expect_identical(format_value(0.0000000123, zap_small = TRUE), "0.00") expect_identical(format_value(0.0000000123, digits = 8), "0.00000001") - expect_identical(format_value(c(0.012, 0.45, -0.03), lead_zero = FALSE), c(".01", ".45", "-.03")) - expect_identical(format_value(c(1.012, 0.45, -0.03), lead_zero = FALSE), c("1.01", ".45", "-.03")) - expect_identical(format_value(c(1.012, 0.45, -0.03), lead_zero = FALSE, decimal_point = ","), c("1,01", ",45", "-,03")) - expect_identical(format_value(c(0.45, -0.03), style_positive = "plus"), c("+0.45", "-0.03")) - expect_identical(format_value(c(0.45, -0.03), style_positive = "plus", lead_zero = FALSE), c("+.45", "-.03")) + expect_identical( + format_value(c(0.012, 0.45, -0.03), lead_zero = FALSE), + c(".01", ".45", "-.03") + ) + expect_identical( + format_value(c(1.012, 0.45, -0.03), lead_zero = FALSE), + c("1.01", ".45", "-.03") + ) + expect_identical( + format_value(c(1.012, 0.45, -0.03), lead_zero = FALSE, decimal_point = ","), + c("1,01", ",45", "-,03") + ) + expect_identical( + format_value(c(0.45, -0.03), style_positive = "plus"), + c("+0.45", "-0.03") + ) + expect_identical( + format_value(c(0.45, -0.03), style_positive = "plus", lead_zero = FALSE), + c("+.45", "-.03") + ) expect_equal( format_value(as.factor(c("A", "B", "A"))), structure(c(1L, 2L, 1L), levels = c("A", "B"), class = "factor"), ignore_attr = TRUE ) expect_identical( - format_value(c(0.45, -0.03), style_positive = "plus", style_negative = "parens", lead_zero = FALSE), + format_value( + c(0.45, -0.03), + style_positive = "plus", + style_negative = "parens", + lead_zero = FALSE + ), c("+.45", "(.03)") ) expect_identical( @@ -41,26 +64,107 @@ test_that("format_value", { expect_identical(format_value(0.00045, digits = 4), "0.0004") }) +test_that("format_value with big_mark", { + # Test basic thousands separator with comma + expect_identical(format_value(1234.56, big_mark = ","), "1,234.56") + expect_identical(format_value(1234567.89, big_mark = ","), "1,234,567.89") + + # Test with space separator + expect_identical(format_value(1234.56, big_mark = " "), "1 234.56") + expect_identical(format_value(1234567.89, big_mark = " "), "1 234 567.89") + + # Test with protect_integers + expect_identical(format_value(1234, big_mark = ",", protect_integers = TRUE), "1,234") + expect_identical(format_value(1234.0, big_mark = ",", protect_integers = TRUE), "1,234") + + # Test with vectors + expect_identical( + format_value(c(1234.56, 987654.32, 12.34), big_mark = ","), + c("1,234.56", "987,654.32", "12.34") + ) + + # Test with negative numbers + expect_identical(format_value(-1234.56, big_mark = ","), "-1,234.56") + expect_identical(format_value(-1234567.89, big_mark = ","), "-1,234,567.89") + + # Test that NULL or empty string disables thousands separator (backward compatibility) + expect_identical(format_value(1234.56, big_mark = NULL), "1234.56") + expect_identical(format_value(1234.56, big_mark = ""), "1234.56") + + # Test very large numbers - when big_mark is provided, scientific notation is suppressed + # to show the full number with separators + expect_identical( + format_value(1234565789101112, big_mark = ","), + "1,234,565,789,101,112.00" + ) + + # Test that without big_mark, very large numbers still use scientific notation + expect_identical(format_value(1234565789101112), "1.23e+15") + + # Test with decimal_point and big_mark together + expect_identical(format_value(1234.56, big_mark = ",", decimal_point = ","), "1,234,56") + expect_identical(format_value(1234.56, big_mark = " ", decimal_point = ","), "1 234,56") + + # Test that percentages are not affected by big_mark (they have % sign) + # Percentages don't typically need thousands separators in their display + expect_identical(format_value(0.95, as_percent = TRUE, big_mark = ","), "95.00%") + expect_identical(format_value(12.345, as_percent = TRUE, big_mark = ","), "1,234.50%") +}) + +test_that("format_number with big_mark", { + # Test that format_number passes big_mark when textual=FALSE + expect_identical(format_number(1234.56, textual = FALSE, big_mark = ","), "1,234.56") + expect_identical( + format_number(1234567.89, textual = FALSE, big_mark = " "), + "1 234 567.89" + ) +}) + test_that("format_ci", { expect_identical( format_ci(c(123, 123, 123, 123), c(123, 12345, 123456, 123456789012), width = "auto"), - c("95% CI [123.00, 123.00]", "95% CI [123.00, 12345.00]", "95% CI [123.00, 1.23e+05]", "95% CI [123.00, 1.23e+11]") + c( + "95% CI [123.00, 123.00]", + "95% CI [123.00, 12345.00]", + "95% CI [123.00, 1.23e+05]", + "95% CI [123.00, 1.23e+11]" + ) ) expect_identical( - format_ci(c(123, 123, 123, 123), c(123, 12345, 123456, 123456789012), width = "auto", digits = 5), + format_ci( + c(123, 123, 123, 123), + c(123, 12345, 123456, 123456789012), + width = "auto", + digits = 5 + ), c( - "95% CI [123.00000, 123.00000]", "95% CI [123.00000, 12345.00000]", - "95% CI [123.00000, 1.23456e+05]", "95% CI [123.00000, 1.23457e+11]" + "95% CI [123.00000, 123.00000]", + "95% CI [123.00000, 12345.00000]", + "95% CI [123.00000, 1.23456e+05]", + "95% CI [123.00000, 1.23457e+11]" ) ) expect_identical( - format_ci(c(123, 123, 123, 123), c(123, 12345, 123456, 123456789012), width = "auto", digits = 0), - c("95% CI [123, 123]", "95% CI [123, 12345]", "95% CI [123, 1e+05]", "95% CI [123, 1e+11]") + format_ci( + c(123, 123, 123, 123), + c(123, 12345, 123456, 123456789012), + width = "auto", + digits = 0 + ), + c( + "95% CI [123, 123]", + "95% CI [123, 12345]", + "95% CI [123, 1e+05]", + "95% CI [123, 1e+11]" + ) ) expect_identical(format_ci(1.24, 0.0000054), "95% CI [1.24, 5.40e-06]") expect_identical(format_ci(1.24, 0.0000054, digits = 0), "95% CI [1, 5e-06]") expect_identical(format_ci(1.24, 0.0000054, zap_small = TRUE), "95% CI [1.24, 0.00]") - expect_identical(format_ci(1.24, 0.0000054, zap_small = TRUE, digits = 0), "95% CI [1, 0]") + expect_identical( + format_ci(1.24, 0.0000054, zap_small = TRUE, digits = 0), + "95% CI [1, 0]" + ) }) test_that("format_ci, bayestestR", { @@ -78,7 +182,9 @@ test_that("format_ci, parameters", { expect_identical( format_ci(parameters::ci(mm)), c( - "95% CI [4.62, 4.94]", "95% CI [0.53, 1.30]", "95% CI [-0.52, 0.40]", + "95% CI [4.62, 4.94]", + "95% CI [0.53, 1.30]", + "95% CI [-0.52, 0.40]", "95% CI [-0.76, 0.66]" ) ) @@ -178,7 +284,13 @@ test_that("format_table, other CI columns", { attr(x, "ci") <- 0.8 attr(x, "ci_test") <- 0.9 test <- utils::capture.output(format_table(x)) - expect_identical(test, c(" 80% CI test 80% CI other 80% CI", "1 [2.43, 5.45] [0.10, 1.30] [0.12, 1.40]")) + expect_identical( + test, + c( + " 80% CI test 80% CI other 80% CI", + "1 [2.43, 5.45] [0.10, 1.30] [0.12, 1.40]" + ) + ) }) @@ -210,7 +322,6 @@ test_that("format_table, multiple CI columns", { ignore_attr = TRUE ) - # d <- data.frame( # Parameter = c("(Intercept)", "wt", "cyl"), # Coefficient = c(39.69, -3.19, -1.51), @@ -244,6 +355,9 @@ test_that("format_table, preserve attributes", { attr(d, "table_footer") <- "This is a footer" attr(d, "table_caption") <- "And the caption" d2 <- insight::format_table(d, digits = 3, preserve_attributes = TRUE) - expect_named(attributes(d2), c("names", "row.names", "class", "table_footer", "table_caption")) + expect_named( + attributes(d2), + c("names", "row.names", "class", "table_footer", "table_caption") + ) expect_identical(attributes(d2)$table_caption, "And the caption") }) diff --git a/tests/testthat/test-lcmm.R b/tests/testthat/test-lcmm.R index fbc85b1e2..28ac5bc75 100644 --- a/tests/testthat/test-lcmm.R +++ b/tests/testthat/test-lcmm.R @@ -68,7 +68,7 @@ test_that("find_formula", { expect_equal( find_formula(m3), - list(classmb = ~X1 + X2 + X3), + list(classmb = ~ X1 + X2 + X3), ignore_attr = TRUE ) })