Data Transformations as shiny Module

Introduction

teal version 0.16 introduced new argument in teal::module called transformers. This argument allows to pass a list of teal_data_module objects that are created using teal_transform_module() function.

The main benefit of teal_transform_module() is the ability to transform data before passing it to the module. This feature allows to extend the regular behavior of existing modules by specifying custom data operations on data inside this module.

teal_transform_module() is a Shiny module that takes ui and server arguments. When provided, teal will execute data transformations for the specified module when it is loaded and whenever the data changes. server extend the logic behind data manipulations, where ui extends filter panel with new UI elements that orchestrate the transformer inputs.

This vignette presents the way on how to manage custom data transformations in teal apps.

Creating your first custom data transformation module

We initialize a simple teal app where we pass iris and mtcars as the input datasets.

library(teal)
data <- within(teal_data(), {
  iris <- iris
  mtcars <- mtcars
})

app <- init(
  data = data,
  modules = teal::example_module()
)

if (interactive()) {
  shinyApp(app$ui, app$server)
}

Single Transformer

Let’s create a simple teal_transform_module that returns the first n number of rows of iris based on the user input.

We do this by creating the ui with the numericInput for the user to input the number of rows to be displayed. In the server function we take in the reactive data and perform this transformation and return the new reactive data.

data <- within(teal_data(), {
  iris <- iris
  mtcars <- mtcars
})
datanames(data) <- c("iris", "mtcars")

my_transformers <- list(
  teal_transform_module(
    label = "Custom transform for iris",
    ui = function(id) {
      ns <- NS(id)
      tags$div(
        numericInput(ns("n_rows"), "Number of rows to subset", value = 6, min = 1, max = 150, step = 1)
      )
    },
    server = function(id, data) {
      moduleServer(id, function(input, output, session) {
        reactive({
          within(data(),
            {
              iris <- head(iris, num_rows)
            },
            num_rows = input$n_rows
          )
        })
      })
    }
  )
)

app <- init(
  data = data,
  modules = teal::example_module(transformers = my_transformers)
)

if (interactive()) {
  shinyApp(app$ui, app$server)
}

Note: It is recommended to return reactive() with teal_data() in server code of a teal_transform_module as this is more robust for maintaining the reactivity of Shiny. If you are planning on using eventReactive() in the server, the event should include data() (example eventReactive(list(input$a, data()), {...})). More in this discussion.

Multiple Transformers

Note that we can add multiple teal transformers by including teal_transform_module in a list.

Let’s add another transformation to the mtcars dataset that creates a column with rownames of mtcars. Note that this module does not have interactive UI elements.

data <- within(teal_data(), {
  iris <- iris
  mtcars <- mtcars
})
datanames(data) <- c("iris", "mtcars")

my_transformers <- list(
  teal_transform_module(
    label = "Custom transform for iris",
    ui = function(id) {
      ns <- NS(id)
      tags$div(
        numericInput(ns("n_rows"), "Number of rows to subset", value = 6, min = 1, max = 150, step = 1)
      )
    },
    server = function(id, data) {
      moduleServer(id, function(input, output, session) {
        reactive({
          within(data(),
            {
              iris <- head(iris, num_rows)
            },
            num_rows = input$n_rows
          )
        })
      })
    }
  ),
  teal_transform_module(
    label = "Custom transform for mtcars",
    ui = function(id) {
      ns <- NS(id)
      tags$div(
        "Adding rownames column to mtcars"
      )
    },
    server = function(id, data) {
      moduleServer(id, function(input, output, session) {
        reactive({
          within(data(), {
            mtcars$rownames <- rownames(mtcars)
            rownames(mtcars) <- NULL
          })
        })
      })
    }
  )
)

app <- init(
  data = data,
  modules = teal::example_module(transformers = my_transformers)
)

if (interactive()) {
  shinyApp(app$ui, app$server)
}