knitr::opts_chunk$set(comment = NA)
teal.picks helps you expose analysis choices in a teal Shiny app in a way that mimicks how people think about data: first which dataset, then which columns, and optionally which values to filter to. You set all those steps at once with picks(); the app renders the right controls and keeps selections consistent when upstream choices change.
Typical workflow:
teal.data::teal_data() object (when you relate multiple tables use join_keys so teal knows how they link).picks() specs: datasets(), optionally variables(), optionally values() for filters or ranges.The examples below show the usual building blocks—not every app needs every level.
library(teal) library(teal.data) library(teal.picks) data <- teal_data() data <- within(data, { ADSL <- teal.data::rADSL ADLB <- teal.data::rADLB }) join_keys(data) <- teal.data::default_cdisc_join_keys[c("ADSL", "ADLB")]
When the analysis can draw from more than one table, let the user pick the active source (here, demographics vs labs).
picks_datasets <- list( source = picks( datasets( choices = c("ADSL", "ADLB"), selected = "ADLB" ) ) )
Add variables() so, after a dataset is chosen, the user picks one or more columns. You can list names explicitly or use tidyselect-style expressions; see ?picks for details.
picks_datasets_variables <- list( adsl_cols = picks( datasets(choices = "ADSL", selected = "ADSL"), variables( choices = c("USUBJID", "AGE", "SEX"), selected = "AGE", multiple = FALSE ) ) )
values() sits after variables(). It adapts to the column type—for example category levels for a PARAM-style variable, or a numeric or date range for continuous data.
picks_datasets_variables_values <- list( labs = picks( datasets(choices = "ADLB", selected = "ADLB"), variables(choices = "PARAM", selected = "PARAM", multiple = FALSE), values( choices = c("ALT", "AST", "CRP", "GLU"), selected = c("ALT", "AST"), multiple = TRUE ) ) )
Each picks() element has choices and selected. You can define them in four ways:
| Approach | Works with |
|---|---|
| Default behavior - no arguments needed for generic usage | datasets(), variables(), values()
| Static — character vector, integer index, or numeric range | datasets(), variables(), values() |
| tidyselect helpers — everything(), starts_with(), where(), … | datasets(), variables() only |
| Function — an R function applied at runtime to the data | datasets(), variables(), values() |
The sections below walk through each approach with runnable examples.
Defaults depend on the slot type.
datasets(), the default choices are all available datasets. A picks() call needs a datasets() as first positional argument, except when check_no_dataset = FALSE.variables(), the default choices is all variables in the selected dataset.values(), the default choices is all values of the selected variable.selected, the default is the first available choice, or all choices when multiple = TRUE.picks( datasets(choices = "ADSL", selected = "ADSL"), variables() ) picks( datasets(choices = "ADSL", selected = "ADSL"), variables(choices = "SEX", selected = "SEX", multiple = FALSE), values() )
Pass a character vector to choices to enumerate options exactly. Use selected to set the default.
Integer indices work too (for example selected = 1L means the first element of
choices).
# Datasets — user may switch between ADSL and ADLB; ADSL is the default p_datasets <- picks( datasets( choices = c("ADSL", "ADLB"), selected = "ADSL" ) ) # Variables — only a named subset is offered; first column pre-selected p_variables <- picks( datasets(choices = "ADSL", selected = "ADSL"), variables( choices = c("AGE", "SEX", "ARM"), selected = "AGE", multiple = FALSE ) ) # Values — categorical filter; two levels pre-selected p_values <- picks( datasets(choices = "ADSL", selected = "ADSL"), variables(choices = "SEX", selected = "SEX", multiple = FALSE), values( choices = c("M", "F"), selected = "F" ) ) p_datasets p_variables p_values
tidyselect helperstidyselect predicates let choices and selected adapt to the actual data at runtime instead of being hard-coded.
This is supported for datasets() and variables().
Commonly used helpers:
tidyselect::everything() — all itemstidyselect::starts_with() / tidyselect::ends_with() / tidyselect::contains() — pattern matchingtidyselect::matches() — regex matchingtidyselect::where(predicate) — columns satisfying a predicate functiontidyselect::all_of() / tidyselect::any_of() — vector-based selection (error-safe or silent)1L, 1L:3L — select by positionNote:
tidyselectis not supported byvalues(). Use explicit vectors or a function there instead (see Functions).
# Datasets — offer any data.frame in the teal_data object p_any_dataset <- picks( datasets( choices = tidyselect::where(is.data.frame), selected = 1L # first dataset by default ) ) # Variables — all numeric columns; first one pre-selected p_numeric_vars <- picks( datasets(choices = "ADSL", selected = "ADSL"), variables( choices = tidyselect::where(is.numeric), selected = 1L, multiple = FALSE ) ) # Variables — columns whose names start with "A"; first two pre-selected p_a_prefix <- picks( datasets(choices = "ADSL", selected = "ADSL"), variables( choices = tidyselect::starts_with("A"), selected = 1L:2L, multiple = TRUE ) ) p_any_dataset p_numeric_vars p_a_prefix
You can pass a plain R function as choices or selected. The function receives the relevant
context object (the current dataset for datasets() and variables(), the current column vector
for values()) and must return the subset to use.
This is the only runtime-dynamic approach supported by values().
# Variables — use the package helper is_categorical() as a column predicate. # Without "des-delayed", the resolver calls it via vapply(data, fn, logical(1)), # so it must accept one column and return a single logical value — which is_categorical() does. picks( datasets(choices = "ADSL", selected = "ADSL"), variables( choices = is_categorical(), selected = 1L, multiple = TRUE ) ) # Values — select only even ages from the AGE column. # Functions passed to values() must carry the "des-delayed" class so the resolver # calls them with the column vector rather than treating them as a column predicate. even_vals <- function(x) sort(unique(x[x %% 2 == 0])) class(even_vals) <- append(class(even_vals), "des-delayed") p_even_ages <- picks( datasets(choices = "ADSL", selected = "ADSL"), variables(choices = "AGE", selected = "AGE", multiple = FALSE), values( choices = even_vals, selected = even_vals ) ) p_even_ages
Use multiple = TRUE when analysts should pass more than one variable into the next step (for example stratifiers or outcomes together).
picks_multiple_variables <- list( demo = picks( datasets(choices = "ADSL", selected = "ADSL"), variables( choices = c("USUBJID", "AGE", "SEX"), selected = c("AGE", "SEX"), multiple = TRUE, ordered = TRUE ) ) )
See ?picks for other arguments that affect how choices/selections are presented to users regardless of
using static, tidyselect or functions to define them.
| | Static | tidyselect | Function |
|---|:---:|:---:|:---:|
| datasets() | yes | yes | yes |
| variables() | yes | yes | yes |
| values() | yes | no | yes |
Use static choices when the set of options is fixed and known at app-development time.
Use tidyselect when you want the choices to adapt to the shape of the data without writing
a custom function. Use functions when the logic is more involved, or when you need runtime
behavior for values().
You can paste the picks objects above into any module that consumes them. To quickly explore picks object(s) in teal apps, the package includes tm_merge(): a small example teal module that connects picks to a merged preview table. That is not required for normal use—it is simply a convenient host while you learn the picks() API.
Run the next block to explore a teal app with different picks patterns
library(shiny) app <- init( data = data, modules = modules( modules( label = "teal.picks patterns", tm_merge( label = "1. Dataset choice", picks = picks_datasets ), tm_merge( label = "2. Dataset & variables", picks = picks_datasets_variables ), tm_merge( label = "3. Dataset, variables & values", picks = picks_datasets_variables_values ), tm_merge( label = "4. Multiple variables", picks = picks_multiple_variables ) ) ) ) if (interactive()) { shinyApp(app$ui, app$server) }
In real apps you usually integrate picks with your own teal::module(). Two functions do the wiring:
picks_ui() — place the selector controls in your picks object in your module UIpicks_srv() — in the module server, pass data as a reactive teal_data object; it returns the resolved picks (what the user chose, after any dynamic choices are applied).tm_picks_preview <- function(label = "Custom picks module", picks) { teal::module( label = label, ui = function(id, picks) { ns <- shiny::NS(id) shiny::tagList( teal.picks::picks_ui(ns("sel"), picks = picks), shiny::tags$h5("Preview (first rows)"), shiny::tableOutput(ns("preview")), shiny::tags$h5("Resolved picks"), shiny::verbatimTextOutput(ns("resolved")) ) }, server = function(id, data, picks) { shiny::moduleServer(id, function(input, output, session) { resolved <- teal.picks::picks_srv("sel", picks = picks, data = data) preview_tbl <- shiny::reactive({ shiny::req(data(), resolved()) ds <- resolved()$datasets$selected vars <- resolved()$variables$selected shiny::req(length(ds) == 1L, length(vars) >= 1L) data()[[ds]][, vars, drop = FALSE] }) output$preview <- shiny::renderTable({ utils::head(preview_tbl(), 8L) }) output$resolved <- shiny::renderPrint({ shiny::req(resolved()) str(resolved(), max.level = 2L, give.attr = FALSE) }) }) }, ui_args = list(picks = picks), server_args = list(picks = picks), datanames = "ADSL" ) } app <- init( data = data, modules = modules( tm_picks_preview( label = "Custom picks module", picks = picks_datasets_variables$adsl_cols ) ) ) if (interactive()) { shinyApp(app$ui, app$server) }
See ?picks_ui and ?picks_srv for container options and multiple named selectors.
?picks — full datasets() / variables() / values() reference.vignette("teal-picks-standalone-shiny", package = "teal.picks") — picks_ui() / picks_srv() in plain Shiny (no init()).teal documentation for init(), modules(), and teal::module().Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.