Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Docs] Refine the vignettes #199

Merged
merged 22 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
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
vedhav marked this conversation as resolved.
Show resolved Hide resolved
vedhav marked this conversation as resolved.
Show resolved Hide resolved
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:
vedhav marked this conversation as resolved.
Show resolved Hide resolved

```{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.
vedhav marked this conversation as resolved.
Show resolved Hide resolved
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
Loading