From dea17ad3c9cfb82f0fab6e6c58d0abdbc36aa575 Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Sun, 3 Dec 2023 09:33:25 -0800 Subject: [PATCH] Assign digits (#1575) * in progress * progress --- DESCRIPTION | 2 +- NAMESPACE | 10 +++ NEWS.md | 2 + R/assign_summary_digits.R | 132 ++++++++++++++++++++++++++++++++------ R/modify_column_hide.R | 2 +- R/select_helpers.R | 2 +- R/styfn.R | 47 ++++++++++++++ R/style_number.R | 59 +++++++++++++++++ R/style_percent.R | 34 ++++++++++ R/style_pvalue.R | 112 ++++++++++++++++++++++++++++++++ R/style_ratio.R | 38 +++++++++++ R/style_sigfig.R | 51 +++++++++++++++ R/tbl_summary.R | 17 +++-- R/utils-as.R | 2 +- man/styfn.Rd | 57 ++++++++++++++++ man/style_number.Rd | 52 +++++++++++++++ man/style_percent.Rd | 59 +++++++++++++++++ man/style_pvalue.Rd | 61 ++++++++++++++++++ man/style_ratio.Rd | 54 ++++++++++++++++ man/style_sigfig.Rd | 73 +++++++++++++++++++++ man/tbl_summary.Rd | 2 +- 21 files changed, 839 insertions(+), 29 deletions(-) create mode 100644 R/styfn.R create mode 100644 R/style_number.R create mode 100644 R/style_percent.R create mode 100644 R/style_pvalue.R create mode 100644 R/style_ratio.R create mode 100644 R/style_sigfig.R create mode 100644 man/styfn.Rd create mode 100644 man/style_number.Rd create mode 100644 man/style_percent.Rd create mode 100644 man/style_pvalue.Rd create mode 100644 man/style_ratio.Rd create mode 100644 man/style_sigfig.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 59407419c5..2a42a98a9c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -44,7 +44,7 @@ URL: https://github.com/ddsjoberg/gtsummary, BugReports: https://github.com/ddsjoberg/gtsummary/issues Imports: broom.helpers, - cards (>= 0.0.0.9002), + cards (>= 0.0.0.9003), cli (>= 3.6.1), dplyr (>= 1.1.3), glue (>= 1.6.2), diff --git a/NAMESPACE b/NAMESPACE index 9f27f52559..6a8cfdeab0 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -33,6 +33,16 @@ export(pier_summary_dichotomous) export(pier_summary_missing_row) export(select) export(starts_with) +export(styfn_number) +export(styfn_percent) +export(styfn_pvalue) +export(styfn_ratio) +export(styfn_sigfig) +export(style_number) +export(style_percent) +export(style_pvalue) +export(style_ratio) +export(style_sigfig) export(tbl_summary) export(vars) export(where) diff --git a/NEWS.md b/NEWS.md index 26ad87aa37..6fecdcc0ad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,8 @@ * If a column is all `NA` it is now removed from the summary table created with `tbl_summary()`. +* Added a family of function `styfn_*()` that are similar to the `style_*()` except they return a styling _function_, rather than a styled value. + * Previously, in a `tbl_summary()` variables that were `c(0, 1)`, `c("no", "yes")`, `c("No", "Yes")`, and `c("NO", "YES")` would default to a dichotomous summary with the `1` and `yes` level being shown in the table. This would occur even in the case when, for example, only `0` was observed. In this release, the line shown for dichotomous variables must be observed OR the unobserved level must be explicitly defined in a factor. #### Internal Updates diff --git a/R/assign_summary_digits.R b/R/assign_summary_digits.R index 57d77736df..cbebcb11a8 100644 --- a/R/assign_summary_digits.R +++ b/R/assign_summary_digits.R @@ -1,20 +1,116 @@ -# assign_summary_digits <- function(data, statistic, type, digits = NULL) { -# # extract the statistics -# statistic <- lapply(statistic, function(x) .extract_glue_elements(x) |> unlist()) -# -# lapply( -# names(statistic), -# function(variable) { -# if (!is.null(digits[[variable]])){ -# return(rep_named(statistic[[variable]], digits[[variable]])) -# } -# -# if (type[[variable]] %in% c("cateogrical", "dichotomous")) { -# -# } -# } -# ) -# -# } +assign_summary_digits <- function(data, statistic, type, digits = NULL) { + # stats returned for all variables + lst_cat_summary_fns <- .categorical_summary_functions(c("n", "p")) + lst_all_fmt_fns <- .categorical_summary_functions() + + # extract the statistics + statistic <- lapply(statistic, function(x) .extract_glue_elements(x) |> unlist()) + + lapply( + names(statistic), + function(variable) { + # if user passed digits AND they've specified every statistic, use the passed value + # otherwise, we need to calculate the defaults, and later we can update with the pieces the user passed + if (!is.null(digits[[variable]])) { + # if a scalar or vector passed, convert it to a list + if (!is.list(digits[[variable]]) && is_vector(digits[[variable]])) { + digits[[variable]] <- as.list(digits[[variable]]) + } + + # if user-passed value is not named, repeat the passed value to the length of 'statistic' + if (!is_named(digits[[variable]])) { + digits[[variable]] <- rep_named(statistic[[variable]], digits[[variable]]) + } + + # convert integers to a proper function + digits[[variable]] <- .convert_integer_to_fmt_fn(digits[[variable]]) + + # if the passed value fully specifies the formatting for each 'statistic', + # then return it. Otherwise, the remaining stat will be filled below + if (setequal(statistic[[variable]], names(digits[[variable]]))) { + return(digits[[variable]]) + } + } + + if (type[[variable]] %in% c("categorical", "dichotomous")) { + return( + c(lst_cat_summary_fns, lst_all_fmt_fns) |> + utils::modifyList(digits[[variable]] %||% list()) + ) + } + + if (type[[variable]] %in% c("continuous", "continuous2")) { + return( + rep_named( + statistic[[variable]], + list(.guess_continuous_summary_digits(data[[variable]])) + ) |> + utils::modifyList(lst_all_fmt_fns) |> + utils::modifyList(digits[[variable]] %||% list()) + ) + } + } + ) |> + stats::setNames(names(statistic)) +} + +.convert_integer_to_fmt_fn <- function(x) { + imap( + x, + function(value, stat_name) { + # if not an integer, simply return the value + if (!is_integerish(value)) return(value) + # if an integer is passed for a percentage, process stat with style_percent() + if (stat_name %in% c("p", "p_miss", "p_nonmiss", "p_unweighted")) + return(styfn_percent(digits = value)) + # otherwise, use style_numer() to style number + return(styfn_number(digits = value)) + } + ) +} + +.guess_continuous_summary_digits <- function(x) { + # if all missing, return 0 + if (all(is.na(x))) return(styfn_number(digits = 0L)) + + # if class is integer, then round everything to nearest integer + if (inherits(x, "integer")) { + return(styfn_number(digits = 0L)) + } + + # otherwise guess the number of dignits to use based on the spread + # calculate the spread of the variable + var_spread <- + stats::quantile(x, probs = c(0.95), na.rm = TRUE) - + stats::quantile(x, probs = c(0.05), na.rm = TRUE) + + styfn_number( + digits = + dplyr::case_when( + var_spread < 0.01 ~ 4L, + var_spread >= 0.01 & var_spread < 0.1 ~ 3L, + var_spread >= 0.1 & var_spread < 10 ~ 2L, + var_spread >= 10 & var_spread < 20 ~ 1L, + var_spread >= 20 ~ 0L + ) + ) +} + +.categorical_summary_functions <- + function(statistics = c(" + N", "N_obs", "N_miss", "N_nonmiss", "n_unweighted", "N_unweighted", + "p_miss", "p_nonmiss", "p_unweighted")) { + lst_defaults <- + c( + c("n", "N", "N_obs", "N_miss", "N_nonmiss", "n_unweighted", "N_unweighted") |> + intersect(statistics) |> + rep_named(list(styfn_number())), + c("p", "p_miss", "p_nonmiss", "p_unweighted") |> + intersect(statistics) |> + rep_named(list(styfn_percent())) + ) + + lst_defaults + } diff --git a/R/modify_column_hide.R b/R/modify_column_hide.R index cfb0b3c170..ab81975dff 100644 --- a/R/modify_column_hide.R +++ b/R/modify_column_hide.R @@ -7,7 +7,7 @@ #' #' @name modify_column_hide #' @family Advanced modifiers -#' @examples +# #' @examples # #' \donttest{ # #' # Example 1 ---------------------------------- # #' # hide 95% CI, and replace with standard error diff --git a/R/select_helpers.R b/R/select_helpers.R index 6fca53ffdc..279428bc5c 100644 --- a/R/select_helpers.R +++ b/R/select_helpers.R @@ -23,7 +23,7 @@ #' @name select_helpers #' @return A character vector of column names selected #' @seealso Review [list, formula, and selector syntax][syntax] used throughout gtsummary -#' @examples +# #' @examples # #' select_ex1 <- # #' trial %>% # #' select(age, response, grade) %>% diff --git a/R/styfn.R b/R/styfn.R new file mode 100644 index 0000000000..e49d7aab54 --- /dev/null +++ b/R/styfn.R @@ -0,0 +1,47 @@ +#' Style Functions +#' +#' Similar to the `style_*()` family of functions, but these functions return +#' a `style_*()` **function** rather than performing the styling. +#' +#' @param digits,big.mark,decimal.mark,scale,prepend_p,symbol,... arguments +#' passed to the `style_*()` functions +#' +#' @return a function +#' @name styfn +#' @family style tools +#' +#' @examples +#' my_style <- styfn_number(digits = 1) +#' my_style(3.14) +NULL + +#' @rdname styfn +#' @export +styfn_number <- function(digits = 0, big.mark = NULL, decimal.mark = NULL, scale = 1, ...) { + function(x) style_number(x, digits = digits, big.mark = big.mark, decimal.mark = decimal.mark, scale = scale, ...) +} + +#' @rdname styfn +#' @export +styfn_sigfig <- function(digits = 2, scale = 1, big.mark = NULL, decimal.mark = NULL, ...) { + function(x) style_sigfig(x, digits = digits, scale = scale, big.mark = big.mark, decimal.mark = decimal.mark, ...) +} + +#' @rdname styfn +#' @export +styfn_pvalue <- function(digits = 1, prepend_p = FALSE, big.mark = NULL, decimal.mark = NULL, ...) { + function(x) styfn_pvalue(x, digits = digits, prepend_p = prepend_p, big.mark = big.mark, decimal.mark = decimal.mark, ...) +} + +#' @rdname styfn +#' @export +styfn_ratio <- function(digits = 2, big.mark = NULL, decimal.mark = NULL, ...) { + function(x) style_ratio(x, digits = digits, big.mark = big.mark, decimal.mark = decimal.mark, ...) +} + +#' @rdname styfn +#' @export +styfn_percent <- function(symbol = FALSE, digits = 0, big.mark = NULL, decimal.mark = NULL, ...) { + function(x) style_percent(x, symbol = symbol, digits = digits, big.mark = big.mark, decimal.mark = decimal.mark, ...) +} + diff --git a/R/style_number.R b/R/style_number.R new file mode 100644 index 0000000000..6181076943 --- /dev/null +++ b/R/style_number.R @@ -0,0 +1,59 @@ +#' Style numbers +#' +#' @param x Numeric vector +#' @param digits Integer or vector of integers specifying the number of decimals +#' to round `x=`. When vector is passed, each integer is mapped 1:1 to the +#' numeric values in `x` +#' @param big.mark Character used between every 3 digits to separate +#' hundreds/thousands/millions/etc. +#' Default is `","`, except when `decimal.mark = ","` when the default is a space. +#' @param decimal.mark The character to be used to indicate the numeric decimal point. +#' Default is `"."` or `getOption("OutDec")` +#' @param scale A scaling factor: x will be multiplied by scale before formatting. +#' @param ... Other arguments passed on to `base::format()` +#' +#' @return formatted character vector +#' @export +#' @family style tools +#' @examples +#' c(0.111, 12.3) %>% style_number(digits = 1) +#' c(0.111, 12.3) %>% style_number(digits = c(1, 0)) +style_number <- function(x, digits = 0, big.mark = NULL, decimal.mark = NULL, + scale = 1, ...) { + # setting defaults ----------------------------------------------------------- + decimal.mark <- + decimal.mark %||% + get_theme_element("style_number-arg:decimal.mark", + default = getOption("OutDec", default = ".") + ) + big.mark <- + big.mark %||% + get_theme_element("style_number-arg:big.mark", + # if decimal is a comma, then making big.mark a thin space, otherwise a comma + default = ifelse(identical(decimal.mark, ","), "\U2009", ",") + ) + + digits <- rep(digits, length.out = length(x)) + + ret <- + map2_chr( + x, digits, + function(.x, .y) { + round2(.x * scale, digits = .y) %>% + format( + big.mark = big.mark, decimal.mark = decimal.mark, nsmall = .y, + scientific = FALSE, trim = TRUE, ... + ) + } + ) + ret[is.na(x)] <- NA_character_ + attributes(ret) <- attributes(unclass(x)) + + ret +} + +# this function assures that 5s are rounded up (and not to even, the default in `round()`) +# code taken from https://github.com/sfirke/janitor/blob/main/R/round_half_up.R +round2 <- function(x, digits = 0) { + trunc(abs(x) * 10 ^ digits + 0.5 + sqrt(.Machine$double.eps)) / 10 ^ digits * sign(as.numeric(x)) +} diff --git a/R/style_percent.R b/R/style_percent.R new file mode 100644 index 0000000000..b0c1cb075d --- /dev/null +++ b/R/style_percent.R @@ -0,0 +1,34 @@ +#' Style percentages +#' +#' @param x numeric vector of percentages +#' @param digits number of digits to round large percentages (i.e. greater than 10%). +#' Smaller percentages are rounded to `digits + 1` places. +#' Default is `0` +#' @param symbol Logical indicator to include percent symbol in output. +#' Default is `FALSE`. +#' @inheritParams style_number +#' @export +#' @return A character vector of styled percentages +#' @family style tools +#' @seealso See Table Gallery \href{https://www.danieldsjoberg.com/gtsummary/articles/gallery.html}{vignette} for example +#' @author Daniel D. Sjoberg +#' @examples +#' percent_vals <- c(-1, 0, 0.0001, 0.005, 0.01, 0.10, 0.45356, 0.99, 1.45) +#' style_percent(percent_vals) +#' style_percent(percent_vals, symbol = TRUE, digits = 1) +style_percent <- function(x, symbol = FALSE, digits = 0, big.mark = NULL, decimal.mark = NULL, ...) { + y <- dplyr::case_when( + x * 100 >= 10 ~ style_number(x * 100, digits = digits, big.mark = big.mark, decimal.mark = decimal.mark, ...), + x * 100 >= 10^(-(digits + 1)) ~ style_number(x * 100, digits = digits + 1, big.mark = big.mark, decimal.mark = decimal.mark, ...), + x > 0 ~ paste0("<", style_number( + x = 10^(-(digits + 1)), digits = digits + 1, big.mark = big.mark, + decimal.mark = decimal.mark, ... + )), + x == 0 ~ "0" + ) + + # adding percent symbol if requested + if (symbol == TRUE) y <- ifelse(!is.na(y), paste0(y, "%"), y) + attributes(y) <- attributes(unclass(x)) + return(y) +} diff --git a/R/style_pvalue.R b/R/style_pvalue.R new file mode 100644 index 0000000000..1a634737d8 --- /dev/null +++ b/R/style_pvalue.R @@ -0,0 +1,112 @@ +#' Style p-values +#' +#' @param x Numeric vector of p-values. +#' @param digits Number of digits large p-values are rounded. Must be 1, 2, or 3. +#' Default is 1. +#' @param prepend_p Logical. Should 'p=' be prepended to formatted p-value. +#' Default is `FALSE` +#' @inheritParams style_number +#' @export +#' @return A character vector of styled p-values +#' @family style tools +#' @seealso See tbl_summary \href{https://www.danieldsjoberg.com/gtsummary/articles/tbl_summary.html}{vignette} for examples +#' @author Daniel D. Sjoberg +#' @examples +#' pvals <- c( +#' 1.5, 1, 0.999, 0.5, 0.25, 0.2, 0.197, 0.12, 0.10, 0.0999, 0.06, +#' 0.03, 0.002, 0.001, 0.00099, 0.0002, 0.00002, -1 +#' ) +#' style_pvalue(pvals) +#' style_pvalue(pvals, digits = 2, prepend_p = TRUE) +style_pvalue <- function(x, digits = 1, prepend_p = FALSE, + big.mark = NULL, decimal.mark = NULL, ...) { + # rounding large p-values to 1 digits + if (digits == 1) { + p_fmt <- + dplyr::case_when( + # allowing some leeway for numeric storage errors + x > 1 + 1e-15 ~ NA_character_, + x < 0 - 1e-15 ~ NA_character_, + x > 0.9 ~ paste0(">", style_number( + x = 0.9, digits = 1, big.mark = big.mark, + decimal.mark = decimal.mark, ... + )), + round2(x, 1) >= 0.2 ~ style_number(x, + digits = 1, big.mark = big.mark, + decimal.mark = decimal.mark, ... + ), + round2(x, 2) >= 0.1 ~ style_number(x, + digits = 2, big.mark = big.mark, + decimal.mark = decimal.mark, ... + ), + x >= 0.001 ~ style_number(x, + digits = 3, big.mark = big.mark, + decimal.mark = decimal.mark, ... + ), + x < 0.001 ~ paste0("<", style_number( + x = 0.001, digits = 3, big.mark = big.mark, + decimal.mark = decimal.mark, ... + )) + ) + } + # rounding large p-values to 2 digits + else if (digits == 2) { + p_fmt <- + dplyr::case_when( + x > 1 + 1e-15 ~ NA_character_, + x < 0 - 1e-15 ~ NA_character_, + x > 0.99 ~ paste0(">", style_number( + x = 0.99, digits = 2, big.mark = big.mark, + decimal.mark = decimal.mark, ... + )), + round2(x, 2) >= 0.1 ~ style_number(x, + digits = 2, big.mark = big.mark, + decimal.mark = decimal.mark, ... + ), + x >= 0.001 ~ style_number(x, + digits = 3, big.mark = big.mark, + decimal.mark = decimal.mark, ... + ), + x < 0.001 ~ paste0("<", style_number( + x = 0.001, digits = 3, big.mark = big.mark, + decimal.mark = decimal.mark, ... + )) + ) + } + + # rounding large pvalues to 3 digit + else if (digits == 3) { + p_fmt <- + dplyr::case_when( + x > 1 + 1e-15 ~ NA_character_, + x < 0 - 1e-15 ~ NA_character_, + x > 0.999 ~ paste0(">", style_number( + x = 0.999, digits = 3, big.mark = big.mark, + decimal.mark = decimal.mark, ... + )), + x >= 0.001 ~ style_number(x, + digits = 3, big.mark = big.mark, + decimal.mark = decimal.mark, ... + ), + x < 0.001 ~ paste0("<", style_number( + x = 0.001, digits = 3, big.mark = big.mark, + decimal.mark = decimal.mark, ... + )) + ) + } else { + stop("The `digits=` argument must be 1, 2, or 3.") + } + + # prepending a p = in front of value + if (prepend_p == TRUE) { + p_fmt <- dplyr::case_when( + is.na(p_fmt) ~ NA_character_, + grepl(pattern = "<|>", x = p_fmt) ~ paste0("p", p_fmt), + # stringr::str_sub(p_fmt, end = 1L) %in% c("<", ">") ~ paste0("p", p_fmt), + TRUE ~ paste0("p=", p_fmt) + ) + } + + attributes(p_fmt) <- attributes(unclass(x)) + return(p_fmt) +} diff --git a/R/style_ratio.R b/R/style_ratio.R new file mode 100644 index 0000000000..7d472b8464 --- /dev/null +++ b/R/style_ratio.R @@ -0,0 +1,38 @@ +#' Style significant figure-like rounding for ratios +#' +#' When reporting ratios, such as relative risk or an odds ratio, we'll often +#' want the rounding to be similar on each side of the number 1. For example, +#' if we report an odds ratio of 0.95 with a confidence interval of 0.70 to 1.24, +#' we would want to round to two decimal places for all values. In other words, +#' 2 significant figures for numbers less than 1 and 3 significant figures 1 and +#' larger. `style_ratio()` performs significant figure-like rounding in this manner. +#' +#' @param x Numeric vector +#' @param digits Integer specifying the number of significant +#' digits to display for numbers below 1. Numbers larger than 1 will be be `digits + 1`. +#' Default is `digits = 2`. +#' @inheritParams style_number +#' @export +#' @return A character vector of styled ratios +#' @family style tools +#' @author Daniel D. Sjoberg +#' @examples +#' c( +#' 0.123, 0.9, 1.1234, 12.345, 101.234, -0.123, +#' -0.9, -1.1234, -12.345, -101.234 +#' ) %>% +#' style_ratio() +style_ratio <- function(x, digits = 2, big.mark = NULL, decimal.mark = NULL, ...) { + x_fmt <- + dplyr::case_when( + round2(abs(x), digits = digits) < 1 ~ + style_sigfig(x, digits = digits, big.mark = big.mark, decimal.mark = decimal.mark, ...), + x > 0 ~ + style_sigfig(pmax(1, x), digits = digits + 1, big.mark = big.mark, decimal.mark = decimal.mark, ...), + x < 0 ~ + style_sigfig(pmin(-1, x), digits = digits + 1, big.mark = big.mark, decimal.mark = decimal.mark, ...), + ) + + attributes(x_fmt) <- attributes(unclass(x)) + x_fmt +} diff --git a/R/style_sigfig.R b/R/style_sigfig.R new file mode 100644 index 0000000000..3d7a4808d5 --- /dev/null +++ b/R/style_sigfig.R @@ -0,0 +1,51 @@ +#' Style significant figure-like rounding +#' +#' Converts a numeric argument into a string that has been rounded to a +#' significant figure-like number. Scientific notation output +#' is avoided, however, and additional significant figures may be displayed for +#' large numbers. For example, if the number of significant digits +#' requested is 2, 123 will be displayed (rather than 120 or 1.2x10^2). +#' +#' @section Details: +#' +#' - Scientific notation output is avoided. +#' +#' - If 2 significant figures are requested, the number is rounded to no more than 2 decimal places. +#' For example, a number will be rounded to 2 decimals places when `abs(x) < 1`, +#' 1 decimal place when `abs(x) >= 1 & abs(x) < 10`, +#' and to the nearest integer when `abs(x) >= 10`. +#' +#' - Additional significant figures +#' may be displayed for large numbers. For example, if the number of +#' significant digits requested is 2, +#' 123 will be displayed (rather than 120 or 1.2x10^2). +#' +#' @param x Numeric vector +#' @param digits Integer specifying the minimum number of significant +#' digits to display +#' @inheritParams style_number +#' @export +#' @return A character vector of styled numbers +#' @family style tools +#' @author Daniel D. Sjoberg +#' @examples +#' c(0.123, 0.9, 1.1234, 12.345, -0.123, -0.9, -1.1234, -132.345, NA, -0.001) %>% +#' style_sigfig() +style_sigfig <- function(x, digits = 2, scale = 1, big.mark = NULL, decimal.mark = NULL, ...) { + # calculating the number of digits to round number + d <- + paste0( + "round2(abs(x * scale), digits = ", digits:1 + 1L, ") ", + "< 10^(", 1:digits - 1, ") - 0.5 * 10^(", -(digits:1), ") ~ ", digits:1, + collapse = ", " + ) %>% + { + paste0("dplyr::case_when(", ., ", TRUE ~ 0)") + } %>% + # converting strings into expressions to run + parse(text = .) %>% + eval() + + # formatting number + style_number(x, digits = d, scale = scale, big.mark = big.mark, decimal.mark = decimal.mark, ...) +} diff --git a/R/tbl_summary.R b/R/tbl_summary.R index fe3769e084..8752f13da8 100644 --- a/R/tbl_summary.R +++ b/R/tbl_summary.R @@ -42,7 +42,7 @@ tbl_summary <- function(data, label = NULL, statistic = list(all_continuous() ~ "{mean} ({sd})", all_categorical() ~ "{n} ({p}%)"), - digits = NULL, + digits = assign_summary_digits(data, statistic, type), type = assign_summary_type(data, include, value), value = NULL, missing = c("ifany", "no", "always"), @@ -82,11 +82,9 @@ tbl_summary <- function(data, missing(statistic), get_theme_element("TODO:fill-this-in", default = statistic), statistic - ) , - digits = digits, + ), sort = sort ) - # fill in unspecified variables cards::fill_formula_selectors( data[include], @@ -94,6 +92,11 @@ tbl_summary <- function(data, get_theme_element("TODO:fill-this-in", default = eval(formals(gtsummary::tbl_summary)[["statistic"]])) ) + cards::process_formula_selectors( + data = .add_summary_type_as_attr(data[include], type), + digits = digits + ) + # save processed function inputs --------------------------------------------- tbl_summary_inputs <- as.list(environment()) call <- match.call() @@ -108,13 +111,15 @@ tbl_summary <- function(data, cards::ard_categorical( data, by = by, - variables = .get_variables_by_type(type, c("categorical", "dichotomous")) + variables = .get_variables_by_type(type, c("categorical", "dichotomous")), + fmt_fn = digits ), # calculate categorical summaries cards::ard_continuous( data, by = by, - variables = .get_variables_by_type(type, c("continuous", "continuous2")) + variables = .get_variables_by_type(type, c("continuous", "continuous2")), + fmt_fn = digits ) ) diff --git a/R/utils-as.R b/R/utils-as.R index 9773511c2e..dc557875fe 100644 --- a/R/utils-as.R +++ b/R/utils-as.R @@ -83,7 +83,7 @@ #' @keywords internal #' @export #' -#' @examples +# #' @examples # #' tbl <- # #' trial %>% # #' tbl_summary(include = c(age, grade)) %>% diff --git a/man/styfn.Rd b/man/styfn.Rd new file mode 100644 index 0000000000..4bd5e8a4d1 --- /dev/null +++ b/man/styfn.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/styfn.R +\name{styfn} +\alias{styfn} +\alias{styfn_number} +\alias{styfn_sigfig} +\alias{styfn_pvalue} +\alias{styfn_ratio} +\alias{styfn_percent} +\title{Style Functions} +\usage{ +styfn_number(digits = 0, big.mark = NULL, decimal.mark = NULL, scale = 1, ...) + +styfn_sigfig(digits = 2, scale = 1, big.mark = NULL, decimal.mark = NULL, ...) + +styfn_pvalue( + digits = 1, + prepend_p = FALSE, + big.mark = NULL, + decimal.mark = NULL, + ... +) + +styfn_ratio(digits = 2, big.mark = NULL, decimal.mark = NULL, ...) + +styfn_percent( + symbol = FALSE, + digits = 0, + big.mark = NULL, + decimal.mark = NULL, + ... +) +} +\arguments{ +\item{digits, big.mark, decimal.mark, scale, prepend_p, symbol, ...}{arguments +passed to the \verb{style_*()} functions} +} +\value{ +a function +} +\description{ +Similar to the \verb{style_*()} family of functions, but these functions return +a \verb{style_*()} \strong{function} rather than performing the styling. +} +\examples{ +my_style <- styfn_number(digits = 1) +my_style(3.14) +} +\seealso{ +Other style tools: +\code{\link{style_number}()}, +\code{\link{style_percent}()}, +\code{\link{style_pvalue}()}, +\code{\link{style_ratio}()}, +\code{\link{style_sigfig}()} +} +\concept{style tools} diff --git a/man/style_number.Rd b/man/style_number.Rd new file mode 100644 index 0000000000..e8001ed93f --- /dev/null +++ b/man/style_number.Rd @@ -0,0 +1,52 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/style_number.R +\name{style_number} +\alias{style_number} +\title{Style numbers} +\usage{ +style_number( + x, + digits = 0, + big.mark = NULL, + decimal.mark = NULL, + scale = 1, + ... +) +} +\arguments{ +\item{x}{Numeric vector} + +\item{digits}{Integer or vector of integers specifying the number of decimals +to round \verb{x=}. When vector is passed, each integer is mapped 1:1 to the +numeric values in \code{x}} + +\item{big.mark}{Character used between every 3 digits to separate +hundreds/thousands/millions/etc. +Default is \code{","}, except when \code{decimal.mark = ","} when the default is a space.} + +\item{decimal.mark}{The character to be used to indicate the numeric decimal point. +Default is \code{"."} or \code{getOption("OutDec")}} + +\item{scale}{A scaling factor: x will be multiplied by scale before formatting.} + +\item{...}{Other arguments passed on to \code{base::format()}} +} +\value{ +formatted character vector +} +\description{ +Style numbers +} +\examples{ +c(0.111, 12.3) \%>\% style_number(digits = 1) +c(0.111, 12.3) \%>\% style_number(digits = c(1, 0)) +} +\seealso{ +Other style tools: +\code{\link{styfn}}, +\code{\link{style_percent}()}, +\code{\link{style_pvalue}()}, +\code{\link{style_ratio}()}, +\code{\link{style_sigfig}()} +} +\concept{style tools} diff --git a/man/style_percent.Rd b/man/style_percent.Rd new file mode 100644 index 0000000000..437b01d0c7 --- /dev/null +++ b/man/style_percent.Rd @@ -0,0 +1,59 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/style_percent.R +\name{style_percent} +\alias{style_percent} +\title{Style percentages} +\usage{ +style_percent( + x, + symbol = FALSE, + digits = 0, + big.mark = NULL, + decimal.mark = NULL, + ... +) +} +\arguments{ +\item{x}{numeric vector of percentages} + +\item{symbol}{Logical indicator to include percent symbol in output. +Default is \code{FALSE}.} + +\item{digits}{number of digits to round large percentages (i.e. greater than 10\%). +Smaller percentages are rounded to \code{digits + 1} places. +Default is \code{0}} + +\item{big.mark}{Character used between every 3 digits to separate +hundreds/thousands/millions/etc. +Default is \code{","}, except when \code{decimal.mark = ","} when the default is a space.} + +\item{decimal.mark}{The character to be used to indicate the numeric decimal point. +Default is \code{"."} or \code{getOption("OutDec")}} + +\item{...}{Other arguments passed on to \code{base::format()}} +} +\value{ +A character vector of styled percentages +} +\description{ +Style percentages +} +\examples{ +percent_vals <- c(-1, 0, 0.0001, 0.005, 0.01, 0.10, 0.45356, 0.99, 1.45) +style_percent(percent_vals) +style_percent(percent_vals, symbol = TRUE, digits = 1) +} +\seealso{ +See Table Gallery \href{https://www.danieldsjoberg.com/gtsummary/articles/gallery.html}{vignette} for example + +Other style tools: +\code{\link{styfn}}, +\code{\link{style_number}()}, +\code{\link{style_pvalue}()}, +\code{\link{style_ratio}()}, +\code{\link{style_sigfig}()} +} +\author{ +Daniel D. Sjoberg +} +\concept{style tools} diff --git a/man/style_pvalue.Rd b/man/style_pvalue.Rd new file mode 100644 index 0000000000..b22006d9f5 --- /dev/null +++ b/man/style_pvalue.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/style_pvalue.R +\name{style_pvalue} +\alias{style_pvalue} +\title{Style p-values} +\usage{ +style_pvalue( + x, + digits = 1, + prepend_p = FALSE, + big.mark = NULL, + decimal.mark = NULL, + ... +) +} +\arguments{ +\item{x}{Numeric vector of p-values.} + +\item{digits}{Number of digits large p-values are rounded. Must be 1, 2, or 3. +Default is 1.} + +\item{prepend_p}{Logical. Should 'p=' be prepended to formatted p-value. +Default is \code{FALSE}} + +\item{big.mark}{Character used between every 3 digits to separate +hundreds/thousands/millions/etc. +Default is \code{","}, except when \code{decimal.mark = ","} when the default is a space.} + +\item{decimal.mark}{The character to be used to indicate the numeric decimal point. +Default is \code{"."} or \code{getOption("OutDec")}} + +\item{...}{Other arguments passed on to \code{base::format()}} +} +\value{ +A character vector of styled p-values +} +\description{ +Style p-values +} +\examples{ +pvals <- c( + 1.5, 1, 0.999, 0.5, 0.25, 0.2, 0.197, 0.12, 0.10, 0.0999, 0.06, + 0.03, 0.002, 0.001, 0.00099, 0.0002, 0.00002, -1 +) +style_pvalue(pvals) +style_pvalue(pvals, digits = 2, prepend_p = TRUE) +} +\seealso{ +See tbl_summary \href{https://www.danieldsjoberg.com/gtsummary/articles/tbl_summary.html}{vignette} for examples + +Other style tools: +\code{\link{styfn}}, +\code{\link{style_number}()}, +\code{\link{style_percent}()}, +\code{\link{style_ratio}()}, +\code{\link{style_sigfig}()} +} +\author{ +Daniel D. Sjoberg +} +\concept{style tools} diff --git a/man/style_ratio.Rd b/man/style_ratio.Rd new file mode 100644 index 0000000000..89b601809a --- /dev/null +++ b/man/style_ratio.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/style_ratio.R +\name{style_ratio} +\alias{style_ratio} +\title{Style significant figure-like rounding for ratios} +\usage{ +style_ratio(x, digits = 2, big.mark = NULL, decimal.mark = NULL, ...) +} +\arguments{ +\item{x}{Numeric vector} + +\item{digits}{Integer specifying the number of significant +digits to display for numbers below 1. Numbers larger than 1 will be be \code{digits + 1}. +Default is \code{digits = 2}.} + +\item{big.mark}{Character used between every 3 digits to separate +hundreds/thousands/millions/etc. +Default is \code{","}, except when \code{decimal.mark = ","} when the default is a space.} + +\item{decimal.mark}{The character to be used to indicate the numeric decimal point. +Default is \code{"."} or \code{getOption("OutDec")}} + +\item{...}{Other arguments passed on to \code{base::format()}} +} +\value{ +A character vector of styled ratios +} +\description{ +When reporting ratios, such as relative risk or an odds ratio, we'll often +want the rounding to be similar on each side of the number 1. For example, +if we report an odds ratio of 0.95 with a confidence interval of 0.70 to 1.24, +we would want to round to two decimal places for all values. In other words, +2 significant figures for numbers less than 1 and 3 significant figures 1 and +larger. \code{style_ratio()} performs significant figure-like rounding in this manner. +} +\examples{ +c( + 0.123, 0.9, 1.1234, 12.345, 101.234, -0.123, + -0.9, -1.1234, -12.345, -101.234 +) \%>\% + style_ratio() +} +\seealso{ +Other style tools: +\code{\link{styfn}}, +\code{\link{style_number}()}, +\code{\link{style_percent}()}, +\code{\link{style_pvalue}()}, +\code{\link{style_sigfig}()} +} +\author{ +Daniel D. Sjoberg +} +\concept{style tools} diff --git a/man/style_sigfig.Rd b/man/style_sigfig.Rd new file mode 100644 index 0000000000..4b5fa691c4 --- /dev/null +++ b/man/style_sigfig.Rd @@ -0,0 +1,73 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/style_sigfig.R +\name{style_sigfig} +\alias{style_sigfig} +\title{Style significant figure-like rounding} +\usage{ +style_sigfig( + x, + digits = 2, + scale = 1, + big.mark = NULL, + decimal.mark = NULL, + ... +) +} +\arguments{ +\item{x}{Numeric vector} + +\item{digits}{Integer specifying the minimum number of significant +digits to display} + +\item{scale}{A scaling factor: x will be multiplied by scale before formatting.} + +\item{big.mark}{Character used between every 3 digits to separate +hundreds/thousands/millions/etc. +Default is \code{","}, except when \code{decimal.mark = ","} when the default is a space.} + +\item{decimal.mark}{The character to be used to indicate the numeric decimal point. +Default is \code{"."} or \code{getOption("OutDec")}} + +\item{...}{Other arguments passed on to \code{base::format()}} +} +\value{ +A character vector of styled numbers +} +\description{ +Converts a numeric argument into a string that has been rounded to a +significant figure-like number. Scientific notation output +is avoided, however, and additional significant figures may be displayed for +large numbers. For example, if the number of significant digits +requested is 2, 123 will be displayed (rather than 120 or 1.2x10^2). +} +\section{Details}{ + +\itemize{ +\item Scientific notation output is avoided. +\item If 2 significant figures are requested, the number is rounded to no more than 2 decimal places. +For example, a number will be rounded to 2 decimals places when \code{abs(x) < 1}, +1 decimal place when \code{abs(x) >= 1 & abs(x) < 10}, +and to the nearest integer when \code{abs(x) >= 10}. +\item Additional significant figures +may be displayed for large numbers. For example, if the number of +significant digits requested is 2, +123 will be displayed (rather than 120 or 1.2x10^2). +} +} + +\examples{ +c(0.123, 0.9, 1.1234, 12.345, -0.123, -0.9, -1.1234, -132.345, NA, -0.001) \%>\% + style_sigfig() +} +\seealso{ +Other style tools: +\code{\link{styfn}}, +\code{\link{style_number}()}, +\code{\link{style_percent}()}, +\code{\link{style_pvalue}()}, +\code{\link{style_ratio}()} +} +\author{ +Daniel D. Sjoberg +} +\concept{style tools} diff --git a/man/tbl_summary.Rd b/man/tbl_summary.Rd index efadc0fac0..f34b6b63dd 100644 --- a/man/tbl_summary.Rd +++ b/man/tbl_summary.Rd @@ -9,7 +9,7 @@ tbl_summary( by = NULL, label = NULL, statistic = list(all_continuous() ~ "{mean} ({sd})", all_categorical() ~ "{n} ({p}\%)"), - digits = NULL, + digits = assign_summary_digits(data, statistic, type), type = assign_summary_type(data, include, value), value = NULL, missing = c("ifany", "no", "always"),