Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add secondary pattern support for pattern arg #1144

Merged
merged 7 commits into from
Nov 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 86 additions & 17 deletions R/modify_columns.R
Original file line number Diff line number Diff line change
Expand Up @@ -1211,7 +1211,8 @@ cols_unhide <- function(
#' and associated uncertainties (e.g., `12.0 ± 0.1`), and any columns specified
#' in `col_uncert` are hidden from appearing the output table.
#'
#' @details
#' @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`
Expand All @@ -1228,7 +1229,7 @@ cols_unhide <- function(
#' operation can be easily formatted using the [sub_missing()] function.
#'
#' This function is part of a set of four column-merging functions. The other
#' two are the general [cols_merge()] function and the specialized
#' 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.
Expand Down Expand Up @@ -1348,7 +1349,8 @@ cols_merge_uncert <- function(
#' dash (e.g., `12.0 — 20.0`). The column specified in `col_end` is dropped from
#' the output table.
#'
#' @details
#' @section Comparison with other column-merging functions:
#'
#' This function could be somewhat replicated using [cols_merge()], however,
#' `cols_merge_range()` employs the following specialized operations for `NA`
#' handling:
Expand All @@ -1367,7 +1369,7 @@ cols_merge_uncert <- function(
#' `col_end` columns for finer control of the replacement values.
#'
#' This function is part of a set of four column-merging functions. The other
#' two are the general [cols_merge()] function and the specialized
#' three are the general [cols_merge()] function and the specialized
#' [cols_merge_uncert()] 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.
Expand Down Expand Up @@ -1501,7 +1503,8 @@ 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.
#'
#' @details
#' @section Comparison with other column-merging functions:
#'
#' This function could be somewhat replicated using [cols_merge()], however,
#' `cols_merge_n_pct()` employs the following specialized semantics for `NA`
#' and zero-value handling:
Expand All @@ -1525,7 +1528,7 @@ cols_merge_resolver <- function(data, col_begin, col_end, sep) {
#' independently in separate [fmt_number()] and [fmt_percent()] calls.
#'
#' This function is part of a set of four column-merging functions. The other
#' two are the general [cols_merge()] function and the specialized
#' three are the general [cols_merge()] function and the specialized
#' [cols_merge_uncert()] and [cols_merge_range()] 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.
Expand Down Expand Up @@ -1645,14 +1648,48 @@ cols_merge_n_pct <- function(
#' @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
#' formatting. We can specify which columns to merge together in the `columns`
#' arrangement. We can specify which columns to merge together in the `columns`
#' argument. The string-combining pattern is given in the `pattern` argument.
#' The first column in the `columns` series operates as the target column (i.e.,
#' will undergo mutation) whereas all following `columns` will be untouched.
#' There is the option to hide the non-target columns (i.e., second and
#' subsequent columns given in `columns`).
#' subsequent columns given in `columns`). The formatting of values in different
#' columns will be preserved upon merging.
#'
#' @section How the `pattern` works:
#'
#' There are two types of templating for the `pattern` string:
#'
#' 1. `{ }` for arranging single column values in a row-wise fashion
#' 2. `<< >>` to surround spans of text that will be removed if any of the
#' contained `{ }` yields a missing value
#'
#' Integer values are placed in `{ }` and those values correspond to the columns
#' involved in the merge, in the order they are provided in the `columns`
#' argument. So the pattern `"{1} ({2}-{3})"` corresponds to the target column
#' value listed first in `columns` and the second and third columns cited
#' (formatted as a range in parentheses). With hypothetical values, this might
#' result as the merged string `"38.2 (3-8)"`.
#'
#' Because some values involved in merging may be missing, it is likely that
#' something like `"38.2 (3-NA)"` would be undesirable. For such cases, placing
#' sections of text in `<< >>` results in the entire span being eliminated if
#' there were to be an `NA` value (arising from `{ }` values). We could instead
#' opt for a pattern like `"{1}<< ({2}-{3})>>"`, which results in `"38.2"` if
#' either columns `{2}` or `{3}` have an `NA` value. We can even use a more
#' complex nesting pattern like `"{1}<< ({2}-<<{3}>>)>>"` to retain a lower
#' limit in parentheses (where `{3}` is `NA`) but remove the range altogether
#' if `{2}` is `NA`.
#'
#' One more thing to note here is that if [sub_missing()] is used on values in
#' a column, those specific values affected won't be considered truly missing by
#' `cols_merge()` (since it's been handled with substitute text). So, the
#' complex pattern `"{1}<< ({2}-<<{3}>>)>>"` might result in something like
#' `"38.2 (3-limit)"` if `sub_missing(..., missing_text = "limit")` were used
#' on the third column supplied in `columns`.
#'
#' @section Comparison with other column-merging functions:
#'
#' @details
#' There are three other column-merging functions that offer specialized
#' behavior that is optimized for common table tasks: [cols_merge_range()],
#' [cols_merge_uncert()], and [cols_merge_n_pct()]. These functions operate
Expand All @@ -1671,16 +1708,17 @@ cols_merge_n_pct <- function(
#' @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. These indices are to be placed in curly braces (e.g., `{1}`). All
#' characters outside of braces are taken to be string literals.
#' pattern. Further details are provided in the *How the `pattern` works*
#' section.
#'
#' @return An object of class `gt_tbl`.
#'
#' @section Examples:
#'
#' Use [`sp500`] to create a **gt** table. Use the `cols_merge()` function to
#' merge the `open` & `close` columns together, and, the `low` & `high` columns
#' (putting an em dash between both). Relabel the columns with [cols_label()].
#' Use a portion of [`sp500`] to create a **gt** table. Use the `cols_merge()`
#' function to merge the `open` & `close` columns together, and, the `low` &
#' `high` columns (putting an em dash between both). Relabel the columns with
#' [cols_label()].
#'
#' ```r
#' sp500 %>%
Expand All @@ -1705,6 +1743,38 @@ cols_merge_n_pct <- 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
#' drop parts of the output text whenever missing values are encountered.
#'
#' ```r
#' gtcars %>%
#' dplyr::filter(year == 2017) %>%
#' dplyr::select(mfr, model, starts_with(c("trq", "mpg"))) %>%
#' gt() %>%
#' fmt_integer(columns = trq_rpm) %>%
#' cols_merge(
#' columns = starts_with("trq"),
#' pattern = "{1}<< ({2} rpm)>>"
#' ) %>%
#' cols_merge(
#' columns = starts_with("mpg"),
#' pattern = "<<{1} city<</{2} hwy>>>>"
#' ) %>%
#' cols_label(
#' mfr = "Manufacturer",
#' model = "Car Model",
#' trq = "Torque",
#' mpg_c = "MPG"
#' )
#' ```
#'
#' \if{html}{\out{
#' `r man_get_image_tag(file = "man_cols_merge_2.png")`
#' }}
#'
#' @family column modification functions
#' @section Function ID:
#' 4-13
Expand All @@ -1731,9 +1801,8 @@ cols_merge <- function(

# 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.
# NOTE: It's also important that `pattern` not be evaluated, for much the same
# reason as above.
# columns[-1] may not evaluate to a sensible result. It's also important
# that `pattern` not be evaluated, for much the same reason as above.

# Get the columns supplied in `hide_columns` as a character vector
suppressWarnings(
Expand Down
75 changes: 69 additions & 6 deletions R/utils_render_common.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Define the contexts
all_contexts <- c("html", "latex", "rtf", "word", "default")

missing_val_token <- "::missing_val::"

validate_contexts <- function(contexts) {

if (!all(contexts %in% all_contexts)) {
Expand Down Expand Up @@ -307,6 +309,43 @@ reorder_styles <- function(data) {
dt_styles_set(data = data, styles = styles_tbl)
}

resolve_secondary_pattern <- function(x) {

while (grepl("<<.*?>>", x)) {

m <- gregexpr("<<[^<]*?>>", x, perl = TRUE)

matched <- unlist(regmatches(x, m))[1]

m_start <- as.integer(m[[1]])
m_length <- attr(m[[1]], "match.length")

if (grepl(missing_val_token, matched)) {

# Remove `matched` text from `x`
x <-
paste0(
substr(x, 0, m_start - 1L),
substr(x, m_start + m_length, 100000)
)

} else {

# Remove `<<` and `>>` from `matched` and insert back into `x`
matched_trimmed <- gsub("^<<|>>$", "", matched)

x <-
paste0(
substr(x, 0, m_start - 1L),
matched_trimmed,
substr(x, m_start + m_length, 100000)
)
}
}

x
}

#' Perform merging of column contents
#'
#' This merges column content together with a pattern and possibly with a `type`
Expand Down Expand Up @@ -334,19 +373,43 @@ perform_col_merge <- function(data, context) {
if (type == "merge") {

mutated_column <- col_merge[[i]]$vars[1]
mutated_column_sym <- sym(mutated_column)

columns <- col_merge[[i]]$vars
pattern <- col_merge[[i]]$pattern

glue_src_na_data <- lapply(as.list(data_tbl[, columns]), FUN = is.na)

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
glue_src_data[[x]]
}
)
glue_src_data <- stats::setNames(glue_src_data, seq_len(length(glue_src_data)))

body <-
dplyr::mutate(
body,
!!mutated_column_sym := as.character(glue_gt(glue_src_data, pattern))
)
glued_cols <- as.character(glue_gt(glue_src_data, pattern))

if (grepl("<<.*?>>", pattern)) {

glued_cols <-
vapply(
glued_cols,
FUN.VALUE = character(1),
USE.NAMES = FALSE,
FUN = resolve_secondary_pattern
)

glued_cols <- gsub("<<|>>", "", glued_cols)
}

glued_cols <- gsub(missing_val_token, "NA", glued_cols, fixed = TRUE)

body[, mutated_column] <- glued_cols

} else if (type == "merge_n_pct") {

Expand Down
Binary file added images/man_cols_merge_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading