Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Module specific filter panels #837

Conversation

gogonzo
Copy link
Contributor

@gogonzo gogonzo commented May 31, 2023

This PR solves insightsengineering/teal.slice#135 use together with insightsengineering/teal.slice#300

I propose following API in which each filter_var (teal_slice) has own id which can be linked with the modules in the mapping argument. id can be specified by the user (see 4th filter_var), and mapping is also unrestricted as far as one uses module names and filter names. Code below means that module called funny would have ADSL_categorical and SE. Except that all modules will have global_filters = "ADSL_numeric" on startup.

teal::teal_filters(
  filter_var(dataname = "ADSL", varname = "categorical", selected = c("a", "b"), multiple = FALSE),
  filter_var(dataname = "ADSL", varname = "categorical2", selected = c("a", "b")),
  filter_var(dataname = "ADSL", varname = "numeric", selected = c(0, 140)),
  filter_var(dataname = "ADSL", varname = "numeric", selected = c(18, Inf), id = "ADULTS"),
  filter_expr(id = "SE", title = "Safety-Evaluable", dataname = "ADSL", expr = "SAFFL == 'Y'"),
  mapping = list(
    funny = c("ADSL_categorical", "SE"),
    funny2 = c("ADSL_categorical", "ADSL_categorical2"),
    global_filters = "ADSL_numeric"
  )
)

Problem is solved mostly in teal by managing three objects:

  1. (left) Each teal module has a separate FilteredData object (left)
  2. (right) slices_global is a single object containing combined teal_slices object from each module.
  3. (middle) slices_map is a single list where each element (named after module) containing character vector of filters used in this module.

local-filter-panel-module specific drawio (1)

Whole implementation is based on relationships between these objects so that update in one causes relevant actions to update the rest (to keep 1,2,3 consistent). These objects are distinguished by the green color on the diagram below and they represent particular state. We expect that these states can be changed or initialized in several ways, which are:

  1. App developer can initialize filter states in any configuration
  2. App user can add any filter on the module level (same way as currently).
  3. App user can remove any filter from the module (same way as currently).
  4. App user can use any filter stored in the slices_global and activate it in any module.
  5. App user can deactivate any filter active in a module.
  6. App user can add any filter on the global level (through filter manager). Details to be discussed...
  7. App user can remove any available filter on the global level (through filter manager). Details to be discussed...

local-filter-panel-module specific drawio (3)

Let me explain all possible scenarios in detail. Numeration below is corresponding to red numbers on the diagram.

  1. App user add/remove a filter on the module level. This action changes the state of FilteredData designated to this module.
  2. Changes in the state are observed and module-slices are compared with global-slices. Comparison is made on id of the filters so it is easy to determine if something has been added, activated or deactivated (removed from module).
  3. In the comparison between module-slices and global-slices has been concluded that completely new filters have been added to the module.
  4. In the comparison between module-slices and global-slices has been concluded that filter has been removed from the module.
  5. If any filter is added for the first time in the application it needs to be copied to slices_global.
  6. Any change of the filters (addition or removal) is followed by the update of the slices_map.
  7. slices_map can be displayed any time when opening filter manager and it is represented by the matrix of TRUE/FALSE.
  8. slices_map could be also changed by the app user by interacting within a filter manager. I imagine this in the way where user can activate any filter kept in slices_global in any module.
  9. Change in the slices_map is observed and compared with module filters.
  10. One result of the comparison could be that module has a new filter activated. This requires running set_filter_state method for module-FilteredData.
  11. Another result of the comparison could be that some filter has been deactivated for the module. This requires runing remove_filter_state method for the module-FilteredData
  12. We plan that app user can also add/remove new filters through filter-manager. This means that slices_global could be changed directly.
  13. Changes in the slices_global are observed and comparison with module-slices is made
    14-15. It could be concluded that new global filters been added or removed which need to be followed by (6).

Challanges

  • in some teal applications modules could have duplicated labels. This can happen when one uses nested tabs. This will cause module-specific to fail as it's impossible to find unique module specified in mapping. Solved: when module-specific filter panel is enabled there is assertion on modules to have a unique label.
  • disable can't be independent and shared in the same time - agreed with @lcd2yyz to remove this functionality. Instead we will be activating/deactivating filters through filter-manager or by quick-add button available in FilterState$ui/srv_active

example app

options(teal.log_level = "TRACE", teal.show_js_log = TRUE)
# options("teal.bs_theme" = bslib::bs_theme(version = 5))
# options(shiny.trace = TRUE)
# todo: change groupCheckbox to include locked (not able to interact with)
#       change groupCheckbox to have some colors (instead of grey)
# todo: available filter should present information about selected values (for example tooltip)

library(shiny)
library(scda)
library(scda.2022)
library(teal.data)
library(teal.transform)
library(teal.modules.general)
pkgload::load_all("teal")
pkgload::load_all("teal.slice")

funny_module <- function (label = "Filter states", datanames = "all") {
  checkmate::assert_string(label)
  module(
    label = label,
    filters = datanames,
    ui = function(id, ...) {
      ns <- NS(id)
      div(
        h2("The following filter calls are generated:"),
        verbatimTextOutput(ns("filter_states")),
        verbatimTextOutput(ns("filter_calls")),
        actionButton(ns("reset"), "reset_to_default")
      )
    },
    server = function(input, output, session, data, filter_panel_api) {
      checkmate::assert_class(data, "tdata")
      observeEvent(input$reset, set_filter_state(filter_panel_api, default_filters))
      output$filter_states <-  renderPrint({
        logger::log_trace("rendering text1")
        filter_panel_api %>% get_filter_state()
      })
      output$filter_calls <- renderText({
        logger::log_trace("rendering text2")
        attr(data, "code")()
      })
    }
  )
}

ADSL <- synthetic_cdisc_data("latest")$adsl
ADSL$empty <- NA
ADSL$logical1 <- FALSE
ADSL$logical <- sample(c(TRUE, FALSE), size = nrow(ADSL), replace = TRUE)
ADSL$numeric <- rnorm(nrow(ADSL))
ADSL$categorical2 <- sample(letters[1:10], size = nrow(ADSL), replace = TRUE)
ADSL$categorical <- sample(letters[1:3], size = nrow(ADSL), replace = TRUE, prob = c(.1, .3, .6))
ADSL$date <- Sys.Date() + seq_len(nrow(ADSL))
ADSL$date2 <- rep(Sys.Date() + 1:3, length.out = nrow(ADSL))
ADSL$datetime <- Sys.time() + seq_len(nrow(ADSL)) * 3600 * 12
ADSL$datetime2 <- rep(Sys.time() + 1:3 * 43200, length.out = nrow(ADSL))

ADSL$numeric[sample(1:nrow(ADSL), size = 10)] <- NA
ADSL$numeric[sample(1:nrow(ADSL), size = 10)] <- Inf
ADSL$logical[sample(1:nrow(ADSL), size = 10)] <- NA
ADSL$date[sample(1:nrow(ADSL), size = 10)] <- NA
ADSL$datetime[sample(1:nrow(ADSL), size = 10)] <- NA
ADSL$categorical2[sample(1:nrow(ADSL), size = 10)] <- NA
ADSL$categorical[sample(1:nrow(ADSL), size = 10)] <- NA

ADTTE <- synthetic_cdisc_data("latest")$adtte
ADRS <- synthetic_cdisc_data("latest")$adrs

ADTTE$numeric <- rnorm(nrow(ADTTE))
ADTTE$numeric[sample(1:nrow(ADTTE), size = 10,)] <- NA

default_filters <- teal::teal_filters(
  filter_var(dataname = "ADSL", varname = "categorical", selected = c("a", "b"), id = "categorical", locked = TRUE),
  filter_var(dataname = "ADSL", varname = "categorical2", selected = c("a", "b")),
  filter_var(dataname = "ADSL", varname = "numeric", selected = c(0, 140), keep_na = TRUE, keep_inf = TRUE),
  filter_var(dataname = "ADSL", varname = "logical", selected = c(T), keep_na = TRUE, keep_inf = TRUE),
  filter_var(dataname = "ADSL", varname = "datetime"),
  filter_var(dataname = "ADSL", varname = "date2"),
  filter_expr(id = "AF", title = "ADULT FEMALE", dataname = "ADSL", expr = "SEX %in% 'F' & AGE >= 18L"),
  filter_expr(id = "SE", title = "Safety-Evaluable", dataname = "ADSL", expr = "SAFFL == 'Y'"),
  filter_var(dataname = "ADSL", varname = "COUNTRY", selected = c("USA", "CAN", "JPN"), fixed = TRUE),
  count_type = "all",
  include_varnames = list(ADSL = c("SEX", "categorical", "categorical2", "numeric", "logical", "date", "datetime", "date2", "datetime2", "COUNTRY")),
  exclude_varnames = list(
    ADTTE = intersect(colnames(ADSL), colnames(ADTTE)),
    ADRS = colnames(ADSL)
  ),
  mapping = list(
    table = c("categorical", "ADSL categorical2"),
    global_filters = "ADSL numeric"
  ),
  global = FALSE
)

app <- init(
  data = cdisc_data(
    cdisc_dataset("ADSL", ADSL),
    cdisc_dataset("ADTTE", ADTTE),
    cdisc_dataset("ADRS", ADRS)
  ),
  modules = modules(
    tm_data_table(
      "table",
      variables_selected = list(ADSL = c("STUDYID", "USUBJID", "SUBJID", "SITEID", "AGE", "SEX")),
      dt_args = list(caption = "ADSL Table Caption")
    ),
    modules(
      label = "tab1",
      funny_module("funny", datanames = NULL),
      funny_module("funny2", datanames = "ADTTE") # will limit datanames to ADTTE and ADSL (parent)
    )
  ),
  filter = default_filters
)


runApp(app)

Example safety

options(shiny.useragg = FALSE)

library(dplyr)
library(scda)
library(scda.2022)
library(teal.modules.general)
library(teal.modules.clinical)
library(nestcolor)
# optional libraries
library(sparkline)

# code>
## Generate Data
ADSL <- synthetic_cdisc_data("latest")$adsl

## Create variable type lists
date_vars_adsl <-
  names(ADSL)[vapply(ADSL, function(x) {
    inherits(x, c("Date", "POSIXct", "POSIXlt"))
  }, logical(1))]
char_vars_adsl <- names(Filter(isTRUE, sapply(ADSL, is.character)))

ADAE <- synthetic_cdisc_data("latest")$adae
ADAETTE <- synthetic_cdisc_data("latest")$adaette
ADAETTE <- ADAETTE %>%
  mutate(is_event = case_when(
    grepl("TOT", .data$PARAMCD, fixed = TRUE) ~ TRUE,
    TRUE ~ CNSR == 0
  )) %>%
  mutate(n_events = case_when(
    grepl("TOT", .data$PARAMCD, fixed = TRUE) ~ as.integer(.data$AVAL),
    TRUE ~ as.integer(is_event)
  )) %>%
  formatters::var_relabel(is_event = "Is an Event") %>%
  formatters::var_relabel(n_events = "Number of Events")
ADAETTE_AE <-
  filter(ADAETTE, grepl("TOT", .data$PARAMCD, fixed = TRUE)) %>% select(-"AVAL")
ADAETTE_OTH <-
  filter(ADAETTE, !(grepl("TOT", .data$PARAMCD, fixed = TRUE)))

ADAETTE_TTE <- ADAETTE %>%
  filter(PARAMCD == "AEREPTTE") %>%
  select(USUBJID, ARM, ARMCD, AVAL)

ADAETTE_AE <-
  full_join(ADAETTE_AE, ADAETTE_TTE, by = c("USUBJID", "ARM", "ARMCD"))
ADAETTE <- rbind(ADAETTE_AE, ADAETTE_OTH)

ADEX <- synthetic_cdisc_data("latest")$adex
ADEX_labels <- formatters::var_labels(ADEX, fill = FALSE)
# Below steps are done to simulate data with TDURD parameter as it is not in the ADEX data from scda package
set.seed(1, kind = "Mersenne-Twister")
ADEX <- ADEX %>%
  distinct(USUBJID, .keep_all = TRUE) %>%
  mutate(
    PARAMCD = "TDURD",
    PARAM = "Overall duration (days)",
    AVAL = sample(
      x = seq(1, 200),
      size = n(),
      replace = TRUE
    ),
    AVALU = "Days",
    PARCAT1 = "OVERALL"
  ) %>%
  bind_rows(ADEX)
ADEX <- ADEX %>%
  filter(PARCAT1 == "OVERALL" &
    PARAMCD %in% c("TDOSE", "TNDOSE", "TDURD"))
formatters::var_labels(ADEX) <- ADEX_labels

ADLB <- synthetic_cdisc_data("latest")$adlb

ADEG <- synthetic_cdisc_data("latest")$adeg

# For real data, ADVS needs some preprocessing like group different ANRIND and BNRIND into abnormal
ADVS <- synthetic_cdisc_data("latest")$advs %>%
  mutate(ONTRTFL = ifelse(AVISIT %in% c("SCREENING", "BASELINE"), "", "Y")) %>%
  formatters::var_relabel(ONTRTFL = "On Treatment Record Flag") %>%
  mutate(ANRIND = as.character(ANRIND), BNRIND = as.character(BNRIND)) %>%
  mutate(
    ANRIND = case_when(
      ANRIND == "HIGH HIGH" ~ "HIGH",
      ANRIND == "LOW LOW" ~ "LOW",
      TRUE ~ ANRIND
    ),
    BNRIND = case_when(
      BNRIND == "HIGH HIGH" ~ "HIGH",
      BNRIND == "LOW LOW" ~ "LOW",
      TRUE ~ BNRIND
    )
  )

ADCM <-
  synthetic_cdisc_data("latest")$adcm %>% mutate(CMSEQ = as.integer(CMSEQ))

# Add study-specific pre-processing: convert arm, param and visit variables to factors
# Sample code:
# ADSL$ACTARM <- factor(ADSL$ACTARM)
# ADAE$AETOXGR <- factor(ADAE$AETOXGR)
# ADLB <- ADLB %>%
#   tern::df_explicit_na(omit_columns = setdiff(names(ADLB), c("PARAM", "PARAMCD", "AVISIT") ))
# ADEX <- ADEX %>%
#   tern::df_explicit_na(omit_columns = setdiff(names(ADEX), c("PARAM", "PARAMCD", "PARCAT2") ))

# define study-specific analysis subgroups and baskets from ADAE
add_event_flags <- function(dat) {
  dat %>%
    dplyr::mutate(
      TMPFL_SER = AESER == "Y",
      TMPFL_REL = AEREL == "Y",
      TMPFL_GR5 = AETOXGR == "5",
      TMP_SMQ01 = !is.na(SMQ01NAM),
      TMP_SMQ02 = !is.na(SMQ02NAM),
      TMP_CQ01 = !is.na(CQ01NAM)
    ) %>%
    formatters::var_relabel(
      TMPFL_SER = "Serious AE",
      TMPFL_REL = "Related AE",
      TMPFL_GR5 = "Grade 5 AE",
      TMP_SMQ01 = aesi_label(dat$SMQ01NAM, dat$SMQ01SC),
      TMP_SMQ02 = aesi_label(dat$SMQ02NAM, dat$SMQ02SC),
      TMP_CQ01 = aesi_label(dat$CQ01NAM)
    )
}

ADAE <- ADAE %>%
  add_event_flags()
# <code

## Reusable Configuration For Modules
arm_vars <- c("ACTARMCD", "ACTARM")

demog_vars_adsl <-
  names(ADSL)[!(names(ADSL) %in% c("USUBJID", "STUDYID", date_vars_adsl))]

cs_arm_var <-
  choices_selected(
    choices = variable_choices(ADSL, subset = arm_vars),
    selected = "ACTARM"
  )

ae_anl_vars <- names(ADAE)[startsWith(names(ADAE), "TMPFL_")]
# flag variables for AE baskets; set to NULL if not applicable to study
aesi_vars <-
  names(ADAE)[startsWith(names(ADAE), "TMP_SMQ") |
    startsWith(names(ADAE), "TMP_CQ")]

## Define code needed to produce the required data sets
ADAE_code <- paste(
  'ADAE <- synthetic_cdisc_data("latest")$adae',
  "add_event_flags <- function(dat) {",
  "  dat %>%",
  "    dplyr::mutate(",
  '      TMPFL_SER = AESER == "Y",',
  '      TMPFL_REL = AEREL == "Y",',
  '      TMPFL_GR5 = AETOXGR == "5",',
  "      TMP_SMQ01 = !is.na(SMQ01NAM),",
  "      TMP_SMQ02 = !is.na(SMQ02NAM),",
  "      TMP_CQ01 = !is.na(CQ01NAM)",
  "    ) %>%",
  "    formatters::var_relabel(",
  '      TMPFL_SER = "Serious AE",',
  '      TMPFL_REL = "Related AE",',
  '      TMPFL_GR5 = "Grade 5 AE",',
  "      TMP_SMQ01 = aesi_label(dat$SMQ01NAM, dat$SMQ01SC),",
  "      TMP_SMQ02 = aesi_label(dat$SMQ02NAM, dat$SMQ02SC),",
  "      TMP_CQ01 = aesi_label(dat$CQ01NAM)",
  "    )",
  "}",
  "ADAE <- ADAE %>%",
  "  add_event_flags()",
  sep = "\n",
  collapse = "\n"
)

ADAETTE_code <-
  paste(
    'ADAETTE <- synthetic_cdisc_data("latest")$adaette',
    "ADAETTE <- ADAETTE %>%",
    "  mutate(is_event = case_when(",
    '    grepl("TOT", .data$PARAMCD, fixed = TRUE) ~ TRUE,',
    "    TRUE ~ CNSR == 0",
    "  )) %>%",
    "  mutate(n_events = case_when(",
    '    grepl("TOT", .data$PARAMCD, fixed = TRUE) ~ as.integer(.data$AVAL),',
    "    TRUE ~ as.integer(is_event)",
    "  )) %>%",
    '  formatters::var_relabel(is_event = "Is an Event") %>%',
    '  formatters::var_relabel(n_events = "Number of Events")',
    "ADAETTE_AE <-",
    '  filter(ADAETTE, grepl("TOT", .data$PARAMCD, fixed = TRUE)) %>% select(-"AVAL")',
    "ADAETTE_OTH <-",
    '  filter(ADAETTE, !(grepl("TOT", .data$PARAMCD, fixed = TRUE)))',
    "",
    "ADAETTE_TTE <- ADAETTE %>%",
    '  filter(PARAMCD == "AEREPTTE") %>%',
    "  select(USUBJID, ARM, ARMCD, AVAL)",
    "",
    "ADAETTE_AE <-",
    '  full_join(ADAETTE_AE, ADAETTE_TTE, by = c("USUBJID", "ARM", "ARMCD"))',
    "ADAETTE <- rbind(ADAETTE_AE, ADAETTE_OTH)",
    sep = "\n",
    collapse = "\n"
  )

ADEX_code <- paste(
  'ADEX <- synthetic_cdisc_data("latest")$adex',
  "ADEX_labels <- formatters::var_labels(ADEX, fill = FALSE)",
  'set.seed(1, kind = "Mersenne-Twister")',
  "ADEX <- ADEX %>%",
  "  distinct(USUBJID, .keep_all = TRUE) %>%",
  "  mutate(",
  '    PARAMCD = "TDURD",',
  '    PARAM = "Overall duration (days)",',
  "    AVAL = sample(",
  "      x = seq(1, 200),",
  "      size = n(),",
  "      replace = TRUE",
  "    ),",
  '    AVALU = "Days",',
  '    PARCAT1 = "OVERALL"',
  "  ) %>%",
  "  bind_rows(ADEX)",
  "ADEX <- ADEX %>%",
  '  filter(PARCAT1 == "OVERALL" &',
  '    PARAMCD %in% c("TDOSE", "TNDOSE", "TDURD"))',
  "formatters::var_labels(ADEX) <- ADEX_labels",
  sep = "\n",
  collapse = "\n"
)

ADVS_code <-
  paste(
    'ADVS <- synthetic_cdisc_data("latest")$advs %>%',
    '  mutate(ONTRTFL = ifelse(AVISIT %in% c("SCREENING", "BASELINE"), "", "Y")) %>%',
    '  formatters::var_relabel(ONTRTFL = "On Treatment Record Flag") %>%',
    "  mutate(ANRIND = as.character(ANRIND), BNRIND = as.character(BNRIND)) %>%",
    "  mutate(",
    "    ANRIND = case_when(",
    '      ANRIND == "HIGH HIGH" ~ "HIGH",',
    '      ANRIND == "LOW LOW" ~ "LOW",',
    "      TRUE ~ ANRIND",
    "    ),",
    "    BNRIND = case_when(",
    '      BNRIND == "HIGH HIGH" ~ "HIGH",',
    '      BNRIND == "LOW LOW" ~ "LOW",',
    "      TRUE ~ BNRIND",
    "    )",
    "  )",
    sep = "\n",
    collapse = "\n"
  )

## Setup App
app <- teal::init(
  data = cdisc_data(
    cdisc_dataset(
      "ADSL",
      ADSL,
      code = 'ADSL <- synthetic_cdisc_data("latest")$adsl',
      vars = list(char_vars_adsl = char_vars_adsl)
    ),
    cdisc_dataset("ADAE", ADAE, code = ADAE_code),
    cdisc_dataset("ADAETTE", ADAETTE, code = ADAETTE_code),
    cdisc_dataset("ADEX", ADEX, code = ADEX_code),
    cdisc_dataset("ADLB", ADLB, code = "ADLB <- synthetic_cdisc_data(\"latest\")$adlb"),
    cdisc_dataset("ADEG", ADEG, code = "ADEG <- synthetic_cdisc_data(\"latest\")$adeg"),
    cdisc_dataset("ADVS", ADVS, code = ADVS_code),
    cdisc_dataset("ADCM", ADCM, code = 'ADCM <- synthetic_cdisc_data("latest")$adcm %>% mutate(CMSEQ = as.integer(CMSEQ))'),
    # code = get_code("app.R")),
    check = TRUE
  ),
  filter = teal::teal_filters(
    teal.slice::filter_var(dataname = "ADSL", varname = "SAFFL", selected = "Y", locked = TRUE),
    teal.slice::filter_var(dataname = "ADSL", varname = "SEX"),
    teal.slice::filter_var(dataname = "ADLB", varname = "AVAL"),
    teal.slice::filter_var(dataname = "ADEX", varname = "AVAL"),
    teal.slice::filter_var(dataname = "ADEG", varname = "AVAL"),
    teal.slice::filter_var(dataname = "ADAE", varname = "AVAL"),
    mapping = list(
      global_filters = "ADSL SAFFL",
      `AE Summary` = "ADAE AVAL",
      `Labs Summary` = "ADLB AVAL"
    )
  ),
  modules = modules(
    tm_front_page(
      label = "Study Information",
      header_text = c("Info about data source" = "Random data are used that have been created with the 'scda' R package"),
      tables = list(`NEST packages used` = data.frame(
        Packages = c(
          "teal.modules.general",
          "teal.modules.clinical",
          "scda",
          "scda.2022"
        )
      ))
    ),
    tm_data_table("Data Table"),
    tm_variable_browser("Variable Browser"),
    tm_t_summary(
      label = "Demographic Table",
      dataname = "ADSL",
      arm_var = cs_arm_var,
      summarize_vars = choices_selected(
        choices = variable_choices(ADSL, demog_vars_adsl),
        selected = c("SEX", "AGE", "RACE")
      )
    ),
    modules(
      label = "Adverse Events",
      tm_t_events_summary(
        label = "AE Summary",
        dataname = "ADAE",
        arm_var = cs_arm_var,
        flag_var_anl = choices_selected(
          choices = variable_choices("ADAE", ae_anl_vars),
          selected = ae_anl_vars,
          keep_order = TRUE
        ),
        flag_var_aesi = choices_selected(
          choices = variable_choices("ADAE", aesi_vars),
          selected = aesi_vars,
          keep_order = TRUE
        ),
        add_total = TRUE
      ),
      tm_t_events(
        label = "AE by Term",
        dataname = "ADAE",
        arm_var = cs_arm_var,
        llt = choices_selected(
          choices = variable_choices(ADAE, c("AETERM", "AEDECOD")),
          selected = c("AEDECOD")
        ),
        hlt = choices_selected(
          choices = variable_choices(ADAE, c("AEBODSYS", "AESOC")),
          selected = "AEBODSYS"
        ),
        add_total = TRUE,
        event_type = "adverse event"
      ),
      tm_t_events_by_grade(
        label = "AE Table by Grade",
        dataname = "ADAE",
        arm_var = cs_arm_var,
        llt = choices_selected(
          choices = variable_choices(ADAE, c("AEDECOD")),
          selected = c("AEDECOD")
        ),
        hlt = choices_selected(
          choices = variable_choices(ADAE, c("AEBODSYS", "AESOC")),
          selected = "AEBODSYS"
        ),
        grade = choices_selected(
          choices = variable_choices(ADAE, c("AETOXGR")),
          selected = "AETOXGR"
        ),
        add_total = TRUE
      ),
      tm_t_events_patyear(
        label = "AE Rates Adjusted for Patient-Years at Risk",
        dataname = "ADAETTE",
        arm_var = cs_arm_var,
        paramcd = choices_selected(
          choices = value_choices(ADAETTE, "PARAMCD", "PARAM"),
          selected = "AETTE1"
        ),
        events_var = choices_selected(
          choices = variable_choices(ADAETTE, "n_events"),
          selected = "n_events",
          fixed = TRUE
        )
      ),
      tm_t_smq(
        label = "Adverse Events by SMQ Table",
        dataname = "ADAE",
        arm_var = choices_selected(
          choices = variable_choices(ADSL, subset = c(arm_vars, "SEX")),
          selected = "ACTARM"
        ),
        add_total = FALSE,
        baskets = choices_selected(
          choices = variable_choices(ADAE, subset = grep("^(SMQ|CQ).*NAM$", names(ADAE), value = TRUE)),
          selected = grep("^(SMQ|CQ).*NAM$", names(ADAE), value = TRUE)
        ),
        scopes = choices_selected(
          choices = variable_choices(ADAE, subset = grep("^SMQ.*SC$", names(ADAE), value = TRUE)),
          selected = grep("^SMQ.*SC$", names(ADAE), value = TRUE),
          fixed = TRUE
        ),
        llt = choices_selected(
          choices = variable_choices(ADAE, subset = c("AEDECOD")),
          selected = "AEDECOD"
        )
      )
    ),
    modules(
      label = "Lab Tables",
      tm_t_summary_by(
        label = "Labs Summary",
        dataname = "ADLB",
        arm_var = cs_arm_var,
        by_vars = choices_selected(
          choices = variable_choices(ADLB, c("PARAM", "AVISIT")),
          selected = c("PARAM", "AVISIT"),
          fixed = TRUE
        ),
        summarize_vars = choices_selected(
          choices = variable_choices(ADLB, c("AVAL", "CHG")),
          selected = c("AVAL")
        ),
        paramcd = choices_selected(
          choices = value_choices(ADLB, "PARAMCD", "PARAM"),
          selected = "ALT"
        )
      ),
      tm_t_shift_by_grade(
        label = "Grade Laboratory Abnormality Table",
        dataname = "ADLB",
        arm_var = cs_arm_var,
        paramcd = choices_selected(
          choices = value_choices(ADLB, "PARAMCD", "PARAM"),
          selected = "ALT"
        ),
        worst_flag_var = choices_selected(
          choices = variable_choices(ADLB, subset = c(
            "WGRLOVFL", "WGRLOFL", "WGRHIVFL", "WGRHIFL"
          )),
          selected = c("WGRLOVFL")
        ),
        worst_flag_indicator = choices_selected(
          value_choices(ADLB, "WGRLOVFL"),
          selected = "Y",
          fixed = TRUE
        ),
        anl_toxgrade_var = choices_selected(
          choices = variable_choices(ADLB, subset = c("ATOXGR")),
          selected = c("ATOXGR"),
          fixed = TRUE
        ),
        base_toxgrade_var = choices_selected(
          choices = variable_choices(ADLB, subset = c("BTOXGR")),
          selected = c("BTOXGR"),
          fixed = TRUE
        ),
        add_total = FALSE
      ),
      tm_t_abnormality_by_worst_grade(
        label = "Laboratory test results with highest grade post-baseline",
        dataname = "ADLB",
        arm_var = choices_selected(
          choices = variable_choices(ADSL, subset = c("ARM", "ARMCD")),
          selected = "ARM"
        ),
        paramcd = choices_selected(
          choices = value_choices(ADLB, "PARAMCD", "PARAM"),
          selected = c("ALT", "CRP", "IGA")
        ),
        add_total = FALSE
      )
    ),
    modules(
      label = "Exposure",
      tm_t_summary_by(
        label = "Exposure Summary",
        dataname = "ADEX",
        arm_var = cs_arm_var,
        by_vars = choices_selected(
          choices = variable_choices(ADEX, c("PARCAT2", "PARAM")),
          selected = c("PARCAT2", "PARAM"),
          fixed = TRUE
        ),
        summarize_vars = choices_selected(
          choices = variable_choices(ADEX, "AVAL"),
          selected = c("AVAL"),
          fixed = TRUE
        ),
        paramcd = choices_selected(
          choices = value_choices(ADEX, "PARAMCD", "PARAM"),
          selected = "TDOSE"
        ),
        denominator = choices_selected(
          choices = c("n", "N", "omit"),
          selected = "n"
        )
      ),
      tm_t_exposure(
        label = "Duration of Exposure Table",
        dataname = "ADEX",
        paramcd = choices_selected(
          choices = value_choices(ADEX, "PARAMCD", "PARAM"),
          selected = "TDURD",
          fixed = TRUE
        ),
        col_by_var = choices_selected(
          choices = variable_choices(ADEX, subset = c(arm_vars, "SEX")),
          selected = "SEX"
        ),
        row_by_var = choices_selected(
          choices = variable_choices(
            ADEX,
            subset = c("RACE", "REGION1", "STRATA1", "SEX")
          ),
          selected = "RACE"
        ),
        parcat = choices_selected(
          choices = value_choices(ADEX, "PARCAT2"),
          selected = "Drug A"
        ),
        add_total = FALSE
      )
    ),
    tm_t_abnormality(
      label = "Vital Signs Abnormality",
      dataname = "ADVS",
      arm_var = cs_arm_var,
      id_var = choices_selected(
        choices = variable_choices(ADSL, subset = "USUBJID"),
        selected = "USUBJID",
        fixed = TRUE
      ),
      by_vars = choices_selected(
        choices = variable_choices(ADVS, subset = c("PARAM", "AVISIT")),
        selected = c("PARAM"),
        keep_order = TRUE
      ),
      grade = choices_selected(
        choices = variable_choices(ADVS, subset = "ANRIND"),
        selected = "ANRIND",
        fixed = TRUE
      ),
      abnormal = list(low = "LOW", high = "HIGH")
    ),
    tm_t_mult_events(
      label = "Concomitant Medication",
      dataname = "ADCM",
      arm_var = cs_arm_var,
      seq_var = choices_selected("CMSEQ", selected = "CMSEQ", fixed = TRUE),
      hlt = choices_selected(
        choices = variable_choices(ADCM, c(
          "ATC1", "ATC2", "ATC3", "ATC4"
        )),
        selected = "ATC2"
      ),
      llt = choices_selected(
        choices = variable_choices(ADCM, c("CMDECOD")),
        selected = "CMDECOD",
        fixed = TRUE
      ),
      add_total = TRUE,
      event_type = "treatment"
    ),
    tm_t_shift_by_arm(
      label = "ECG Shift Table by Arm",
      dataname = "ADEG",
      arm_var = cs_arm_var,
      paramcd = choices_selected(value_choices(ADEG, "PARAMCD"),
        selected = "HR"
      ),
      visit_var = choices_selected(value_choices(ADEG, "AVISIT"),
        selected = "POST-BASELINE MINIMUM"
      ),
      aval_var = choices_selected(
        variable_choices(ADEG, subset = "ANRIND"),
        selected = "ANRIND",
        fixed = TRUE
      ),
      base_var = choices_selected(
        variable_choices(ADEG, subset = "BNRIND"),
        selected = "BNRIND",
        fixed = TRUE
      )
    ),
    tm_g_lineplot(
      label = "Line Plot",
      dataname = "ADLB",
      strata = cs_arm_var,
      x = choices_selected(variable_choices(ADLB, "AVISIT"), "AVISIT", fixed = TRUE),
      y = choices_selected(variable_choices(ADLB, c(
        "AVAL", "BASE", "CHG", "PCHG"
      )), "AVAL"),
      y_unit = choices_selected(variable_choices(ADLB, "AVALU"), "AVALU", fixed = TRUE),
      paramcd = choices_selected(variable_choices(ADLB, "PARAMCD"), "PARAMCD", fixed = TRUE),
      param = choices_selected(value_choices(ADLB, "PARAMCD", "PARAM"), "ALT"),
      plot_height = c(1000L, 200L, 4000L)
    )
  ),
  header = div(
    class = "",
    style = "margin-bottom: 2px;",
    tags$h1(
      "Example Safety App with teal.modules.clinical modules",
      tags$span("SPA", class = "pull-right")
    )
  ),
  footer = tags$p(class = "text-muted", "Source: teal.gallery package")
)

shinyApp(app$ui, app$server)

@gogonzo gogonzo added the core label May 31, 2023
@gogonzo gogonzo changed the title 135 multiple filterpanels@filter panel refactor@main Module specific filter panels May 31, 2023
@gogonzo gogonzo marked this pull request as ready for review June 8, 2023 08:13
gogonzo and others added 9 commits June 8, 2023 13:35
- avoid observers to trigger when the app starts
- change id when the duplicated state is added
- disable filter manager only when is global
- deep copy filters on init
moved teal.code to suggests
gogonzo added 4 commits June 15, 2023 10:12
- global filter is a singleton
- local filter set on nested-tabs level
- resolve module$filters before creating FilteredData
@gogonzo gogonzo merged commit fde15a4 into filter_panel_refactor@main Jun 16, 2023
@gogonzo gogonzo deleted the 135_multiple_filterpanels@filter_panel_refactor@main branch June 16, 2023 14:06
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant