Shiny Tutorial

Mark Dunning

Last modified: 13 Sep 2017

An Introduction to Shiny

R packages

You will need the following R packages to run this tutorial

install.packages("shiny")
install.packages("tidyverse")
install.packages("plotly")
source("http://www.bioconductor.org/biocLite.R")
biocLite("breastCancerNKI")

References

What is Shiny

  • A way to run R code interactively
  • A great way of letting others access your data without requiring Bioinformatics skills
  • A way of building web portals without requiring javascript or other technologies
  • But not a substitute for an interface such as RStudio

First look at a Shiny app

Create a new app by the following menu options in RStudio. When prompted for a name of the app, call it test or something.

File -> New File -> Shiny web app

A file called app.R should appear in RStudio, and RStudio will have created a new directory with the app name that you specified (e.g. test). The app.R script defines the UI (user interface), code to be run by the server and ends with an instruction to run the app. Note that you might see some older Shiny apps that were defined in two separate files (UI.R and server.R). If you view this file in RStudio, the option to run the app will appear in the top toolbar.

Hitting the Run App Button will create a new RStudio window this example app. Congratulations, you have just created your first shiny app 🎉

You could also view in a web browser. Please note that when presented “live” the Shiny app will appear below. On the static HTML pages linked from the course website, there will be grey box below.

In this example app we are creating a histogram of a pre-defined dataset (faithful), with the user being able to specify how many bins to use in the histogram. Let’s first look at the part of the script that defines the interface.

ui <- fluidPage(
   
   # Application title
   titlePanel("Old Faithful Geyser Data"),
   
   # Sidebar with a slider input for number of bins 
   sidebarLayout(
      sidebarPanel(
         sliderInput("bins",
                     "Number of bins:",
                     min = 1,
                     max = 50,
                     value = 30)
      ),
      
      # Show a plot of the generated distribution
      mainPanel(
         plotOutput("distPlot")
      )
   )
)

HTML and other formatting

HTML elements can be added by a number of functions that parallel common HTML tags. e.g. h1, h2 for headers and a to create a hyperlink. The helpText function can also be used to add explanatory text below a particular input option. In the following code we add headers, some help text and a hyperlink to a reference about histograms. Images can also be added using the img function.

You can try copy-and-pasting this code over the default sidebarPanel function call in the app.R file you have created.

      ######
      sidebarPanel(

        h1("Data  Exploration Options"),
        h2("The histogram"),
         sliderInput("bins",
                     "Number of bins:",
                     min = 1,
                     max = 50,
                     value = 30),
        helpText("This slider defines the number of bins to divide the data into"),
        a("More about the histogram here", href="https://en.wikipedia.org/wiki/Histogram"),
        br(),
        p("App developed by University of Cambridge"),
        img(src="http://www.cruk.cam.ac.uk/sites/all/themes/cambridge_theme/images/interface/main-logo-small.png")
      ),
      ######

Inputs

This histogram example uses the sliderInput function which allows the user to select a numeric value for the number of bins to allocate the data to between some lower and upper bound. We also have control over the amount by which the values change when the user slides the bar with step, and the text label displayed above the bar. (See ?sliderInput for details).

Other types of input are available for us to choose from and follow a similar syntax. i.e. each type of input has a value and label argument

  • selectInput
    • select a value from a drop-down box
  • checkboxInput
    • set a value to true or false
  • radioButons
    • select a value from a list
  • textInput
    • type some text
  • dateInput
    • select a date
  • fileInput
    • special input to upload a file

To add an extra input option, we need to add it as an argument to the sidebarPanel function. We could add a option to colour the histogram using the radioButton function. The order of the arguments to sidebarPanel dictates the order in which the various elements are rendered in the HTML.

Try copying-and-pasting this code into your app.R file

      ######
      sidebarPanel(

        radioButtons("colour","Colour of histogram",choices=c("red","green","blue"),selected="red"),
         sliderInput("bins",
                     "Number of bins:",
                     min = 1,
                     max = 50,
                     value = 30)
      ),
      ######

You should see a set of radio buttons appear in the side panel. However, selecting a different option has no effect on the histogram. To do this we need to modify the code in the server section of our app.R script. This part of the script defines how each of the outputs that appear in the main panel are generated.

In this example, we just have a histogram of the faithful dataset. In the UI part of the script we have defined a plot called distPlot.

      mainPanel(
         plotOutput("distPlot")
      )

The server script must specify how distPlot is created and assign it to the list of outputs.

  • distPlot is defined to be the result of renderPlot
  • to access the value that the user has selected from the slider we use input$bins
  • the number of bins in the histogram is re-calculated whenever a different value is selected in the slider
  • the histogram is re-draw using the current value of bins

Note that we use “base” graphics to plot the histogram, but shiny is equally happy to use ggplot2, lattice, or whatever your favourite way of plotting is. The plot itself is static, but you can incorporate interactive graphs as we will see later.

server <- function(input, output) {
   
   output$distPlot <- renderPlot({
      # generate bins based on input$bins from ui.R
      x    <- faithful[, 2] 
      bins <- seq(min(x), max(x), length.out = input$bins + 1)
      
      # draw the histogram with the specified number of bins
      hist(x, breaks = bins, col = 'darkgray', border = 'white')
   })
}

To modify the colour according to what the user has selected, we change the col argument in the hist function from the original darkgray to input$colour.

# Try replacing the server function in your app.R and see what happens
server <- function(input, output) {
   
   output$distPlot <- renderPlot({
      # generate bins based on input$bins from ui.R
      x    <- faithful[, 2] 
      bins <- seq(min(x), max(x), length.out = input$bins + 1)
      
      # draw the histogram with the specified number of bins
      #Now change the colour of the histogram too
      hist(x, breaks = bins, col = input$colour, border = 'white')
   })
}

In this example, the value of input$colour is passed directly to the plotting function. This works because red, green, blue are valid colours. We could change the values displayed in the drop-down to be more human-readable, but need to make sure they get transformed appropriately. Changing the configuration of the radioButtons to the following wouldn’t work.

        radioButtons("colour","Histogram Colour",choices=c("I want Red","I want Green","I want Blue"),selected="I want Red"),

However, we can access the value of input$colour and create a new variable accordingly. For example we could have some kind of control structure

server <- function(input, output) {
   
   output$distPlot <- renderPlot({
      # generate bins based on input$bins from ui.R
      x    <- faithful[, 2] 
      bins <- seq(min(x), max(x), length.out = input$bins + 1)
      
        if(input$colour == "I want Red"){
          mycol <- "red"
        } else if (input$colour == "I want Green"){
          mycol <- "green"
  
        } else mycol <- "blue"
 
      # draw the histogram with the specified number of bins

      
      hist(x, breaks = bins, col = mycol, border = 'white')
   })
   
}

Exercises

  • Allow the user to define the title on the plot by adding a textInput
    • HINT: the main argument to the hist function lets you set the plot title.
  • The choice of red, green or blue is somewhat limiting. Create a dropdown box (selectInput) to allow the user to choose from any of the pre-defined colours in R. (HINT: the function colours returns the names of all valid colours as a vector)
  • OPTIONAL:- use numericInput boxes to allow the user to define a colour using its red, green and blue values.
    • the rgb function will convert a set of numeric values into a hexademical string that can be used as an argument for plotting.
    • the RGB value for red is 255,0,0

Outputs

We can add new outputs by adding new arguments to mainPanel. Let’s say we want to show a boxplot of the data in addition to the histogram. First we add a call to plotOutput in the mainPanel code with our choice of object name. Let’s call it boxPlot

      # Show a plot of the generated distribution
      mainPanel(
         plotOutput("distPlot"),
         plotOutput("boxPlot")
      )
   )
)

We now need to modify the server code to create an object ouput$boxPlot using the renderPlot function. Inside the body of renderPlot we write the code to create the boxplot.

However, the following server code does not create a working app. Can you work out why? 🤔

server <- function(input, output) {
   
   output$distPlot <- renderPlot({
      # generate bins based on input$bins from ui.R
      x    <- faithful[, 2] 
      bins <- seq(min(x), max(x), length.out = input$bins + 1)
      
      # draw the histogram with the specified number of bins

      
      hist(x, breaks = bins, col = input$colour, border = 'white',main=input$title)
   })
   
   output$boxPlot <- renderPlot({

       boxplot(x,horizontal=TRUE,col=input$colour)

   })
}

Aside from a plot, other types of output include

  • renderText
  • renderPrint
  • renderDataTable
  • renderTable

We will add a numerical summary of our dataset and an interactive table of the raw data values. This elements can be added to the mainPanel using verbatimTextOutput type

      # Show a plot of the generated distribution
      mainPanel(
         plotOutput("distPlot"),
         verbatimTextOutput("summary"),
         dataTableOutput("data")
      )

In the server function we include the code to compute the relevant outputs. As part of the server function definition we can also include code to load required packages and datasets. In order to overcome the error observed above we can define the variable x that is used in plotting at the start of the server code. This line of code will only get run once each when the server is started.

x <- faithful[,2]
server <- function(input, output) {
     x    <- faithful[, 2] 
         
   output$distPlot <- renderPlot({
      # generate bins based on input$bins from ui.R
      bins <- seq(min(x), max(x), length.out = input$bins + 1)
      
      # draw the histogram with the specified number of bins

      
      hist(x, breaks = bins, col = input$colour, border = 'white')
   })
   
   output$boxPlot <- renderPlot({

       boxplot(x,col=input$colour)

   })
   
   output$summary <- renderPrint({
     summary(x)
   })


}

The results panel is now getting a bit cluttered. If we wish we could change the layout so each distinct output is displayed in a different panel. This can be done with the tabsetPanel function. We can also add the option to view the data in an interactive table with dataTableOutput.

      mainPanel(
        tabsetPanel(
          tabPanel("Data", dataTableOutput("data")),
          tabPanel("Summary", verbatimTextOutput("summary")),
          tabPanel("Histogram",plotOutput("distPlot")),
          tabPanel("Boxplot", plotOutput("boxPlot"))
          
        )

      )

The final code for the app is given below:-

library(shiny)

# Define UI for application that draws a histogram
ui <- fluidPage(
   
   # Application title
   titlePanel("Old Faithful Geyser Data"),
   
   # Sidebar with a slider input for number of bins 
   sidebarLayout(
      sidebarPanel(

        radioButtons("colour","Colour of histogram",choices=c("red","green","blue"),selected="red"),
         sliderInput("bins",
                     "Number of bins:",
                     min = 1,
                     max = 50,
                     value = 30)
      ),
      
      # Show a plot of the generated distribution
      mainPanel(
        tabsetPanel(
          tabPanel("Data", dataTableOutput("data")),
          tabPanel("Histogram",plotOutput("distPlot")),
          tabPanel("Summary", verbatimTextOutput("summary")),
          tabPanel("Boxplot", plotOutput("boxPlot"))
          
        )

      )
   )
)

# Define server logic required to draw a histogram
server <- function(input, output) {
    x    <- faithful[, 2] 
   output$distPlot <- renderPlot({
      # generate bins based on input$bins from ui.R

      bins <- seq(min(x), max(x), length.out = input$bins + 1)
      
      # draw the histogram with the specified number of bins

      
      hist(x, breaks = bins, col = input$colour, border = 'white',main=input$title)
   })
   
   output$summary <- renderPrint({
     summary(x)
   })
   
   output$boxPlot <- renderPlot({
      boxplot(x,horizontal = TRUE,col=input$colour)
     
   })
   

   output$data <- renderDataTable(faithful)
}

# Run the application 
shinyApp(ui = ui, server = server)