diff --git a/.lintr b/.lintr index 33fb8ec3..b26207c8 100644 --- a/.lintr +++ b/.lintr @@ -2,6 +2,7 @@ linters: linters_with_defaults( line_length_linter = line_length_linter(120), cyclocomp_linter = NULL, object_usage_linter = NULL, + pipe_consistency_linter = NULL, object_name_linter = object_name_linter( styles = c("snake_case", "symbols"), regexes = c(ANL = "^ANL_?[0-9A-Za-z_]*$", ADaM = "^r?AD[A-Z]{2,5}_?[0-9A-Za-z_]*$") diff --git a/DESCRIPTION b/DESCRIPTION index 27648b22..014a968f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -37,6 +37,7 @@ Imports: teal.code (>= 0.7.0), teal.data (>= 0.8.0), teal.logger (>= 0.4.0), + teal.picks (>= 0.1.0), teal.reporter (>= 0.6.0), teal.widgets (>= 0.5.0), tern (>= 0.9.7), @@ -49,10 +50,12 @@ Suggests: testthat (>= 3.2.3), withr (>= 3.0.0) Remotes: - insightsengineering/osprey + insightsengineering/osprey, + insightsengineering/teal.picks Config/Needs/verdepcheck: insightsengineering/osprey, rstudio/shiny, - insightsengineering/teal, insightsengineering/teal.slice, - insightsengineering/teal.transform, mllg/checkmate, tidyverse/dplyr, + insightsengineering/teal, insightsengineering/teal.picks, + insightsengineering/teal.slice, insightsengineering/teal.transform, + mllg/checkmate, tidyverse/dplyr, insightsengineering/formatters, tidyverse/ggplot2, r-lib/lifecycle, daroczig/logger, rstudio/shinyvalidate, insightsengineering/teal.code, insightsengineering/teal.logger, insightsengineering/teal.reporter, diff --git a/NAMESPACE b/NAMESPACE index 9d07d672..c2bc0c88 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,7 @@ # Generated by roxygen2: do not edit by hand +S3method(tm_g_ae_oview,default) +S3method(tm_g_ae_oview,pick) export(label_aevar) export(plot_decorate_output) export(quick_filter) diff --git a/R/tm_g_ae_oview.R b/R/tm_g_ae_oview.R index 67126e8d..6442957f 100644 --- a/R/tm_g_ae_oview.R +++ b/R/tm_g_ae_oview.R @@ -1,21 +1,27 @@ -#' Teal module for the `AE` overview +#' @title Teal module for the `AE` overview #' #' @description #' #' Display the `AE` overview plot as a shiny module #' +#' This is an S3 generic that dispatches on the class of `flag_var_anl`: +#' - [choices_selected][teal.transform::choices_selected()] dispatches to the +#' default method. +#' - [picks][teal.picks::picks()] dispatches to the picks method. +#' #' @inheritParams teal.widgets::standard_layout #' @inheritParams teal::module #' @inheritParams argument_convention -#' @param flag_var_anl ([`teal.transform::choices_selected`]) -#' `choices_selected` object with variables used to count adverse event +#' @param flag_var_anl Either a ([`teal.transform::choices_selected`]) +#' `choices_selected` object or a (`[teal.picks::variables()]`) +#' object with variables used to count adverse event #' sub-groups (e.g. Serious events, Related events, etc.) -#' +#' @param dataname (`character(1)`) Name of the events dataset. Required when +#' using the default method with [choices_selected][teal.transform::choices_selected()]. +#' Ignored by the `.picks` method. #' @inherit argument_convention return #' @inheritSection teal::example_module Reporting #' -#' @export -#' #' @examples #' data <- teal_data() %>% #' within({ @@ -47,7 +53,7 @@ #' join_keys(data) <- default_cdisc_join_keys[names(data)] #' #' ADAE <- data[["ADAE"]] -#' +#' # Using default method (choices selected) #' app <- init( #' data = data, #' modules = modules( @@ -73,17 +79,33 @@ #' shinyApp(app$ui, app$server) #' } #' +#' @export tm_g_ae_oview <- function(label, dataname, arm_var, flag_var_anl, - fontsize = c(5, 3, 7), - plot_height = c(600L, 200L, 2000L), - plot_width = NULL, - transformators = list()) { + fontsize, + plot_height, + plot_width, + transformators) { + UseMethod("tm_g_ae_oview", arm_var) +} + +#' @rdname tm_g_ae_oview +#' @export +tm_g_ae_oview.default <- function(label, + dataname, + arm_var, + flag_var_anl, + fontsize = c(5, 3, 7), + plot_height = c(600L, 200L, 2000L), + plot_width = NULL, + transformators = list()) { message("Initializing tm_g_ae_oview") + checkmate::assert_class(arm_var, classes = "choices_selected") checkmate::assert_class(flag_var_anl, classes = "choices_selected") + checkmate::assert( checkmate::check_number(fontsize, finite = TRUE), checkmate::assert( @@ -104,235 +126,14 @@ tm_g_ae_oview <- function(label, lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" ) - args <- as.list(environment()) - - module( + tm_g_ae_oview.pick( label = label, - server = srv_g_ae_oview, - server_args = list( - label = label, - dataname = dataname, - plot_height = plot_height, - plot_width = plot_width - ), - ui = ui_g_ae_oview, - ui_args = args, - transformators = transformators, - datanames = c("ADSL", dataname) + dataname = dataname, + arm_var = teal.picks::as.picks(arm_var), + flag_var_anl = teal.picks::as.picks(flag_var_anl), + fontsize, + plot_height, + plot_width, + transformators ) } - -ui_g_ae_oview <- function(id, ...) { - ns <- NS(id) - args <- list(...) - teal.widgets::standard_layout( - output = teal.widgets::white_small_well( - plot_decorate_output(id = ns(NULL)) - ), - encoding = tags$div( - teal.widgets::optionalSelectInput( - ns("arm_var"), - "Arm Variable", - choices = get_choices(args$arm_var$choices), - selected = args$arm_var$selected, - multiple = FALSE - ), - selectInput( - ns("arm_ref"), - "Control", - choices = get_choices(args$arm_var$choices), - selected = args$arm_var$selected - ), - selectInput( - ns("arm_trt"), - "Treatment", - choices = get_choices(args$arm_var$choices), - selected = args$arm_var$selected - ), - selectInput( - ns("flag_var_anl"), - "Flags", - choices = get_choices(args$flag_var_anl$choices), - selected = args$flag_var_anl$selected, - multiple = TRUE - ), - teal.widgets::panel_item( - "Confidence interval settings", - teal.widgets::optionalSelectInput( - ns("diff_ci_method"), - "Method for Difference of Proportions CI", - choices = ci_choices, - selected = ci_choices[1], - multiple = FALSE - ), - teal.widgets::optionalSliderInput( - ns("conf_level"), - "Confidence Level", - min = 0.5, - max = 1, - value = 0.95 - ) - ), - teal.widgets::optionalSelectInput( - ns("axis"), - "Axis Side", - choices = c("Left" = "left", "Right" = "right"), - selected = "left", - multiple = FALSE - ), - ui_g_decorate( - ns(NULL), - fontsize = args$fontsize, - titles = "AE Overview", - footnotes = "" - ) - ) - ) -} - -srv_g_ae_oview <- function(id, - data, - dataname, - label, - plot_height, - plot_width) { - 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.osprey") - iv <- reactive({ - ANL <- data()[[dataname]] - - iv <- shinyvalidate::InputValidator$new() - iv$add_rule("arm_var", shinyvalidate::sv_required( - message = "Arm Variable is required" - )) - iv$add_rule("arm_var", ~ if (!is.factor(ANL[[.]])) { - "Arm Var must be a factor variable" - }) - iv$add_rule("arm_var", ~ if (nlevels(ANL[[.]]) < 2L) { - "Selected Arm Var must have at least two levels" - }) - iv$add_rule("flag_var_anl", shinyvalidate::sv_required( - message = "At least one Flag is required" - )) - rule_diff <- function(value, other) { - if (isTRUE(value == other)) "Control and Treatment must be different" - } - iv$add_rule("arm_trt", rule_diff, other = input$arm_ref) - iv$add_rule("arm_ref", rule_diff, other = input$arm_trt) - iv$enable() - iv - }) - - decorate_output <- srv_g_decorate( - id = NULL, plt = plot_r, - plot_height = plot_height, plot_width = plot_width - ) - font_size <- decorate_output$font_size - pws <- decorate_output$pws - - observeEvent(list(input$diff_ci_method, input$conf_level), { - req(!is.null(input$diff_ci_method) && !is.null(input$conf_level)) - diff_ci_method <- input$diff_ci_method - conf_level <- input$conf_level - updateTextAreaInput(session, - "foot", - value = sprintf( - "Note: %d%% CI is calculated using %s", - round(conf_level * 100), - name_ci(diff_ci_method) - ) - ) - }) - - observeEvent(input$arm_var, ignoreNULL = TRUE, { - ANL <- data()[[dataname]] - arm_var <- input$arm_var - arm_val <- ANL[[arm_var]] - choices <- levels(arm_val) - - if (length(choices) == 1) { - trt_index <- 1 - } else { - trt_index <- 2 - } - - updateSelectInput( - session, - "arm_ref", - selected = choices[1], - choices = choices - ) - updateSelectInput( - session, - "arm_trt", - selected = choices[trt_index], - choices = choices - ) - }) - - output_q <- shiny::debounce( - millis = 200, - r = reactive({ - obj <- data() - teal.reporter::teal_card(obj) <- - c( - teal.reporter::teal_card(obj), - teal.reporter::teal_card("## Module's output(s)") - ) - obj <- teal.code::eval_code(obj, "library(dplyr)") - - ANL <- obj[[dataname]] - - teal::validate_has_data(ANL, min_nrow = 10, msg = sprintf("%s has not enough data", dataname)) - - teal::validate_inputs(iv()) - - validate(need( - input$arm_trt %in% ANL[[input$arm_var]] && input$arm_ref %in% ANL[[input$arm_var]], - "Treatment or Control not found in Arm Variable. Perhaps they have been filtered out?" - )) - - q1 <- obj %>% - teal.code::eval_code( - code = as.expression(c( - bquote(anl_labels <- formatters::var_labels(.(as.name(dataname)), fill = FALSE)), - bquote( - flags <- .(as.name(dataname)) %>% - select(all_of(.(input$flag_var_anl))) %>% - rename_at(vars(.(input$flag_var_anl)), function(x) paste0(x, ": ", anl_labels[x])) - ) - )) - ) - - teal.reporter::teal_card(q1) <- c(teal.reporter::teal_card(q1), "### Plot") - - teal.code::eval_code( - q1, - code = as.expression(c( - bquote( - plot <- osprey::g_events_term_id( - term = flags, - id = .(as.name(dataname))[["USUBJID"]], - arm = .(as.name(dataname))[[.(input$arm_var)]], - arm_N = table(ADSL[[.(input$arm_var)]]), - ref = .(input$arm_ref), - trt = .(input$arm_trt), - diff_ci_method = .(input$diff_ci_method), - conf_level = .(input$conf_level), - axis_side = .(input$axis), - fontsize = .(font_size()), - draw = TRUE - ) - ) - )) - ) - }) - ) - - plot_r <- reactive(output_q()[["plot"]]) - set_chunk_dims(pws, output_q) - }) -} diff --git a/R/tm_g_ae_oview_picks.R b/R/tm_g_ae_oview_picks.R new file mode 100644 index 00000000..19c622ad --- /dev/null +++ b/R/tm_g_ae_oview_picks.R @@ -0,0 +1,398 @@ +#' @rdname tm_g_ae_oview +#' @examples +#' data <- teal_data() %>% +#' within({ +#' library(dplyr) +#' ADSL <- rADSL +#' ADAE <- rADAE +#' .add_event_flags <- function(dat) { +#' dat <- dat %>% +#' mutate( +#' TMPFL_SER = AESER == "Y", +#' TMPFL_REL = AEREL == "Y", +#' TMPFL_GR5 = AETOXGR == "5", +#' AEREL1 = (AEREL == "Y" & ACTARM == "A: Drug X"), +#' AEREL2 = (AEREL == "Y" & ACTARM == "B: Placebo") +#' ) +#' labels <- c( +#' "Serious AE", "Related AE", "Grade 5 AE", +#' "AE related to A: Drug X", "AE related to B: Placebo" +#' ) +#' cols <- c("TMPFL_SER", "TMPFL_REL", "TMPFL_GR5", "AEREL1", "AEREL2") +#' for (i in seq_along(labels)) { +#' attr(dat[[cols[i]]], "label") <- labels[i] +#' } +#' dat +#' } +#' ADAE <- .add_event_flags(ADAE) +#' }) +#' join_keys(data) <- default_cdisc_join_keys[names(data)] +#' ADAE <- data[["ADAE"]] +#' # Using the picks method +#' app <- init( +#' data = data, +#' modules = modules( +#' tm_g_ae_oview( +#' label = "AE Overview", +#' dataname = "ADAE", +#' arm_var = teal.picks::variables( +#' choices = dplyr::starts_with("ACTARM"), +#' selected = "ACTARMCD" +#' ), +#' flag_var_anl = teal.picks::variables( +#' choices = c("TMPFL_SER", "TMPFL_REL", "TMPFL_GR5", "AEREL1", "AEREL2"), +#' selected = "AEREL1" +#' ), +#' plot_height = c(600, 200, 2000) +#' ) +#' ) +#' ) +#' if (interactive()) { +#' shinyApp(app$ui, app$server) +#' } +#' @export +tm_g_ae_oview.pick <- function( + label, # nolint: object_name_linter. + dataname, + arm_var = teal.picks::variables( + choices = teal.picks::is_categorical(min.len = 2), + selected = 1L + ), + flag_var_anl = teal.picks::variables( + choices = teal.picks::is_categorical(min.len = 2), + selected = 1L + ), + fontsize = c(5, 3, 7), + plot_height = c(600L, 200L, 2000L), + plot_width = NULL, + transformators = list() +) { + message("Initializing tm_g_ae_oview") + + arm_var <- teal.picks::picks(teal.picks::datasets(dataname), arm_var) + flag_var_anl <- teal.picks::picks( + teal.picks::datasets(dataname), + flag_var_anl + ) + + checkmate::assert_class(arm_var, "picks") + if (isTRUE(attr(arm_var$variables, "multiple"))) { + warning( + "`arm_var` accepts only a single variable selection. ", + "Forcing `teal.picks::variables(multiple)` to FALSE." + ) + attr(arm_var$variables, "multiple") <- FALSE + } + + checkmate::assert_class(flag_var_anl, "picks") + if (isTRUE(attr(flag_var_anl$variables, "multiple"))) { + warning( + "`flag_var_anl` accepts only a single variable selection. ", + "Forcing `teal.picks::variables(multiple)` to FALSE." + ) + attr(flag_var_anl$variables, "multiple") <- FALSE + } + + checkmate::assert( + checkmate::check_number(fontsize, finite = TRUE), + checkmate::assert( + combine = "and", + .var.name = "fontsize", + checkmate::check_numeric( + fontsize, + len = 3, + any.missing = FALSE, + finite = TRUE + ), + checkmate::check_numeric( + fontsize[1], + lower = fontsize[2], + upper = fontsize[3] + ) + ) + ) + checkmate::assert_numeric( + plot_height, + len = 3, + any.missing = FALSE, + finite = TRUE + ) + checkmate::assert_numeric( + plot_height[1], + lower = plot_height[2], + upper = plot_height[3], + .var.name = "plot_height" + ) + checkmate::assert_numeric( + plot_width, + len = 3, + any.missing = FALSE, + null.ok = TRUE, + finite = TRUE + ) + checkmate::assert_numeric( + plot_width[1], + lower = plot_width[2], + upper = plot_width[3], + null.ok = TRUE, + .var.name = "plot_width" + ) + + args <- as.list(environment()) + + module( + label = label, + server = srv_g_ae_oview.picks, + server_args = args[names(args) %in% names(formals(srv_g_ae_oview.picks))], + ui = ui_g_ae_oview.picks, + ui_args = args[names(args) %in% names(formals(ui_g_ae_oview.picks))], + transformators = transformators, + datanames = c("ADSL", dataname) + ) +} + +# nolint start: object_name_linter. +ui_g_ae_oview.picks <- function( + # nolint end: object_name_linter. + id, + arm_var, + flag_var_anl, + fontsize +) { + ns <- NS(id) + + teal.widgets::standard_layout( + output = teal.widgets::white_small_well( + plot_decorate_output(id = ns(NULL)) + ), + encoding = tags$div( + tags$label("Encodings", class = "text-primary"), + tags$div( + tags$strong("Arm variable"), + teal.picks::picks_ui(id = ns("arm_var"), picks = arm_var) + ), + tags$div( + tags$strong("Flag variables"), + teal.picks::picks_ui(id = ns("flag_var_anl"), picks = flag_var_anl) + ), + selectInput( + ns("arm_ref"), + "Control", + choices = NULL + ), + selectInput( + ns("arm_trt"), + "Treatment", + choices = NULL + ), + teal.widgets::panel_item( + "Confidence interval settings", + teal.widgets::optionalSelectInput( + ns("diff_ci_method"), + "Method for Difference of Proportions CI", + choices = ci_choices, + selected = ci_choices[1], + multiple = FALSE + ), + teal.widgets::optionalSliderInput( + ns("conf_level"), + "Confidence Level", + min = 0.5, + max = 1, + value = 0.95 + ) + ), + teal.widgets::optionalSelectInput( + ns("axis"), + "Axis Side", + choices = c("Left" = "left", "Right" = "right"), + selected = "left", + multiple = FALSE + ), + ui_g_decorate( + ns(NULL), + fontsize = fontsize, + titles = "AE Overview", + footnotes = "" + ) + ) + ) +} + +# nolint start: object_name_linter. +srv_g_ae_oview.picks <- function( + # nolint end: object_name_linter. + id, + data, + arm_var, + flag_var_anl, + plot_height, + plot_width +) { + checkmate::assert_class(data, "reactive") + checkmate::assert_class(isolate(data()), "teal_data") + + moduleServer(id, function(input, output, session) { + # Initialize picks selectors + selectors <- teal.picks::picks_srv( + picks = list( + arm_var = arm_var, + flag_var_anl = flag_var_anl + ), + data = data + ) + + # Merge datasets based on picks selections + merged <- teal.picks::merge_srv( + "merge", + data = data, + selectors = selectors, + output_name = "ANL" + ) + + teal.logger::log_shiny_input_changes(input, namespace = "teal.osprey") + + decorate_output <- srv_g_decorate( + id = NULL, + plt = plot_r, + plot_height = plot_height, + plot_width = plot_width + ) + font_size <- decorate_output$font_size + pws <- decorate_output$pws + + observeEvent(list(input$diff_ci_method, input$conf_level), { + req(!is.null(input$diff_ci_method) && !is.null(input$conf_level)) + diff_ci_method <- input$diff_ci_method + conf_level <- input$conf_level + updateTextAreaInput( + session, + "foot", + value = sprintf( + "Note: %d%% CI is calculated using %s", + round(conf_level * 100), + name_ci(diff_ci_method) + ) + ) + }) + + observeEvent(merged$variables()$arm_var, { + arm_var_name <- merged$variables()$arm_var + arm_dataset <- selectors$arm_var()$datasets$selected + req(arm_var_name, arm_dataset) + + arm_data <- data()[[arm_dataset]] + choices <- levels(arm_data[[arm_var_name]]) + + if (length(choices) == 1) { + trt_index <- 1 + } else { + trt_index <- 2 + } + + updateSelectInput( + session, + "arm_ref", + selected = choices[1], + choices = choices + ) + updateSelectInput( + session, + "arm_trt", + selected = choices[trt_index], + choices = choices + ) + }) + + output_q <- shiny::debounce( + millis = 200, + r = reactive({ + qenv <- merged$data() + + arm_var_name <- selectors$arm_var()$variables$selected + arm_dataset <- selectors$arm_var()$datasets$selected + + teal.reporter::teal_card(qenv) <- + c( + teal.reporter::teal_card(qenv), + teal.reporter::teal_card("## Module's output(s)") + ) + qenv <- teal.code::eval_code(qenv, "library(dplyr)") + + ANL <- qenv[["ANL"]] + + arm_var_name <- merged$variables()$arm_var + flag_var_name <- merged$variables()$flag_var_anl + + teal::validate_has_data( + ANL, + min_nrow = 10, + msg = "Analysis data set must have at least 10 data points" + ) + + # Original variable name and dataset for arm_N calculation on the source dataset + arm_var_orig <- selectors$arm_var()$variables$selected + arm_dataset <- selectors$arm_var()$datasets$selected + + shiny::validate( + shiny::need( + length(flag_var_name) > 0, + "A Flag Variable needs to be selected." + ), + shiny::need( + length(arm_var_name) > 0, + "An Arm Variable needs to be selected." + ) + ) + + validate(need( + input$arm_trt %in% + ANL[[arm_var_name]] && + input$arm_ref %in% ANL[[arm_var_name]], + "Treatment or Control not found in Arm Variable. Perhaps they have been filtered out?" + )) + q1 <- qenv %>% + teal.code::eval_code( + code = as.expression(c( + bquote(anl_labels <- formatters::var_labels(ANL, fill = FALSE)), + bquote( + flags <- ANL %>% + select(all_of(.(flag_var_name))) %>% + rename_at(.(flag_var_name), function(x) { + paste0(x, ": ", anl_labels[x]) + }) + ) + )) + ) + + teal.reporter::teal_card(q1) <- c( + teal.reporter::teal_card(q1), + "### Plot" + ) + teal.code::eval_code( + q1, + code = as.expression(c( + bquote( + plot <- osprey::g_events_term_id( + term = flags, + id = ANL$USUBJID, + arm = ANL[[.(arm_var_name)]], + arm_N = table(ANL[[.(arm_var_name)]]), + ref = .(input$arm_ref), + trt = .(input$arm_trt), + diff_ci_method = .(input$diff_ci_method), + conf_level = .(input$conf_level), + axis_side = .(input$axis), + fontsize = .(font_size()), + draw = TRUE + ) + ) + )) + ) + }) + ) + + plot_r <- reactive(output_q()[["plot"]]) + set_chunk_dims(pws, output_q) + }) +} diff --git a/inst/WORDLIST b/inst/WORDLIST index 21a72e78..a8ba8c59 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -8,6 +8,7 @@ UI Xuefeng chot ci +datanames dichotomization funder houx diff --git a/man/tm_g_ae_oview.Rd b/man/tm_g_ae_oview.Rd index 375f1212..2853dc0f 100644 --- a/man/tm_g_ae_oview.Rd +++ b/man/tm_g_ae_oview.Rd @@ -1,7 +1,9 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/tm_g_ae_oview.R +% Please edit documentation in R/tm_g_ae_oview.R, R/tm_g_ae_oview_picks.R \name{tm_g_ae_oview} \alias{tm_g_ae_oview} +\alias{tm_g_ae_oview.default} +\alias{tm_g_ae_oview.pick} \title{Teal module for the \code{AE} overview} \usage{ tm_g_ae_oview( @@ -9,6 +11,30 @@ tm_g_ae_oview( dataname, arm_var, flag_var_anl, + fontsize, + plot_height, + plot_width, + transformators +) + +\method{tm_g_ae_oview}{default}( + label, + dataname, + arm_var, + flag_var_anl, + fontsize = c(5, 3, 7), + plot_height = c(600L, 200L, 2000L), + plot_width = NULL, + transformators = list() +) + +\method{tm_g_ae_oview}{pick}( + label, + dataname, + arm_var = teal.picks::variables(choices = teal.picks::is_categorical(min.len = 2), + selected = 1L), + flag_var_anl = teal.picks::variables(choices = teal.picks::is_categorical(min.len = 2), + selected = 1L), fontsize = c(5, 3, 7), plot_height = c(600L, 200L, 2000L), plot_width = NULL, @@ -19,17 +45,18 @@ tm_g_ae_oview( \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{dataname}{(\code{character(1)})\cr -analysis data used in the teal module, needs to be -available in the list passed to the \code{data} argument of \code{\link[teal:init]{teal::init()}}.} +\item{dataname}{(\code{character(1)}) Name of the events dataset. Required when +using the default method with \link[teal.transform:choices_selected]{choices_selected}. +Ignored by the \code{.picks} method.} \item{arm_var}{(\code{choices_selected})\cr object with all available choices and the pre-selected option for variable names that can be used as \code{arm_var}. See \code{\link[teal.transform:choices_selected]{teal.transform::choices_selected()}} for details. Column \code{arm_var} in the \code{dataname} has to be a factor.} -\item{flag_var_anl}{(\code{\link[teal.transform:choices_selected]{teal.transform::choices_selected}}) -\code{choices_selected} object with variables used to count adverse event +\item{flag_var_anl}{Either a (\code{\link[teal.transform:choices_selected]{teal.transform::choices_selected}}) +\code{choices_selected} object or a (\verb{[teal.picks::variables()]}) +object with variables used to count adverse event sub-groups (e.g. Serious events, Related events, etc.)} \item{fontsize}{(\code{numeric(1)} or \code{numeric(3)})\cr @@ -51,6 +78,13 @@ the \code{\link[teal:teal_modules]{teal::module()}} object. } \description{ Display the \code{AE} overview plot as a shiny module + +This is an S3 generic that dispatches on the class of \code{flag_var_anl}: +\itemize{ +\item \link[teal.transform:choices_selected]{choices_selected} dispatches to the +default method. +\item \link[teal.picks:picks]{picks} dispatches to the picks method. +} } \section{Reporting}{ @@ -99,7 +133,7 @@ data <- teal_data() \%>\% join_keys(data) <- default_cdisc_join_keys[names(data)] ADAE <- data[["ADAE"]] - +# Using default method (choices selected) app <- init( data = data, modules = modules( @@ -125,4 +159,54 @@ if (interactive()) { shinyApp(app$ui, app$server) } +data <- teal_data() \%>\% + within({ + library(dplyr) + ADSL <- rADSL + ADAE <- rADAE + .add_event_flags <- function(dat) { + dat <- dat \%>\% + mutate( + TMPFL_SER = AESER == "Y", + TMPFL_REL = AEREL == "Y", + TMPFL_GR5 = AETOXGR == "5", + AEREL1 = (AEREL == "Y" & ACTARM == "A: Drug X"), + AEREL2 = (AEREL == "Y" & ACTARM == "B: Placebo") + ) + labels <- c( + "Serious AE", "Related AE", "Grade 5 AE", + "AE related to A: Drug X", "AE related to B: Placebo" + ) + cols <- c("TMPFL_SER", "TMPFL_REL", "TMPFL_GR5", "AEREL1", "AEREL2") + for (i in seq_along(labels)) { + attr(dat[[cols[i]]], "label") <- labels[i] + } + dat + } + ADAE <- .add_event_flags(ADAE) + }) +join_keys(data) <- default_cdisc_join_keys[names(data)] +ADAE <- data[["ADAE"]] +# Using the picks method +app <- init( + data = data, + modules = modules( + tm_g_ae_oview( + label = "AE Overview", + dataname = "ADAE", + arm_var = teal.picks::variables( + choices = dplyr::starts_with("ACTARM"), + selected = "ACTARMCD" + ), + flag_var_anl = teal.picks::variables( + choices = c("TMPFL_SER", "TMPFL_REL", "TMPFL_GR5", "AEREL1", "AEREL2"), + selected = "AEREL1" + ), + plot_height = c(600, 200, 2000) + ) + ) +) +if (interactive()) { + shinyApp(app$ui, app$server) +} } diff --git a/tests/testthat/test-tm_g_ae_oview.R b/tests/testthat/test-tm_g_ae_oview.R new file mode 100644 index 00000000..157f71a7 --- /dev/null +++ b/tests/testthat/test-tm_g_ae_oview.R @@ -0,0 +1,69 @@ +arm_var_cs <- teal.transform::choices_selected( + selected = "ACTARM", + choices = c("ACTARM", "ACTARMCD") +) + +flag_var_cs <- teal.transform::choices_selected( + selected = "TMPFL_SER", + choices = c("TMPFL_SER", "TMPFL_REL", "TMPFL_GR5") +) + +arm_var_picks <- teal.picks::variables( + choices = teal.picks::is_categorical(min.len = 2), + selected = 1L +) + +flag_var_picks <- teal.picks::variables( + choices = c("TMPFL_SER", "TMPFL_REL", "TMPFL_GR5"), + selected = "TMPFL_SER" +) + +testthat::describe("tm_g_ae_oview argument verification", { + testthat::it("fails when arm_var is pick but flag_var_anl is choices_selected", { + testthat::expect_error( + tm_g_ae_oview( + label = "AE Overview", + dataname = "ADAE", + arm_var = arm_var_picks, + flag_var_anl = flag_var_cs + ), + class = "error" + ) + }) + + testthat::it("fails when arm_var is choices_selected but flag_var_anl is pick", { + testthat::expect_error( + tm_g_ae_oview( + label = "AE Overview", + dataname = "ADAE", + arm_var = arm_var_cs, + flag_var_anl = flag_var_picks + ), + class = "error" + ) + }) +}) + +testthat::describe("tm_g_ae_oview module creation", { + testthat::it("creates a teal module using choices_selected (default method)", { + mod <- tm_g_ae_oview( + label = "AE Overview", + dataname = "ADAE", + arm_var = arm_var_cs, + flag_var_anl = flag_var_cs, + plot_height = c(600, 200, 2000) + ) + testthat::expect_s3_class(mod, "teal_module") + }) + + testthat::it("creates a teal module using picks (.pick method)", { + mod <- tm_g_ae_oview( + label = "AE Overview", + dataname = "ADAE", + arm_var = arm_var_picks, + flag_var_anl = flag_var_picks, + plot_height = c(600, 200, 2000) + ) + testthat::expect_s3_class(mod, "teal_module") + }) +})