Shiny

Interactive graphics displayed in a web browser. Very easy to create. Our task – create an interactive volcano plot for viewing the output of differential expression analysis.

Step 1: It Works!

Create a folder for our project; call it Volcano. Now add two plain-text files ui.R and server.R.

|-- Volcano
    |-- ui.R
    |-- server.R

Add the following code to ui.R. This specifies the 'user interface' to be a page with a side-bar. The page will have a header at the top, a side bar panel, and a main panel.

library(shiny)

shinyUI(pageWithSidebar(
    headerPanel("Volcano!"),
    sidebarPanel(),
    mainPanel()
))

Now add the following code to server.R. This doesn't do anything!

library(shiny)

shinyServer(function(input, output) {
})

OK, now, start R and run the following commands. Make sure the argument to runApp is the path to the folder you created.

library(shiny)
runApp("Volcano")

That's it! Not very exciting.

Step 2: greetings person how R you?

Keep your R session and web browser running.

a. Let's ask the user to identify themselves by editing ui.R

library(shiny)

shinyUI(pageWithSidebar(
    headerPanel("Volcano!"),
    sidebarPanel(
        textInput("name", "Your name:")),
    mainPanel()
))

Return to your browser, re-load the page, and check out what you can do! See an error? Probably you forgot a comma at the end of a line or made another mistake. Read the error, correct ui.R, and reload the page.

b. OK, the server.R is going to take your input and return some output. It does this using the arguments input and output. Each of these is a list, with elements named after fields (like "name", above) or return values. The following takes the name input by user, creates a greeting, and renders the result in a way that shiny can work with.

library(shiny)

shinyServer(function(input, output) {
    output$greeting <- renderText(
        paste("greetings", input$name, "how R you?"))
})

You should be able to re-load the page in the browser. It might be the case that the screen goes grey, in which case there is an error in your server code. Check back at where you started R to see what is going wrong, fix the problem, and re-load the page.

c. Notice that shiny doesn't appear to do anything when you enter a name. Actually, it's waiting to have to do something – so far, there's no feedback for the user, so no need to formulate a greeting. So…

In the ui.R file, arrange for the greeting to be displayed by updating the mainPanel:

library(shiny)

shinyUI(pageWithSidebar(
    headerPanel("Volcano!"),
    sidebarPanel(
        textInput("name", "Your name:")),
    mainPanel(
        textOutput('greeting'))
))

Reload the page in the browser and try entering or changing your name. Try editing the server code so that it only responds if the user has entered a name.

library(shiny)

shinyServer(function(input, output) {
    output$greeting <- renderText({
        if (nchar(input$name) == 0)
            character()
        else
            paste("greetings", input$name, "how R you?")
    })
})

Cool!

Volcano! data

We'll go through the same same routine, modify the user interface to accept different input, then the server to respond to the new input, and finally the user interface that will trigger the server. The instructions are shorter, because the changes are pretty self-explanatory.

a. Add a fileInput() widget to your user interface. The 'dataFile' string is the name of a variable that will contain information about how to upload the file.

library(shiny)

shinyUI(pageWithSidebar(
    headerPanel("Volcano!"),
    sidebarPanel(
        fileInput("datafile", "Choose topTable CSV File")),
    mainPanel()
))

Return to your browser, re-load the page, and check out what you can do!

b. Implement the actual data input on the server. This uses the renderDataTable function to return a table to the user interface. The "dataFile" variable is NULL if the user has not selected a file, and has several elements if the user has selected a file.

library(shiny)

shinyServer(function(input, output) {
    output$toptable <- renderDataTable({
        if (is.null(input$datafile))
            return(NULL)
        read.csv(input$datafile$datapath)
    }, options=list(pageLength=5))
})

You should be able to re-load your page in the browser, but there is not any new functionality visible.

c. Finally, modify ui.R to display the head of the table

library(shiny)

shinyUI(pageWithSidebar(
    headerPanel("Volcano!"),
    sidebarPanel(
        fileInput('datafile', 'Choose topTable CSV File')),
    mainPanel(
        dataTableOutput('toptable'))
))

Check out what you've created now.

Volcano!

Let's now create a volcano plot.

a. We don't need to change the user input, so jump directly to (b).

b. First we'll implement this to read in the data, but we'll change that… I'll make my plot using lattice, but use ggplot2 or whatever environment you'd like; remember to load any additional required libraries in the server.

library(shiny)
library(lattice)

shinyServer(function(input, output) {

    output$toptable <- renderDataTable({
        if (is.null(input$datafile))
            return(NULL)
        read.csv(input$datafile$datapath)
    }, options=list(pageLength=5))

    output$volcano <- renderPlot({
        if (is.null(input$datafile))
            return(NULL)
        csv <- read.csv(input$datafile$datapath)
        print(xyplot(-log10(pvalue) ~ log2FoldChange, csv))
    })
})

c. And for displaying the plot (first, above the table)

library(shiny)

shinyUI(pageWithSidebar(
    headerPanel("Volcano!"),
    sidebarPanel(
        fileInput('datafile', 'Choose topTable CSV File')),
    mainPanel(
        dataTableOutput('toptable'),
        plotOutput('volcano', width="400px"))
))

Check out your handiwork! (How do I know what to do? I'm reading the shiny tutorial and looking at the help pages, e.g., ?plotOutput).

Exercises

  1. Clean up the server code so that only one function reads in the data. Use the reactive function to indicate that the data input depends on user input. Adjust the top table and volcano plot functions to use this single data input function.

  2. Update the server code to order the top table from largest to smallest absolute value log fold change, and to return only the top 20 values for visualization.

    After exercises 1 and 2 my server.R looks like

    library(shiny)
    library(lattice)
    
    shinyServer(function(input, output) {
    
        dataInput <- reactive({
            if (is.null(input$datafile))
                return(NULL)
            read.csv(input$datafile$datapath)
        })
    
        output$toptable <- renderDataTable({
            csv <- dataInput()
            if (is.null(csv))
                return(NULL)
            o <- order(abs(csv$log2FoldChange),
                decreasing=TRUE)
            csv[head(o, 20),]
        }, options=list(pageLength=5))
    
        output$volcano <- renderPlot({
            csv <- dataInput()
            if (is.null(csv))
                return(NULL)
            print(xyplot(-log10(pvalue) ~ log2FoldChange, csv))
        })
    })
    
  3. Add some input widgets to ui.R, e.g., to allow the user to specify different arguments to read.csv, and implement these in server.R.

  4. Check out the ReportingTools and AnalysisPageServer packages for pre-built interactive reports.