diff --git a/R/dt_cols_merge.R b/R/dt_cols_merge.R index 3ed7f0d6ab..d22c543314 100644 --- a/R/dt_cols_merge.R +++ b/R/dt_cols_merge.R @@ -17,7 +17,7 @@ dt_col_merge_add <- function(data, col_merge) { dt_col_merge_set(data = data, col_merge = added) } -dt_col_merge_entry <- function(vars, type, pattern = NULL, ...) { +dt_col_merge_entry <- function(vars, rows, type, pattern = NULL, ...) { if (!(type %in% c("merge", "merge_range", "merge_uncert", "merge_n_pct"))) { cli::cli_abort("Invalid `type` provided.") @@ -25,6 +25,7 @@ dt_col_merge_entry <- function(vars, type, pattern = NULL, ...) { list( vars = vars, + rows = rows, type = type, pattern = pattern, ... diff --git a/R/modify_columns.R b/R/modify_columns.R index 0e470570fd..ecda6e005c 100644 --- a/R/modify_columns.R +++ b/R/modify_columns.R @@ -1,6 +1,7 @@ #' Set the alignment of columns #' #' @description +#' #' The individual alignments of columns (which includes the column labels and #' all of their data cells) can be modified. We have the option to align text to #' the `left`, the `center`, and the `right`. In a less explicit manner, we can @@ -8,6 +9,7 @@ #' the data type (with the `auto` option). #' #' @details +#' #' When you create a **gt** table object using [gt()], automatic alignment of #' column labels and their data cells is performed. By default, left-alignment #' is applied to columns of class `character`, `Date`, or `POSIXct`; @@ -147,6 +149,7 @@ determine_which_character_number <- function( #' Align all numeric values in a column along the decimal mark #' #' @description +#' #' For numeric columns that contain values with decimal portions, it is #' sometimes useful to have them lined up along the decimal mark for easier #' readability. We can do this with `cols_align_decimal()` and provide any @@ -359,6 +362,7 @@ align_to_char <- function(x, align_at = ".") { #' Set the widths of columns #' #' @description +#' #' Manual specifications of column widths can be performed using the #' `cols_width()` function. We choose which columns get specific widths. This #' can be in units of pixels (easily set by use of the [px()] helper function), @@ -368,6 +372,7 @@ align_to_char <- function(x, align_at = ".") { #' dimension. #' #' @details +#' #' Column widths can be set as absolute or relative values (with px and #' percentage values). Those columns not specified are treated as having #' variable width. The sizing behavior for column widths depends on the @@ -522,6 +527,7 @@ cols_width <- function( #' Relabel one or more columns #' #' @description +#' #' Column labels can be modified from their default values (the names of the #' columns from the input table data). When you create a **gt** table object #' using [gt()], column names effectively become the column labels. While this @@ -666,6 +672,7 @@ cols_label <- function( #' Relabel columns with a function #' #' @description +#' #' Column labels can be modified from their default values (the names of the #' columns from the input table data). When you create a **gt** table object #' using [gt()], column names effectively become the column labels. While this @@ -863,6 +870,7 @@ cols_label_with <- function( #' Move one or more columns #' #' @description +#' #' On those occasions where you need to move columns this way or that way, we #' can make use of the `cols_move()` function. While it's true that the movement #' of columns can be done upstream of **gt**, it is much easier and less error @@ -873,6 +881,7 @@ cols_label_with <- function( #' in the table. #' #' @details +#' #' The columns supplied in `columns` must all exist in the table and none of #' them can be in the `after` argument. The `after` column must also exist and #' only one column should be provided here. If you need to place one or columns @@ -983,6 +992,7 @@ cols_move <- function( #' Move one or more columns to the start #' #' @description +#' #' We can easily move set of columns to the beginning of the column series and #' we only need to specify which `columns`. It's possible to do this upstream of #' **gt**, however, it is easier with this function and it presents less @@ -991,6 +1001,7 @@ cols_move <- function( #' table). #' #' @details +#' #' The columns supplied in `columns` must all exist in the table. If you need to #' place one or columns at the end of the column series, the #' [cols_move_to_end()] function should be used. More control is offered with @@ -1089,6 +1100,7 @@ cols_move_to_start <- function( #' Move one or more columns to the end #' #' @description +#' #' It's possible to move a set of columns to the end of the column series, we #' only need to specify which `columns` are to be moved. While this can be done #' upstream of **gt**, this function makes to process much easier and it's less @@ -1096,6 +1108,7 @@ cols_move_to_start <- function( #' preserved (same with the ordering of all other columns in the table). #' #' @details +#' #' The columns supplied in `columns` must all exist in the table. If you need to #' place one or columns at the start of the column series, the #' [cols_move_to_start()] function should be used. More control is offered with @@ -1193,6 +1206,7 @@ cols_move_to_end <- function( #' Hide one or more columns #' #' @description +#' #' The `cols_hide()` function allows us to hide one or more columns from #' appearing in the final output table. While it's possible and often desirable #' to omit columns from the input table data before introduction to the [gt()] @@ -1201,6 +1215,7 @@ cols_move_to_end <- function( #' of those columns is not necessary. #' #' @details +#' #' The hiding of columns is internally a rendering directive, so, all columns #' that are 'hidden' are still accessible and useful in any expression provided #' to a `rows` argument. Furthermore, the `cols_hide()` function (as with many @@ -1304,6 +1319,7 @@ cols_hide <- function( #' Unhide one or more columns #' #' @description +#' #' The `cols_unhide()` function allows us to take one or more hidden columns #' (usually made so via the [cols_hide()] function) and make them visible #' in the final output table. This may be important in cases where the user @@ -1401,6 +1417,7 @@ cols_unhide <- function( #' Merge data from two or more columns to a single column #' #' @description +#' #' This function takes input from two or more columns and allows the contents to #' be merged them into a single column, using a pattern that specifies the #' arrangement. We can specify which columns to merge together in the `columns` @@ -1411,6 +1428,34 @@ cols_unhide <- function( #' subsequent columns given in `columns`). The formatting of values in different #' columns will be preserved upon merging. #' +#' @inheritParams cols_align +#' @param columns The columns that will participate in the merging process. The +#' first column name provided will be the target column (i.e., undergo +#' mutation) and the other columns will serve to provide input. +#' @param hide_columns Any column names provided here will have their state +#' changed to `hidden` (via internal use of [cols_hide()] if they aren't +#' already hidden. This is convenient if the shared purpose of these specified +#' columns is only to provide string input to the target column. To suppress +#' any hiding of columns, `FALSE` can be used here. +#' @param rows Rows that will participate in the merging process. Providing +#' [everything()] (the default) results in all rows in `columns` undergoing +#' merging. Alternatively, we can supply a vector of row identifiers within +#' [c()], a vector of row indices, or a helper function focused on selections. +#' The select helper functions are: [starts_with()], [ends_with()], +#' [contains()], [matches()], [one_of()], [num_range()], and [everything()]. +#' We can also use a standalone predicate expression to filter down to the +#' rows we need (e.g., `[colname_1] > 100 & [colname_2] < 50`). +#' @param pattern A formatting pattern that specifies the arrangement of the +#' `column` values and any string literals. The pattern uses numbers (within +#' `{ }`) that correspond to the indices of columns provided in `columns`. If +#' two columns are provided in `columns` and we would like to combine the cell +#' data onto the first column, `"{1} {2}"` could be used. If a pattern isn't +#' provided then a space-separated pattern that includes all `columns` will be +#' generated automatically. Further details are provided in the *How the +#' `pattern` works* section. +#' +#' @return An object of class `gt_tbl`. +#' #' @section How the `pattern` works: #' #' There are two types of templating for the `pattern` string: @@ -1451,23 +1496,6 @@ cols_unhide <- function( #' similarly, where the non-target columns can be optionally hidden from the #' output table through the `autohide` option. #' -#' @inheritParams cols_align -#' @param columns The columns that will participate in the merging process. The -#' first column name provided will be the target column (i.e., undergo -#' mutation) and the other columns will serve to provide input. -#' @param hide_columns Any column names provided here will have their state -#' changed to `hidden` (via internal use of [cols_hide()] if they aren't -#' already hidden. This is convenient if the shared purpose of these specified -#' columns is only to provide string input to the target column. To suppress -#' any hiding of columns, `FALSE` can be used here. -#' @param pattern A formatting pattern that specifies the arrangement of the -#' `column` values and any string literals. We need to use column numbers -#' (corresponding to the position of columns provided in `columns`) within the -#' pattern. Further details are provided in the *How the `pattern` works* -#' section. -#' -#' @return An object of class `gt_tbl`. -#' #' @section Examples: #' #' Use a portion of [`sp500`] to create a **gt** table. Use the `cols_merge()` @@ -1498,7 +1526,6 @@ cols_unhide <- function( #' `r man_get_image_tag(file = "man_cols_merge_1.png")` #' }} #' -#' #' Use a portion of [`gtcars`] to create a **gt** table. Use the `cols_merge()` #' function to merge the `trq` & `trq_rpm` columns together, and, the `mpg_c` & #' `mpg_h` columns. Given the presence of `NA` values, we can use patterns that @@ -1540,7 +1567,8 @@ cols_merge <- function( data, columns, hide_columns = columns[-1], - pattern = paste0("{", seq_along(columns), "}", collapse = " ") + rows = everything(), + pattern = NULL ) { # Perform input object validation @@ -1554,6 +1582,18 @@ cols_merge <- function( excl_stub = FALSE ) + + if (is.null(pattern)) { + pattern <- paste0("{", seq_along(columns), "}", collapse = " ") + } + + # Resolve the rows supplied in the `rows` argument + resolved_rows_idx <- + resolve_rows_i( + expr = {{ rows }}, + data = data + ) + # NOTE: It's important that `hide_columns` NOT be evaluated until after the # previous line has run. Otherwise, the default `hide_columns` value of # columns[-1] may not evaluate to a sensible result. It's also important @@ -1598,6 +1638,7 @@ cols_merge <- function( data = data, col_merge = dt_col_merge_entry( vars = columns, + rows = resolved_rows_idx, type = "merge", pattern = pattern ) @@ -1607,6 +1648,7 @@ cols_merge <- function( #' Merge columns to a value-with-uncertainty column #' #' @description +#' #' The `cols_merge_uncert()` function is a specialized variant of the #' [cols_merge()] function. It takes as input a base value column (`col_val`) #' and either: (1) a single uncertainty column, or (2) two columns representing @@ -1615,29 +1657,6 @@ cols_merge <- function( #' and associated uncertainties (e.g., `12.0 ± 0.1`), and any columns specified #' in `col_uncert` are hidden from appearing the output table. #' -#' @section Comparison with other column-merging functions: -#' -#' This function could be somewhat replicated using [cols_merge()] in the case -#' where a single column is supplied for `col_uncert`, however, -#' `cols_merge_uncert()` employs the following specialized semantics for `NA` -#' handling: -#' -#' 1. `NA`s in `col_val` result in missing values for the merged column (e.g., -#' `NA` + `0.1` = `NA`) -#' 2. `NA`s in `col_uncert` (but not `col_val`) result in base values only for -#' the merged column (e.g., `12.0` + `NA` = `12.0`) -#' 3. `NA`s both `col_val` and `col_uncert` result in missing values for the -#' merged column (e.g., `NA` + `NA` = `NA`) -#' -#' Any resulting `NA` values in the `col_val` column following the merge -#' operation can be easily formatted using the [sub_missing()] function. -#' -#' This function is part of a set of four column-merging functions. The other -#' three are the general [cols_merge()] function and the specialized -#' [cols_merge_range()] and [cols_merge_n_pct()] functions. These functions -#' operate similarly, where the non-target columns can be optionally hidden from -#' the output table through the `hide_columns` or `autohide` options. -#' #' @inheritParams cols_align #' @param col_val A single column name that contains the base values. This is #' the column where values will be mutated. @@ -1649,6 +1668,14 @@ cols_merge <- function( #' `col_val`, respectively) should be provided. Since we often don't want the #' uncertainty value columns in the output table, we can automatically hide #' any `col_uncert` columns through the `autohide` option. +#' @param rows Rows that will participate in the merging process. Providing +#' [everything()] (the default) results in all rows in `columns` undergoing +#' merging. Alternatively, we can supply a vector of row identifiers within +#' [c()], a vector of row indices, or a helper function focused on selections. +#' The select helper functions are: [starts_with()], [ends_with()], +#' [contains()], [matches()], [one_of()], [num_range()], and [everything()]. +#' We can also use a standalone predicate expression to filter down to the +#' rows we need (e.g., `[colname_1] > 100 & [colname_2] < 50`). #' @param sep The separator text that contains the uncertainty mark for a single #' uncertainty value. The default value of `" +/- "` indicates that an #' appropriate plus/minus mark will be used depending on the output context. @@ -1661,6 +1688,29 @@ cols_merge <- function( #' #' @return An object of class `gt_tbl`. #' +#' @section Comparison with other column-merging functions: +#' +#' This function could be somewhat replicated using [cols_merge()] in the case +#' where a single column is supplied for `col_uncert`, however, +#' `cols_merge_uncert()` employs the following specialized semantics for `NA` +#' handling: +#' +#' 1. `NA`s in `col_val` result in missing values for the merged column (e.g., +#' `NA` + `0.1` = `NA`) +#' 2. `NA`s in `col_uncert` (but not `col_val`) result in base values only for +#' the merged column (e.g., `12.0` + `NA` = `12.0`) +#' 3. `NA`s both `col_val` and `col_uncert` result in missing values for the +#' merged column (e.g., `NA` + `NA` = `NA`) +#' +#' Any resulting `NA` values in the `col_val` column following the merge +#' operation can be easily formatted using the [sub_missing()] function. +#' +#' This function is part of a set of four column-merging functions. The other +#' three are the general [cols_merge()] function and the specialized +#' [cols_merge_range()] and [cols_merge_n_pct()] functions. These functions +#' operate similarly, where the non-target columns can be optionally hidden from +#' the output table through the `hide_columns` or `autohide` options. +#' #' @section Examples: #' #' Use [`exibble`] to create a **gt** table, keeping only the `currency` and @@ -1698,6 +1748,7 @@ cols_merge_uncert <- function( data, col_val, col_uncert, + rows = everything(), sep = " +/- ", autohide = TRUE ) { @@ -1713,12 +1764,20 @@ cols_merge_uncert <- function( sep = sep ) + # Resolve the rows supplied in the `rows` argument + resolved_rows_idx <- + resolve_rows_i( + expr = {{ rows }}, + data = data + ) + # Create an entry and add it to the `_col_merge` attribute data <- dt_col_merge_add( data = data, col_merge = dt_col_merge_entry( vars = resolved$columns, + rows = resolved_rows_idx, type = "merge_uncert", pattern = resolved$pattern, sep = sep @@ -1746,6 +1805,7 @@ cols_merge_uncert <- function( #' Merge two columns to a value range column #' #' @description +#' #' The `cols_merge_range()` function is a specialized variant of the #' [cols_merge()] function. It operates by taking a two columns that constitute #' a range of values (`col_begin` and `col_end`) and merges them into a single @@ -1753,6 +1813,28 @@ cols_merge_uncert <- function( #' dash (e.g., `12.0 — 20.0`). The column specified in `col_end` is dropped from #' the output table. #' +#' @inheritParams cols_align +#' @param col_begin A column that contains values for the start of the range. +#' @param col_end A column that contains values for the end of the range. +#' @param rows Rows that will participate in the merging process. Providing +#' [everything()] (the default) results in all rows in `columns` undergoing +#' merging. Alternatively, we can supply a vector of row identifiers within +#' [c()], a vector of row indices, or a helper function focused on selections. +#' The select helper functions are: [starts_with()], [ends_with()], +#' [contains()], [matches()], [one_of()], [num_range()], and [everything()]. +#' We can also use a standalone predicate expression to filter down to the +#' rows we need (e.g., `[colname_1] > 100 & [colname_2] < 50`). +#' @param sep The separator text that indicates the values are ranged. The +#' default value of `"--"` indicates that an en dash will be used for the +#' range separator. Using `"---"` will be taken to mean that an em dash should +#' be used. Should you want these special symbols to be taken literally, they +#' can be supplied within the base [I()] function. +#' @param autohide An option to automatically hide the column specified as +#' `col_end`. Any columns with their state changed to hidden will behave +#' the same as before, they just won't be displayed in the finalized table. +#' +#' @return An object of class `gt_tbl`. +#' #' @section Comparison with other column-merging functions: #' #' This function could be somewhat replicated using [cols_merge()], however, @@ -1778,20 +1860,6 @@ cols_merge_uncert <- function( #' operate similarly, where the non-target columns can be optionally hidden from #' the output table through the `hide_columns` or `autohide` options. #' -#' @inheritParams cols_align -#' @param col_begin A column that contains values for the start of the range. -#' @param col_end A column that contains values for the end of the range. -#' @param sep The separator text that indicates the values are ranged. The -#' default value of `"--"` indicates that an en dash will be used for the -#' range separator. Using `"---"` will be taken to mean that an em dash should -#' be used. Should you want these special symbols to be taken literally, they -#' can be supplied within the base [I()] function. -#' @param autohide An option to automatically hide the column specified as -#' `col_end`. Any columns with their state changed to hidden will behave -#' the same as before, they just won't be displayed in the finalized table. -#' -#' @return An object of class `gt_tbl`. -#' #' @section Examples: #' #' Use [`gtcars`] to create a **gt** table, keeping only the `model`, `mpg_c`, @@ -1825,6 +1893,7 @@ cols_merge_range <- function( data, col_begin, col_end, + rows = everything(), sep = "--", autohide = TRUE ) { @@ -1840,12 +1909,20 @@ cols_merge_range <- function( sep = sep ) + # Resolve the rows supplied in the `rows` argument + resolved_rows_idx <- + resolve_rows_i( + expr = {{ rows }}, + data = data + ) + # Create an entry and add it to the `_col_merge` attribute data <- dt_col_merge_add( data = data, col_merge = dt_col_merge_entry( vars = resolved$columns, + rows = resolved_rows_idx, type = "merge_range", pattern = resolved$pattern, sep = sep @@ -1900,6 +1977,7 @@ cols_merge_resolver <- function(data, col_begin, col_end, sep) { #' Merge two columns to combine counts and percentages #' #' @description +#' #' The `cols_merge_n_pct()` function is a specialized variant of the #' [cols_merge()] function. It operates by taking two columns that constitute #' both a count (`col_n`) and a fraction of the total population (`col_pct`) and @@ -1907,6 +1985,25 @@ cols_merge_resolver <- function(data, col_begin, col_end, sep) { #' counts and their associated percentages (e.g., `12 (23.2%)`). The column #' specified in `col_pct` is dropped from the output table. #' +#' @inheritParams cols_align +#' @param col_n A column that contains values for the count component. +#' @param col_pct A column that contains values for the percentage component. +#' This column should be formatted such that percentages are displayed (e.g., +#' with `fmt_percent()`). +#' @param rows Rows that will participate in the merging process. Providing +#' [everything()] (the default) results in all rows in `columns` undergoing +#' merging. Alternatively, we can supply a vector of row identifiers within +#' [c()], a vector of row indices, or a helper function focused on selections. +#' The select helper functions are: [starts_with()], [ends_with()], +#' [contains()], [matches()], [one_of()], [num_range()], and [everything()]. +#' We can also use a standalone predicate expression to filter down to the +#' rows we need (e.g., `[colname_1] > 100 & [colname_2] < 50`). +#' @param autohide An option to automatically hide the column specified as +#' `col_pct`. Any columns with their state changed to hidden will behave +#' the same as before, they just won't be displayed in the finalized table. +#' +#' @return An object of class `gt_tbl`. +#' #' @section Comparison with other column-merging functions: #' #' This function could be somewhat replicated using [cols_merge()], however, @@ -1937,17 +2034,6 @@ cols_merge_resolver <- function(data, col_begin, col_end, sep) { #' operate similarly, where the non-target columns can be optionally hidden from #' the output table through the `hide_columns` or `autohide` options. #' -#' @inheritParams cols_align -#' @param col_n A column that contains values for the count component. -#' @param col_pct A column that contains values for the percentage component. -#' This column should be formatted such that percentages are displayed (e.g., -#' with `fmt_percent()`). -#' @param autohide An option to automatically hide the column specified as -#' `col_pct`. Any columns with their state changed to hidden will behave -#' the same as before, they just won't be displayed in the finalized table. -#' -#' @return An object of class `gt_tbl`. -#' #' @section Examples: #' #' Use [`pizzaplace`] to create a **gt** table that displays the counts and @@ -2003,6 +2089,7 @@ cols_merge_n_pct <- function( data, col_n, col_pct, + rows = everything(), autohide = TRUE ) { @@ -2017,12 +2104,20 @@ cols_merge_n_pct <- function( sep = "" ) + # Resolve the rows supplied in the `rows` argument + resolved_rows_idx <- + resolve_rows_i( + expr = {{ rows }}, + data = data + ) + # Create an entry and add it to the `_col_merge` attribute data <- dt_col_merge_add( data = data, col_merge = dt_col_merge_entry( vars = resolved$columns, + rows = resolved_rows_idx, type = "merge_n_pct", pattern = resolved$pattern, sep = "" diff --git a/R/utils_render_common.R b/R/utils_render_common.R index 4ad0dc4b6e..11beda48b3 100644 --- a/R/utils_render_common.R +++ b/R/utils_render_common.R @@ -66,8 +66,8 @@ render_formats <- function(data, context) { # Render input data to output data where formatting is specified for (fmt in formats) { - # Determine if the formatter has a function relevant to the - # context; if not, use the `default` function (which should + # Determine if the formatting function has a function relevant to + # the context; if not, use the `default` function (which should # always be present) if (context %in% names(fmt$func)) { eval_func <- context @@ -75,7 +75,7 @@ render_formats <- function(data, context) { eval_func <- "default" } - # Obtain compatibility information for the formatter function + # Obtain compatibility information for the formatting function compat <- fmt$compat # Get the rows to which the formatting should be constrained @@ -392,24 +392,42 @@ perform_col_merge <- function(data, context) { if (type == "merge") { + # + # The `cols_merge()` formatting case + # + mutated_column <- col_merge[[i]]$vars[1] - columns <- col_merge[[i]]$vars - pattern <- col_merge[[i]]$pattern + columns <- col_merge[[i]][["vars"]] + rows <- col_merge[[i]][["rows"]] + pattern <- col_merge[[i]][["pattern"]] + + glue_src_na_data <- lapply(as.list(data_tbl[rows, columns]), FUN = is.na) - glue_src_na_data <- lapply(as.list(data_tbl[, columns]), FUN = is.na) + glue_src_data <- as.list(body[rows, columns]) - glue_src_data <- as.list(body[, columns]) glue_src_data <- lapply( seq_along(glue_src_data), FUN = function(x) { - glue_src_data[[x]][ - glue_src_data[[x]] == "NA" & glue_src_na_data[[x]] - ] <- missing_val_token + + # The source data (and 'source data' here means data that's already + # been formatted and converted to `character`) having a character + # `"NA"` value signals that the value should *probably* be treated + # as missing (we are relatively certain it wasn't modified by + # `sub_missing()`, a case where we consider the value *not* to be + # missing because it was handled later) but we also want to + # corroborate that with the original data values (checking for true + # missing data there) + + missing_cond <- glue_src_data[[x]] == "NA" & glue_src_na_data[[x]] + missing_cond[is.na(missing_cond)] <- TRUE + + glue_src_data[[x]][missing_cond] <- missing_val_token glue_src_data[[x]] } ) + glue_src_data <- stats::setNames(glue_src_data, seq_len(length(glue_src_data))) glued_cols <- as.character(glue_gt(glue_src_data, pattern)) @@ -429,12 +447,17 @@ perform_col_merge <- function(data, context) { glued_cols <- gsub(missing_val_token, "NA", glued_cols, fixed = TRUE) - body[, mutated_column] <- glued_cols + body[rows, mutated_column] <- glued_cols } else if (type == "merge_n_pct") { - mutated_column <- col_merge[[i]]$vars[1] - second_column <- col_merge[[i]]$vars[2] + # + # The `cols_merge_n_pct()` formatting case + # + + mutated_column <- col_merge[[i]][["vars"]][1] + second_column <- col_merge[[i]][["vars"]][2] + rows <- col_merge[[i]][["rows"]] # This is a fixed pattern pattern <- "{1} ({2})" @@ -451,7 +474,8 @@ perform_col_merge <- function(data, context) { # An `NA` value in either column should exclude that row from # processing via `glue_gt()` rows_to_format_idx <- which(!(na_1_rows | na_2_rows)) - rows_to_format_idx <- setdiff(rows_to_format_idx, zero_rows_idx) + rows_to_format_idx <- base::setdiff(rows_to_format_idx, zero_rows_idx) + rows_to_format_idx <- base::intersect(rows_to_format_idx, rows) body[rows_to_format_idx, mutated_column] <- as.character( @@ -466,14 +490,18 @@ perform_col_merge <- function(data, context) { } else if (type == "merge_uncert" && length(col_merge[[i]]$vars) == 3) { - # Case where lower and upper certainties provided as input columns + # + # The `cols_merge_uncert()` case where lower and upper certainties + # were provided as input columns + # - mutated_column <- col_merge[[i]]$vars[1] - lu_column <- col_merge[[i]]$vars[2] - uu_column <- col_merge[[i]]$vars[3] + mutated_column <- col_merge[[i]][["vars"]][1] + lu_column <- col_merge[[i]][["vars"]][2] + uu_column <- col_merge[[i]][["vars"]][3] + rows <- col_merge[[i]][["rows"]] - pattern_equal <- col_merge[[i]]$pattern - sep <- col_merge[[i]]$sep + pattern_equal <- col_merge[[i]][["pattern"]] + sep <- col_merge[[i]][["sep"]] # Transform the separator text depending on specific # inputs and the `context` @@ -514,8 +542,8 @@ perform_col_merge <- function(data, context) { na_lu_and_uu <- na_lu_rows & na_uu_rows lu_equals_uu <- data_tbl[[lu_column]] == data_tbl[[uu_column]] & !na_lu_or_uu - rows_to_format_equal <- which(!na_1_rows & lu_equals_uu) - rows_to_format_unequal <- which(!na_1_rows & !na_lu_and_uu & !lu_equals_uu) + rows_to_format_equal <- base::intersect(which(!na_1_rows & lu_equals_uu), rows) + rows_to_format_unequal <- base::intersect(which(!na_1_rows & !na_lu_and_uu & !lu_equals_uu), rows) body[rows_to_format_equal, mutated_column] <- as.character( @@ -545,11 +573,17 @@ perform_col_merge <- function(data, context) { } else { - mutated_column <- col_merge[[i]]$vars[1] - second_column <- col_merge[[i]]$vars[2] + # + # The `cols_merge_range()` and `cols_merge_uncert()` (standard + # uncertainties) formatting cases + # + + mutated_column <- col_merge[[i]][["vars"]][1] + second_column <- col_merge[[i]][["vars"]][2] + rows <- col_merge[[i]][["rows"]] - pattern <- col_merge[[i]]$pattern - sep <- col_merge[[i]]$sep + pattern <- col_merge[[i]][["pattern"]] + sep <- col_merge[[i]][["sep"]] # Transform the separator text depending on specific # inputs and the `context` @@ -560,12 +594,11 @@ perform_col_merge <- function(data, context) { na_1_rows <- is.na(data_tbl[[mutated_column]]) na_2_rows <- is.na(data_tbl[[second_column]]) - rows_to_format <- - if (type == "merge_range") { - which(!(na_1_rows & na_2_rows)) - } else if (type == "merge_uncert") { - which(!(na_1_rows | na_2_rows)) - } + if (type == "merge_range") { + rows_to_format <- base::intersect(which(!(na_1_rows & na_2_rows)), rows) + } else if (type == "merge_uncert") { + rows_to_format <- base::intersect(which(!(na_1_rows | na_2_rows)), rows) + } body[rows_to_format, mutated_column] <- as.character( diff --git a/man/cols_merge.Rd b/man/cols_merge.Rd index 890595b439..156c1c885c 100644 --- a/man/cols_merge.Rd +++ b/man/cols_merge.Rd @@ -8,7 +8,8 @@ cols_merge( data, columns, hide_columns = columns[-1], - pattern = paste0("{", seq_along(columns), "}", collapse = " ") + rows = everything(), + pattern = NULL ) } \arguments{ @@ -24,11 +25,23 @@ already hidden. This is convenient if the shared purpose of these specified columns is only to provide string input to the target column. To suppress any hiding of columns, \code{FALSE} can be used here.} +\item{rows}{Rows that will participate in the merging process. Providing +\code{\link[=everything]{everything()}} (the default) results in all rows in \code{columns} undergoing +merging. Alternatively, we can supply a vector of row identifiers within +\code{\link[=c]{c()}}, a vector of row indices, or a helper function focused on selections. +The select helper functions are: \code{\link[=starts_with]{starts_with()}}, \code{\link[=ends_with]{ends_with()}}, +\code{\link[=contains]{contains()}}, \code{\link[=matches]{matches()}}, \code{\link[=one_of]{one_of()}}, \code{\link[=num_range]{num_range()}}, and \code{\link[=everything]{everything()}}. +We can also use a standalone predicate expression to filter down to the +rows we need (e.g., \verb{[colname_1] > 100 & [colname_2] < 50}).} + \item{pattern}{A formatting pattern that specifies the arrangement of the -\code{column} values and any string literals. We need to use column numbers -(corresponding to the position of columns provided in \code{columns}) within the -pattern. Further details are provided in the \emph{How the \code{pattern} works} -section.} +\code{column} values and any string literals. The pattern uses numbers (within +\code{{ }}) that correspond to the indices of columns provided in \code{columns}. If +two columns are provided in \code{columns} and we would like to combine the cell +data onto the first column, \code{"{1} {2}"} could be used. If a pattern isn't +provided then a space-separated pattern that includes all \code{columns} will be +generated automatically. Further details are provided in the \emph{How the +\code{pattern} works} section.} } \value{ An object of class \code{gt_tbl}. diff --git a/man/cols_merge_n_pct.Rd b/man/cols_merge_n_pct.Rd index 7a4633bd8e..e742bf53ca 100644 --- a/man/cols_merge_n_pct.Rd +++ b/man/cols_merge_n_pct.Rd @@ -4,7 +4,7 @@ \alias{cols_merge_n_pct} \title{Merge two columns to combine counts and percentages} \usage{ -cols_merge_n_pct(data, col_n, col_pct, autohide = TRUE) +cols_merge_n_pct(data, col_n, col_pct, rows = everything(), autohide = TRUE) } \arguments{ \item{data}{A table object that is created using the \code{\link[=gt]{gt()}} function.} @@ -15,6 +15,15 @@ cols_merge_n_pct(data, col_n, col_pct, autohide = TRUE) This column should be formatted such that percentages are displayed (e.g., with \code{fmt_percent()}).} +\item{rows}{Rows that will participate in the merging process. Providing +\code{\link[=everything]{everything()}} (the default) results in all rows in \code{columns} undergoing +merging. Alternatively, we can supply a vector of row identifiers within +\code{\link[=c]{c()}}, a vector of row indices, or a helper function focused on selections. +The select helper functions are: \code{\link[=starts_with]{starts_with()}}, \code{\link[=ends_with]{ends_with()}}, +\code{\link[=contains]{contains()}}, \code{\link[=matches]{matches()}}, \code{\link[=one_of]{one_of()}}, \code{\link[=num_range]{num_range()}}, and \code{\link[=everything]{everything()}}. +We can also use a standalone predicate expression to filter down to the +rows we need (e.g., \verb{[colname_1] > 100 & [colname_2] < 50}).} + \item{autohide}{An option to automatically hide the column specified as \code{col_pct}. Any columns with their state changed to hidden will behave the same as before, they just won't be displayed in the finalized table.} diff --git a/man/cols_merge_range.Rd b/man/cols_merge_range.Rd index 20468c2710..c86ac7fb44 100644 --- a/man/cols_merge_range.Rd +++ b/man/cols_merge_range.Rd @@ -4,7 +4,14 @@ \alias{cols_merge_range} \title{Merge two columns to a value range column} \usage{ -cols_merge_range(data, col_begin, col_end, sep = "--", autohide = TRUE) +cols_merge_range( + data, + col_begin, + col_end, + rows = everything(), + sep = "--", + autohide = TRUE +) } \arguments{ \item{data}{A table object that is created using the \code{\link[=gt]{gt()}} function.} @@ -13,6 +20,15 @@ cols_merge_range(data, col_begin, col_end, sep = "--", autohide = TRUE) \item{col_end}{A column that contains values for the end of the range.} +\item{rows}{Rows that will participate in the merging process. Providing +\code{\link[=everything]{everything()}} (the default) results in all rows in \code{columns} undergoing +merging. Alternatively, we can supply a vector of row identifiers within +\code{\link[=c]{c()}}, a vector of row indices, or a helper function focused on selections. +The select helper functions are: \code{\link[=starts_with]{starts_with()}}, \code{\link[=ends_with]{ends_with()}}, +\code{\link[=contains]{contains()}}, \code{\link[=matches]{matches()}}, \code{\link[=one_of]{one_of()}}, \code{\link[=num_range]{num_range()}}, and \code{\link[=everything]{everything()}}. +We can also use a standalone predicate expression to filter down to the +rows we need (e.g., \verb{[colname_1] > 100 & [colname_2] < 50}).} + \item{sep}{The separator text that indicates the values are ranged. The default value of \code{"--"} indicates that an en dash will be used for the range separator. Using \code{"---"} will be taken to mean that an em dash should diff --git a/man/cols_merge_uncert.Rd b/man/cols_merge_uncert.Rd index 2ef9546b6a..8d580a6295 100644 --- a/man/cols_merge_uncert.Rd +++ b/man/cols_merge_uncert.Rd @@ -4,7 +4,14 @@ \alias{cols_merge_uncert} \title{Merge columns to a value-with-uncertainty column} \usage{ -cols_merge_uncert(data, col_val, col_uncert, sep = " +/- ", autohide = TRUE) +cols_merge_uncert( + data, + col_val, + col_uncert, + rows = everything(), + sep = " +/- ", + autohide = TRUE +) } \arguments{ \item{data}{A table object that is created using the \code{\link[=gt]{gt()}} function.} @@ -21,6 +28,15 @@ case two columns (representing lower and upper uncertainty values away from uncertainty value columns in the output table, we can automatically hide any \code{col_uncert} columns through the \code{autohide} option.} +\item{rows}{Rows that will participate in the merging process. Providing +\code{\link[=everything]{everything()}} (the default) results in all rows in \code{columns} undergoing +merging. Alternatively, we can supply a vector of row identifiers within +\code{\link[=c]{c()}}, a vector of row indices, or a helper function focused on selections. +The select helper functions are: \code{\link[=starts_with]{starts_with()}}, \code{\link[=ends_with]{ends_with()}}, +\code{\link[=contains]{contains()}}, \code{\link[=matches]{matches()}}, \code{\link[=one_of]{one_of()}}, \code{\link[=num_range]{num_range()}}, and \code{\link[=everything]{everything()}}. +We can also use a standalone predicate expression to filter down to the +rows we need (e.g., \verb{[colname_1] > 100 & [colname_2] < 50}).} + \item{sep}{The separator text that contains the uncertainty mark for a single uncertainty value. The default value of \code{" +/- "} indicates that an appropriate plus/minus mark will be used depending on the output context. diff --git a/tests/testthat/_snaps/cols_merge.md b/tests/testthat/_snaps/cols_merge.md index 0be005536b..e39b00f341 100644 --- a/tests/testthat/_snaps/cols_merge.md +++ b/tests/testthat/_snaps/cols_merge.md @@ -19,6 +19,13 @@ Output [1] "\n \n \n \n \n \n \n \n \n \n\n \n\n \n\n \n\n \n\n \n \n \n
b
Part ione
Part iitwo
Part iiithree
Part ivfour
Part vfive
" +--- + + Code + . + Output + [1] "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n \n \n \n
mpgcyldisphpdratqsecvsamgearcarb
21.061601103.9016.460144
21.061601103.90 (2.875)17.020144
22.84108933.8518.611141
21.462581103.08 (3.215)19.441031
18.783601753.1517.020032
" + # the `cols_merge_uncert()` function works correctly Code @@ -26,6 +33,20 @@ Output [1] "\n \n \n \n \n \n \n \n \n \n\n \n\n \n\n \n\n \n\n \n \n \n
b
2.3 ± 0.06A
6.3 ± 0.07B
2.5 ± 0.08C
2.4 ± 0.09D
6.5 ± 0.10E
" +--- + + Code + . + Output + [1] "\n \n \n \n \n \n \n \n \n \n\n \n\n \n\n \n\n \n\n \n \n \n
rowb
2.3A
6.3 ± 0.07B
2.5C
2.4 ± 0.09D
6.5E
" + +# the `cols_merge_uncert()` fn works nicely with different error bounds + + Code + . + Output + [1] "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
value
34.5+1.8
−2.1
29.2
36.3
31.6+NA
−1.8
28.5
30.9
NA
NA
Inf
30.0 ± 0.0
32.0+0.0
−0.1
34.0
NaN
" + # the `cols_merge_range()` function works correctly Code @@ -54,6 +75,13 @@ Output [1] "\n \n \n \n \n \n \n \n \n \n\n \n\n \n\n \n\n \n\n \n \n \n
b
i–6one
ii–7two
iii–8three
iv–9four
v–10five
" +--- + + Code + . + Output + [1] "\n \n \n \n \n \n \n \n \n \n\n \n\n \n\n \n\n \n\n \n \n \n
a
16
27–two
38
49–four
510
" + # the `cols_merge_n_pct()` function works correctly Code @@ -61,3 +89,10 @@ Output [1] "\n \n \n \n \n \n \n \n \n \n\n \n\n \n\n \n\n \n\n \n \n \n
b
1 (6.00%)A
2 (7.00%)B
3 (8.00%)C
4 (9.00%)D
5 (10.00%)E
" +--- + + Code + . + Output + [1] "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
a
1 (7.1%)
5
0
2 (14.3%)
NA
6
5
NA
0
NA
" + diff --git a/tests/testthat/test-cols_merge.R b/tests/testthat/test-cols_merge.R index 54e8348908..42bfbc3372 100644 --- a/tests/testthat/test-cols_merge.R +++ b/tests/testthat/test-cols_merge.R @@ -23,7 +23,9 @@ tbl_na <- dplyr::tibble( a = 1:4, b = c(1, NA, 3, NA), - c = c(1, 2, NA, NA) + c = c(1, 2, NA, NA), + d = c("1", "2", NA_character_, NA_character_), + e = c(TRUE, FALSE, NA, NA) ) # Function to skip tests if Suggested packages not available on system @@ -173,6 +175,20 @@ test_that("The function `cols_merge()` works correctly", { cols_merge(columns = c(row, a)) %>% render_as_html() ) + + # Use `cols_merge()` with a vector of `rows` which limits the rows + # that participate in the merging process + gt_tbl_4 <- + mtcars_short %>% + gt() %>% + cols_merge( + columns = c(drat, wt), + rows = c(2, 4), + pattern = "{1} ({2})" + ) + + # Perform snapshot test + gt_tbl_4 %>% render_as_html() %>% expect_snapshot() }) test_that("The secondary pattern language works well in `cols_merge()`", { @@ -248,6 +264,54 @@ test_that("The secondary pattern language works well in `cols_merge()`", { (tbl_gt_7 %>% render_formats_test("html"))[["a"]], rep("X", 4) ) + + tbl_gt_9 <- + tbl_gt %>% + cols_merge(columns = c(a, b, d), pattern = "{1}{2}<<{3}>>") + + expect_equal( + (tbl_gt_9 %>% render_formats_test("html"))[["a"]], + c("111", "2NA2", "33", "4NA") + ) + + tbl_gt_10 <- + tbl_gt %>% + sub_missing(missing_text = "X") %>% + cols_merge(columns = c(a, b, d), pattern = "{1}{2}<<{3}>>") + + expect_equal( + (tbl_gt_10 %>% render_formats_test("html"))[["a"]], + c("111", "2X2", "33X", "4XX") + ) + + tbl_gt_11 <- + tbl_gt %>% + cols_merge(columns = c(a, b, e), pattern = "{1}{2}<<{3}>>") + + expect_equal( + (tbl_gt_11 %>% render_formats_test("html"))[["a"]], + c("11TRUE", "2NAFALSE", "33", "4NA") + ) + + tbl_gt_12 <- + tbl_gt %>% + sub_missing(missing_text = "X") %>% + cols_merge(columns = c(a, b, e), pattern = "{1}{2}<<{3}>>") + + expect_equal( + (tbl_gt_12 %>% render_formats_test("html"))[["a"]], + c("11TRUE", "2XFALSE", "33X", "4XX") + ) + + tbl_gt_13 <- + tbl_gt %>% + sub_missing(missing_text = "X") %>% + cols_merge(columns = c(a, b, e), rows = c(1, 3), pattern = "{1}{2}<<{3}>>") + + expect_equal( + (tbl_gt_13 %>% render_formats_test("html"))[["a"]], + c("11TRUE", "2", "33X", "4") + ) }) test_that("The `cols_merge_uncert()` function works correctly", { @@ -389,6 +453,20 @@ test_that("The `cols_merge_uncert()` function works correctly", { # Perform snapshot test gt_tbl_1 %>% render_as_html() %>% expect_snapshot() + + # Use `cols_merge_uncert()` with a vector of `rows` which limits the rows + # that participate in the merging process + gt_tbl_2 <- + tbl %>% + gt() %>% + cols_merge_uncert( + col_val = row, + col_uncert = a, + rows = c(2, 4) + ) + + # Perform snapshot test + gt_tbl_2 %>% render_as_html() %>% expect_snapshot() }) test_that("The `cols_merge_uncert()` fn works nicely with different error bounds", { @@ -448,6 +526,20 @@ test_that("The `cols_merge_uncert()` fn works nicely with different error bounds "32.0(+0.0, -0.1)", "34.0(+0.1, -NaN)", "NaN" ) ) + + # Use `cols_merge_uncert()` with a vector of `rows` which limits the rows + # that participate in the merging process + gt_tbl_1 <- + tbl_uncert %>% + gt() %>% + cols_merge_uncert( + col_val = "value", + col_uncert = c("lu", "uu"), + rows = c(1, 4, 6:11) + ) + + # Perform snapshot test + gt_tbl_1 %>% render_as_html() %>% expect_snapshot() }) test_that("The `cols_merge_range()` function works correctly", { @@ -691,6 +783,19 @@ test_that("The `cols_merge_range()` function works correctly", { # Perform snapshot test gt_tbl_4 %>% render_as_html() %>% expect_snapshot() + + # Use `cols_merge_range()` with a vector of `rows` which limits the rows + # that participate in the merging process + gt_tbl_5 <- + gt(tbl, rowname_col = "row") %>% + cols_merge_range( + col_begin = a, + col_end = b, + rows = c(2, 4) + ) + + # Perform snapshot test + gt_tbl_5 %>% render_as_html() %>% expect_snapshot() }) test_that("The `cols_merge_n_pct()` function works correctly", { @@ -771,4 +876,19 @@ test_that("The `cols_merge_n_pct()` function works correctly", { # Perform snapshot test gt_tbl_1 %>% render_as_html() %>% expect_snapshot() + + # Use `cols_merge_n_pct()` with a vector of `rows` which limits the rows + # that participate in the merging process + gt_tbl_2 <- + tbl_n_pct %>% + gt() %>% + cols_merge_n_pct( + col_n = a, + col_pct = b, + rows = c(1, 4) + ) %>% + fmt_percent(columns = b, decimals = 1) + + # Perform snapshot test + gt_tbl_2 %>% render_as_html() %>% expect_snapshot() })