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")
library(shiny)
ui_hello <- fluidPage(titlePanel("Hello world"))
server_hello <- function(input, output, session) { }
app_hello <- shinyApp(ui_hello, server_hello)
app_hello
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.
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 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"))))
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/
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.
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.
Run your app with:
runApp("yourdirectoryname")
scp
. Your instructor will give you details for how to do this.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)
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. )
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.)
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)
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.
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?
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 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.
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))
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)
What’s going on here?
The observeEvent
code is run only when the specified input updates. It can be used to observe any input being updated, but using it in combination with an actionButton
is typical. Unlike other code we’ve written, the code in the observeEvent
isn’t about getting back to a consistent state, and it isn’t idempotent. It matters that it only runs exactly when we want it to – if it ran when input$text
changed, we would have an infinite loop!
updateTextInput
changes the value of the input. There are corresponding functions for other types of input.
Add buttons to your genome browser to navigate left and right, using the shift
function for GRanges.
(Optional, more difficult) Add buttons to zoom in and out.
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)
Without care, Shiny might create a bit of a nightmare:
( 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:
shinyApp( )
, for interactive use.Also worth considering:
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.Care also needs to be taken when Shiny apps form a part of published results:
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 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))
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.
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 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=...))
.