diff --git a/NEWS.md b/NEWS.md index 4d7dc6edb3..9e883c853b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,7 @@ ### Enhancements * Replaced `synthetic_cdisc_data` with refactored `synthetic_cdisc_dataset` function to speed up dataset loading in tests/examples. +* Added titles and footnotes to `tm_a_gee`. ### Miscellaneous * Package now uses `scda.2022` rather than `scda.2021` in SUGGESTS. diff --git a/R/tm_a_gee.R b/R/tm_a_gee.R index fe83d195c8..81ca23ecd2 100644 --- a/R/tm_a_gee.R +++ b/R/tm_a_gee.R @@ -21,11 +21,14 @@ template_a_gee <- function(output_table, visit_var, split_covariates, cor_struct, - conf_level = 0.95) { + conf_level = 0.95, + basic_table_args = teal.widgets::basic_table_args()) { y <- list() y$model <- list() y$table <- list() + all_basic_table_args <- teal.widgets::resolve_basic_table_args(basic_table_args) + model_list <- add_expr( list(), substitute( @@ -59,14 +62,28 @@ template_a_gee <- function(output_table, add_expr( list(), if (output_table == "t_gee_cov") { - quote(result_table <- tern.gee::as.rtable(model_fit, type = "cov")) + substitute( + expr = { + result_table <- tern.gee::as.rtable(model_fit, type = "cov") + subtitles(result_table) <- st + main_footer(result_table) <- mf + }, + env = list( + st = basic_table_args$subtitles, + mf = basic_table_args$main_footer + ) + ) } else if (output_table == "t_gee_coef") { substitute( expr = { result_table <- tern.gee::as.rtable(model_fit, type = "coef", conf_level = conf_level) + subtitles(result_table) <- st + main_footer(result_table) <- mf }, env = list( - conf_level = conf_level + conf_level = conf_level, + st = basic_table_args$subtitles, + mf = basic_table_args$main_footer ) ) } else if (output_table == "t_gee_lsmeans") { @@ -82,12 +99,16 @@ template_a_gee <- function(output_table, alt_counts_df = dataname_lsmeans ) + subtitles(result_table) <- st + main_footer(result_table) <- mf result_table }, env = list( dataname_lsmeans = as.name(dataname_lsmeans), conf_level = conf_level, - input_arm_var = input_arm_var + input_arm_var = input_arm_var, + st = basic_table_args$subtitles, + mf = basic_table_args$main_footer ) ) } @@ -107,6 +128,60 @@ template_a_gee <- function(output_table, #' @export #' #' @examples +#' library(scda) +#' +#' ADSL <- synthetic_cdisc_dataset("latest", "adsl") +#' ADQS <- synthetic_cdisc_dataset("latest", "adqs") %>% +#' dplyr::filter(ABLFL != "Y" & ABLFL2 != "Y") %>% +#' dplyr::mutate( +#' AVISIT = as.factor(AVISIT), +#' AVISITN = rank(AVISITN) %>% +#' as.factor() %>% +#' as.numeric() %>% +#' as.factor(), +#' AVALBIN = AVAL < 50 # Just as an example to get a binary endpoint. +#' ) %>% +#' droplevels() +#' +#' app <- init( +#' data = cdisc_data( +#' cdisc_dataset("ADSL", ADSL, +#' code = 'synthetic_cdisc_dataset("latest", "adsl")' +#' ), +#' cdisc_dataset("ADQS", ADQS, +#' code = 'ADQS <- synthetic_cdisc_dataset("latest", "adqs") %>% +#' dplyr::filter(ABLFL != "Y" & ABLFL2 != "Y") %>% +#' dplyr::mutate( +#' AVISIT = as.factor(AVISIT), +#' AVISITN = rank(AVISITN) %>% +#' as.factor() %>% +#' as.numeric() %>% +#' as.factor(), +#' AVALBIN = AVAL < 50 # Just as an example to get a binary endpoint. +#' ) %>% +#' droplevels()' +#' ) +#' ), +#' modules = modules( +#' tm_a_gee( +#' label = "GEE", +#' dataname = "ADQS", +#' aval_var = choices_selected("AVALBIN", fixed = TRUE), +#' id_var = choices_selected(c("USUBJID", "SUBJID"), "USUBJID"), +#' arm_var = choices_selected(c("ARM", "ARMCD"), "ARM"), +#' visit_var = choices_selected(c("AVISIT", "AVISITN"), "AVISIT"), +#' paramcd = choices_selected( +#' choices = value_choices(ADQS, "PARAMCD", "PARAM"), +#' selected = "FKSI-FWB" +#' ), +#' cov_var = choices_selected(c("BASE", "AGE", "SEX"), NULL) +#' ) +#' ) +#' ) +#' if (interactive()) { +#' shiny::shinyApp(app$ui, app$server) +#' } +#' tm_a_gee <- function(label, dataname, parentname = ifelse( @@ -183,6 +258,7 @@ ui_gee <- function(id, ...) { teal.widgets::standard_layout( output = teal.widgets::white_small_well( + shiny::h3(shiny::textOutput(ns("gee_title"))), teal.widgets::table_with_settings_ui(ns("table")) ), encoding = shiny::div( @@ -378,6 +454,10 @@ srv_gee <- function(id, adsl_input_r = adsl_inputs, anl_q = anl_q ) + + # Initially hide the output title because there is no output yet. + shinyjs::show("gee_title") + # To do in production: add validations. ## table_r ---- @@ -385,9 +465,19 @@ srv_gee <- function(id, output_table <- input$output_table conf_level <- as.numeric(input$conf_level) col_source <- merged$anl_input_r()$columns_source + filter_info <- merged$anl_input_r()$filter_info req(output_table) + basic_table_args$subtitles <- paste0( + "Analysis Variable: ", col_source$aval_var, + ", Endpoint: ", filter_info$paramcd[[1]]$selected[[1]], + ifelse(length(col_source$split_covariates) == 0, "", + paste(", Covariates:", paste(col_source$split_covariates, collapse = ", ")) + ) + ) + basic_table_args$main_footer <- c(paste("Correlation Structure:", input$cor_struct)) + my_calls <- template_a_gee( output_table = output_table, data_model_fit = "ANL", @@ -399,11 +489,25 @@ srv_gee <- function(id, id_var = col_source$id_var, arm_var = col_source$arm_var, visit_var = col_source$visit_var, - cor_struct = input$cor_struct + cor_struct = input$cor_struct, + basic_table_args = basic_table_args ) teal.code::eval_code(merged$anl_q(), as.expression(my_calls)) }) + output$gee_title <- shiny::renderText({ + # Input on output type. + output_table <- input$output_table + + output_title <- switch( + output_table, + "t_gee_cov" = "Residual Covariance Matrix Estimate", + "t_gee_coef" = "Model Coefficients", + "t_gee_lsmeans" = "LS Means Estimates" + ) + output_title + }) + table_r <- shiny::reactive({ table_q()[["result_table"]] }) diff --git a/man/template_a_gee.Rd b/man/template_a_gee.Rd index e3ca62d0ee..fb9fe8ee98 100644 --- a/man/template_a_gee.Rd +++ b/man/template_a_gee.Rd @@ -16,7 +16,8 @@ template_a_gee( visit_var, split_covariates, cor_struct, - conf_level = 0.95 + conf_level = 0.95, + basic_table_args = teal.widgets::basic_table_args() ) } \arguments{ @@ -44,6 +45,13 @@ variable names that can be used as \code{visit} variable. Must be a factor in \c \item{conf_level}{(\code{numeric})\cr value for the confidence level within the range of (0, 1).} + +\item{basic_table_args}{optional, (\code{basic_table_args})\cr +object created by \code{\link[teal.widgets:basic_table_args]{teal.widgets::basic_table_args()}} with settings for the module table. +The argument is merged with option \code{teal.basic_table_args} and with default module arguments +(hard coded in the module body). + +For more details, see the vignette: \code{vignette("custom-basic-table-arguments", package = "teal.widgets")}.} } \description{ Template for Generalized Estimating Equations (GEE) analysis module diff --git a/man/tm_a_gee.Rd b/man/tm_a_gee.Rd index 94e8ed13b5..8a12a595c9 100644 --- a/man/tm_a_gee.Rd +++ b/man/tm_a_gee.Rd @@ -88,3 +88,59 @@ For more details, see the vignette: \code{vignette("custom-basic-table-arguments \description{ Teal Module: Teal module for Generalized Estimating Equations (GEE) analysis } +\examples{ +library(scda) + +ADSL <- synthetic_cdisc_dataset("latest", "adsl") +ADQS <- synthetic_cdisc_dataset("latest", "adqs") \%>\% + dplyr::filter(ABLFL != "Y" & ABLFL2 != "Y") \%>\% + dplyr::mutate( + AVISIT = as.factor(AVISIT), + AVISITN = rank(AVISITN) \%>\% + as.factor() \%>\% + as.numeric() \%>\% + as.factor(), + AVALBIN = AVAL < 50 # Just as an example to get a binary endpoint. + ) \%>\% + droplevels() + +app <- init( + data = cdisc_data( + cdisc_dataset("ADSL", ADSL, + code = 'synthetic_cdisc_dataset("latest", "adsl")' + ), + cdisc_dataset("ADQS", ADQS, + code = 'ADQS <- synthetic_cdisc_dataset("latest", "adqs") \%>\% + dplyr::filter(ABLFL != "Y" & ABLFL2 != "Y") \%>\% + dplyr::mutate( + AVISIT = as.factor(AVISIT), + AVISITN = rank(AVISITN) \%>\% + as.factor() \%>\% + as.numeric() \%>\% + as.factor(), + AVALBIN = AVAL < 50 # Just as an example to get a binary endpoint. + ) \%>\% + droplevels()' + ) + ), + modules = modules( + tm_a_gee( + label = "GEE", + dataname = "ADQS", + aval_var = choices_selected("AVALBIN", fixed = TRUE), + id_var = choices_selected(c("USUBJID", "SUBJID"), "USUBJID"), + arm_var = choices_selected(c("ARM", "ARMCD"), "ARM"), + visit_var = choices_selected(c("AVISIT", "AVISITN"), "AVISIT"), + paramcd = choices_selected( + choices = value_choices(ADQS, "PARAMCD", "PARAM"), + selected = "FKSI-FWB" + ), + cov_var = choices_selected(c("BASE", "AGE", "SEX"), NULL) + ) + ) +) +if (interactive()) { + shiny::shinyApp(app$ui, app$server) +} + +}