From 6accb86356a37f16102bf33d80debc87c75de5b9 Mon Sep 17 00:00:00 2001 From: kartikeya kirar Date: Tue, 13 Feb 2024 20:37:00 +0530 Subject: [PATCH] Fixing vignettes (#183) this is part of https://github.com/insightsengineering/teal.transform/pull/176 - [x] Sanity check of all vignettes, make sure there is no typo, no wrong format, etc. --------- Signed-off-by: Marcin <133694481+m7pr@users.noreply.github.com> Co-authored-by: m7pr Co-authored-by: Marcin <133694481+m7pr@users.noreply.github.com> --- vignettes/data-extract-merge.Rmd | 65 +++++++++++++---------- vignettes/data-extract.Rmd | 60 ++++++++++++---------- vignettes/data-merge.Rmd | 88 +++++++++++++++++++------------- vignettes/teal-transform.Rmd | 3 +- 4 files changed, 124 insertions(+), 92 deletions(-) diff --git a/vignettes/data-extract-merge.Rmd b/vignettes/data-extract-merge.Rmd index 6bdb4d83..f6143c73 100644 --- a/vignettes/data-extract-merge.Rmd +++ b/vignettes/data-extract-merge.Rmd @@ -17,22 +17,24 @@ knitr::opts_chunk$set( ) ``` -`teal.transform` provides `merge_expression_srv` which converts `data_extract_srv` into R expression to transform data -for analytical purposes. For example, we may wish to select `AGE` from `ADSL` and select `AVAL` from `ADTTE` filtered -for rows where `PARAMCD` is `OS` and merge the results (using the primary keys) to create an analysis dataset -`ANL` to be used in the module, as this diagram shows: +`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: ```{r echo=FALSE, out.width='100%'} knitr::include_graphics("./images/data_extract_spec/basic_concept.png") ``` -In the following code block we create a `data_extract_spec` object per dataset as illustrated above. +In the following code block, we create a `data_extract_spec` object for each dataset, as illustrated above. ```{r} library(teal.transform) +library(teal.widgets) +library(teal.data) library(shiny) -adsl_extract <- teal.transform::data_extract_spec( +adsl_extract <- data_extract_spec( dataname = "ADSL", select = select_spec( label = "Select variable:", @@ -43,7 +45,7 @@ adsl_extract <- teal.transform::data_extract_spec( ) ) -adtte_extract <- teal.transform::data_extract_spec( +adtte_extract <- data_extract_spec( dataname = "ADTTE", select = select_spec( choices = c("AVAL", "ASEQ"), @@ -63,24 +65,24 @@ data_extracts <- list(adsl_extract = adsl_extract, adtte_extract = adtte_extract #### Example module -Here, we create the `merge_ui` and the `merge_srv` functions which will be used to create the `ui` and the `srv` -components of the shiny app, respectively. +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. ```{r} merge_ui <- function(id, data_extracts) { ns <- NS(id) - teal.widgets::standard_layout( - output = teal.widgets::white_small_well( + standard_layout( + output = white_small_well( verbatimTextOutput(ns("expr")), dataTableOutput(ns("data")) ), encoding = div( - teal.transform::data_extract_ui( + data_extract_ui( ns("adsl_extract"), # must correspond with data_extracts list names label = "ADSL extract", data_extracts[[1]] ), - teal.transform::data_extract_ui( + data_extract_ui( ns("adtte_extract"), # must correspond with data_extracts list names label = "ADTTE extract", data_extracts[[2]] @@ -91,16 +93,19 @@ merge_ui <- function(id, data_extracts) { merge_srv <- function(id, datasets, data_extracts, join_keys) { moduleServer(id, function(input, output, session) { - selector_list <- teal.transform::data_extract_multiple_srv(data_extracts, datasets, join_keys) - merged_data <- teal.transform::merge_expression_srv( + selector_list <- data_extract_multiple_srv(data_extracts, datasets, join_keys) + merged_data <- merge_expression_srv( selector_list = selector_list, datasets = datasets, join_keys = join_keys, merge_function = "dplyr::left_join" ) - ANL <- reactive({ # nolint - eval(envir = list2env(datasets), expr = as.expression(merged_data()$expr)) + + ANL <- reactive({ + data_list <- lapply(datasets, function(ds) ds()) + eval(envir = list2env(data_list), expr = as.expression(merged_data()$expr)) }) + output$expr <- renderText(paste(merged_data()$expr, collapse = "\n")) output$data <- renderDataTable(ANL()) }) @@ -108,13 +113,14 @@ 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 `data.frame` objects) and `join_keys` list. `merge_expression_srv` returns a reactive list containing merge expression and information needed to perform the transformation - see more in `merge_expression_srv` documentation. +(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 -`data_extract_srv` module depends on either a list of reactive or non-reactive `data.frame` objects. Here, we show the -usage of a list of `data.frame` objects as input to `datasets` -where a list of necessary join keys per `data.frame` object is required: +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} @@ -122,20 +128,23 @@ where a list of necessary join keys per `data.frame` object is required: ADSL <- teal.transform::rADSL # nolint ADTTE <- teal.transform::rADTTE # nolint -# create a list of data.frame objects -datasets <- list(ADSL = ADSL, ADTTE = ADTTE) +# create a list of reactive data.frame objects +datasets <- list( + ADSL = reactive(ADSL), + ADTTE = reactive(ADTTE) +) # create join_keys -join_keys <- teal.data::join_keys( - teal.data::join_key("ADSL", "ADSL", c("STUDYID", "USUBJID")), - teal.data::join_key("ADSL", "ADTTE", c("STUDYID", "USUBJID")), - teal.data::join_key("ADTTE", "ADTTE", c("STUDYID", "USUBJID", "PARAMCD")) +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 -Finally, we pass `merge_ui` and `merge_srv` to the `ui` and `server` functions of the `shinyApp` +Finally, we include `merge_ui` and `merge_srv` to the UI and server component of the `shinyApp`, respectively, using the `data_extract`s defined in the first code block and the `datasets` object: ```{r eval=FALSE} diff --git a/vignettes/data-extract.Rmd b/vignettes/data-extract.Rmd index 9bde1e2e..b109d865 100644 --- a/vignettes/data-extract.Rmd +++ b/vignettes/data-extract.Rmd @@ -17,35 +17,37 @@ knitr::opts_chunk$set( ) ``` -There are times when an app developer wants to showcase more than just one fixed slice of their dataset in their -custom module. Relinquishing control of the application to a user demands the developer gives their users a degree -of freedom. In case of analyzing data, `teal` allows app developers to open up their applications to users, letting them -decide exactly what app data to analyze in the module. - -A lot of `teal` modules use `data_extract_spec` objects and modules to tackle user input. You can find many examples in -e.g. `teal.modules.general` and `teal.modules.clinical`. +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`. ### `data_extract_spec` -`data_extract_spec`'s task is two-fold: create a UI component in a `shiny` application and pass the user input from the - UI to the module itself. Having that formulated, let's have a look at how it supports both its responsibilities. +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 -In order to showcase different initialization options of `data_extract_spec`, first we define a `shiny` module which -uses `data_extract_ui` and `data_extract_srv` designed to handle `data_extract_spec` objects. The module creates a UI -component for a single `data_extract_spec` and prints a list of values returned from `data_extract_srv` module. Please see -package documentation for more information about `data_extract_ui` and `data_extract_srv`. +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} library(teal.transform) +library(teal.widgets) +library(teal.data) library(shiny) extract_ui <- function(id, data_extract) { ns <- NS(id) - teal.widgets::standard_layout( - output = teal.widgets::white_small_well(verbatimTextOutput(ns("output"))), + standard_layout( + output = white_small_well(verbatimTextOutput(ns("output"))), encoding = data_extract_ui(ns("data_extract"), label = "variable", data_extract) ) } @@ -66,29 +68,31 @@ extract_srv <- function(id, datasets, data_extract, join_keys) { #### Example data -`data_extract_srv` module depends on either a list of reactive or non-reactive `data.frame` objects. Here, we show the -usage of a list of `data.frame` objects as input to `datasets` -where a list of necessary join keys per `data.frame` object is required: +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 # nolint ADTTE <- teal.transform::rADTTE # nolint -# create a list of data.frame objects -datasets <- list(ADSL = ADSL, ADTTE = ADTTE) - +# create a list of reactive data.frame objects +datasets <- list( + ADSL = reactive(ADSL), + ADTTE = reactive(ADTTE) +) # create join_keys -join_keys <- teal.data::join_keys( - teal.data::join_key("ADSL", "ADSL", c("STUDYID", "USUBJID")), - teal.data::join_key("ADSL", "ADTTE", c("STUDYID", "USUBJID")), - teal.data::join_key("ADTTE", "ADTTE", c("STUDYID", "USUBJID", "PARAMCD")) +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")) ) ``` 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. +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. ```{r} simple_des <- data_extract_spec( @@ -100,7 +104,7 @@ simple_des <- data_extract_spec( #### Shiny app -Finally, we pass `extract_ui` to the `ui` of the `shinyApp` and we use `extract_srv` in the server function of the `shinyApp`: +Finally, we include `extract_ui` in the UI of the `shinyApp`, and utilize `extract_srv` in the server function of the `shinyApp`: ```{r eval=FALSE} shinyApp( diff --git a/vignettes/data-merge.Rmd b/vignettes/data-merge.Rmd index 0c78302d..8df6b372 100644 --- a/vignettes/data-merge.Rmd +++ b/vignettes/data-merge.Rmd @@ -17,26 +17,33 @@ knitr::opts_chunk$set( ) ``` -Combining datasets is a crucial step when using modules with more than one dataset. In the context of `teal`, we use -the term "merge" to combine datasets where two functions are offered `merge_expression_module` and `merge_expression_srv`. Depending on the specific scenario, one or the other shall be used. +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. -When no processing of the `data_extract` list is required, the `merge_expression_module` function is used to read the data and the `data_extract_spec`'s list and apply the merging. It is a wrapper that combines `data_extract_multiple_srv()` and `merge_expression_srv()` see below for more details. -With additional processing of the `data_extract` list input, `merge_expression_srv()` can be combined with `data_extract_multiple_srv()` or `data_extract_srv()` to customize the `selector_list` input. +Use `merge_expression_module` when there is no need to process the `data_extract` list. +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. -In the coming sections, we will show examples of both scenarios. +The following sections provide examples for both scenarios. ### `merge_expression_module` -With `merge_expression_module` solely, all you would need is 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 every `data.frame` object. +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 ```{r} library(teal.transform) +library(teal.widgets) +library(teal.data) library(shiny) -adsl_extract <- teal.transform::data_extract_spec( +adsl_extract <- data_extract_spec( dataname = "ADSL", select = select_spec( label = "Select variable:", @@ -47,7 +54,7 @@ adsl_extract <- teal.transform::data_extract_spec( ) ) -adtte_extract <- teal.transform::data_extract_spec( +adtte_extract <- data_extract_spec( dataname = "ADTTE", select = select_spec( choices = c("AVAL", "ASEQ"), @@ -61,18 +68,18 @@ data_extracts <- list(adsl_extract = adsl_extract, adtte_extract = adtte_extract merge_ui <- function(id, data_extracts) { ns <- NS(id) - teal.widgets::standard_layout( - output = teal.widgets::white_small_well( + standard_layout( + output = white_small_well( verbatimTextOutput(ns("expr")), dataTableOutput(ns("data")) ), encoding = div( - teal.transform::data_extract_ui( + data_extract_ui( ns("adsl_extract"), # must correspond with data_extracts list names label = "ADSL extract", data_extracts[[1]] ), - teal.transform::data_extract_ui( + data_extract_ui( ns("adtte_extract"), # must correspond with data_extracts list names label = "ADTTE extract", data_extracts[[2]] @@ -83,7 +90,7 @@ merge_ui <- function(id, data_extracts) { merge_module <- function(id, datasets, data_extracts, join_keys) { moduleServer(id, function(input, output, session) { - merged_data <- teal.transform::merge_expression_module( + merged_data <- merge_expression_module( data_extract = data_extracts, datasets = datasets, join_keys = join_keys, @@ -91,7 +98,8 @@ merge_module <- function(id, datasets, data_extracts, join_keys) { ) ANL <- reactive({ # nolint - eval(envir = list2env(datasets), expr = as.expression(merged_data()$expr)) + data_list <- lapply(datasets, function(ds) ds()) + eval(envir = list2env(data_list), expr = as.expression(merged_data()$expr)) }) output$expr <- renderText(paste(merged_data()$expr, collapse = "\n")) output$data <- renderDataTable(ANL()) @@ -102,14 +110,17 @@ merge_module <- function(id, datasets, data_extracts, join_keys) { ADSL <- teal.transform::rADSL # nolint ADTTE <- teal.transform::rADTTE # nolint -# create a list of data.frame objects -datasets <- list(ADSL = ADSL, ADTTE = ADTTE) +# create a list of reactive data.frame objects +datasets <- list( + ADSL = reactive(ADSL), + ADTTE = reactive(ADTTE) +) # create join_keys -join_keys <- teal.data::join_keys( - teal.data::join_key("ADSL", "ADSL", c("STUDYID", "USUBJID")), - teal.data::join_key("ADSL", "ADTTE", c("STUDYID", "USUBJID")), - teal.data::join_key("ADTTE", "ADTTE", c("STUDYID", "USUBJID", "PARAMCD")) +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")) ) ``` @@ -126,12 +137,15 @@ shinyApp( ### `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 take place even though `ADTTE` is not used or needed here. Here, the developer might update the `selector_list` input in a reactive manner so that it gets updated based on conditions set by the developer. Below, we reuse the input from above and we update the `app` server so that the `adtte_extract` is removed from the `selector_list` input when no `ADTTE` variable is selected and the `reactive_selector_list` is passed to `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. +Here, the developer might update the `selector_list` input reactively so that it gets updated based on conditions set by the developer. +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`: ```{r} merge_module <- function(id, datasets, data_extracts, join_keys) { moduleServer(id, function(input, output, session) { - selector_list <- teal.transform::data_extract_multiple_srv(data_extracts, datasets, join_keys) + selector_list <- data_extract_multiple_srv(data_extracts, datasets, join_keys) reactive_selector_list <- reactive({ if (is.null(selector_list()$adtte_extract) || length(selector_list()$adtte_extract()$select) == 0) { selector_list()[names(selector_list()) != "adtte_extract"] @@ -140,7 +154,7 @@ merge_module <- function(id, datasets, data_extracts, join_keys) { } }) - merged_data <- teal.transform::merge_expression_srv( + merged_data <- merge_expression_srv( selector_list = reactive_selector_list, datasets = datasets, join_keys = join_keys, @@ -148,7 +162,8 @@ merge_module <- function(id, datasets, data_extracts, join_keys) { ) ANL <- reactive({ # nolint - eval(envir = list2env(datasets), expr = as.expression(merged_data()$expr)) + data_list <- lapply(datasets, function(ds) ds()) + eval(envir = list2env(data_list), expr = as.expression(merged_data()$expr)) }) output$expr <- renderText(paste(merged_data()$expr, collapse = "\n")) output$data <- renderDataTable(ANL()) @@ -169,22 +184,25 @@ shinyApp( `merge_expression_module` is replaced here with three parts: -1) `selector_list`: 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. -2) `reactive_selector_list`: intermediate reactive list updating `selector_list` content -3) `merged_data`: output of `merge_expression_srv` using `reactive_selector_list` as input +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. +2) `reactive_selector_list`: an intermediate reactive list updating `selector_list` content. +3) `merged_data`: the output of `merge_expression_srv` using `reactive_selector_list` as input. ### Output from merging Both merge functions, `merge_expression_srv` and `merge_expression_module`, return a reactive object which contains a list of the following elements: -- `expr`: code needed to replicate merged dataset -- `columns_source`: list of columns selected per selector -- `keys`: the keys of the merged dataset -- `filter_info`: filters that are applied on the data +- `expr`: code needed to replicate merged dataset. +- `columns_source`: list of columns selected per selector. +- `keys`: the keys of the merged dataset. +- `filter_info`: filters that are applied on the data. -These elements can be further used inside the server to retrieve and use information about the selections, data, filters, ... +These elements can be further used inside the server to retrieve and use information about the selections, data, filters, etc. ## Merging of non `CDISC` datasets -General datasets do not share the same relationships as `CDISC` datasets thus these relationships must be specified by the `join_keys` functions. For more information, please refer to the `Join Keys` [vignette](https://insightsengineering.github.io/teal.data/latest-tag/articles/join-keys). -The data merge module respects the relationships given by the user and in the case of multiple datasets to merge, the order is specified by the order of elements in the `data_extract` argument of the `merge_expression_module` function. Merging groups of datasets with complex relationships can quickly become challenging to specify so please take extra care when setting this up. +General datasets do not have the same relationships as `CDISC` datasets, so these relationships must be specified using the `join_keys` functions. +For more information, please refer to the `Join Keys` [vignette](https://insightsengineering.github.io/teal.data/latest-tag/articles/join-keys). +The data merge module respects the relationships given by the user. +In the case of multiple datasets to merge, the order is specified by the order of elements in the `data_extract` argument of the `merge_expression_module` function. +Merging groups of datasets with complex relationships can quickly become challenging to specify so please take extra care when setting this up. diff --git a/vignettes/teal-transform.Rmd b/vignettes/teal-transform.Rmd index 99f87b89..b94a41ab 100644 --- a/vignettes/teal-transform.Rmd +++ b/vignettes/teal-transform.Rmd @@ -19,7 +19,8 @@ knitr::opts_chunk$set( ## Introduction -The `teal.transform` package is an integral component of the `teal` framework. It serves a dual purpose: +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.