Skip to content

Commit

Permalink
Enhance the cols_merge_uncert() function (#888)
Browse files Browse the repository at this point in the history
* Add wip rendering for uneven uncert vals

* Update documentation for `cols_merge_uncert()`

* Add CSS class for two-value uncertainties

* Update utils_render_common.R

* Add testthat tests

* Add rendering for LaTeX and RTF outputs

* Update test-cols_merge.R

* Move CSS rule into class

* Use `is_false()` instead of `isFALSE()`

* Make corrections to testthat tests

* Ensure that single NA values are kept

* Update test-cols_merge.R

* Make changes based on code review
  • Loading branch information
rich-iannone authored Mar 25, 2022
1 parent d18e5ce commit 0832f57
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 110 deletions.
58 changes: 31 additions & 27 deletions R/modify_columns.R
Original file line number Diff line number Diff line change
Expand Up @@ -958,31 +958,29 @@ cols_unhide <- function(data,
)
}

#' Merge two columns to a value & uncertainty column
#' Merge columns to a value-with-uncertainty column
#'
#' @description
#' The `cols_merge_uncert()` function is a specialized variant of the
#' [cols_merge()] function. It operates by taking a base value column
#' (`col_val`) and an uncertainty column (`col_uncert`) and merges them into a
#' single column. What results is a column with values and associated
#' uncertainties (e.g., `12.0 ± 0.1`), and, the column specified in `col_uncert`
#' is dropped from the output table.
#' [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
#' lower and upper uncertainty bounds. These columns will be essentially merged
#' in a single column (that of `col_val`). What results is a column with values
#' and associated uncertainties (e.g., `12.0 ± 0.1`), and any columns specified
#' in `col_uncert` are hidden from appearing the output table.
#'
#' @details
#' This function could be somewhat replicated using [cols_merge()], however,
#' 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:
#'
#' \enumerate{
#' \item `NA`s in `col_val` result in missing values for the merged
#' column (e.g., `NA` + `0.1` = `NA`)
#' \item `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`)
#' \item `NA`s both `col_val` and `col_uncert` result in
#' missing values for the merged column (e.g., `NA` + `NA` =
#' `NA`)
#' }
#' 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 [fmt_missing()] function.
Expand All @@ -996,17 +994,23 @@ cols_unhide <- function(data,
#' @inheritParams cols_align
#' @param col_val A single column name that contains the base values. This is
#' the column where values will be mutated.
#' @param col_uncert A single column name that contains the uncertainty values.
#' These values will be combined with those in `col_val`. We have the option
#' to automatically hide the `col_uncert` column through `autohide`.
#' @param sep The separator text that contains the uncertainty mark. The
#' default value of `" +/- "` indicates that an appropriate plus/minus mark
#' will be used depending on the output context. Should you want this special
#' symbol to be taken literally, it can be supplied within the base [I()]
#' function.
#' @param autohide An option to automatically hide the column specified as
#' `col_uncert`. Any columns with their state changed to hidden will behave
#' @param col_uncert Either one or two column names that contain the uncertainty
#' values. The most common case involves supplying a single column with
#' uncertainties; these values will be combined with those in `col_val`. Less
#' commonly, lower and upper uncertainty bounds may be different. For that
#' case two columns (representing lower and upper uncertainty values away from
#' `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 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.
#' Should you want this special symbol to be taken literally, it can be
#' supplied within the [I()] function.
#' @param autohide An option to automatically hide any columns specified in
#' `col_uncert`. Any columns with their state changed to 'hidden' will behave
#' the same as before, they just won't be displayed in the finalized table.
#' By default, this is set to `TRUE`.
#'
#' @return An object of class `gt_tbl`.
#'
Expand Down
59 changes: 32 additions & 27 deletions R/tab_create_modify.R
Original file line number Diff line number Diff line change
Expand Up @@ -2095,36 +2095,41 @@ preprocess_tab_option <- function(option, var_name, type) {

# Perform pre-processing on the option depending on `type`
option <-
switch(type,
overflow = {
if (isTRUE(option)) {
"auto"
} else if (isFALSE(option)) {
"hidden"
} else {
option
}
},
px = {
if (is.numeric(option)) {
px(option)
} else {
option
}
},
option
switch(
type,
overflow = {
if (isTRUE(option)) {
"auto"
} else if (is_false(option)) {
"hidden"
} else {
option
}
},
px = {
if (is.numeric(option)) {
px(option)
} else {
option
}
},
option
)

# Perform checkmate assertions by `type`
switch(type,
logical = checkmate::assert_logical(
option, len = 1, any.missing = FALSE, .var.name = var_name),
overflow =,
px =,
value = checkmate::assert_character(
option, len = 1, any.missing = FALSE, .var.name = var_name),
values = checkmate::assert_character(
option, min.len = 1, any.missing = FALSE, .var.name = var_name)
switch(
type,
logical = checkmate::assert_logical(
option, len = 1, any.missing = FALSE, .var.name = var_name
),
overflow =,
px =,
value = checkmate::assert_character(
option, len = 1, any.missing = FALSE, .var.name = var_name
),
values = checkmate::assert_character(
option, min.len = 1, any.missing = FALSE, .var.name = var_name
)
)

option
Expand Down
128 changes: 99 additions & 29 deletions R/utils_render_common.R
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,13 @@ perform_col_merge <- function(data,

col_merge <- dt_col_merge_get(data = data)
body <- dt_body_get(data = data)
data_tbl <- dt_data_get(data = data)

if (length(col_merge) == 0) {
return(data)
}

for (i in seq(col_merge)) {
for (i in seq_along(col_merge)) {

type <- col_merge[[i]]$type

Expand All @@ -292,20 +293,17 @@ perform_col_merge <- function(data,
columns <- col_merge[[i]]$vars
pattern <- col_merge[[i]]$pattern

glue_src_data <- body[, columns] %>% as.list()
glue_src_data <- as.list(body[, columns])
glue_src_data <- stats::setNames(glue_src_data, seq_len(length(glue_src_data)))

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

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

data_tbl <- dt_data_get(data = data)

mutated_column <- col_merge[[i]]$vars[1]
second_column <- col_merge[[i]]$vars[2]

Expand All @@ -327,18 +325,91 @@ perform_col_merge <- function(data,
rows_to_format_idx <- setdiff(rows_to_format_idx, zero_rows_idx)

body[rows_to_format_idx, mutated_column] <-
glue_gt(
list(
"1" = body[[mutated_column]][rows_to_format_idx],
"2" = body[[second_column]][rows_to_format_idx]
),
pattern
) %>%
as.character()
as.character(
glue_gt(
list(
"1" = body[[mutated_column]][rows_to_format_idx],
"2" = body[[second_column]][rows_to_format_idx]
),
pattern
)
)

} else {
} else if (type == "merge_uncert" && length(col_merge[[i]]$vars) == 3) {

data_tbl <- dt_data_get(data = data)
# Case where lower and upper certainties 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]

pattern_equal <- col_merge[[i]]$pattern
sep <- col_merge[[i]]$sep

# Transform the separator text depending on specific
# inputs and the `context`
sep <-
sep %>%
context_dash_mark(context = context) %>%
context_plusminus_mark(context = context)

if (context == "html") {

pattern_unequal <-
paste0(
"<<1>><span class=\"gt_two_val_uncert\">",
"+<<3>><br>",
context_minus_mark(context = context), "<<2>>",
"</span>"
)

} else if (context == "latex") {

pattern_unequal <- "$<<1>>^{+<<3>>}_{-<<2>>}$"

} else if (context == "rtf") {

pattern_unequal <- "<<1>>(+<<3>>, -<<2>>)"
}

# Determine rows where NA values exist
na_1_rows <- is.na(data_tbl[[mutated_column]])
na_lu_rows <- is.na(data_tbl[[lu_column]])
na_uu_rows <- is.na(data_tbl[[uu_column]])
na_lu_or_uu <- na_lu_rows | na_uu_rows
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)

body[rows_to_format_equal, mutated_column] <-
as.character(
glue_gt(
list(
"1" = body[[mutated_column]][rows_to_format_equal],
"2" = body[[lu_column]][rows_to_format_equal],
"sep" = sep
),
pattern_equal
)
)

body[rows_to_format_unequal, mutated_column] <-
as.character(
glue_gt(
list(
"1" = body[[mutated_column]][rows_to_format_unequal],
"2" = body[[lu_column]][rows_to_format_unequal],
"3" = body[[uu_column]][rows_to_format_unequal]
),
pattern_unequal,
.open = "<<",
.close = ">>"
)
)

} else {

mutated_column <- col_merge[[i]]$vars[1]
second_column <- col_merge[[i]]$vars[2]
Expand All @@ -365,21 +436,20 @@ perform_col_merge <- function(data,
}

body[rows_to_format, mutated_column] <-
glue_gt(
list(
"1" = body[[mutated_column]][rows_to_format],
"2" = body[[second_column]][rows_to_format],
"sep" = sep
),
pattern
) %>%
as.character()
as.character(
glue_gt(
list(
"1" = body[[mutated_column]][rows_to_format],
"2" = body[[second_column]][rows_to_format],
"sep" = sep
),
pattern
)
)
}
}

data <- dt_body_set(data = data, body = body)

data
dt_body_set(data = data, body = body)
}

#' Suitably replace `NA` values in the `groups_df` data frame
Expand Down
9 changes: 9 additions & 0 deletions inst/css/gt_styles_default.scss
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,15 @@
font-size: 65%;
}

.gt_two_val_uncert {
display: inline-block;
line-height: 1em;
text-align: right;
font-size: 60%;
vertical-align: -0.25em;
margin-left: 0.1em;
}

.gt_footnote_marks {
font-style: italic;
font-weight: normal;
Expand Down
Loading

0 comments on commit 0832f57

Please sign in to comment.