Setup

install.packages("shiny")
install.packages("DT")

source("https://bioconductor.org/biocLite.R")
biocLite(c(
    "BSgenome.Scerevisiae.UCSC.sacCer3",
    "TxDb.Scerevisiae.UCSC.sacCer3.sgdGene",
    "GenomicRanges",
    "rtracklayer",
    "Gviz"))

Please make sure you are running R 3.3 and Bioconductor 3.3 or higher. Bioconductor 3.3 in particular is vastly more stable than 3.2.

# To upgrade an old Bioconductor installation
source("https://bioconductor.org/biocLite.R")
biocLite("BiocUpgrade")

Hello, world

library(shiny)

ui_hello <- fluidPage(titlePanel("Hello world"))

server_hello <- function(input, output, session) { }

app_hello <- shinyApp(ui_hello, server_hello)
app_hello
Shiny applications not supported in static R Markdown documents

Let’s explore what we’ve just created to understand better what is going on.

class(ui_hello)
## [1] "shiny.tag.list" "list"
as.character(ui_hello)
## [1] "<div class=\"container-fluid\">\n  <h2>Hello world</h2>\n</div>"

fluidPage made an object that generates HTML. Shiny has similar functions for the various inputs and outputs it offers, and for various ways to lay out a page. Shiny also has functions for most HTML tags, such as p() and h1(). Attributes such as style can be specified as keyword arguments to these functions. Literal HTML can be given using the HTML() function.

class(app_hello)

# Various ways of running an app
runApp(app_hello)
runGadget(app_hello)
runGadget(app_hello, viewer=dialogViewer("App 1"))
print(app_hello)
app_hello

Our application app can be run using runApp or runGadget. Oddly, but following the lead of ggplot2, it can also be run by printing it out.

input and output

The UI component of an app will normally define various inputs and outputs. The job of the server is then to take those inputs and render outputs.

The server runs in R. It opens a port and waits for a web browser client to contact it. Its first job will be to convert the UI to HTML and sent it to the web browser, along with some Javascript that reads the inputs and sends them back to the server as they change, and displays outputs that the server sends back to the client. It then calls your server function, which defines how to compute outputs from inputs. As the user updates inputs, the web browser sends these changes back to the server, and the server recomputes outputs as necessary.

In the example below, there are three inputs, modulo, step, and title, accessed using the input argument, and one output, plotout. A way to “render” plotout is stored into the output argument. (Unusually for R, output can be mutated.)

ui_modulo <- fluidPage(
    titlePanel("Counting modulo app"),
    sliderInput("modulo", "Modulo", 1, 20, 10),
    sliderInput("step", "Step", 1, 20, 3),
    textInput("title", "Plot title", "A plot"),
    plotOutput("plotout"))

server_modulo <- function(input, output, session) {
    output$plotout <- renderPlot({
        plot((seq_len(100)*input$step) %% input$modulo, main=input$title)
    })
}

shinyApp(ui_modulo, server_modulo, options=list(height=800))
Shiny applications not supported in static R Markdown documents

Shiny offers various different ways to lay out an app. The sidebar layout is popular. You also have complete freedom to use HTML and CSS as well. Try specifying style= in one of the layout functions or add your own div(), etc.

# An alternative UI layout
ui_modulo_2 <- fluidPage(
    titlePanel("Counting modulo app, sidebar layout"),
    sidebarLayout(
        sidebarPanel(
            sliderInput("modulo", "Modulo", 1, 20, 10),
            sliderInput("step", "Step", 1, 20, 3),
            textInput("title", "Plot title", "A plot")),
        mainPanel(
            plotOutput("plotout"))))

Shiny apps and Shiny Server

A Shiny app consists of a directory containing a file called “app.R”. The last statement in “app.R” should be a call to shinyApp( ).

(An older style of writing apps separates this into two files, “ui.R” and “server.R”.)

Within R, an app directory can be run with runApp("directoryname").

Shiny app directories can also be served with a webserver daemon called Shiny Server, which currently only runs on Linux. This is similar to webservers such as Apache or Nginx, but also serves Shiny apps and Rmarkdown documents with Shiny components.

https://www.rstudio.com/products/shiny/download-server/

App directory upload challenge

  1. Create a directory for an app. Name the directory in a way that will be unique, such as with your own name or a secret identity. We are going to upload it to a shared directory on a server.

  2. Save the code from one of the apps above to “app.R” in your directory. Modify the code to look or do something different if you like, such as plotting your favourite function.

  3. Run your app with:

runApp("yourdirectoryname")
  1. Upload your app to our server with scp. Your instructor will give you details for how to do this.

Reactive expressions save recomputation

We’ve seen pieces of code wrapped in functions such as renderPlot( ). This is called a “reactive context”, and is a common pattern in Shiny. Reactive contexts contain code that might need to be re-run if their inputs change.

Reactive expressions are another kind of reactive context. They provide a way to save computation. Within the server function, a reactive expression is created by wrapping an expression with the function reactive( ). To actually obtain the value of the expression, from another reactive context, it is called with no arguments. The code in the reactive expression only needs to be re-run if its inputs have changed, otherwise its cached value can be used. Reactive expressions are lazy, they are only computed when they are needed by another reactive context.

A reactive expression can serve as an input to other reactive contexts. If the user enters new values, a branching chain of cached values might be invalidated. Shiny eagerly observes outputs, so if this wave of invalidation reaches an output, Shiny recomputes the output and whatever it depends on.

This will make more sense with some examples.

# Example of a reactive expression
y <- reactive(input$x + 1)

#or
y <- reactive({
    input$x + 1
})

#or
y <- reactive({
    return(input$x + 1)
})


#Later, in another reactive context where the value of the expression is needed
...
    y()
...

Let’s look at a complete example.

ui_pythagorus <- fluidPage(
    titlePanel("Hypotenuse app"),
    sliderInput("a", "Length a", 0, 10, 5),
    sliderInput("b", "Length b", 0, 10, 5),
    textOutput("result"))
    
server_pythagorus <- function(input, output, server) {
    a2 <- reactive({
        cat("Compute a squared.\n")
        
        input$a ** 2
    })
    
    b2 <- reactive({
        cat("Compute b squared.\n")
        
        input$b ** 2
    })
    
    output$result <- renderText({
        cat("Compute hypotenuse.\n")
        h <- sqrt(a2() + b2())
        cat("Done computing hypotenuse.\n")
        
        paste0("The hypotenuse is ", h)
    })
}

shinyApp(ui_pythagorus, server_pythagorus)
Shiny applications not supported in static R Markdown documents

Run this code in R, try adjusting one or other of the inputs and observe what is printed to the console.

cat has a side effect, printing to the console, which lets us peek at what is going on under the hood here. Except when debugging you should avoid side effects within reactive expressions. Examples of side effects are printing, plotting, or altering global variables with <<-. Side effects break the reactive abstraction. For example a renderPlot( ) should not rely on a reactive( ) to perform the actual plotting.

( Errors, eg calling stop( ), are not considered side effects and are fine in reactive expressions. Errors are cached just like values, and will be correctly thrown to the reactive context attempting to get the value of the reactive expression each time. )

Tea challenge

Muriel and Ronald are having an argument about tea. Muriel claims to be able to tell if tea or milk is poured into a cup first. An experiment is devised to test Muriel’s claim. Eight cups of tea are made, four with tea first and four with milk first, and Muriel’s accuracy is tested. She classifies all eight cups correctly. The results seem to confirm Muriel’s claim, but Ronald wants to know how likely a result like this would have been if Muriel’s supposed ability was simply luck.

permutations <- function(items)
    do.call(cbind, lapply(seq_along(items), 
        function(i) rbind(items[i], permutations(items[-i]))))

tea <- 3
milk <- 3
tea_correct <- 2
milk_correct <- 2

x <- c(rep(0,tea), rep(1,milk))
y <- c(rep(0,tea_correct), rep(1,tea-tea_correct), 
       rep(0,milk-milk_correct), rep(1,milk_correct))
statistic <- sum(x == y)
x_perms <- permutations(x) # <- this is slow
distribution <- colSums(x_perms == y)
p <- mean(distribution >= statistic)

p
## [1] 0.5

We’d like to explore how this test works with different inputs, but avoid unnecessary computation, especially calls to permutations. Even with eight cups there were quite a lot of permutations to consider. (It’s possible to write much more efficient code than the above. In R you would normally use fisher.test(x,y,alternative="greater"). However, for the sake of a slightly silly exercise we will use the above.)

Ronald’s test as a Shiny app

ui_tea <- fluidPage(
    titlePanel("Ronald's exact test"),
    numericInput("tea", "Tea first", 3),
    numericInput("milk", "Milk first", 3),
    numericInput("tea_correct", "Tea first correctly called", 2),
    numericInput("milk_correct", "Milk first correctly called", 2),
    textOutput("p_text"))

permutations <- function(items)
    do.call(cbind, lapply(seq_along(items), 
        function(i) rbind(items[i], permutations(items[-i]))))

server_tea <- function(input, output, server) {
    output$p_text <- renderText( withProgress(message="Computing p", {
        x <- c(rep(0,input$tea), rep(1,input$milk))
        y <- c(rep(0,input$tea_correct), rep(1,input$tea-input$tea_correct), 
               rep(0,input$milk-input$milk_correct), rep(1,input$milk_correct))
        statistic <- sum(x == y)
        x_perms <- permutations(x) # <- this is slow
        distribution <- colSums(x_perms == y)
        p <- mean(distribution >= statistic)

        paste0("p-value is ",p)
    }))
}

shinyApp(ui_tea, server_tea)
Shiny applications not supported in static R Markdown documents

Challenge

  1. Use what you have just learned to make this app more responsive. The slow part is the call to the permutations function. We would like to avoid re-running this unnecessarily.

  2. In the story, there were four cups of tea first and four cups of milk first, and Muriel was correct all eight times. Can Ronald reasonably reject the idea that Muriel’s ability is due to chance?

tabsetPanel: what you can’t see doesn’t need to be computed

It is possible to divide your up app up into a set of tabs. One very useful thing about this is that outputs on a tab that aren’t currently visible aren’t immediately recomputed.

ui_tea_tabset <- fluidPage(
    titlePanel("Ronald's exact test"),
    tabsetPanel(
        tabPanel("Input",
            br(),
            numericInput("tea", "Tea first", 3),
            numericInput("milk", "Milk first", 3),
            numericInput("tea_correct", "Tea first correctly called", 2),
            numericInput("milk_correct", "Milk first correctly called", 2)),
        tabPanel("Result",
            br(),
            textOutput("p_text"))))

shinyApp(ui_tea_tabset, server_tea, options=list(height=500))
Shiny applications not supported in static R Markdown documents

Shiny offers three layouts for this with similar functionality: tabsetPanel, navlistPanel, and navbarPage.

There is an older style of user interface in which computations are delayed until a button is pressed. It is possible to do this with Shiny, using actionButton, and observeEvent to set some reactiveValues as a sort of secondary input. Also relevant, the isolate function lets you look at a reactive value without creating a dependency on it. However when you must fight against a library this hard it is a hint not to solve the problem this way.

Genome browser

Genome browser challenge, part 1

The following code produces a diagram for a region of a genome. Your collaborator is asking you to make diagrams for a whole lot of different locations in the genome. Create a Shiny app to create these diagrams for them.

library(GenomicRanges)
library(Gviz)
library(rtracklayer)
library(BSgenome.Scerevisiae.UCSC.sacCer3)
library(TxDb.Scerevisiae.UCSC.sacCer3.sgdGene)

genome <- BSgenome.Scerevisiae.UCSC.sacCer3
txdb <- TxDb.Scerevisiae.UCSC.sacCer3.sgdGene

# We want to be able to interactively specify this location:
location <- GRanges("chrI:140000-180000", seqinfo=seqinfo(genome))

axis_track <- GenomeAxisTrack()
seq_track <- SequenceTrack(genome)
gene_track <- GeneRegionTrack(
    txdb, genome=genome, name="Genes", showId=TRUE, shape="arrow")

# Load data, at a reasonable level of detail for width(location)
n <- min(width(location), 1000)
d1 <- rtracklayer::summary(
    BigWigFile("forward.bw"), location, n, "max")[[1]]
d2 <- rtracklayer::summary(
    BigWigFile("reverse.bw"), location, n, "max")[[1]]
data_track <- DataTrack(
    d1, data=rbind(d1$score,-d2$score), groups=c(1,2), 
    name="PAT-seq", type="l", col="#000000", legend=FALSE)

plotTracks(
    list(axis_track, seq_track, gene_track, data_track),
    chromosome=as.character(seqnames(location)), 
    from=start(location), to=end(location))

Updating inputs

We’ll now look at something a little less reactive and more event-driven.

ui_updater <- fluidPage(
    titlePanel("Updating inputs demonstration"),
    textInput("text", "A text input", "I keep saying"),
    actionButton("button", "A button"))

server_updater <- function(input,output,session) {
    observeEvent(input$button, {
        updateTextInput(session, "text", value=paste(input$text, "without a shirt"))
    })
}

shinyApp(ui_updater, server_updater)
Shiny applications not supported in static R Markdown documents

What’s going on here?

Genome browser challenge, part 2

  1. Add buttons to your genome browser to navigate left and right, using the shift function for GRanges.

  2. (Optional, more difficult) Add buttons to zoom in and out.

Modules

Shiny modules are reusable parts of a Shiny app. The main issue here is avoiding collisions in the input and output namespaces.

A Shiny module consists of a function create the UI and a function to be called within the server function using callModule.

The UI should prefix all the input and output IDs using NS( ). The callModule function magically handles prefixing for the server component.

ns <- NS("somenamespace")
ns("foo")
## [1] "somenamespace-foo"
ns("bar")
## [1] "somenamespace-bar"

Here is an example of everything in action:

# Define a trivial module

# In the UI, we use NS( ) to prefix all of the ids
mymodule_ui <- function(id) {
    ns <- NS(id)
    
    div(
        numericInput(ns("number"), "Number", 5),
        textOutput(ns("result")))
}

# callModule( ) will magically prefix ids for us in the server function
mymodule_server <- function(input, output, session, multiply_by) {
    output$result <- renderText({
        paste0(input$number, " times ", multiply_by, " is ", input$number * multiply_by) 
    })
}


# Use the module in an app

ui_mult <- fluidPage(
    titlePanel("Demonstration of modules"),
    h2("Instance 1"),
    mymodule_ui("mod1"),
    h2("Instance 2"),
    mymodule_ui("mod2"))

server_mult <- function(input, output, session) {
    callModule(mymodule_server, "mod1", multiply_by=3)
    callModule(mymodule_server, "mod2", multiply_by=7)
    
    #Debugging
    observe( print(reactiveValuesToList(input)) )
}

shinyApp(ui_mult, server_mult)
Shiny applications not supported in static R Markdown documents

Best practice?

Without care, Shiny might create a bit of a nightmare:

  • Analyses that can be only performed via a UI.
  • Apps that can’t be used as a part of larger apps.
  • Complex parameter settings that aren’t recorded in either a URL or using R code.

( Shiny supports bookmarking, but it isn’t enabled by default and requires UI code to be wrapped in function(request) .... )

Ideally the language and library would nudge us away from such a scenario. Shiny seems to be shoving us toward it, by making good practices hard work. To write re-usable code you may need to provide:

  • The two functions needed to define a module.
  • A function to create a shinyApp( ), for interactive use.
  • R functions that do the actual computation of your app.

Also worth considering:

  • A Shiny app can return a value with stopApp( ). If the app lets you select points or brush regions, it could return what these were, to make it easy to switch back to working with R on the console.
  • A Shiny module’s server function can return a value, which could be a list containing reactive expressions so that it can act as a data source as well as a sink.

Care also needs to be taken when Shiny apps form a part of published results:

  • When delivering a Shiny app, doing so somewhere that R won’t accidentally be upgraded under it (possibly breaking it or changing the results it reports).

Genome browser challenge, part 3

Adapt your genome browser to be a Shiny module. It should be usable with the following code:

# Load your module code
source("browser.R")

ui_usebrowser <- fluidPage( 
    titlePanel("Using a genome browser module"),
    browser_ui("browser"))

server_usebrowser <- function(input,output,session) {
    callModule(browser_server, "browser")
}

shinyApp(ui_usebrowser, server_usebrowser)

DataTables

DataTables is a very flexible Javascript library for displaying tables, which can be used from Shiny.

DataTables has built-in pagination and search. Only the current page or search results need to be sent to the client, so a DataTable can display a very large data frame without having to send the whole thing over the network.

DataTables allows rows to be selected, so it can serve as way to select from a large number of items.

Shiny has a version of DataTables built in, but the version offered by the DT library is better. Annoyingly they use the same function names, so we need to use :: to specify the one we want.

library(TxDb.Scerevisiae.UCSC.sacCer3.sgdGene)
txdb <- TxDb.Scerevisiae.UCSC.sacCer3.sgdGene

gene_df <- data.frame(
    gene=names(genes(txdb)), 
    location=as.character(genes(txdb)), 
    row.names=NULL, stringsAsFactors=FALSE)

ui_table <- fluidPage(
    titlePanel("Gene table"),
    DT::dataTableOutput("genes"))

server_table <- function(input,output,session) {
    output$genes <- DT::renderDataTable(
            server = TRUE,
            selection = "single",
            options = list(pageLength=10), {
        gene_df
    })
    
    observeEvent(input$genes_rows_selected, {
        cat("Selected:\n")
        print(input$genes_rows_selected)
        print(gene_df[ input$genes_rows_selected, ])
        # Wouldn't it be nice if something happened in response to this?
    })
    
    # Debugging:
    # observe(print(reactiveValuesToList(input)))
}

shinyApp(ui_table, server_table, options=list(height=600))
Shiny applications not supported in static R Markdown documents

It’s interesting that a DataTable can act as both an output and an input. Similarly a plotOutput can accept input such as clicking and dragging a rectangle (“brushing”). UI elements like this allow a “brushing and linking” style of interface with multiple views of a data set, and the user directly interacting with the displayed data. This may seems like a circular concept, but in our server function we may regard the inputs and outputs of a UI element as separate things, and we still only need to calculate output based on input, so there is no problem.

Genome browser challenge, part 4

Add your genome browser module to the above DataTables app. When a row is selected, the genome browser should go to the appropriate location.

Shiny in Rmarkdown

Shiny can also be used in Rmarkdown. This document is an example.

The YAML header should look something like:

---
title: "A title"
output: html_document
runtime: shiny
---

Shiny UI and server code can then be placed in R code blocks. input, output, and session are available as in server functions. You only need UI for inputs, as renderXXX functions magically create their corresponding xxxOutput.

```{r echo=FALSE}
sliderInput("amount", "Amount", 0, 10, 5)
renderText({ paste0("Amount is ", input$amount) })
```



Modules also work:

```{r echo=FALSE}
mymodule_ui("mod3")
callModule(mymodule_server, "mod3", multiply_by=9)
```



Shiny app objects and app directories can also be shown, as has been done throughout this document, but they are enclosed in an <iframe>, so the size is fixed. The size can be specified with shinyApp(..., options=list(width=..., height=...)).

Solutions to challenges