safetyGraphics Shiny App - Custom Workflows

Jeremy Wildfire

2020-01-15

Overview

The safetyGraphics package is designed to serve as a flexible and extensible platform for analyzing clinical trial safety. This vignette shows how users can configure the safetyGraphics shiny app with workflows using customized charts and data.

Version 1.1 of the package allows users to preload their own charts and data sets for use in the safetyGraphics Shiny Application. Many types of clinical trial lab data are supported in conjunction with 4 types of charts - Static charts, Plotly charts, Shiny Modules and HTML widgets. As shown in the next section, loading data is simple, but adding custom charts is slightly more complex. Behind the scenes, all 4 chart types have two major technical components: code and metadata. As such, making a custom chart requires the user to create an R script that defines the chart’s code and metadata. We provide several examples below that describe the step-by-step process for creating a chart.

Adding Custom Data

Run safetyGraphicsApp(loadData=TRUE) to preload all data.frames from your current R session in a new instance of the safetyGraphics Shiny app. While users can still manually load additional .csv or .sas7bdat files in the app as needed, this programatic alternative allows for more flexibility for loading other data sources, and can help to automate situations where users want to load multiple files. For example, running the code below will preload several example data sets saved on our GitHub site.

#Load in data sets with different data standards
data_url <- 'https://raw.githubusercontent.com/SafetyGraphics/SafetyGraphics.github.io/master/pilot/'
SDTM<-read.csv(paste0(data_url,'SampleData_SDTM.csv'))
SDTM_Partial<-read.csv(paste0(data_url,'SampleData_PartialSDTM.csv'))
AdAM_Partial<-read.csv(paste0(data_url,'SampleData_PartialADaM.csv'))
NoStandard<-read.csv (paste0(data_url,'SampleData_NoStandard.csv'))
AdAM <- adlbc #loadData=TRUE overrides the default behavior where adlbc is automatically preloaded into the app

#Initialize the app with data from the session
safetyGraphicsApp(loadData=TRUE)

Clicking on the data tab shows the 5 pre-loaded data sets with their different data standards.

This data load process can easily be combined with the chart workflow described below (including customSettings.R programs) to create easily reusable, custom workflows.

Steps to Create a Custom Chart

There are 4 steps required to create a custom chart for use in safetyGraphics:

  1. Create custom chart code
  2. Add new settings to the app (if needed)
  3. Add the chart to the app
  4. Initialize the app

The remainder of this vignette is dedicated to 4 examples that describe the process for creating custom charts in more detail. Note that full code for all examples are available in Appendix 3 at the end of this vignette.

Example 1 - Hello World

To create a trivially simple custom chart, make a file called customSettings.R with the following code:

# Step 1 - Write custom chart code 
helloWorld <- function(data,settings){
  plot(-1:1, -1:1)
  text(runif(20, -1,1),runif(20, -1,1),"Hello World")
}

# Step 2 - Initialize Custom Settings 
# Not Applicable!

# Step 3 - Initialize the custom chart 
addChart( 
  chart=”hello_world”,
  main=”helloWorld",
  label=”Hello World”, 
)

Then initialize the app (Step 4) by running:

setwd('/path/to/the/file/above')
safetyGraphicsApp()

Once the app opens, click the charts tab to view the new custom “hello_world” chart.

Example 2 - Detailed Walkthrough

Our second example goes through the chart creation process step-by-step for a more realistic example. An understanding of the underlying infrastructure for safetyGraphics will help here, so we recommend reviewing the first introductory vignette before diving in.

Step 1 - Create custom chart code

The first, and most complicated, step is to write the code for the custom chart. It’s easiest to break this process down in to 3 smaller steps.

  1. Create customSettings.R file
  2. Make static code using sample data
  3. Covert static chart code to Function

Step 1.1 - Create customSettings.R file

First, create a file called customSettings.R and save it in a designated directory. All of the code in the following steps goes in this file. Note that when you run the app, custom medatadata files will be saved in the same directory as the customSettings.R file.

Step 1.2 - Make Static Code Using Sample Data

Next, write code that creates your chart using a sample data set (the adlbc data set included with safetyGraphics is a good option). Note that all common charting packages are supported including ggplot2, lattice and base R graphics. Our example makes a histogram showing the distribution of lab values:

library(safetyGraphics)
library(ggplot2)
ggplot(
    data = adlbc, 
    aes(x = PARAM, y = AVAL)
) +
 geom_boxplot(fill =‘blue’) +
 scale_y_log10() +
 theme_bw() +
 theme(
  axis.text.x = element_text(angle = 25, hjust = 1),    
  axis.text = element_text(size = 12), 
  axis.title = element_text(size = 12)
)

As expected, running this code creates a simple box plot.

Step 1.3 - Convert Static Chart Code to a Function

After the chart is working using the sample data, you need to update it to a function that will work with any data set that the user loads in the shiny app. Replace hard coded parameters with references to the settings defined in the safetyGraphics settings object as shown below.

This is the hardest part of creating a custom chart, so we’ve provided some additional notes about this process below. There is also a technical appendix at the end of this document that provides more details about the metadata/settings objects used in this step. Finally, feel free to ask ask us questions if you run in to problems.

  • The code to create the chart must be wrapped in a function that takes 2 parameters as inputs: data and settings as shown in line 1 above. When the chart function is called by Shiny, these parameters are generated dynamically within the app; data represents the user selection in the Data module, and settings represents the selections in the Settings Module.
  • The data parameter is saved as a data.frame, and a preview of the current data is conveniently available in the data tab.
  • The settings parameter is saved as a list and is slightly more complex. Each setting shown on the settings page has a unique ID (called a text_key in the package) that gives its position in the settings list. In our example, the “Measure column” setting has the ID measure_col and is accessed in the charting function via settings$measure_col. Additional technical documentation about the settings list is provided in Appendix 1.
  • Lines 2-7 above create a new data frame called mapped_data. This isn’t required, but it simplifies the code somewhat and helps to avoid non-standard evaluation in the chart function.
  • Note that we dynamically identify the Value and Measure columns in lines 4 and 5 by referencing the settings object. This code, as opposed to directly specifying a column name like data$PARAM, allows the chart to work with any data standard.
  • Line 9 initializes the chart using ggplot() with the newly derived mapped_data.
  • Line 13 introduces a custom setting - settings[["boxcolor"]] - so that the user can specify a color for the bars in the shiny app if desired. All custom settings must be initialized using the addSetting() function; this process is described in Step 2 below.
  • Finally, note that the process for defining custom “htmlwidget” and “shiny module” charts is slightly different than the sample code above; example 3 provides basic example for a shiny module, and htmlwidgets are discussed in Appendix 2.

Step 2 - Add custom settings to the App

Next, we’ll add any new settings to the app by calling the addSetting() function in our customSettings.R function.

First, we need to determine which settings are already defined. As noted above, our chart uses 3 settings: value_col, measure_col and box_color. In most cases you can just examine the settingsMetadata data frame, which contains all settings available in safetyGraphics, along with details about each. Either view it with view(settingsMetadata) or use code like this:

In some complex cases, it might be easier to examine an example settings object directly (rather than the data frame representation). To do this, you can create a shell settings object and then check to see if the settings exist:

As shown in the output above, our example uses 2 pre-loaded settings (measure_col and value_col) and one new setting (boxcolor). We add boxcolor to the metadata using the addSetting() function as shown below.

addSetting( 
  text_key="boxcolor", 
  label="Box Plot Color", 
  description="The color of the boxes",
  setting_type="character",
  setting_cat="appearance", 
  default="gray"
) 

You could repeat as needed for multiple settings. For full details about each parameter see ?addSetting, which matches up with the column names in ?settingsMetadata.

Step 3 - Add the chart to the App

Next, we need to add the chart itself to the app using addChart(). We’d add the example above as follows:

addChart( 
  chart="labdist", 
  main="labdist", 
  label="Lab Distribution - static", 
  requiredSettings=c("boxcolor","value_col","measure_col"),
  type="static”
 )

For full details about each parameter see ?addChart, which matches up with the column names in ?chartMetadata.

Step 4 - Initialize the app

Finally, make sure your working directory is set to the location of you customSettings.R and call safetyGraphicsApp(). The settings page for the new “Lab Distribution – Static” chart will look like this:

And here is the chart:

There are options that will let you save the custom code in other locations and/or automatically add a chart each time you run the app. See ?safetyGraphicsApp, ?addChart and ?addSettings for details.

Example 3 - Interactive Histogram

We can expand on our static boxplot from Example 2 by adding some interactivity. Placing our boxplot in a Shiny module will allow the user to make chart-specific aesthetic adjustments on the fly. In this example, we’ve added the ability to add/remove individual data points, transform the y-axis scale, and show/hide outliers:

This example expands on the static boxplot example in the following ways: - Most of the underlying code for manipulating the dataset and creating the ggplot2 figure is identical to the static boxplot example. However, we’ve added some conditional statements to modify the boxplot based on the user selections. - Instead of a single chart function, we now have two: the module UI function, and the module server function. The UI function has the same name as the server function, with _UI appended. These functions should be specified (or sourced from an external file) within customSettings.R. - data and settings are passed to the module server function as reactive objects.

The full code for the custom script is saved in Appendix 3.

Example 4 - Custom htmlwidget (Coming soon!)

Custom htmlwidgets are not currently supported via addChart() and addSetting()`, but we plan to add support in future release.

For now, please contact the package development team or file an issue on GitHub if you would like to add a custom htmlwidget to safetyGraphics, and we can discuss technical details.

Appendix 1 - Technical Details

This appendix provides a detailed technical description of the settings framework used in the application. As described in the introductory vignette, the settings Shiny module allows users to make a wide range of customizations to the charts for any given study. Understanding the underlying technical details of this settings customization process is perhaps the most complicated aspect of designing custom charts for safetyGraphics. This appendix is broken in to 2 sections, the first describes the structure of the settings themselves, the second describes the metadata used to generate the settings when the package is built.

Settings Structure

Behind the scenes, the settings are stored as a list, which is updated in real time as the user makes changes in the Shiny settings module. A small chunk of a typical settings list is shown below. Note that this was generated using generateSettings(standard="adam"). More details on this and other functions related to settings is provided below.