From 87f18de150702dfa13759e7aa2d66b76ebbf02b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Tue, 9 Dec 2025 17:31:48 +0100 Subject: [PATCH 01/14] Initial commit --- DESCRIPTION | 1 + NAMESPACE | 1 + R/tm_gt_tbl_summary.R | 241 +++++++++++++++++++++++++++++++++++++++ man/tm_gt_tbl_summary.Rd | 82 +++++++++++++ 4 files changed, 325 insertions(+) create mode 100644 R/tm_gt_tbl_summary.R create mode 100644 man/tm_gt_tbl_summary.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 7eeda59a5..c23ff293c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -32,6 +32,7 @@ Imports: bslib (>= 0.8.0), checkmate (>= 2.1.0), colourpicker (>= 1.3.0), + crane, dplyr (>= 1.1.0), DT (>= 0.13), forcats (>= 1.0.0), diff --git a/NAMESPACE b/NAMESPACE index 9a8bb56e3..3f9bd315d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -24,6 +24,7 @@ export(tm_g_distribution) export(tm_g_response) export(tm_g_scatterplot) export(tm_g_scatterplotmatrix) +export(tm_gt_tbl_summary) export(tm_missing_data) export(tm_outliers) export(tm_rmarkdown) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R new file mode 100644 index 000000000..457552d98 --- /dev/null +++ b/R/tm_gt_tbl_summary.R @@ -0,0 +1,241 @@ +#' `teal` module: Summary table +#' +#' Generates a table summary from a dataset using gtsummary. +#' +#' @inheritParams teal::module +#' @inheritParams shared_params +#' @param ... Other argumments passed (eventually) to gtsummary::tbl_summary() +#' @param table (`data_extract_spec` or `list` of multiple `data_extract_spec`) +#' Object with all available choices with pre-selected option for being summarized. +#' @inherit shared_params return +#' +#' @section Decorating Module: +#' +#' This module generates the following objects, which can be modified in place using decorators: +#' - `table` (`gtsummary` - output of `crane::tbl_roche_summary()`) +#' +#' A Decorator is applied to the specific output using a named list of `teal_transform_module` objects. +#' The name of this list corresponds to the name of the output to which the decorator is applied. +#' See code snippet below: +#' +#' ``` +#' tm_t_crosstable( +#' ..., # arguments for module +#' decorators = list( +#' table = teal_transform_module(...) # applied to the `table` output +#' ) +#' ) +#' ``` +#' For additional details and examples of decorators, refer to the vignette +#' `vignette("decorate-module-output", package = "teal.modules.general")`. +#' +#' To learn more please refer to the vignette +#' `vignette("transform-module-output", package = "teal")` or the [`teal::teal_transform_module()`] documentation. +#' +#' @inheritSection teal::example_module Reporting +#' @export +#' @examples +#' data <- within(teal_data(), { +#' ADSL <- teal.data::rADSL +#' }) +#' join_keys(data) <- default_cdisc_join_keys[names(data)] +#' app <- init( +#' data = data, +#' modules = modules( +#' tm_gt_tbl_summary( +#' table= data_extract_spec(dataname = "ADSL"), +#' by = data_extract_spec(dataname = "ADSL", "SEX"), +#' include = data_extract_spec(dataname = "ADSL", +#' select = select_spec( +#' choices = c("SITEID", "COUNTRY", "ACTARM"), +#' selected = "SITEID", +#' multiple = TRUE, +#' fixed = FALSE +#' ) +#' ) +#' ) +#' ) +#' ) +#'if (interactive()) { +#' shinyApp(app$ui, app$server) +#' } +tm_gt_tbl_summary <- function( + label = "Table summary", + # table, + # passed to tbl_summary() + by, + col_label = NULL, + statistics = list(all_continuous() ~ "{median} ({p25}, {p75})", all_categorical() ~ + "{n} ({p}%)"), + digits = NULL, + type = NULL, + value = NULL, + missing = c("ifany", "no", "always"), + missing_text = "", + missing_stat = "{N_miss}", + sort = all_categorical(FALSE) ~ "alphanumeric", + percent = c("column", "row", "cell"), + include = NULL, + + transformators = list(), + decorators = list() +) { + message("Initializing tm_gt_tbl_summary") + checkmate::assert_string(label) + # if (inherits(by, "data_extract_spec")) table <- list(by) + # checkmate::assert_list(by, types = "data_extract_spec") + # assert_single_selection(by) + assert_decorators(decorators, "table") + + # Make UI args + args <- as.list(environment()) + + data_extract_list <- list(table = table) + + module <- module( + label = label, + server = srv_gt_tbl_summary, + ui = ui_gt_tbl_summary, + ui_args = args, + server_args = c(data_extract_list, + as.list( + by = by, + col_label = col_label, + statistics = statistics, + digits = digits, + type = type, + value = value, + missing = missing, + nonmissing_text = nonmissing_text, + nonmissing_stat = nonmissing_stat, + sort = sort, + percent = percent, + include = include), + decorators = decorators), + transformators = transformators, + datanames = teal.transform::get_extract_datanames(data_extract_list) + ) + attr(module, "teal_bookmarkable") <- TRUE + module + + +} + + +ui_gt_tbl_summary <- function(id, ...) { + args <- list(by = by, + # col_label = col_label, + # statistics = statistics, + # digits = digits, + # type = type, + # value = value, + missing = missing, + # nonmissing_text = nonmissing_text, + # nonmissing_stat = nonmissing_stat, + # sort = sort, + percent = percent, + include = include) + ns <- NS(id) + + teal.widgets::standard_layout( + output = teal.widgets::white_small_well( + textOutput(ns("title")), + teal.widgets::table_with_settings_ui(ns("table")) + ), + encoding = tags$div( + tags$label("Encodings", class = "text-primary"), + teal.transform::datanames_input(list(by, include)), + teal.transform::data_extract_ui(ns("by"), label = "Variable(s) to stratify with", by), + teal.transform::data_extract_ui(ns("include"), label = "Variable(s) to include", include), + radioButtons( + ns("missing"), + label = "Display NA counts", + choices = c("If any" = "ifany", "No" = "no", "Always" = "always"), + selected = "ifany" + ), + radioButtons( + ns("percent"), + label = "Percentage based on", + choices = c("Column" = "column", "Column" = "row", "Cell" = "cell"), + selected = "column" + ), + ui_decorate_teal_data(ns("decorator"), decorators = select_decorators(args$decorators, "table")) + ), + pre_output = pre_output, + post_output = post_output + ) +} + +srv_gt_tbl_summary <- function(id, + data, + by, + col_label, + statistics, + digits, + type, + value, + missing, + nonmissing_text, + nonmissing_stat, + sort, + percent, + include, + decorators) { + checkmate::assert_class(data, "reactive") + checkmate::assert_class(isolate(data()), "teal_data") + moduleServer(id, function(input, output, session) { + teal.logger::log_shiny_input_changes(input, namespace = "teal.modules.general") + + + # table, + # by, + # col_label, + # statistics, + # digits, + # type, + # value, + # missing, + # nonmissing_text, + # nonmissing_stat, + # sort, + # percent, + # include + + + qenv <- reactive({ + obj <- data() + teal.reporter::teal_card(obj) <- + c( + teal.reporter::teal_card(obj), + teal.reporter::teal_card("## Module's output(s)") + ) + teal.code::eval_code(obj, "library(crane);library(dplyr)") + }) + + qenv_table <- reactive({ + q <- qenv() + witin(q, { + gt <- crane::tbl_roche_summary(ADSL) + }, + table = as.name(table)) + + }) + + + # by + # label columns + # statistics + # digits + # type + # value + # nonmissing + # nonmissing_text + # nonmissing_stat + # sort + # percent + # include + + # crane::tbl_roche_summary + qenv_table + }) +} diff --git a/man/tm_gt_tbl_summary.Rd b/man/tm_gt_tbl_summary.Rd new file mode 100644 index 000000000..0da447e99 --- /dev/null +++ b/man/tm_gt_tbl_summary.Rd @@ -0,0 +1,82 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tm_gt_tbl_summary.R +\name{tm_gt_tbl_summary} +\alias{tm_gt_tbl_summary} +\title{\code{teal} module: Summary table} +\usage{ +tm_gt_tbl_summary( + label = "label", + table, + ..., + transformators = list(), + decorators = list() +) +} +\arguments{ +\item{label}{(\code{character(1)}) Label shown in the navigation item for the module or module group. +For \code{modules()} defaults to \code{"root"}. See \code{Details}.} + +\item{table}{(\code{data_extract_spec} or \code{list} of multiple \code{data_extract_spec}) +Object with all available choices with pre-selected option for being summarized.} + +\item{...}{Other argumments passed (eventually) to gtsummary::tbl_summary()} + +\item{transformators}{(\code{list} of \code{teal_transform_module}) that will be applied to transform module's data input. +To learn more check \code{vignette("transform-input-data", package = "teal")}.} + +\item{decorators}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} +(named \code{list} of lists of \code{teal_transform_module}) optional, +decorator for tables or plots included in the module output reported. +The decorators are applied to the respective output objects. + +See section "Decorating Module" below for more details.} +} +\value{ +Object of class \code{teal_module} to be used in \code{teal} applications. +} +\description{ +Generates a table summary from a dataset using gtsummary. +} +\section{Decorating Module}{ + + +This module generates the following objects, which can be modified in place using decorators: +\itemize{ +\item \code{table} (\code{gtsummary} - output of \code{crane::tbl_roche_summary()}) +} + +A Decorator is applied to the specific output using a named list of \code{teal_transform_module} objects. +The name of this list corresponds to the name of the output to which the decorator is applied. +See code snippet below: + +\if{html}{\out{
}}\preformatted{tm_t_crosstable( + ..., # arguments for module + decorators = list( + table = teal_transform_module(...) # applied to the `table` output + ) +) +}\if{html}{\out{
}} + +For additional details and examples of decorators, refer to the vignette +\code{vignette("decorate-module-output", package = "teal.modules.general")}. + +To learn more please refer to the vignette +\code{vignette("transform-module-output", package = "teal")} or the \code{\link[teal:teal_transform_module]{teal::teal_transform_module()}} documentation. +} + +\section{Reporting}{ + + + +This module returns an object of class \code{teal_module}, that contains a \code{server} function. +Since the server function returns a \code{teal_report} object, this makes this module reportable, which means that +the reporting functionality will be turned on automatically by the \code{teal} framework. + +For more information on reporting in \code{teal}, see the vignettes: +\itemize{ +\item \code{vignette("reportable-shiny-application", package = "teal.reporter")} +\item \code{vignette("adding-support-for-reporting-to-custom-modules", package = "teal")} +} + +} + From 68e950c6d654e04a79932ce6203533a50e92149a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Wed, 10 Dec 2025 12:30:38 +0100 Subject: [PATCH 02/14] UI works --- R/tm_gt_tbl_summary.R | 167 ++++++++++++++++++++++++++++-------------- 1 file changed, 113 insertions(+), 54 deletions(-) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index 457552d98..2d87ecd1f 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -63,10 +63,10 @@ tm_gt_tbl_summary <- function( label = "Table summary", # table, # passed to tbl_summary() - by, + by = NULL, col_label = NULL, - statistics = list(all_continuous() ~ "{median} ({p25}, {p75})", all_categorical() ~ - "{n} ({p}%)"), + statistics = list(all_continuous() ~ "{median} ({p25}, {p75})", + all_categorical() ~ "{n} ({p}%)"), digits = NULL, type = NULL, value = NULL, @@ -90,30 +90,25 @@ tm_gt_tbl_summary <- function( # Make UI args args <- as.list(environment()) - data_extract_list <- list(table = table) - + data_extract_list <- list(by = by, include = include) module <- module( label = label, server = srv_gt_tbl_summary, ui = ui_gt_tbl_summary, ui_args = args, - server_args = c(data_extract_list, - as.list( - by = by, - col_label = col_label, - statistics = statistics, - digits = digits, - type = type, - value = value, - missing = missing, - nonmissing_text = nonmissing_text, - nonmissing_stat = nonmissing_stat, - sort = sort, - percent = percent, - include = include), - decorators = decorators), - transformators = transformators, - datanames = teal.transform::get_extract_datanames(data_extract_list) + server_args = list( + col_label = col_label, + statistics = statistics, + digits = digits, + type = type, + value = value, + # missing = missing, + missing_text = missing_text, + missing_stat = missing_stat, + sort = sort, + # percent = percent + decorators = decorators), + transformators = transformators ) attr(module, "teal_bookmarkable") <- TRUE module @@ -123,20 +118,20 @@ tm_gt_tbl_summary <- function( ui_gt_tbl_summary <- function(id, ...) { - args <- list(by = by, - # col_label = col_label, - # statistics = statistics, - # digits = digits, - # type = type, - # value = value, - missing = missing, - # nonmissing_text = nonmissing_text, - # nonmissing_stat = nonmissing_stat, - # sort = sort, - percent = percent, - include = include) + # args <- list(by = by, + # # col_label = col_label, + # # statistics = statistics, + # # digits = digits, + # # type = type, + # # value = value, + # missing = missing, + # # nonmissing_text = nonmissing_text, + # # nonmissing_stat = nonmissing_stat, + # # sort = sort, + # percent = percent, + # include = include) ns <- NS(id) - + args <- list(...) teal.widgets::standard_layout( output = teal.widgets::white_small_well( textOutput(ns("title")), @@ -144,9 +139,9 @@ ui_gt_tbl_summary <- function(id, ...) { ), encoding = tags$div( tags$label("Encodings", class = "text-primary"), - teal.transform::datanames_input(list(by, include)), - teal.transform::data_extract_ui(ns("by"), label = "Variable(s) to stratify with", by), - teal.transform::data_extract_ui(ns("include"), label = "Variable(s) to include", include), + teal.transform::datanames_input(args[c("by", "include")]), + teal.transform::data_extract_ui(ns("by"), label = "Variable(s) to stratify with", args$by), + teal.transform::data_extract_ui(ns("include"), label = "Variable(s) to include", args$include), radioButtons( ns("missing"), label = "Display NA counts", @@ -156,13 +151,13 @@ ui_gt_tbl_summary <- function(id, ...) { radioButtons( ns("percent"), label = "Percentage based on", - choices = c("Column" = "column", "Column" = "row", "Cell" = "cell"), + choices = c("Column" = "column", "Row" = "row", "Cell" = "cell"), selected = "column" ), ui_decorate_teal_data(ns("decorator"), decorators = select_decorators(args$decorators, "table")) ), - pre_output = pre_output, - post_output = post_output + pre_output = args$pre_output, + post_output = args$post_output ) } @@ -175,8 +170,8 @@ srv_gt_tbl_summary <- function(id, type, value, missing, - nonmissing_text, - nonmissing_stat, + missing_text, + missing_stat, sort, percent, include, @@ -186,7 +181,6 @@ srv_gt_tbl_summary <- function(id, moduleServer(id, function(input, output, session) { teal.logger::log_shiny_input_changes(input, namespace = "teal.modules.general") - # table, # by, # col_label, @@ -204,6 +198,8 @@ srv_gt_tbl_summary <- function(id, qenv <- reactive({ obj <- data() + validate(need(is_single_dataset(list(by, include)), "Variables should come from the same dataset")) + req(by, include) teal.reporter::teal_card(obj) <- c( teal.reporter::teal_card(obj), @@ -212,30 +208,93 @@ srv_gt_tbl_summary <- function(id, teal.code::eval_code(obj, "library(crane);library(dplyr)") }) - qenv_table <- reactive({ - q <- qenv() - witin(q, { - gt <- crane::tbl_roche_summary(ADSL) - }, - table = as.name(table)) - - }) - + summary_args <- reactive({ + dataset <- if (!is.null(by)) { + by$dataname + } else { + include$dataname + } # by + if (!is.null(by)) { + by_variable <- by$dataset + } # label columns + if (!is.null(col_label)) { + labels <- input$col_label + } + # statistics + # digits # type # value # nonmissing + if (input$missing != "ifany") { + nonmissing <- input$missing + } + # nonmissing_text + if (!identical(missing_text, "")) { + nonmissing_text <- input$missing_text + } # nonmissing_stat + if (!identical(missing_stat, "{N_miss}")) { + nonmissing_stat <- missing_stat + } # sort # percent + if (input$percent != "column") { + percent <- input$percent + } + # include + if (!is.null(include)) { + include_variables <- include$dataset + } + + table_crane <- call("tbl_roche_summary", + data = as.name(dataset) + ) + + }) + + output_q <- reactive({ + q <- req(qenv(), summary_args()) + within(q, { + table <- table_crane + }, + # table_crane = call("cran::tbl_roche_summary", table = as.name(by$dataname))) + table_crane = summary_args() + ) + }) + + # arg <- quote(argument) + # call("cran::tbl_roche_summary", arg = arg) + + # browser() + # crane::tbl_roche_summary - qenv_table + # browser(expr= is(output_q(), "qenv.error")) + + decorated_output_q <- srv_decorate_teal_data( + id = "decorator", + data = output_q, + decorators = select_decorators(decorators, "table"), + expr = quote(table) + ) + + table_r <- reactive({ + # req(iv_r()$is_valid()) + req(decorated_output_q())[["table"]] + }) + + teal.widgets::table_with_settings_srv( + id = "table", + table_r = table_r + ) + + decorated_output_q }) } From 788ae2ef97ff69110eb185fd513566d42d1cc0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Wed, 10 Dec 2025 17:11:22 +0100 Subject: [PATCH 03/14] Working version --- R/tm_gt_tbl_summary.R | 261 ++++++++++++++++++++++++------------------ 1 file changed, 152 insertions(+), 109 deletions(-) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index 2d87ecd1f..88b2471b9 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -43,42 +43,44 @@ #' data = data, #' modules = modules( #' tm_gt_tbl_summary( -#' table= data_extract_spec(dataname = "ADSL"), +#' table = data_extract_spec(dataname = "ADSL"), #' by = data_extract_spec(dataname = "ADSL", "SEX"), -#' include = data_extract_spec(dataname = "ADSL", -#' select = select_spec( -#' choices = c("SITEID", "COUNTRY", "ACTARM"), -#' selected = "SITEID", -#' multiple = TRUE, -#' fixed = FALSE -#' ) -#' ) +#' include = data_extract_spec( +#' dataname = "ADSL", +#' select = select_spec( +#' choices = c("SITEID", "COUNTRY", "ACTARM"), +#' selected = "SITEID", +#' multiple = TRUE, +#' fixed = FALSE +#' ) +#' ) #' ) #' ) #' ) -#'if (interactive()) { +#' if (interactive()) { #' shinyApp(app$ui, app$server) #' } tm_gt_tbl_summary <- function( - label = "Table summary", - # table, - # passed to tbl_summary() - by = NULL, - col_label = NULL, - statistics = list(all_continuous() ~ "{median} ({p25}, {p75})", - all_categorical() ~ "{n} ({p}%)"), - digits = NULL, - type = NULL, - value = NULL, - missing = c("ifany", "no", "always"), - missing_text = "", - missing_stat = "{N_miss}", - sort = all_categorical(FALSE) ~ "alphanumeric", - percent = c("column", "row", "cell"), - include = NULL, - - transformators = list(), - decorators = list() + label = "Table summary", + # table, + # passed to tbl_summary() + by = NULL, + col_label = NULL, + statistics = list( + all_continuous() ~ "{median} ({p25}, {p75})", + all_categorical() ~ "{n} ({p}%)" + ), + digits = NULL, + type = NULL, + value = NULL, + missing = c("ifany", "no", "always"), + missing_text = "", + missing_stat = "{N_miss}", + sort = all_categorical(FALSE) ~ "alphanumeric", + percent = c("column", "row", "cell"), + include = NULL, + transformators = list(), + decorators = list() ) { message("Initializing tm_gt_tbl_summary") checkmate::assert_string(label) @@ -90,13 +92,13 @@ tm_gt_tbl_summary <- function( # Make UI args args <- as.list(environment()) - data_extract_list <- list(by = by, include = include) module <- module( label = label, server = srv_gt_tbl_summary, ui = ui_gt_tbl_summary, ui_args = args, server_args = list( + by = by, col_label = col_label, statistics = statistics, digits = digits, @@ -106,14 +108,14 @@ tm_gt_tbl_summary <- function( missing_text = missing_text, missing_stat = missing_stat, sort = sort, - # percent = percent - decorators = decorators), + # percent = percent, + include = include, + decorators = decorators + ), transformators = transformators ) attr(module, "teal_bookmarkable") <- TRUE module - - } @@ -135,13 +137,20 @@ ui_gt_tbl_summary <- function(id, ...) { teal.widgets::standard_layout( output = teal.widgets::white_small_well( textOutput(ns("title")), - teal.widgets::table_with_settings_ui(ns("table")) + # teal.widgets::table_with_settings_ui(ns("table")) + gt::gt_output(ns("table")) ), encoding = tags$div( tags$label("Encodings", class = "text-primary"), teal.transform::datanames_input(args[c("by", "include")]), - teal.transform::data_extract_ui(ns("by"), label = "Variable(s) to stratify with", args$by), - teal.transform::data_extract_ui(ns("include"), label = "Variable(s) to include", args$include), + teal.transform::data_extract_ui(ns("by"), + label = "Variable(s) to stratify with", , + data_extract_spec = args$by + ), + teal.transform::data_extract_ui(ns("include"), + label = "Variable(s) to include", + data_extract_spec = args$include + ), radioButtons( ns("missing"), label = "Display NA counts", @@ -169,11 +178,11 @@ srv_gt_tbl_summary <- function(id, digits, type, value, - missing, + # missing, missing_text, missing_stat, sort, - percent, + # percent, include, decorators) { checkmate::assert_class(data, "reactive") @@ -181,25 +190,27 @@ srv_gt_tbl_summary <- function(id, moduleServer(id, function(input, output, session) { teal.logger::log_shiny_input_changes(input, namespace = "teal.modules.general") - # table, + # table, # by, - # col_label, - # statistics, - # digits, - # type, - # value, + # col_label, + # statistics, + # digits, + # type, + # value, # missing, - # nonmissing_text, - # nonmissing_stat, - # sort, + # nonmissing_text, + # nonmissing_stat, + # sort, # percent, # include qenv <- reactive({ - obj <- data() - validate(need(is_single_dataset(list(by, include)), "Variables should come from the same dataset")) - req(by, include) + obj <- req(data()) + if (!is.null(input$by) || !is.null(input$include)) { + validate(need(is_single_dataset(list(by = input$by, include = input$include)), "Variables should come from the same dataset")) + browser() + } teal.reporter::teal_card(obj) <- c( teal.reporter::teal_card(obj), @@ -209,75 +220,106 @@ srv_gt_tbl_summary <- function(id, }) summary_args <- reactive({ + # browser() + # by_input <- names(input)[endsWith(names(input), "-select")] + # selector_list <- teal.transform::data_extract_multiple_srv( + # data_extract = list(by = input$`by-dataset_ADSL_singleextract-select`, include = input$`include-dataset_ADSL_singleextract-select`), + # datasets = data) + dataset <- if (!is.null(by)) { by$dataname - } else { + } else { include$dataname - } - - # by - if (!is.null(by)) { - by_variable <- by$dataset - } - # label columns - if (!is.null(col_label)) { - labels <- input$col_label - } - - # statistics - - # digits - # type - # value - # nonmissing - if (input$missing != "ifany") { - nonmissing <- input$missing } - # nonmissing_text - if (!identical(missing_text, "")) { - nonmissing_text <- input$missing_text - } - # nonmissing_stat - if (!identical(missing_stat, "{N_miss}")) { - nonmissing_stat <- missing_stat - } - # sort - # percent - if (input$percent != "column") { - percent <- input$percent - } - - # include - if (!is.null(include)) { - include_variables <- include$dataset - } + # validate(need(!is.null(dataset), "Specify variables to stratify or to include on the summary table."), + # need(teal.transform::is_single_dataset(by, include), "Input from multiple tables: this module doesn't accept that.") + # ) + # + # nam_input <- names(input) + # # by + # if (!is.null(by)) { + # # browser() + # isolate({by_variable <- input[[nam_input[startsWith(nam_input, "by") & endsWith(nam_input, "select")]]]}) + # } + # # label columns + # if (!is.null(col_label)) { + # labels <- col_label + # } + # + # # statistics + # if (!is.null(statistics)) { + # validate(need(all(vapply(statistics, is, class2 = "formula", logical(1L))), "All elements of statistics should be formulas")) + # stats <- statistics + # } + # + # # digits + # if (!is.null(digits)) { + # integer <- is.integer(digits) && length(digits) >= 1L + # functions <- is.function(digits) || all(vapply(digits, is.function, logical(1L))) + # validate(need(any(integer || functions), "digits should be integer(s) or a function (or list of)")) + # } + # # type + # if (!is.null(type)) { + # possible_types <- c("continuous", "continuous2", "categorical", "dichotomous") + # validate(need(length(type) == 1L && type %in% possible_types, + # paste0("One of: c(", toString(dQuote(possible_types)), ").") + # )) + # } + # + # # value + # if (!is.null(type)) { + # possible_types <- c("continuous", "continuous2", "categorical", "dichotomous") + # validate(need(length(type) == 1L && type %in% possible_types, + # paste0("One of: c(", toString(dQuote(possible_types)), ").") + # )) + # } + # + # # nonmissing + # if (req(input$missing) != "ifany") { + # nonmissing <- input$missing + # } + # + # # nonmissing_text + # if (!identical(missing_text, "")) { + # valiate(need(is.character(missing_text), "Must be a character.")) + # nonmissing_text <- input$missing_text + # } + # # nonmissing_stat + # if (!identical(missing_stat, "{N_miss}")) { + # valiate(need(is.character(missing_stat), "Must be a character to be parsed by glue.")) + # nonmissing_stat <- missing_stat + # } + # # sort + # if (!is.null(sort)) { + # validate(need(all(vapply(statistics, is, class2 = "formula", logical(1L))), "All elements of sort should be formulas")) + # } + # # percent + # if (req(input$percent) != "column") { + # percent <- input$percent + # } + # + # # include + # if (!is.null(include)) { + # isolate({include_variables <- input[[nam_input[startsWith(nam_input, "include") & endsWith(nam_input, "select")]]]}) + # } table_crane <- call("tbl_roche_summary", - data = as.name(dataset) - ) - + data = as.name(dataset) + ) }) output_q <- reactive({ q <- req(qenv(), summary_args()) - within(q, { - table <- table_crane - }, - # table_crane = call("cran::tbl_roche_summary", table = as.name(by$dataname))) - table_crane = summary_args() + browser() + within(q, + { + table <- table_crane + }, + table_crane = summary_args() ) }) - # arg <- quote(argument) - # call("cran::tbl_roche_summary", arg = arg) - - # browser() - - - # crane::tbl_roche_summary - # browser(expr= is(output_q(), "qenv.error")) - decorated_output_q <- srv_decorate_teal_data( id = "decorator", data = output_q, @@ -286,8 +328,9 @@ srv_gt_tbl_summary <- function(id, ) table_r <- reactive({ - # req(iv_r()$is_valid()) - req(decorated_output_q())[["table"]] + req(decorated_output_q()) + table <- decorated_output_q()[["table"]] + gtsummary::as_gt(table) }) teal.widgets::table_with_settings_srv( @@ -295,6 +338,6 @@ srv_gt_tbl_summary <- function(id, table_r = table_r ) - decorated_output_q + table_r }) } From 264ed8fdf0691033e6e2e03aa43b7231f88c6401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Thu, 11 Dec 2025 10:05:02 +0100 Subject: [PATCH 04/14] Version working --- R/tm_gt_tbl_summary.R | 76 ++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index 88b2471b9..831a71346 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -5,7 +5,6 @@ #' @inheritParams teal::module #' @inheritParams shared_params #' @param ... Other argumments passed (eventually) to gtsummary::tbl_summary() -#' @param table (`data_extract_spec` or `list` of multiple `data_extract_spec`) #' Object with all available choices with pre-selected option for being summarized. #' @inherit shared_params return #' @@ -19,7 +18,7 @@ #' See code snippet below: #' #' ``` -#' tm_t_crosstable( +#' tm_gt_tbl_summary( #' ..., # arguments for module #' decorators = list( #' table = teal_transform_module(...) # applied to the `table` output @@ -43,7 +42,6 @@ #' data = data, #' modules = modules( #' tm_gt_tbl_summary( -#' table = data_extract_spec(dataname = "ADSL"), #' by = data_extract_spec(dataname = "ADSL", "SEX"), #' include = data_extract_spec( #' dataname = "ADSL", @@ -135,16 +133,12 @@ ui_gt_tbl_summary <- function(id, ...) { ns <- NS(id) args <- list(...) teal.widgets::standard_layout( - output = teal.widgets::white_small_well( - textOutput(ns("title")), - # teal.widgets::table_with_settings_ui(ns("table")) - gt::gt_output(ns("table")) - ), + output = gt::gt_output(ns("table")), encoding = tags$div( tags$label("Encodings", class = "text-primary"), teal.transform::datanames_input(args[c("by", "include")]), teal.transform::data_extract_ui(ns("by"), - label = "Variable(s) to stratify with", , + label = "Variable(s) to stratify with", data_extract_spec = args$by ), teal.transform::data_extract_ui(ns("include"), @@ -187,6 +181,9 @@ srv_gt_tbl_summary <- function(id, decorators) { checkmate::assert_class(data, "reactive") checkmate::assert_class(isolate(data()), "teal_data") + checkmate::assert_character(missing_text, len = 1L) + checkmate::assert_character(missing_stat, len = 1L) + moduleServer(id, function(input, output, session) { teal.logger::log_shiny_input_changes(input, namespace = "teal.modules.general") @@ -204,13 +201,12 @@ srv_gt_tbl_summary <- function(id, # percent, # include + # if (!is.null(by) || !is.null(include)) { + # validate(need(is_single_dataset(list(by = by, include = include)), "Variables should come from the same dataset.")) + # } qenv <- reactive({ obj <- req(data()) - if (!is.null(input$by) || !is.null(input$include)) { - validate(need(is_single_dataset(list(by = input$by, include = input$include)), "Variables should come from the same dataset")) - browser() - } teal.reporter::teal_card(obj) <- c( teal.reporter::teal_card(obj), @@ -220,18 +216,21 @@ srv_gt_tbl_summary <- function(id, }) summary_args <- reactive({ + # req(qenv()) # browser() # by_input <- names(input)[endsWith(names(input), "-select")] # selector_list <- teal.transform::data_extract_multiple_srv( # data_extract = list(by = input$`by-dataset_ADSL_singleextract-select`, include = input$`include-dataset_ADSL_singleextract-select`), # datasets = data) + # browser() + dataset <- if (!is.null(by)) { by$dataname } else { include$dataname } - + # # validate(need(!is.null(dataset), "Specify variables to stratify or to include on the summary table."), # need(teal.transform::is_single_dataset(by, include), "Input from multiple tables: this module doesn't accept that.") # ) @@ -304,40 +303,35 @@ srv_gt_tbl_summary <- function(id, # isolate({include_variables <- input[[nam_input[startsWith(nam_input, "include") & endsWith(nam_input, "select")]]]}) # } - table_crane <- call("tbl_roche_summary", - data = as.name(dataset) - ) + call("tbl_roche_summary", + data = as.name(dataset) + ) }) output_q <- reactive({ - q <- req(qenv(), summary_args()) - browser() + q <- req(qenv()) + table_call <- req(summary_args()) within(q, - { - table <- table_crane - }, - table_crane = summary_args() + expr = {table <- table_crane}, + table_crane = table_call ) }) - decorated_output_q <- srv_decorate_teal_data( - id = "decorator", - data = output_q, - decorators = select_decorators(decorators, "table"), - expr = quote(table) - ) - - table_r <- reactive({ - req(decorated_output_q()) - table <- decorated_output_q()[["table"]] - gtsummary::as_gt(table) - }) - - teal.widgets::table_with_settings_srv( - id = "table", - table_r = table_r - ) + # decorated_output_q <- srv_decorate_teal_data( + # id = "decorator", + # data = output_q, + # decorators = select_decorators(decorators, "table"), + # expr = quote(table) + # ) + # + # table_r <- reactive({ + # req(decorated_output_q()) + # table <- decorated_output_q()[["table"]] + # gtsummary::as_gt(table) + # }) + output$table <- gt::render_gt({ + gtsummary::as_gt(output_q()[["table"]])}) - table_r + output_q() }) } From f4cd741fc5528dc0029895490e9c524b5e7b27f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Thu, 11 Dec 2025 10:29:40 +0100 Subject: [PATCH 05/14] All arguments are added --- R/tm_gt_tbl_summary.R | 198 ++++++++++++++++++++++++------------------ 1 file changed, 114 insertions(+), 84 deletions(-) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index 831a71346..47ad6de25 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -230,81 +230,111 @@ srv_gt_tbl_summary <- function(id, } else { include$dataname } - # - # validate(need(!is.null(dataset), "Specify variables to stratify or to include on the summary table."), - # need(teal.transform::is_single_dataset(by, include), "Input from multiple tables: this module doesn't accept that.") - # ) - # - # nam_input <- names(input) - # # by - # if (!is.null(by)) { - # # browser() - # isolate({by_variable <- input[[nam_input[startsWith(nam_input, "by") & endsWith(nam_input, "select")]]]}) - # } - # # label columns - # if (!is.null(col_label)) { - # labels <- col_label - # } - # - # # statistics - # if (!is.null(statistics)) { - # validate(need(all(vapply(statistics, is, class2 = "formula", logical(1L))), "All elements of statistics should be formulas")) - # stats <- statistics - # } - # - # # digits - # if (!is.null(digits)) { - # integer <- is.integer(digits) && length(digits) >= 1L - # functions <- is.function(digits) || all(vapply(digits, is.function, logical(1L))) - # validate(need(any(integer || functions), "digits should be integer(s) or a function (or list of)")) - # } - # # type - # if (!is.null(type)) { - # possible_types <- c("continuous", "continuous2", "categorical", "dichotomous") - # validate(need(length(type) == 1L && type %in% possible_types, - # paste0("One of: c(", toString(dQuote(possible_types)), ").") - # )) - # } - # - # # value - # if (!is.null(type)) { - # possible_types <- c("continuous", "continuous2", "categorical", "dichotomous") - # validate(need(length(type) == 1L && type %in% possible_types, - # paste0("One of: c(", toString(dQuote(possible_types)), ").") - # )) - # } - # - # # nonmissing - # if (req(input$missing) != "ifany") { - # nonmissing <- input$missing - # } - # - # # nonmissing_text - # if (!identical(missing_text, "")) { - # valiate(need(is.character(missing_text), "Must be a character.")) - # nonmissing_text <- input$missing_text - # } - # # nonmissing_stat - # if (!identical(missing_stat, "{N_miss}")) { - # valiate(need(is.character(missing_stat), "Must be a character to be parsed by glue.")) - # nonmissing_stat <- missing_stat - # } - # # sort - # if (!is.null(sort)) { - # validate(need(all(vapply(statistics, is, class2 = "formula", logical(1L))), "All elements of sort should be formulas")) - # } - # # percent - # if (req(input$percent) != "column") { - # percent <- input$percent - # } + + validate(need(!is.null(dataset), "Specify variables to stratify or to include on the summary table."), + need(teal.transform::is_single_dataset(by, include), "Input from multiple tables: this module doesn't accept that.") + ) + + nam_input <- names(input) + # by + if (!is.null(by)) { + # browser() + by_variable <- input[[nam_input[startsWith(nam_input, "by") & endsWith(nam_input, "select")]]] + } else { + by_variable <- NULL + } + + # label columns + if (!is.null(col_label)) { + labels <- col_label + } + + # statistics + if (!is.null(statistics)) { + validate(need(all(vapply(statistics, is, class2 = "formula", logical(1L))), "All elements of statistics should be formulas")) + } + + # digits + if (!is.null(digits)) { + integer <- is.integer(digits) && length(digits) >= 1L + functions <- is.function(digits) || all(vapply(digits, is.function, logical(1L))) + validate(need(any(integer || functions), "digits should be integer(s) or a function (or list of)")) + } + # type + if (!is.null(type)) { + possible_types <- c("continuous", "continuous2", "categorical", "dichotomous") + validate(need(length(type) == 1L && type %in% possible_types, + paste0("One of: c(", toString(dQuote(possible_types)), ").") + )) + } + + # value + if (!is.null(type)) { + possible_types <- c("continuous", "continuous2", "categorical", "dichotomous") + validate(need(length(type) == 1L && type %in% possible_types, + paste0("One of: c(", toString(dQuote(possible_types)), ").") + )) + } + + # nonmissing + + # nonmissing_text + if (!identical(missing_text, "")) { + valiate(need(is.character(missing_text), "Must be a character.")) + nonmissing_text <- missing_text + } else { + nonmissing_text <- missing_text + } + # nonmissing_stat + if (!identical(missing_stat, "{N_miss}")) { + valiate(need(is.character(missing_stat), "Must be a character to be parsed by glue.")) + nonmissing_stat <- missing_stat + } else { + nonmissing_stat <- missing_stat + } + # sort + if (!is.null(sort)) { + validate(need(all(vapply(statistics, is, class2 = "formula", logical(1L))), "All elements of sort should be formulas")) + } + # percent + if (req(input$percent) != "column") { + percent <- input$percent + } # # # include - # if (!is.null(include)) { - # isolate({include_variables <- input[[nam_input[startsWith(nam_input, "include") & endsWith(nam_input, "select")]]]}) - # } + if (!is.null(include)) { + include_variables <- input[[nam_input[startsWith(nam_input, "include") & endsWith(nam_input, "select")]]] + } else { + include_variables <- NULL + } + # table, + # by, + # col_label, + # statistics, + # digits, + # type, + # value, + # missing, + # nonmissing_text, + # nonmissing_stat, + # sort, + # percent, + # include call("tbl_roche_summary", - data = as.name(dataset) + data = as.name(dataset), + by = by_variable, + label = col_label, + statistic = statistics, + digits = digits, + type = type, + value = value, + nonmissing = input$missing, + nonmissing_text = nonmissing_text, + nonmissing_stat = nonmissing_stat, + sort = sort, + percent = input$percent, + include = include_variables ) }) @@ -317,21 +347,21 @@ srv_gt_tbl_summary <- function(id, ) }) - # decorated_output_q <- srv_decorate_teal_data( - # id = "decorator", - # data = output_q, - # decorators = select_decorators(decorators, "table"), - # expr = quote(table) - # ) - # - # table_r <- reactive({ - # req(decorated_output_q()) - # table <- decorated_output_q()[["table"]] - # gtsummary::as_gt(table) - # }) + decorated_output_q <- srv_decorate_teal_data( + id = "decorator", + data = output_q, + decorators = select_decorators(decorators, "table"), + expr = quote(table) + ) + + table_r <- reactive({ + req(decorated_output_q()) + table <- decorated_output_q()[["table"]] + gtsummary::as_gt(table) + }) output$table <- gt::render_gt({ gtsummary::as_gt(output_q()[["table"]])}) - output_q() + decorated_output_q }) } From 320b80282e8340d0a4954282a63cb74cc90b1cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Thu, 11 Dec 2025 10:51:38 +0100 Subject: [PATCH 06/14] Fix some issues --- R/tm_gt_tbl_summary.R | 44 ++++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index 47ad6de25..c24be6387 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -65,18 +65,18 @@ tm_gt_tbl_summary <- function( by = NULL, col_label = NULL, statistics = list( - all_continuous() ~ "{median} ({p25}, {p75})", - all_categorical() ~ "{n} ({p}%)" + gtsummary::all_continuous() ~ c("{mean} ({sd})", "{median}", "{min} - {max}"), + gtsummary::all_categorical() ~ "{n} ({p}%)" ), digits = NULL, type = NULL, value = NULL, missing = c("ifany", "no", "always"), missing_text = "", - missing_stat = "{N_miss}", - sort = all_categorical(FALSE) ~ "alphanumeric", + missing_stat = "{N_nonmiss}", + sort = gtsummary::all_categorical(FALSE) ~ "alphanumeric", percent = c("column", "row", "cell"), - include = NULL, + include = tidyselect::everything(), transformators = list(), decorators = list() ) { @@ -147,13 +147,13 @@ ui_gt_tbl_summary <- function(id, ...) { ), radioButtons( ns("missing"), - label = "Display NA counts", - choices = c("If any" = "ifany", "No" = "no", "Always" = "always"), - selected = "ifany" + label = "Display missing counts:", + choices = c("No" = "no", "If any" = "ifany", "Always" = "always"), + selected = "no" ), radioButtons( ns("percent"), - label = "Percentage based on", + label = "Percentage based on:", choices = c("Column" = "column", "Row" = "row", "Cell" = "cell"), selected = "column" ), @@ -246,7 +246,7 @@ srv_gt_tbl_summary <- function(id, # label columns if (!is.null(col_label)) { - labels <- col_label + checkmate::check_character(col_label) } # statistics @@ -258,7 +258,7 @@ srv_gt_tbl_summary <- function(id, if (!is.null(digits)) { integer <- is.integer(digits) && length(digits) >= 1L functions <- is.function(digits) || all(vapply(digits, is.function, logical(1L))) - validate(need(any(integer || functions), "digits should be integer(s) or a function (or list of)")) + validate(need(any(integer || functions), "digits should be integer(s) or a function (or list of).")) } # type if (!is.null(type)) { @@ -280,28 +280,20 @@ srv_gt_tbl_summary <- function(id, # nonmissing_text if (!identical(missing_text, "")) { - valiate(need(is.character(missing_text), "Must be a character.")) - nonmissing_text <- missing_text - } else { - nonmissing_text <- missing_text + validate(need(is.character(missing_text), "Must be a character.")) } + # nonmissing_stat if (!identical(missing_stat, "{N_miss}")) { - valiate(need(is.character(missing_stat), "Must be a character to be parsed by glue.")) - nonmissing_stat <- missing_stat - } else { - nonmissing_stat <- missing_stat + validate(need(is.character(missing_stat), "Must be a character to be parsed by glue.")) } + # sort if (!is.null(sort)) { validate(need(all(vapply(statistics, is, class2 = "formula", logical(1L))), "All elements of sort should be formulas")) } # percent - if (req(input$percent) != "column") { - percent <- input$percent - } - # - # # include + # include if (!is.null(include)) { include_variables <- input[[nam_input[startsWith(nam_input, "include") & endsWith(nam_input, "select")]]] } else { @@ -330,8 +322,8 @@ srv_gt_tbl_summary <- function(id, type = type, value = value, nonmissing = input$missing, - nonmissing_text = nonmissing_text, - nonmissing_stat = nonmissing_stat, + nonmissing_text = missing_text, + nonmissing_stat = missing_stat, sort = sort, percent = input$percent, include = include_variables From ff56f5b0449fdf40d6210c8be93e615fc54d5e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Thu, 11 Dec 2025 10:55:13 +0100 Subject: [PATCH 07/14] Fix typo --- R/tm_gt_tbl_summary.R | 54 ++++++++++++++++++++++------------------ man/tm_gt_tbl_summary.Rd | 53 ++++++++++++++++++++++++++++++++------- 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index c24be6387..6ea455cfb 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -4,7 +4,7 @@ #' #' @inheritParams teal::module #' @inheritParams shared_params -#' @param ... Other argumments passed (eventually) to gtsummary::tbl_summary() +#' @param ... Other arguments passed (eventually) to gtsummary::tbl_summary() #' Object with all available choices with pre-selected option for being summarized. #' @inherit shared_params return #' @@ -231,8 +231,9 @@ srv_gt_tbl_summary <- function(id, include$dataname } - validate(need(!is.null(dataset), "Specify variables to stratify or to include on the summary table."), - need(teal.transform::is_single_dataset(by, include), "Input from multiple tables: this module doesn't accept that.") + validate( + need(!is.null(dataset), "Specify variables to stratify or to include on the summary table."), + need(teal.transform::is_single_dataset(by, include), "Input from multiple tables: this module doesn't accept that.") ) nam_input <- names(input) @@ -263,16 +264,18 @@ srv_gt_tbl_summary <- function(id, # type if (!is.null(type)) { possible_types <- c("continuous", "continuous2", "categorical", "dichotomous") - validate(need(length(type) == 1L && type %in% possible_types, - paste0("One of: c(", toString(dQuote(possible_types)), ").") + validate(need( + length(type) == 1L && type %in% possible_types, + paste0("One of: c(", toString(dQuote(possible_types)), ").") )) } # value if (!is.null(type)) { possible_types <- c("continuous", "continuous2", "categorical", "dichotomous") - validate(need(length(type) == 1L && type %in% possible_types, - paste0("One of: c(", toString(dQuote(possible_types)), ").") + validate(need( + length(type) == 1L && type %in% possible_types, + paste0("One of: c(", toString(dQuote(possible_types)), ").") )) } @@ -314,28 +317,30 @@ srv_gt_tbl_summary <- function(id, # include call("tbl_roche_summary", - data = as.name(dataset), - by = by_variable, - label = col_label, - statistic = statistics, - digits = digits, - type = type, - value = value, - nonmissing = input$missing, - nonmissing_text = missing_text, - nonmissing_stat = missing_stat, - sort = sort, - percent = input$percent, - include = include_variables - ) + data = as.name(dataset), + by = by_variable, + label = col_label, + statistic = statistics, + digits = digits, + type = type, + value = value, + nonmissing = input$missing, + nonmissing_text = missing_text, + nonmissing_stat = missing_stat, + sort = sort, + percent = input$percent, + include = include_variables + ) }) output_q <- reactive({ q <- req(qenv()) table_call <- req(summary_args()) within(q, - expr = {table <- table_crane}, - table_crane = table_call + expr = { + table <- table_crane + }, + table_crane = table_call ) }) @@ -352,7 +357,8 @@ srv_gt_tbl_summary <- function(id, gtsummary::as_gt(table) }) output$table <- gt::render_gt({ - gtsummary::as_gt(output_q()[["table"]])}) + gtsummary::as_gt(output_q()[["table"]]) + }) decorated_output_q }) diff --git a/man/tm_gt_tbl_summary.Rd b/man/tm_gt_tbl_summary.Rd index 0da447e99..74ac9208a 100644 --- a/man/tm_gt_tbl_summary.Rd +++ b/man/tm_gt_tbl_summary.Rd @@ -5,9 +5,20 @@ \title{\code{teal} module: Summary table} \usage{ tm_gt_tbl_summary( - label = "label", - table, - ..., + label = "Table summary", + by = NULL, + col_label = NULL, + statistics = list(gtsummary::all_continuous() ~ c("{mean} ({sd})", "{median}", + "{min} - {max}"), gtsummary::all_categorical() ~ "{n} ({p}\%)"), + digits = NULL, + type = NULL, + value = NULL, + missing = c("ifany", "no", "always"), + missing_text = "", + missing_stat = "{N_nonmiss}", + sort = gtsummary::all_categorical(FALSE) ~ "alphanumeric", + percent = c("column", "row", "cell"), + include = tidyselect::everything(), transformators = list(), decorators = list() ) @@ -16,11 +27,6 @@ tm_gt_tbl_summary( \item{label}{(\code{character(1)}) Label shown in the navigation item for the module or module group. For \code{modules()} defaults to \code{"root"}. See \code{Details}.} -\item{table}{(\code{data_extract_spec} or \code{list} of multiple \code{data_extract_spec}) -Object with all available choices with pre-selected option for being summarized.} - -\item{...}{Other argumments passed (eventually) to gtsummary::tbl_summary()} - \item{transformators}{(\code{list} of \code{teal_transform_module}) that will be applied to transform module's data input. To learn more check \code{vignette("transform-input-data", package = "teal")}.} @@ -30,6 +36,9 @@ decorator for tables or plots included in the module output reported. The decorators are applied to the respective output objects. See section "Decorating Module" below for more details.} + +\item{...}{Other arguments passed (eventually) to gtsummary::tbl_summary() +Object with all available choices with pre-selected option for being summarized.} } \value{ Object of class \code{teal_module} to be used in \code{teal} applications. @@ -49,7 +58,7 @@ A Decorator is applied to the specific output using a named list of \code{teal_t The name of this list corresponds to the name of the output to which the decorator is applied. See code snippet below: -\if{html}{\out{
}}\preformatted{tm_t_crosstable( +\if{html}{\out{
}}\preformatted{tm_gt_tbl_summary( ..., # arguments for module decorators = list( table = teal_transform_module(...) # applied to the `table` output @@ -80,3 +89,29 @@ For more information on reporting in \code{teal}, see the vignettes: } +\examples{ +data <- within(teal_data(), { + ADSL <- teal.data::rADSL +}) +join_keys(data) <- default_cdisc_join_keys[names(data)] +app <- init( + data = data, + modules = modules( + tm_gt_tbl_summary( + by = data_extract_spec(dataname = "ADSL", "SEX"), + include = data_extract_spec( + dataname = "ADSL", + select = select_spec( + choices = c("SITEID", "COUNTRY", "ACTARM"), + selected = "SITEID", + multiple = TRUE, + fixed = FALSE + ) + ) + ) + ) +) +if (interactive()) { + shinyApp(app$ui, app$server) +} +} From 920b107d0ce5ca9a2a33acce144e258e39a10f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Thu, 11 Dec 2025 11:04:37 +0100 Subject: [PATCH 08/14] Add words --- inst/WORDLIST | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inst/WORDLIST b/inst/WORDLIST index 03cc63b69..50ad25f7d 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -11,8 +11,10 @@ facetting funder ggmosaic ggplot +gtsummary pre qq reportable sortable tabset +tbl From 5708226df23562d7112dd7705153c992050a3b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Thu, 11 Dec 2025 11:29:55 +0100 Subject: [PATCH 09/14] Fix deselection of include variables --- R/tm_gt_tbl_summary.R | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index 6ea455cfb..ca7dcfdc6 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -297,10 +297,10 @@ srv_gt_tbl_summary <- function(id, } # percent # include - if (!is.null(include)) { - include_variables <- input[[nam_input[startsWith(nam_input, "include") & endsWith(nam_input, "select")]]] - } else { - include_variables <- NULL + # browser() + include_variables <- input[[nam_input[startsWith(nam_input, "include") & endsWith(nam_input, "select")]]] + if (is.null(include_variables)) { + include_variables <- formals(tm_gt_tbl_summary)$include } # table, # by, @@ -336,6 +336,7 @@ srv_gt_tbl_summary <- function(id, output_q <- reactive({ q <- req(qenv()) table_call <- req(summary_args()) + # browser() within(q, expr = { table <- table_crane From 585a999b9763c5cc0af422d1bc2e40232a646d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Thu, 11 Dec 2025 11:38:55 +0100 Subject: [PATCH 10/14] Fix some corner cases --- R/tm_gt_tbl_summary.R | 58 ++++++++----------------------------------- 1 file changed, 11 insertions(+), 47 deletions(-) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index ca7dcfdc6..f667e745d 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -187,23 +187,6 @@ srv_gt_tbl_summary <- function(id, moduleServer(id, function(input, output, session) { teal.logger::log_shiny_input_changes(input, namespace = "teal.modules.general") - # table, - # by, - # col_label, - # statistics, - # digits, - # type, - # value, - # missing, - # nonmissing_text, - # nonmissing_stat, - # sort, - # percent, - # include - - # if (!is.null(by) || !is.null(include)) { - # validate(need(is_single_dataset(list(by = by, include = include)), "Variables should come from the same dataset.")) - # } qenv <- reactive({ obj <- req(data()) @@ -212,18 +195,16 @@ srv_gt_tbl_summary <- function(id, teal.reporter::teal_card(obj), teal.reporter::teal_card("## Module's output(s)") ) - teal.code::eval_code(obj, "library(crane);library(dplyr)") + teal.code::eval_code(obj, "library(crane)") }) summary_args <- reactive({ - # req(qenv()) - # browser() - # by_input <- names(input)[endsWith(names(input), "-select")] - # selector_list <- teal.transform::data_extract_multiple_srv( - # data_extract = list(by = input$`by-dataset_ADSL_singleextract-select`, include = input$`include-dataset_ADSL_singleextract-select`), - # datasets = data) - # browser() + req(qenv()) + # table + if (!is.null(by) || !is.null(include)) { + validate(need(is_single_dataset(list(by = by, include = include)), "Variables should come from the same dataset.")) + } dataset <- if (!is.null(by)) { by$dataname @@ -237,12 +218,10 @@ srv_gt_tbl_summary <- function(id, ) nam_input <- names(input) - # by + + # by: input + corner cases if (!is.null(by)) { - # browser() by_variable <- input[[nam_input[startsWith(nam_input, "by") & endsWith(nam_input, "select")]]] - } else { - by_variable <- NULL } # label columns @@ -279,7 +258,7 @@ srv_gt_tbl_summary <- function(id, )) } - # nonmissing + # nonmissing: input # nonmissing_text if (!identical(missing_text, "")) { @@ -295,26 +274,12 @@ srv_gt_tbl_summary <- function(id, if (!is.null(sort)) { validate(need(all(vapply(statistics, is, class2 = "formula", logical(1L))), "All elements of sort should be formulas")) } - # percent - # include - # browser() + # percent: input + # include: input + corner cases include_variables <- input[[nam_input[startsWith(nam_input, "include") & endsWith(nam_input, "select")]]] if (is.null(include_variables)) { include_variables <- formals(tm_gt_tbl_summary)$include } - # table, - # by, - # col_label, - # statistics, - # digits, - # type, - # value, - # missing, - # nonmissing_text, - # nonmissing_stat, - # sort, - # percent, - # include call("tbl_roche_summary", data = as.name(dataset), @@ -336,7 +301,6 @@ srv_gt_tbl_summary <- function(id, output_q <- reactive({ q <- req(qenv()) table_call <- req(summary_args()) - # browser() within(q, expr = { table <- table_crane From 872179bb84b35982ac255b97a299c42ff0c209c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Thu, 11 Dec 2025 11:47:55 +0100 Subject: [PATCH 11/14] Fix example and some validation --- R/tm_gt_tbl_summary.R | 46 +++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index f667e745d..f9db5777a 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -42,15 +42,19 @@ #' data = data, #' modules = modules( #' tm_gt_tbl_summary( -#' by = data_extract_spec(dataname = "ADSL", "SEX"), -#' include = data_extract_spec( -#' dataname = "ADSL", -#' select = select_spec( -#' choices = c("SITEID", "COUNTRY", "ACTARM"), -#' selected = "SITEID", -#' multiple = TRUE, -#' fixed = FALSE -#' ) +#' by = data_extract_spec(dataname = "ADSL", +#' select = select_spec( +#' choices = c("SEX", "COUNTRY", "SITEID", "ACTARM"), +#' selected = "SEX", +#' multiple = FALSE) +#' ), +#' include = data_extract_spec(dataname = "ADSL", +#' select = select_spec( +#' choices = c("SITEID", "COUNTRY", "ACTARM"), +#' selected = "SITEID", +#' multiple = TRUE, +#' fixed = FALSE +#' ) #' ) #' ) #' ) @@ -60,8 +64,6 @@ #' } tm_gt_tbl_summary <- function( label = "Table summary", - # table, - # passed to tbl_summary() by = NULL, col_label = NULL, statistics = list( @@ -82,9 +84,13 @@ tm_gt_tbl_summary <- function( ) { message("Initializing tm_gt_tbl_summary") checkmate::assert_string(label) - # if (inherits(by, "data_extract_spec")) table <- list(by) - # checkmate::assert_list(by, types = "data_extract_spec") - # assert_single_selection(by) + if (inherits(by, "data_extract_spec")) { + checkmate::assert_list(list(by), types = "data_extract_spec", null.ok = TRUE, any.missing = FALSE, all.missing = FALSE) + assert_single_selection(list(by)) + } + if (inherits(include, "data_extract_spec")) { + checkmate::assert_list(list(include), types = "data_extract_spec", any.missing = FALSE, all.missing = FALSE) + } assert_decorators(decorators, "table") # Make UI args @@ -118,18 +124,6 @@ tm_gt_tbl_summary <- function( ui_gt_tbl_summary <- function(id, ...) { - # args <- list(by = by, - # # col_label = col_label, - # # statistics = statistics, - # # digits = digits, - # # type = type, - # # value = value, - # missing = missing, - # # nonmissing_text = nonmissing_text, - # # nonmissing_stat = nonmissing_stat, - # # sort = sort, - # percent = percent, - # include = include) ns <- NS(id) args <- list(...) teal.widgets::standard_layout( From 2033c58197e0ede3fb3fc7409c9783c0f806467d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Thu, 11 Dec 2025 12:21:17 +0100 Subject: [PATCH 12/14] Fix documentation --- NAMESPACE | 1 + R/tm_gt_tbl_summary.R | 46 +++++++----- man/tm_gt_tbl_summary.Rd | 154 +++++++++++++++++++++++++++++++++++---- 3 files changed, 168 insertions(+), 33 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 3f9bd315d..1e010af17 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -37,3 +37,4 @@ import(teal.transform) importFrom(dplyr,"%>%") importFrom(dplyr,.data) importFrom(lifecycle,deprecated) +importFrom(methods,is) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index f9db5777a..0872ede2f 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -4,14 +4,29 @@ #' #' @inheritParams teal::module #' @inheritParams shared_params -#' @param ... Other arguments passed (eventually) to gtsummary::tbl_summary() -#' Object with all available choices with pre-selected option for being summarized. -#' @inherit shared_params return +#' @param by (`data_extract_spec` or `list` of multiple `data_extract_spec`) +#' Object with all available choices with pre-selected option for how to split the rows +#' +#' `data_extract_spec` must not allow multiple selection. +#' @param include (`data_extract_spec` or `list` of multiple `data_extract_spec`) +#' Object with all available choices with pre-selected option for which columns to include as rows. #' +#' `data_extract_spec` can allow multiple selection in this case. +#' @param col_label Used to override default labels in summary table, e.g. `list(age = "Age, years")`. +#' The default for each variable is the column label attribute, `attr(., 'label')`. +#' If no label has been set, the column name is used. +#' @inheritParams gtsummary::tbl_summary +#' @inherit shared_params return +#' @param missing_text String indicating text shown on missing row. +#' @param missing_stat statistic to show on missing row. Default is `"{N_miss}"`. +#' Possible values are `N_miss`, `N_obs`, `N_nonmiss`, `p_miss`, `p_nonmiss`. +#' @inheritSection gtsummary::tbl_summary statistic argument +#' @inheritSection gtsummary::tbl_summary digits argument +#' @inheritSection gtsummary::tbl_summary type and value arguments #' @section Decorating Module: #' #' This module generates the following objects, which can be modified in place using decorators: -#' - `table` (`gtsummary` - output of `crane::tbl_roche_summary()`) +#' - `table` (`gtsummary` - output of [`crane::tbl_roche_summary()`]) #' #' A Decorator is applied to the specific output using a named list of `teal_transform_module` objects. #' The name of this list corresponds to the name of the output to which the decorator is applied. @@ -33,6 +48,7 @@ #' #' @inheritSection teal::example_module Reporting #' @export +#' @importFrom methods is #' @examples #' data <- within(teal_data(), { #' ADSL <- teal.data::rADSL @@ -66,18 +82,16 @@ tm_gt_tbl_summary <- function( label = "Table summary", by = NULL, col_label = NULL, - statistics = list( + statistic = list( gtsummary::all_continuous() ~ c("{mean} ({sd})", "{median}", "{min} - {max}"), gtsummary::all_categorical() ~ "{n} ({p}%)" ), digits = NULL, type = NULL, value = NULL, - missing = c("ifany", "no", "always"), missing_text = "", missing_stat = "{N_nonmiss}", sort = gtsummary::all_categorical(FALSE) ~ "alphanumeric", - percent = c("column", "row", "cell"), include = tidyselect::everything(), transformators = list(), decorators = list() @@ -104,7 +118,7 @@ tm_gt_tbl_summary <- function( server_args = list( by = by, col_label = col_label, - statistics = statistics, + statistic = statistic, digits = digits, type = type, value = value, @@ -162,15 +176,13 @@ srv_gt_tbl_summary <- function(id, data, by, col_label, - statistics, + statistic, digits, type, value, - # missing, missing_text, missing_stat, sort, - # percent, include, decorators) { checkmate::assert_class(data, "reactive") @@ -223,9 +235,9 @@ srv_gt_tbl_summary <- function(id, checkmate::check_character(col_label) } - # statistics - if (!is.null(statistics)) { - validate(need(all(vapply(statistics, is, class2 = "formula", logical(1L))), "All elements of statistics should be formulas")) + # statistic + if (!is.null(statistic)) { + validate(need(all(vapply(statistic, is, class2 = "formula", logical(1L))), "All elements of statistic should be formulas")) } # digits @@ -266,20 +278,20 @@ srv_gt_tbl_summary <- function(id, # sort if (!is.null(sort)) { - validate(need(all(vapply(statistics, is, class2 = "formula", logical(1L))), "All elements of sort should be formulas")) + validate(need(all(vapply(sort, is, class2 = "formula", logical(1L))), "All elements of sort should be formulas")) } # percent: input # include: input + corner cases include_variables <- input[[nam_input[startsWith(nam_input, "include") & endsWith(nam_input, "select")]]] if (is.null(include_variables)) { - include_variables <- formals(tm_gt_tbl_summary)$include + include_variables <- formals(tbl_summary)$include } call("tbl_roche_summary", data = as.name(dataset), by = by_variable, label = col_label, - statistic = statistics, + statistic = statistic, digits = digits, type = type, value = value, diff --git a/man/tm_gt_tbl_summary.Rd b/man/tm_gt_tbl_summary.Rd index 74ac9208a..48020578b 100644 --- a/man/tm_gt_tbl_summary.Rd +++ b/man/tm_gt_tbl_summary.Rd @@ -8,16 +8,14 @@ tm_gt_tbl_summary( label = "Table summary", by = NULL, col_label = NULL, - statistics = list(gtsummary::all_continuous() ~ c("{mean} ({sd})", "{median}", + statistic = list(gtsummary::all_continuous() ~ c("{mean} ({sd})", "{median}", "{min} - {max}"), gtsummary::all_categorical() ~ "{n} ({p}\%)"), digits = NULL, type = NULL, value = NULL, - missing = c("ifany", "no", "always"), missing_text = "", missing_stat = "{N_nonmiss}", sort = gtsummary::all_categorical(FALSE) ~ "alphanumeric", - percent = c("column", "row", "cell"), include = tidyselect::everything(), transformators = list(), decorators = list() @@ -27,6 +25,51 @@ tm_gt_tbl_summary( \item{label}{(\code{character(1)}) Label shown in the navigation item for the module or module group. For \code{modules()} defaults to \code{"root"}. See \code{Details}.} +\item{by}{(\code{data_extract_spec} or \code{list} of multiple \code{data_extract_spec}) +Object with all available choices with pre-selected option for how to split the rows + +\code{data_extract_spec} must not allow multiple selection.} + +\item{col_label}{Used to override default labels in summary table, e.g. \code{list(age = "Age, years")}. +The default for each variable is the column label attribute, \code{attr(., 'label')}. +If no label has been set, the column name is used.} + +\item{statistic}{(\code{\link[gtsummary:syntax]{formula-list-selector}})\cr +Specifies summary statistics to display for each variable. The default is +\code{list(all_continuous() ~ "{median} ({p25}, {p75})", all_categorical() ~ "{n} ({p}\%)")}. +See below for details.} + +\item{digits}{(\code{\link[gtsummary:syntax]{formula-list-selector}})\cr +Specifies how summary statistics are rounded. Values may be either integer(s) +or function(s). If not specified, default formatting is assigned +via \code{assign_summary_digits()}. See below for details.} + +\item{type}{(\code{\link[gtsummary:syntax]{formula-list-selector}})\cr +Specifies the summary type. Accepted value are +\code{c("continuous", "continuous2", "categorical", "dichotomous")}. +If not specified, default type is assigned via +\code{assign_summary_type()}. See below for details.} + +\item{value}{(\code{\link[gtsummary:syntax]{formula-list-selector}})\cr +Specifies the level of a variable to display on a single row. +The gtsummary type selectors, e.g. \code{all_dichotomous()}, cannot be used +with this argument. Default is \code{NULL}. See below for details.} + +\item{missing_text}{String indicating text shown on missing row.} + +\item{missing_stat}{statistic to show on missing row. Default is \code{"{N_miss}"}. +Possible values are \code{N_miss}, \code{N_obs}, \code{N_nonmiss}, \code{p_miss}, \code{p_nonmiss}.} + +\item{sort}{(\code{\link[gtsummary:syntax]{formula-list-selector}})\cr +Specifies sorting to perform for categorical variables. +Values must be one of \code{c("alphanumeric", "frequency")}. +Default is \code{all_categorical(FALSE) ~ "alphanumeric"}.} + +\item{include}{(\code{data_extract_spec} or \code{list} of multiple \code{data_extract_spec}) +Object with all available choices with pre-selected option for which columns to include as rows. + +\code{data_extract_spec} can allow multiple selection in this case.} + \item{transformators}{(\code{list} of \code{teal_transform_module}) that will be applied to transform module's data input. To learn more check \code{vignette("transform-input-data", package = "teal")}.} @@ -36,9 +79,6 @@ decorator for tables or plots included in the module output reported. The decorators are applied to the respective output objects. See section "Decorating Module" below for more details.} - -\item{...}{Other arguments passed (eventually) to gtsummary::tbl_summary() -Object with all available choices with pre-selected option for being summarized.} } \value{ Object of class \code{teal_module} to be used in \code{teal} applications. @@ -51,7 +91,7 @@ Generates a table summary from a dataset using gtsummary. This module generates the following objects, which can be modified in place using decorators: \itemize{ -\item \code{table} (\code{gtsummary} - output of \code{crane::tbl_roche_summary()}) +\item \code{table} (\code{gtsummary} - output of \code{\link[crane:tbl_roche_summary]{crane::tbl_roche_summary()}}) } A Decorator is applied to the specific output using a named list of \code{teal_transform_module} objects. @@ -73,6 +113,84 @@ To learn more please refer to the vignette \code{vignette("transform-module-output", package = "teal")} or the \code{\link[teal:teal_transform_module]{teal::teal_transform_module()}} documentation. } +\section{statistic argument}{ + + +The statistic argument specifies the statistics presented in the table. The +input dictates the summary statistics presented in the table. For example, +\code{statistic = list(age ~ "{mean} ({sd})")} would report the mean and +standard deviation for age; \code{statistic = list(all_continuous() ~ "{mean} ({sd})")} +would report the mean and standard deviation for all continuous variables. + +The values are interpreted using \code{\link[glue:glue]{glue::glue()}} syntax: +a name that appears between curly brackets will be interpreted as a function +name and the formatted result of that function will be placed in the table. + +For categorical variables, the following statistics are available to display: +\code{{n}} (frequency), \code{{N}} (denominator), \code{{p}} (percent). + +For continuous variables, \strong{any univariate function may be used}. +The most commonly used functions are \code{{median}}, \code{{mean}}, \code{{sd}}, \code{{min}}, +and \code{{max}}. +Additionally, \verb{{p##}} is available for percentiles, where \verb{##} is an integer from 0 to 100. +For example, \code{p25: quantile(probs=0.25, type=2)}. + +When the summary type is \code{"continuous2"}, pass a vector of statistics. +Each element of the vector will result in a separate row in the summary table. + +For both categorical and continuous variables, statistics on the number of +missing and non-missing observations and their proportions are available to +display. +\itemize{ +\item \code{{N_obs}} total number of observations +\item \code{{N_miss}} number of missing observations +\item \code{{N_nonmiss}} number of non-missing observations +\item \code{{p_miss}} percentage of observations missing +\item \code{{p_nonmiss}} percentage of observations not missing +} + +} + +\section{digits argument}{ + + +The digits argument specifies the the number of digits (or formatting function) +statistics are rounded to. + +The values passed can either be a single integer, a vector of integers, a +function, or a list of functions. If a single integer or function is passed, +it is recycled to the length of the number of statistics presented. +For example, if the statistic is \code{"{mean} ({sd})"}, it is equivalent to +pass \code{1}, \code{c(1, 1)}, \code{label_style_number(digits=1)}, and +\code{list(label_style_number(digits=1), label_style_number(digits=1))}. + +Named lists are also accepted to change the default formatting for a single +statistic, e.g. \code{list(sd = label_style_number(digits=1))}. + +} + +\section{type and value arguments}{ + + +There are four summary types. Use the \code{type} argument to change the default summary types. +\itemize{ +\item \code{"continuous"} summaries are shown on a \emph{single row}. Most numeric +variables default to summary type continuous. +\item \code{"continuous2"} summaries are shown on \emph{2 or more rows} +\item \code{"categorical"} \emph{multi-line} summaries of nominal data. Character variables, +factor variables, and numeric variables with fewer than 10 unique levels default to +type categorical. To change a numeric variable to continuous that +defaulted to categorical, use \code{type = list(varname ~ "continuous")} +\item \code{"dichotomous"} categorical variables that are displayed on a \emph{single row}, +rather than one row per level of the variable. +Variables coded as \code{TRUE}/\code{FALSE}, \code{0}/\code{1}, or \code{yes}/\code{no} are assumed to be dichotomous, +and the \code{TRUE}, \code{1}, and \code{yes} rows are displayed. +Otherwise, the value to display must be specified in the \code{value} +argument, e.g. \code{value = list(varname ~ "level to show")} +} + +} + \section{Reporting}{ @@ -98,15 +216,19 @@ app <- init( data = data, modules = modules( tm_gt_tbl_summary( - by = data_extract_spec(dataname = "ADSL", "SEX"), - include = data_extract_spec( - dataname = "ADSL", - select = select_spec( - choices = c("SITEID", "COUNTRY", "ACTARM"), - selected = "SITEID", - multiple = TRUE, - fixed = FALSE - ) + by = data_extract_spec(dataname = "ADSL", + select = select_spec( + choices = c("SEX", "COUNTRY", "SITEID", "ACTARM"), + selected = "SEX", + multiple = FALSE) + ), + include = data_extract_spec(dataname = "ADSL", + select = select_spec( + choices = c("SITEID", "COUNTRY", "ACTARM"), + selected = "SITEID", + multiple = TRUE, + fixed = FALSE + ) ) ) ) From 007a48e20d50c9991a1b92cc4af91007e03633ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla=20Sancho?= Date: Thu, 11 Dec 2025 12:22:02 +0100 Subject: [PATCH 13/14] Improve style --- R/tm_gt_tbl_summary.R | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/R/tm_gt_tbl_summary.R b/R/tm_gt_tbl_summary.R index 0872ede2f..8632801f1 100644 --- a/R/tm_gt_tbl_summary.R +++ b/R/tm_gt_tbl_summary.R @@ -58,19 +58,22 @@ #' data = data, #' modules = modules( #' tm_gt_tbl_summary( -#' by = data_extract_spec(dataname = "ADSL", -#' select = select_spec( -#' choices = c("SEX", "COUNTRY", "SITEID", "ACTARM"), -#' selected = "SEX", -#' multiple = FALSE) +#' by = data_extract_spec( +#' dataname = "ADSL", +#' select = select_spec( +#' choices = c("SEX", "COUNTRY", "SITEID", "ACTARM"), +#' selected = "SEX", +#' multiple = FALSE +#' ) #' ), -#' include = data_extract_spec(dataname = "ADSL", -#' select = select_spec( -#' choices = c("SITEID", "COUNTRY", "ACTARM"), -#' selected = "SITEID", -#' multiple = TRUE, -#' fixed = FALSE -#' ) +#' include = data_extract_spec( +#' dataname = "ADSL", +#' select = select_spec( +#' choices = c("SITEID", "COUNTRY", "ACTARM"), +#' selected = "SITEID", +#' multiple = TRUE, +#' fixed = FALSE +#' ) #' ) #' ) #' ) From 42080fe06c2f33723b436d3100c649e5a154da3b Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:32:32 +0000 Subject: [PATCH 14/14] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/tm_gt_tbl_summary.Rd | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/man/tm_gt_tbl_summary.Rd b/man/tm_gt_tbl_summary.Rd index 48020578b..555e2c97a 100644 --- a/man/tm_gt_tbl_summary.Rd +++ b/man/tm_gt_tbl_summary.Rd @@ -216,19 +216,22 @@ app <- init( data = data, modules = modules( tm_gt_tbl_summary( - by = data_extract_spec(dataname = "ADSL", - select = select_spec( - choices = c("SEX", "COUNTRY", "SITEID", "ACTARM"), - selected = "SEX", - multiple = FALSE) + by = data_extract_spec( + dataname = "ADSL", + select = select_spec( + choices = c("SEX", "COUNTRY", "SITEID", "ACTARM"), + selected = "SEX", + multiple = FALSE + ) ), - include = data_extract_spec(dataname = "ADSL", - select = select_spec( - choices = c("SITEID", "COUNTRY", "ACTARM"), - selected = "SITEID", - multiple = TRUE, - fixed = FALSE - ) + include = data_extract_spec( + dataname = "ADSL", + select = select_spec( + choices = c("SITEID", "COUNTRY", "ACTARM"), + selected = "SITEID", + multiple = TRUE, + fixed = FALSE + ) ) ) )