Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions inst/WORDLIST
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ CDISC
Forkers
UI
mimicks
pre
reactives
runnable
schemas
stratifiers
tidyselect
226 changes: 226 additions & 0 deletions vignettes/teal-picks-choices-and-selections.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
---
title: "Choices and selections in teal.picks"
author: "NEST CoreDev"
date: "`r Sys.Date()`"
vignette: >
%\VignetteIndexEntry{Choices and selections in teal.picks}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
output:
rmarkdown::html_vignette:
toc: true
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(comment = NA)
```

## Overview

Every `picks()` object is built from three kinds of building blocks: `datasets()`, `variables()`,
and `values()`. Each one carries two core arguments:

- `choices` — the full set of options the app user can pick from.
- `selected` — the subset that is pre-selected when the app starts.

```
picks(
datasets(choices = <what datasets the user may choose>, selected = <default dataset>),
variables(choices = <what columns the user may choose>, selected = <default columns>),
values(choices = <what values the user may filter to>, selected = <default filter values>)
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need a new vignette, since we already have a vignette with the overall information like this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged to the main vignette here. On the other hand now the vignette is not as short as it used to be.

```

Choices and selections can be expressed in three ways:

| Approach | Works with |
|---|---|
| Explicit — character vector, integer index, or numeric range | `datasets()`, `variables()`, `values()` |
Copy link
Copy Markdown
Contributor

@averissimo averissimo May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a default row of what happens when defaults are assumed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice suggestion. Done here.

| `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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just move this information and the below to the main vignette

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in here!


```{r data, message = FALSE}
library(teal.data)
library(teal.picks)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
library(teal.picks)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need 1 of these for the vignette to build :-) it cannot be silent as in examples

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed anyway as the data is loaded by the main vignette


data <- teal_data()
data <- within(data, {
ADSL <- data.frame(
USUBJID = sprintf("S%03d", 1:10),
AGE = sample(40:75, 10, replace = TRUE),
SEX = rep(c("M", "F"), 5),
ARM = rep(c("A", "B"), 5),
stringsAsFactors = FALSE
)
ADLB <- data.frame(
USUBJID = rep(sprintf("S%03d", 1:10), each = 3),
PARAM = rep(c("ALT", "AST", "CRP"), 10),
AVAL = round(rnorm(30, mean = 40, sd = 8), 1),
stringsAsFactors = FALSE
)
})

join_keys(data) <- join_keys(
teal.data::join_key("ADSL", "ADLB", keys = "USUBJID")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These datasets are very incomplete, why not use teal.data::rADXX and teal.data::default_cdisc_join_keys[c("ADLB")]

Or add STUDYID to at least have the primary keys.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the teal.data synthetic datasets, as you suggested here. I think it is an improvement

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the teal.data datasets as suggested. Here.

)
```

---

## 1. Explicit choices
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is explicit the right term?

User-defined, static, ?

Should it also reference what happens when choice doesn't exist

resolver(
  teal.picks::picks(teal.picks::datasets("ADSL"), teal.picks::variables(c("yada"))),
  teal.data::teal_data(ADSL = teal.data::rADSL)
)
#' warnings...
#' <picks>
#'   <datasets>:
#'     choices: ADSL
#'     selected: ADSL
#'     multiple=FALSE, ordered=FALSE, fixed=TRUE
#'   <variables>:
#'     choices: ~
#'     selected: ~
#'     multiple=FALSE, ordered=FALSE, fixed=TRUE, allow-clear=FALSE


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`).

```{r explicit}
# 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"
)
)

resolver(x = p_datasets, data = as.list(data))
resolver(x = p_variables, data = as.list(data))
resolver(x = p_values, data = as.list(data))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is resolver an exported funtion? Do user need to know this function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not need it. Indeed it is confusing. I removed here. It is exported, btw.

```

---

## 2. `tidyselect` helpers

`tidyselect` predicates let `choices` and `selected` adapt to the actual data at runtime instead
of being hard-coded. This is supported for `datasets()` and `variables()`.
Comment thread
averissimo marked this conversation as resolved.
Outdated

Commonly used helpers:

- `tidyselect::everything()` — all items
- `tidyselect::starts_with()` / `tidyselect::ends_with()` / `tidyselect::contains()` — pattern matching
- `tidyselect::matches()` — regex matching
- `tidyselect::where(predicate)` — columns satisfying a predicate function
- `tidyselect::all_of()` / `tidyselect::any_of()` — vector-based selection (error-safe or silent)
- Integer indices such as `1L`, `1L:3L` — select by position

> Note: `tidyselect` is not supported by `values()`. Use explicit vectors or a function
> there instead (see [Section 3](#functions)).

```{r tidyselect}
# 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
)
)

resolver(x = p_any_dataset, data = as.list(data))
resolver(x = p_numeric_vars, data = as.list(data))
resolver(x = p_a_prefix, data = as.list(data))
```

---

## 3. Functions {#functions}

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()`.

```{r functions}
# 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.
p_cat_vars <- 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
)
)

resolver(x = p_cat_vars, data = as.list(data))
resolver(x = p_even_ages, data = as.list(data))
```

---

## Summary

| | Explicit | `tidyselect` | Function |
|---|:---:|:---:|:---:|
| `datasets()` | yes | yes | yes |
| `variables()` | yes | yes | yes |
| `values()` | yes | **no** | yes |

Use **explicit** 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()`.

## See also

- `?picks` — full reference for `picks()`, `datasets()`, `variables()`, and `values()`.
- `?tidyselectors` — package-specific tidyselect helpers such as `is_categorical()`.
- `vignette("teal-picks-in-teal", package = "teal.picks")` — wiring `picks` into a `teal` module.
- `vignette("teal-picks-standalone-shiny", package = "teal.picks")` — using `picks` in plain Shiny.
Loading