diff --git a/inst/design/basic_concept.drawio b/inst/design/basic_concept.drawio new file mode 100644 index 00000000..851014e5 --- /dev/null +++ b/inst/design/basic_concept.drawio @@ -0,0 +1,475 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vignettes/data-extract-merge.Rmd b/vignettes/data-extract-merge.Rmd index 85ba37f5..dd34f573 100644 --- a/vignettes/data-extract-merge.Rmd +++ b/vignettes/data-extract-merge.Rmd @@ -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. + +Basic Concept of teal.transform + +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 @@ -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 @@ -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) @@ -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} @@ -154,3 +174,5 @@ shinyApp( } ) ``` + +Shiny app output for Data Extract and Merge diff --git a/vignettes/data-extract.Rmd b/vignettes/data-extract.Rmd index 416b0061..9116c03e 100644 --- a/vignettes/data-extract.Rmd +++ b/vignettes/data-extract.Rmd @@ -17,13 +17,11 @@ 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` @@ -31,48 +29,13 @@ The role of `data_extract_spec` is twofold: to create a UI component in a `shiny 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 @@ -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. @@ -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`: @@ -114,3 +114,5 @@ shinyApp( } ) ``` + +Shiny app output for Data Extract diff --git a/vignettes/data-merge.Rmd b/vignettes/data-merge.Rmd index 4f2cee18..8552d7c0 100644 --- a/vignettes/data-merge.Rmd +++ b/vignettes/data-merge.Rmd @@ -17,16 +17,14 @@ knitr::opts_chunk$set( ) ``` -Combining datasets is an essential step when working with modules that involve multiple datasets. -Within the context of `teal`, we use the term "merge" to refer to the process of combining datasets. -To support this, two functions are provided: `merge_expression_module` and `merge_expression_srv`, each tailored for different use cases. +Joining datasets is an essential step when working with relational datasets. -Use `merge_expression_module` when there is no need to process the `data_extract` list. +To support this, two functions are provided depending on how to process the `data_extract_spec` object: + +1. `merge_expression_module` can be used when there is no need to process the list of `data_extract_spec`. This function reads the data and the list of `data_extract_spec` objects and applies the merging. -Essentially, it serves as a wrapper that combines `data_extract_multiple_srv()` and `merge_expression_srv()`. -Further details are provided below. -In scenarios where additional processing of the `data_extract` list is necessary, -`merge_expression_srv()` can be used along with `data_extract_multiple_srv()` or `data_extract_srv()` to customize the `selector_list` input. +Essentially, it serves as a wrapper that combines `data_extract_multiple_srv()` and `merge_expression_srv()`. +2. `merge_expression_srv` and `data_extract_multiple_srv` can be used in scenarios where additional processing of the list of `data_extract_spec` is necessary or `data_extract_srv()` to customize the `selector_list` input. The following sections provide examples for both scenarios. @@ -35,14 +33,35 @@ The following sections provide examples for both scenarios. Using `merge_expression_module` alone requires a list of `data_extract_spec` objects for the `data_extract` argument, a list of reactive or non-reactive `data.frame` objects, and a list of join keys corresponding to each `data.frame` object. -#### App code +#### 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 the Data Extracts + +```{r} adsl_extract <- data_extract_spec( dataname = "ADSL", select = select_spec( @@ -65,30 +84,42 @@ adtte_extract <- data_extract_spec( ) data_extracts <- list(adsl_extract = adsl_extract, adtte_extract = adtte_extract) +``` + +#### Step 3/5 - Creating the 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 -merge_module <- function(id, datasets, data_extracts, join_keys) { +```{r} +merge_srv <- function(id, datasets, data_extracts, join_keys) { moduleServer(id, function(input, output, session) { merged_data <- merge_expression_module( data_extract = data_extracts, @@ -105,36 +136,24 @@ merge_module <- function(id, datasets, data_extracts, join_keys) { output$data <- renderDataTable(ANL()) }) } - -# 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 ```{r eval=FALSE} shinyApp( ui = fluidPage(merge_ui("data_merge", data_extracts)), server = function(input, output, session) { - merge_module("data_merge", datasets, data_extracts, join_keys) + merge_srv("data_merge", datasets, data_extracts, join_keys) } ) ``` +Shiny app output for Data Extract + + +
+ ### `data_extract_multiple_srv` + `merge_expression_srv` In the scenario above, if the user deselects the `ADTTE` variable, the merging between `ADTTE` and `ADSL` would still occur, even though `ADTTE` is not used or needed. @@ -142,8 +161,10 @@ Here, the developer might update the `selector_list` input in a reactive manner Below, we reuse the input from above and update the app server so that the `adtte_extract` is removed from the selector_list input when no `ADTTE` variable is selected. The `reactive_selector_list` is then passed to `merge_expression_srv`: +#### Modifying the Server Logic + ```{r} -merge_module <- function(id, datasets, data_extracts, join_keys) { +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) reactive_selector_list <- reactive({ @@ -171,17 +192,19 @@ merge_module <- function(id, datasets, data_extracts, join_keys) { } ``` -#### Shiny app +#### Updating the `shiny` app ```{r eval=FALSE} shinyApp( ui = fluidPage(merge_ui("data_merge", data_extracts)), server = function(input, output, session) { - merge_module("data_merge", datasets, data_extracts, join_keys) + merge_srv("data_merge", datasets, data_extracts, join_keys) } ) ``` +Shiny app output for Data Extract + `merge_expression_module` is replaced here with three parts: 1) `selector_list`: the output of `data_extract_multiple_srv`, which loops over the list of `data_extract` given and runs `data_extract_srv` for each one, returning a list of reactive objects. diff --git a/vignettes/images/app-data-extract-merge.png b/vignettes/images/app-data-extract-merge.png new file mode 100644 index 00000000..a54904a2 Binary files /dev/null and b/vignettes/images/app-data-extract-merge.png differ diff --git a/vignettes/images/app-data-extract.png b/vignettes/images/app-data-extract.png new file mode 100644 index 00000000..a3728ac7 Binary files /dev/null and b/vignettes/images/app-data-extract.png differ diff --git a/vignettes/images/app-data-merge-1.png b/vignettes/images/app-data-merge-1.png new file mode 100644 index 00000000..4cbd38ba Binary files /dev/null and b/vignettes/images/app-data-merge-1.png differ diff --git a/vignettes/images/app-data-merge-2.png b/vignettes/images/app-data-merge-2.png new file mode 100644 index 00000000..487d130e Binary files /dev/null and b/vignettes/images/app-data-merge-2.png differ diff --git a/vignettes/images/basic_concept.svg b/vignettes/images/basic_concept.svg new file mode 100644 index 00000000..59cc580b --- /dev/null +++ b/vignettes/images/basic_concept.svg @@ -0,0 +1 @@ +
ADSL
ADSL
USUBJID
USUBJID
STUDYID
STUDYID
AGE
AGE
SEX
SEX
BMRKR1
BMRKR1
Foregin and Primary Key
Foregin and Primary Key
Other Columns
Other Columns
STUDYID
STUDYID
PARAMCD
PARAMCD
AVAL
AVAL
AVALC
AVALC
ASEQ
ASEQ
OS
OS
PFS
PFS
OS
OS
OS
OS
OS
OS
EFS
EFS
PFS
PFS
USUBJID
USUBJID
ADTTE
ADTTE
Selected ADSL
Selected ADSL
USUBJID
USUBJID
STUDYID
STUDYID
AGE
AGE
Select
Select
Select + Filter
Select + Filt...
STUDYID
STUDYID
PARAMCD
PARAMCD
AVAL
AVAL
OS
OS
OS
OS
OS
OS
OS
OS
USUBJID
USUBJID
Selected and Filtered ADTTE
Selected and Filtered ADTTE
Merge
Merge
STUDYID
STUDYID
AVAL
AVAL
USUBJID
USUBJID
ANL
ANL
AGE
AGE
Text is not SVG - cannot display
\ No newline at end of file diff --git a/vignettes/images/data_extract_spec/basic_concept.png b/vignettes/images/data_extract_spec/basic_concept.png deleted file mode 100644 index 5d34ee31..00000000 Binary files a/vignettes/images/data_extract_spec/basic_concept.png and /dev/null differ diff --git a/vignettes/teal-transform.Rmd b/vignettes/teal-transform.Rmd index b94a41ab..91a9a71e 100644 --- a/vignettes/teal-transform.Rmd +++ b/vignettes/teal-transform.Rmd @@ -22,13 +22,16 @@ knitr::opts_chunk$set( The `teal.transform` package is an integral component of the `teal` framework. It serves a dual purpose: -- For `teal` module developers, it offers a standardized user interface for column selection from datasets and facilitates dataset merging, resulting in the creation of analysis datasets for use within their modules. - For `teal` application developers, it provides a means to specify which dataset columns can be accessed through the user interface, streamlining column selection within their applications. +- For `teal` module developers, it offers a standardized user interface for column selection from datasets and facilitates dataset merging, resulting in the creation of analysis datasets for use within their modules. ## Data Extraction and Data Merging +The primary goal of `teal.transform` to to provide functions that help in abstracting the process of Data Extraction and Data Merging in UI elements of the `shiny` app. +This helps in reducing the amount of code required to create a UI elements that directly transform the data to perform the required analysis. This is how the app user gains flexibility to transform their data right from the UI. + +To explore the combined use of data extraction and merging to see the full use of `teal.transform`, please explore the [Combining data-extract with data-merge](data-extract-merge.html) vignette. + To delve into the process of selecting specific data columns, please consult the [Data Extraction](data-extract.html) vignette. For comprehensive information regarding data merging, please refer to the [Data Merge](data-merge.html) vignette. - -To explore the combined use of data extraction and merging, please explore the [Combining data-extract with data-merge](data-extract-merge.html) vignette.