Skip to content

Commit

Permalink
[Docs] Refine the vignettes (#199)
Browse files Browse the repository at this point in the history
Closes #185 

Changes:
1. Updated the diagram so it's more clear on what teal.transform does.
2. ~Added a small vignette explaining how to create `data_extract_spec`~
3. Update the existing vignette's example code without `teal.widgets`
dependency as it's not needed.
4. Added a small note at the top of every vignette warning that the
functions might change.
5. Refined the vignettes 
6. Add the screenshot of the app outputs in the examples.

---------

Signed-off-by: Vedha Viyash <49812166+vedhav@users.noreply.github.com>
Co-authored-by: Aleksander Chlebowski <114988527+chlebowa@users.noreply.github.com>
  • Loading branch information
vedhav and chlebowa committed Feb 16, 2024
1 parent beb3ea6 commit 848921f
Show file tree
Hide file tree
Showing 11 changed files with 677 additions and 151 deletions.
475 changes: 475 additions & 0 deletions inst/design/basic_concept.drawio

Large diffs are not rendered by default.

142 changes: 82 additions & 60 deletions vignettes/data-extract-merge.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,63 @@ knitr::opts_chunk$set(
)
```

`teal.transform` provides `merge_expression_srv`, which converts `data_extract_srv` into `R` expressions to transform data
for analytical purposes.
For example, you may wish to select `AGE` from `ADSL` and select `AVAL` from `ADTTE`, filtered for rows where `PARAMCD` is `OS`, and then merge the results using the primary keys to create an analysis dataset `ANL`.
This diagram illustrates the concept:
`teal.transform` allows the app user to oversee transforming a relational set of data objects into the final dataset for analysis.
User actions create a R expression that subsets and merges the input data.

```{r echo=FALSE, out.width='100%'}
knitr::include_graphics("./images/data_extract_spec/basic_concept.png")
```
In the following example we will create an analysis dataset `ANL` by:

In the following code block, we create a `data_extract_spec` object for each dataset, as illustrated above.
1. Selecting the column `AGE` from `ADSL`
2. Selecting the column `AVAL` and filtering the rows where `PARAMCD` is `OS` from `ADTTE`
3. Merging the results from the above datasets using the primary keys.

<img src="images/basic_concept.svg" alt="Basic Concept of teal.transform" style="width: 100%;" />

Note that primary key columns are maintained when selecting columns from datasets.

Let's see how to achieve this dynamic `select`, `filter`, and `merge` operations in a `shiny` app using `teal.transform`.

#### Step 1/5 - Preparing the Data

```{r}
library(teal.transform)
library(teal.widgets)
library(teal.data)
library(shiny)
# Define data.frame objects
ADSL <- teal.transform::rADSL
ADTTE <- teal.transform::rADTTE
# create a list of reactive data.frame objects
datasets <- list(
ADSL = reactive(ADSL),
ADTTE = reactive(ADTTE)
)
# create join_keys
join_keys <- join_keys(
join_key("ADSL", "ADSL", c("STUDYID", "USUBJID")),
join_key("ADSL", "ADTTE", c("STUDYID", "USUBJID")),
join_key("ADTTE", "ADTTE", c("STUDYID", "USUBJID", "PARAMCD"))
)
```


#### Step 2/5 - Creating data extract specifications

In the following code block, we create a `data_extract_spec` object for each dataset, as illustrated above.
It is created by the `data_extract_spec()` function which takes in four arguments:

1. `dataname` is the name of the dataset to be extracted.
2. `select` helps specify the columns from which we wish to allow the app user to select. It can be generated using the function `select_spec()`. In the case of `ADSL`, we restrict the selection to `AGE`, `SEX`, and `BMRKR1`, with `AGE` being the default selection.
3. `filter` helps specify the values of a variable we wish to filter during extraction. It can be generated using the function `filter_spec()`. In the case of `ADTTE`, we filter the variable `PARAMCD` by allowing users to choose from `CRSD`, `EFS`, `OS`, and `PFS`, with `OS` being the default filter.
4. `reshape` is a boolean which helps to specify if the data needs to be reshaped from long to wide format. By default it is set to `FALSE`.

```{r}
adsl_extract <- data_extract_spec(
dataname = "ADSL",
select = select_spec(
label = "Select variable:",
choices = c("AGE", "BMRKR1"),
choices = c("AGE", "SEX", "BMRKR1"),
selected = "AGE",
multiple = TRUE,
fixed = FALSE
Expand All @@ -48,7 +83,7 @@ adsl_extract <- data_extract_spec(
adtte_extract <- data_extract_spec(
dataname = "ADTTE",
select = select_spec(
choices = c("AVAL", "ASEQ"),
choices = c("AVAL", "AVALC", "ASEQ"),
selected = "AVAL",
multiple = TRUE,
fixed = FALSE
Expand All @@ -63,34 +98,49 @@ adtte_extract <- data_extract_spec(
data_extracts <- list(adsl_extract = adsl_extract, adtte_extract = adtte_extract)
```

#### Example module
#### Step 3/5 - Creating the UI

Here, we define the `merge_ui` and `merge_srv` functions, which will be used to create the UI and the server
components of the `shiny` app, respectively.
Here, we define the `merge_ui` function, which will be used to create the UI components for the `shiny` app.

Note that we take in the list of `data_extract` objects as input, and make use of the `data_extract_ui` function to create our UI.

```{r}
merge_ui <- function(id, data_extracts) {
ns <- NS(id)
standard_layout(
output = white_small_well(
sidebarLayout(
sidebarPanel(
h3("Encoding"),
div(
data_extract_ui(
ns("adsl_extract"), # must correspond with data_extracts list names
label = "ADSL extract",
data_extracts[[1]]
),
data_extract_ui(
ns("adtte_extract"), # must correspond with data_extracts list names
label = "ADTTE extract",
data_extracts[[2]]
)
)
),
mainPanel(
h3("Output"),
verbatimTextOutput(ns("expr")),
dataTableOutput(ns("data"))
),
encoding = div(
data_extract_ui(
ns("adsl_extract"), # must correspond with data_extracts list names
label = "ADSL extract",
data_extracts[[1]]
),
data_extract_ui(
ns("adtte_extract"), # must correspond with data_extracts list names
label = "ADTTE extract",
data_extracts[[2]]
)
)
)
}
```

#### Step 4/5 - Creating the Server Logic

Here, we define the `merge_srv` function, which will be used to create the server logic for the `shiny` app.

This function takes as arguments the datasets (as a list of reactive `data.frame`), the data extract specifications created above (the `data_extract` list), and the `join_keys` object (read more about the `join_keys` in the [Join Keys vignette of `teal.data`](https://insightsengineering.github.io/teal.data/latest-tag/articles/join-keys.html)).
We make use of the `merge_expression_srv` function to get a reactive list containing merge expression and information needed to perform the transformation - see more in `merge_expression_srv` documentation.
We print this expression in the UI and also evaluate it to get the final `ANL` dataset which is also displayed as a table in the UI.

```{r}
merge_srv <- function(id, datasets, data_extracts, join_keys) {
moduleServer(id, function(input, output, session) {
selector_list <- data_extract_multiple_srv(data_extracts, datasets, join_keys)
Expand All @@ -111,39 +161,9 @@ merge_srv <- function(id, datasets, data_extracts, join_keys) {
}
```

Output from `data_extract_srv` (`reactive`) should be passed to `merge_expression_srv` together with `datasets`
(list of reactive `data.frame` objects) and `join_keys` object.
`merge_expression_srv` returns a reactive list containing merge expression and information needed to perform the transformation - see more in `merge_expression_srv` documentation.

#### Example data

The `data_extract_srv` module depends on a list of reactive or non-reactive `data.frame` objects.
Here, we demonstrate the usage of a list of reactive `data.frame` objects as input to `datasets`,
along with a list of necessary join keys per `data.frame` object:


```{r}
# Define data.frame objects
ADSL <- teal.transform::rADSL
ADTTE <- teal.transform::rADTTE
# create a list of reactive data.frame objects
datasets <- list(
ADSL = reactive(ADSL),
ADTTE = reactive(ADTTE)
)
# create join_keys
join_keys <- join_keys(
join_key("ADSL", "ADSL", c("STUDYID", "USUBJID")),
join_key("ADSL", "ADTTE", c("STUDYID", "USUBJID")),
join_key("ADTTE", "ADTTE", c("STUDYID", "USUBJID", "PARAMCD"))
)
```

#### Shiny app
#### Step 5/5 - Creating the `shiny` App

Finally, we include `merge_ui` and `merge_srv` to the UI and server component of the `shinyApp`, respectively,
Finally, we include `merge_ui` and `merge_srv` in the UI and server components of the `shinyApp`, respectively,
using the `data_extract`s defined in the first code block and the `datasets` object:

```{r eval=FALSE}
Expand All @@ -154,3 +174,5 @@ shinyApp(
}
)
```

<img src="images/app-data-extract-merge.png" alt="Shiny app output for Data Extract and Merge" style="width: 100%;" />
84 changes: 43 additions & 41 deletions vignettes/data-extract.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -17,62 +17,25 @@ knitr::opts_chunk$set(
)
```

There are times when an app developer wants to offer users more flexibility in analyzing data within their custom module.
In such cases, relinquishing control of the application to users requires developers to provide a degree of freedom.
With `teal`, app developers can open up their applications to users, allowing them to decide exactly which app data to
analyze within the module.

Many `teal` modules leverage `data_extract_spec` objects and modules to handle user input.
Examples can be found in `teal.modules.general` and `teal.modules.clinical`.
A `teal` module can leverage the use of `data_extract_spec` objects to handle and process the user input.
Examples can be found in the [modules from the `teal.modules.clinical` package](https://insightsengineering.github.io/teal.modules.clinical/latest-tag/reference/index.html).

### `data_extract_spec`

The role of `data_extract_spec` is twofold: to create a UI component in a `shiny` application and to pass user input
from the UI to the module itself.
Let's delve into how it fulfills both of these responsibilities.

#### Example module

To demonstrate different initialization options of `data_extract_spec`, let's first define a `shiny` module that
utilizes `data_extract_ui` and `data_extract_srv` to handle `data_extract_spec` objects.
This module creates a UI component for a single `data_extract_spec` and prints a list of values returned from the `data_extract_srv` module.
For more information about `data_extract_ui` and `data_extract_srv`, please refer to the package documentation.
#### Step 1/4 - Preparing the Data

```{r}
library(teal.transform)
library(teal.widgets)
library(teal.data)
library(shiny)
extract_ui <- function(id, data_extract) {
ns <- NS(id)
standard_layout(
output = white_small_well(verbatimTextOutput(ns("output"))),
encoding = data_extract_ui(ns("data_extract"), label = "variable", data_extract)
)
}
extract_srv <- function(id, datasets, data_extract, join_keys) {
moduleServer(id, function(input, output, session) {
reactive_extract_input <- data_extract_srv("data_extract", datasets, data_extract, join_keys)
s <- reactive({
format_data_extract(reactive_extract_input())
})
output$output <- renderPrint({
cat(s())
})
})
}
```


#### Example data

The `data_extract_srv` module depends on a list of reactive or non-reactive `data.frame` objects.
Here, we demonstrate the usage of a list of reactive `data.frame` objects as input to `datasets`,
along with a list of necessary join keys per `data.frame` object:

```{r}
# Define data.frame objects
ADSL <- teal.transform::rADSL
ADTTE <- teal.transform::rADTTE
Expand All @@ -90,6 +53,8 @@ join_keys <- join_keys(
)
```

#### Step 2/4 - Creating a `data_extract_spec` Object

Consider the following example, where we create two UI elements, one to filter on a specific level from `SEX` variable,
and a second one to select a variable from `c("BMRKR1", "AGE")`.
`data_extract_spec` object is handed over to the `shiny` app and gives instructions to generate UI components.
Expand All @@ -102,7 +67,42 @@ simple_des <- data_extract_spec(
)
```

#### Shiny app
#### Step 3/4 - Creating the `shiny` UI and Server Modules

To demonstrate different initialization options of `data_extract_spec`, let's first define a `shiny` module that
utilizes `data_extract_ui` and `data_extract_srv` to handle `data_extract_spec` objects.
This module creates a UI component for a single `data_extract_spec` and prints a list of values returned from the `data_extract_srv` module.
For more information about `data_extract_ui` and `data_extract_srv`, please refer to the package documentation.

```{r}
extract_ui <- function(id, data_extract) {
ns <- NS(id)
sidebarLayout(
sidebarPanel(
h3("Encoding"),
data_extract_ui(ns("data_extract"), label = "variable", data_extract)
),
mainPanel(
h3("Output"),
verbatimTextOutput(ns("output"))
)
)
}
extract_srv <- function(id, datasets, data_extract, join_keys) {
moduleServer(id, function(input, output, session) {
reactive_extract_input <- data_extract_srv("data_extract", datasets, data_extract, join_keys)
s <- reactive({
format_data_extract(reactive_extract_input())
})
output$output <- renderPrint({
cat(s())
})
})
}
```

#### Step 4/4 - Creating the `shiny` App

Finally, we include `extract_ui` in the UI of the `shinyApp`, and utilize `extract_srv` in the server function of the `shinyApp`:

Expand All @@ -114,3 +114,5 @@ shinyApp(
}
)
```

<img src="images/app-data-extract.png" alt="Shiny app output for Data Extract" style="width: 100%;" />
Loading

0 comments on commit 848921f

Please sign in to comment.