picks - #942
picks#942
Conversation
|
I have read the CLA Document and I hereby sign the CLA 2 out of 3 committers have signed the CLA. |
# Pull Request <!--- Replace `#nnn` with your issue link for reference. --> Fixes #996 <img width="868" height="722" alt="image" src="https://github.com/user-attachments/assets/529ef615-74f7-426e-b67a-b33618321fb4" /> I also renamed the test file to follow the standard naming convention test-{module_name}.R I don't see any obvious conflict with #942 as there isn't practically no change on the module itself.
# Pull Request - Fixes #999 ### Changes description - [x] Creates framework to use for `gt`-type data calls - Implements modules - [x] `tm_gtsummary` with `gtsummary` backend - [x] `tm_tbl_roche_summary` - [x] `tm_tbl_listing` - [ ] Discuss solution - [ ] Discuss removal of `.fun` argument in favor of static implementation only - [ ] Documentation _(pending technical discussion)_ ### Technical details Creates internal functions called `{tm/srv/ui}_gt_template` which implement a very flexible approach to arbitrary named arguments (also known as dots `...`). Name dots are dynamically generated because there are no common parameters between target functions. - **`data_extract_spec`** (future `teal.picks::picks()`): Print them dynamically on encoding panel. It will use data from `teal.data` object - **`choices_selected`** (future `teal.picsk::variables()`: Print an optional select input on ecoding panel - Other named dots: Considered static arguments for target function and not rendered in UI This assumes that any module needs at least 2 implemented functions `tm_<module_name>` `srv_<module_name>_partial` with an optional `ui_<module_name>_partial` The `partial` allows for custom logic and setting custom and optional UI. Note: no need of partial for `crane::tbl_listing` as it has no custom logic. `tbl_summary` and `tbl_roche_summary` need as we're using radio buttons and custom validation. Below is an example of a partial for `gt_summary::tbl_summary` (must return a qenv-like object to keep with `teal` practices) ```r srv_gtsummary_partial <- function(id, data, .fun_quo, ..., summary_args_r) { moduleServer(id, function(input, output, session) { summary_args_processed <- reactive({ tbl_summary_args <- req(summary_args_r()) # Arguments forwarded from the main server function (template) tbl_summary_args$missing <- input$missing # Additional argument from custom UI tbl_summary_args$percent <- input$percent # Additional argument from custom UI tbl_summary_args }) validated_q <- reactive({ # Custom validation for gtsummary q <- req(data()) summary_args <- req(summary_args_processed()) validate( need( length(summary_args$include) != 0L && all(!summary_args$include %in% summary_args$by), "Variables to stratify with and variables to include should be different" ), ) q }) srv_gt_template_partial( id = id, data = validated_q, .fun_quo = .fun_quo, ..., summary_args_r = summary_args_processed ) }) } ``` ### Example app Note: 1. Last module uses internal function that works just the same as `tm_gtsummary` (without validation) - Shows how flexible this implementation is 1. requires `teal.widgets@999-tm_gtsummary` branch for `tbl_listing` module to work _(it splits the table in 2)_ ```r pkgload::load_all("../teal.widgets") pkgload::load_all("../teal.modules.general") add_overall_decorator <- function(col_label = "**Overall** \nN = {style_number(N)}", statistic = NULL, digits = NULL, last = TRUE, enable = TRUE) { teal_transform_module( label = "Overall", ui = function(id) { ns <- NS(id) tags$div( bslib::input_switch( ns("enable"), label = "Enable it?", value = enable ), bslib::input_switch( ns("last"), label = "Show as last column", value = last ) ) }, server = function(id, data) { moduleServer(id, function(input, output, session) { teal.logger::log_shiny_input_changes(input, namespace = "teal.modules.gtsummary") observeEvent(input$enable, { shinyjs::toggleState("last", input$enable) }) reactive({ if (!input$enable) { data() } else { q <- within(data(), { table <- gtsummary::add_overall(table, col_label = col_label, statistic = statistic, last = last, digits = digits ) }, last = input$last, statistic = rlang::enexpr(statistic), digits = rlang::enexpr(digits), col_label = col_label ) validate_qenv(q) q } }) }) } ) } data <- within(teal.data::teal_data(), { ADSL <- teal.data::rADSL ADTTE <- teal.data::rADTTE }) join_keys(data) <- default_cdisc_join_keys[names(data)] app <- init( data = data, modules = modules( tm_tbl_roche_summary(label = "Roche Summary (no arguments)"), tm_tbl_summary(label = "GT Summary (no arguments)"), tm_tbl_roche_summary( label = "Roche Summary", by = teal.picks::picks( datasets(c("ADSL", "ADTTE"), "ADTTE"), variables(c("SEX", "COUNTRY", "SITEID", "ACTARM", "CNSR", "PARAMCD"), "SEX") ), include = teal.picks::picks( datasets(c("ADSL", "ADTTE"), "ADSL"), variables(c("SITEID", "COUNTRY", "ACTARM", "SEX"), "SITEID", multiple = TRUE) ), decorators = list(table = add_overall_decorator()) ), tm_tbl_summary( label = "GT Summary", by = teal.picks::picks( datasets(c("ADSL", "ADTTE"), "ADSL"), variables(c("SEX", "COUNTRY", "SITEID", "ACTARM"), "SEX") ), include = teal.picks::picks( datasets(c("ADSL", "ADTTE"), "ADSL"), variables(c("SITEID", "COUNTRY", "ACTARM"), "SITEID", multiple = TRUE) ) ), tm_tbl_listing( label = "Table Listing", dataname = "ADSL", decorators = list(listing = add_overall_decorator()) ), tm_gt_template( label = "GT Template (with gtsumary::tbl_summary)", .fun = gtsummary::tbl_summary, by = teal.picks::picks( datasets("ADSL", "ADSL"), variables(c("SEX", "COUNTRY", "SITEID", "ACTARM")) ), include = teal.picks::picks( datasets("ADSL", "ADSL"), variables(c("SITEID", "COUNTRY", "ACTARM"), "SITEID", multiple = TRUE) ), missing = teal.picks::values(c("no", "ifany", "always"), "ifany", multiple = FALSE), percent = teal.picks::values(c("column", "row", "cell"), "column", multiple = FALSE) ) ) ) |> shiny::runApp() ``` --------- Signed-off-by: André Veríssimo <211358+averissimo@users.noreply.github.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Dony Unardi <donyunardi@gmail.com>
Co-authored-by: Marcin <133694481+m7pr@users.noreply.github.com> Signed-off-by: André Veríssimo <211358+averissimo@users.noreply.github.com>
Closes insightsengineering/NEST-roadmap#36
Check also with:
validate_input()-- previously named as "picks" teal#1642In this PR, due to the fact that
data_extract_specis not 1:1 convertible topicks, I propose to have an S3 method in eachtm_to switch between data_extract_spec/picks depending on the input type. Eventually,data_extract_specvariant will be removed completely after deprecation period.