Topic 6: Deeper dive into customizing the parameters of teal.modules.clinical modules (10 minutes)

The choices_selected function from teal.transform is a powerful utility for managing variable selections in teal modules. It provides a flexible way to specify choices and default selections for inputs, with support for both eager and delayed evaluation.

This topic will cover common scenarios app developers face when building teal applications from existing modules: - Limiting variable choices in module inputs - Providing choices without knowing the dataset structure upfront - Adapting to different datasets dynamically

Introduction to choices_selected

Basic Usage

The choices_selected function creates an object that specifies both available choices and selected defaults for UI inputs.

The choices can be provided in two ways:

  • Statically as character vectors
  • Dynamically by referencing the dataset (when using within a teal application that does not yet know the full data structure).

teal example with clinical module

Let’s start with a basic teal application that uses choices_selected:

library(teal.modules.clinical)

# Create sample data
data <- teal_data() |>
  within({
    ADSL <- pharmaverseadam::adsl

    ADSL$ARM <- as.factor(ADSL$ARM)
    ADSL$ARMCD <- as.factor(ADSL$ARMCD)
    ADSL$SEX <- as.factor(ADSL$SEX)
    ADSL$RACE <- as.factor(ADSL$RACE)

    attr(ADSL$ARM, "label") <- "Actual Treatment Arm"
    attr(ADSL$ARMCD, "label") <- "Actual Treatment Arm Code"
    attr(ADSL$SEX, "label") <- "Sex"
    attr(ADSL$RACE, "label") <- "Race"
})

join_keys(data) <- default_cdisc_join_keys[names(data)]

# Basic application using choices_selected
app <- init(
  data = data,
  modules = modules(
    tm_t_summary(
      label = "Demographic Table",
      dataname = "ADSL",
      arm_var = choices_selected(c("ARM", "ARMCD"), "ARM"),
      add_total = TRUE,
      summarize_vars = choices_selected(
        c("SEX", "RACE", "AGE"),
        selected = c("SEX", "RACE", "AGE")
      ),
      useNA = "ifany"
    )
  )
)

runApp(app)
#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 680
library(teal.modules.clinical)

# Create sample data
data <- teal_data() |>
  within({
    ADSL <- pharmaverseadam::adsl

    ADSL$ARM <- as.factor(ADSL$ARM)
    ADSL$ARMCD <- as.factor(ADSL$ARMCD)
    ADSL$SEX <- as.factor(ADSL$SEX)
    ADSL$RACE <- as.factor(ADSL$RACE)

    attr(ADSL$ARM, "label") <- "Actual Treatment Arm"
    attr(ADSL$ARMCD, "label") <- "Actual Treatment Arm Code"
    attr(ADSL$SEX, "label") <- "Sex"
    attr(ADSL$RACE, "label") <- "Race"
})

join_keys(data) <- default_cdisc_join_keys[names(data)]

# Basic application using choices_selected
app <- init(
  data = data,
  modules = modules(
    tm_t_summary(
      label = "Demographic Table",
      dataname = "ADSL",
      arm_var = choices_selected(c("ARM", "ARMCD"), "ARM"),
      add_total = TRUE,
      summarize_vars = choices_selected(
        c("SEX", "RACE", "AGE"),
        selected = c("SEX", "RACE", "AGE")
      ),
      useNA = "ifany"
    )
  )
)

runApp(app)

Using value_choices

The value_choices function retrieves the actual choices from a dataset based on variable names.

  • Requires a dataset to extract the values from specified columns
    • Dataset can be provided directly or as a reference to be resolved later
  • When multiple columns are provided: it combines unique values from all specified columns

Using variable_choices

The variable_choices function allows you to dynamically select variables based on dataset characteristics.

Eager evaluation vs. Delayed evaluation

The variable_choices function can be used in two modes:

  • Eager evaluation computes choices immediately, allowing for inspecting the choices beforehand (just as shown above).
  • Delayed evaluation allows choices to be determined at runtime, which is useful when dataset structure isn’t known beforehand (see example below).

Application with Dynamic Variable Selection

# Application that automatically finds numeric variables
app_dynamic <- init(
  data = data,
  modules = modules(
    tm_t_summary(
      label = "Demographic Table",
      dataname = "ADSL",
      arm_var = choices_selected(c("ARM", "ARMCD"), "ARM"),
      add_total = TRUE,
      summarize_vars = analysis_vars,
      useNA = "ifany"
    )
  )
)

runApp(app_dynamic)

teal example with Kaplan-Meier module

Using Kaplan-Meier module from teal.modules.clinical

library(teal.data)
library(teal.modules.clinical)
library(teal.transform)

data_km <- teal_data() |>
  within({
  ADSL <- pharmaverseadam::adsl

  ADSL$ARM <- as.factor(ADSL$ARM)
  ADSL$ARMCD <- as.factor(ADSL$ARMCD)
  ADSL$SEX <- as.factor(ADSL$SEX)
  ADSL$RACE <- as.factor(ADSL$RACE)

  ADTTE <- pharmaverseadam::adtte_onco
  ADTTE$ARM <- as.factor(ADTTE$ARM)
  ADTTE$ARMCD <- as.factor(ADTTE$ARMCD)
  ADTTE$AVALU = as.factor("DAYS")
})
join_keys(data_km) <- default_cdisc_join_keys[names(data_km)]

arm_ref_comp <- list(
  ACTARMCD = list(
    ref = "Pbo",
    comp = c("Xan_Hi", "Xan_Lo")
  ),
  ARM = list(
    ref = "Placebo",
    comp = c("Xanomeline High Dose", "Xanomeline Low Dose")
  )
)

app <- init(
  data = data_km,
  modules = modules(
    tm_g_km(
      label = "Kaplan-Meier Plot",
      dataname = "ADTTE",
      arm_var = choices_selected(
        variable_choices("ADSL", c("ARM", "ARMCD", "ACTARMCD")),
        "ARM"
      ),
      paramcd = choices_selected(
        value_choices("ADTTE", "PARAMCD", "PARAM"),
        "OS"
      ),
      arm_ref_comp = arm_ref_comp,
      strata_var = choices_selected(
        variable_choices("ADSL", c("SEX", "RACE")),
        "RACE"
      ),
      facet_var = choices_selected(
        variable_choices(ADSL, c("SEX", "RACE")),
        NULL
      )
    )
  )
)

shinyApp(ui = app$ui, server = app$server)
#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 680
library(teal.data)
library(teal.modules.clinical)
library(teal.transform)

data_km <- teal_data() |>
  within({
  ADSL <- pharmaverseadam::adsl

  ADSL$ARM <- as.factor(ADSL$ARM)
  ADSL$ARMCD <- as.factor(ADSL$ARMCD)
  ADSL$SEX <- as.factor(ADSL$SEX)
  ADSL$RACE <- as.factor(ADSL$RACE)

  ADTTE <- pharmaverseadam::adtte_onco
  ADTTE$ARM <- as.factor(ADTTE$ARM)
  ADTTE$ARMCD <- as.factor(ADTTE$ARMCD)
  ADTTE$AVALU = as.factor("DAYS")
})
join_keys(data_km) <- default_cdisc_join_keys[names(data_km)]

arm_ref_comp <- list(
  ACTARMCD = list(
    ref = "Pbo",
    comp = c("Xan_Hi", "Xan_Lo")
  ),
  ARM = list(
    ref = "Placebo",
    comp = c("Xanomeline High Dose", "Xanomeline Low Dose")
  )
)

app <- init(
  data = data_km,
  modules = modules(
    tm_g_km(
      label = "Kaplan-Meier Plot",
      dataname = "ADTTE",
      arm_var = choices_selected(
        variable_choices("ADSL", c("ARM", "ARMCD", "ACTARMCD")),
        "ARM"
      ),
      paramcd = choices_selected(
        value_choices("ADTTE", "PARAMCD", "PARAM"),
        "OS"
      ),
      arm_ref_comp = arm_ref_comp,
      strata_var = choices_selected(
        variable_choices("ADSL", c("SEX", "RACE")),
        "RACE"
      ),
      facet_var = choices_selected(
        variable_choices(ADSL, c("SEX", "RACE")),
        NULL
      )
    )
  )
)

shinyApp(ui = app$ui, server = app$server)

πŸ› οΈ Exercise

Fill in the code below to create a teal application that uses choices_selected to limit the variable choices for a summary table module.

  • group_var with Treatment variable,
  • y with Analysis variable,
  • param with Parameter of Interest
library(dplyr)
library(forcats)
library(teal.transform)
library(teal.modules.clinical)

data <- teal_data()
data <- within(data, {
  ADSL <- pharmaverseadam::adsl

  ADSL$ARM <- as.factor(ADSL$ARM)
  ADSL$ARMCD <- as.factor(ADSL$ARMCD)
  ADSL$ACTARMCD <- as.factor(ADSL$ACTARMCD)

  ADLB <- pharmaverseadam::adlb |>
    mutate(
      AVISIT = as.factor(AVISIT),
      AVISIT == fct_reorder(AVISIT, AVISITN, min),
      AVALU = "DAYS"
    )

  ADLB$ARM <- as.factor(ADLB$ARM)
  ADLB$ARMCD <- as.factor(ADLB$ARMCD)
  ADLB$ACTARMCD <- as.factor(ADLB$ACTARMCD)
})
join_keys(data) <- default_cdisc_join_keys[names(data)]

app <- init(
  data = data,
  modules = modules(
    tm_g_lineplot(
      label = "Line Plot",
      dataname = "ADLB",
      group_var = choices_selected(), # Select Treatment variable,
      y = choices_selected(), # Select Analysis variable,
      param = choices_selected() # Select Parameter of Interest
    )
  )
)
runApp(app)
library(dplyr)
library(forcats)
library(teal.transform)
library(teal.modules.clinical)

data <- teal_data()
data <- within(data, {
  ADSL <- pharmaverseadam::adsl

  ADSL$ARM <- as.factor(ADSL$ARM)
  ADSL$ARMCD <- as.factor(ADSL$ARMCD)
  ADSL$ACTARMCD <- as.factor(ADSL$ACTARMCD)

  ADLB <- pharmaverseadam::adlb |>
    mutate(
      AVISIT = as.factor(AVISIT),
      AVISIT == fct_reorder(AVISIT, AVISITN, min),
      AVALU = "DAYS"
    )

  ADLB$ARM <- as.factor(ADLB$ARM)
  ADLB$ARMCD <- as.factor(ADLB$ARMCD)
  ADLB$ACTARMCD <- as.factor(ADLB$ACTARMCD)
})
join_keys(data) <- default_cdisc_join_keys[names(data)]

app <- init(
  data = data,
  modules = modules(
    tm_g_lineplot(
      label = "Line Plot",
      dataname = "ADLB",
      group_var = choices_selected(
        variable_choices("ADSL", c("ARM", "ARMCD", "ACTARMCD")),
        "ARM"
      ),
      y = choices_selected(
        variable_choices("ADLB", c("AVAL", "BASE", "CHG", "PCHG")),
        "AVAL"
      ),
      param = choices_selected(
        value_choices("ADLB", "PARAMCD", "PARAM"),
        "ALT"
      )
    )
  )
)
runApp(app)

πŸ“š What did we learn?

The choices_selected and value_choices functions provides powerful flexibility for managing variable and value selections in teal applications. By combining it with variable_choices and understanding when to use eager vs. delayed evaluation, you can create robust applications that adapt to different datasets and provide users with appropriate choices.

Key takeaways: - Use variable_choices with subset functions for dynamic filtering - Delayed evaluation helps when dataset structure is unknown - Important with teal_data_modules - Smart defaults improve user experience across different datasets

πŸ’­ What did we not cover?

data_extract_spec is the underlying specification that powers choices_selected in the modules. It provides a more general framework for defining choices and selections, and can be customized further for advanced use cases.

The data_extract_ui and data_extract_srv functions can be used in modules to create custom UI and server logic for data extraction based on data_extract_spec and choices_selected (via teal.modules.clinical::cs_to_des_select).

Useful links for further exploration:

🌐 References