You can make interactive web applications easily and entirely in R with Shiny. Shiny apps are a great way to share your analysis with others without them having to run any R themselves.
This lecture will cover
To run the code on your computer download this .zip file, open shiny.Rmd
, then click Run Document
. Warning: you have to run the code from the .zip file to get the apps in this lecture to work.
Running the code below in the R console will start up a simple Shiny app that plots a histogram and lets the user choose the number of bins in the histogram using a slider. The code that powers the app is included, and relevant parts of the code will be highlighted yellow as you interact with the app.
if (!require('shiny')) install.packages("shiny")
runExample("01_hello")
A Shiny app is composed of two fundamental parts:
These two parts can either be included in one file called app.R or in two separate files called ui.R and server.R. Both methods work equivalently, and it’s a matter of preference which one you choose and may depend on if your app is very short or long.
This is the bare minimum code (it does nothing - a blank page shows up) if the app is on one app.R file:
library(shiny)
ui <- fluidPage()
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
For two separate files, ui.R will contain
library(shiny)
shinyUI(fluidPage(
))
and server.R has
library(shiny)
shinyServer(function(input, output) {
})
If the two files are stored in a directory called my_app
, then you can run the app with:
runApp("my_app")
Extra: if you want your code to show up alongside your app like in the example, then change the display mode to showcase
.
runApp("my_app", display.mode = "showcase")
The coolness of Shiny apps comes from its use of reactivity. This means that your apps come alive when you build reactive objects that interact with the user.
You construct a reactive object in two parts:
Here’s the same example as before (with the histogram code rewritten with ggplot2).
In the ui section below, there is an input/widget object sliderInput
that codes for a slider that the user can use to choose the number of bins in the histogram, and there’s an output object plotObject
that displays the histogram.
In the server section below, the code that generates the histogram is returned in a renderPlot
function that is saved to the output
vector under the same outputId distPlot
as specified in plotOutput
. The user-supplied number of bins from the slider is available in the input
vector under the same inputId bins
as in sliderInput
.
library(shiny)
library(ggplot2)
ui <- fluidPage(
# Sidebar with a slider input for the number of bins
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "bins",
label = "Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput(outputId = "distPlot")
)
)
)
server <- function(input, output) {
# Expression that generates a histogram. The expression is
# wrapped in a call to renderPlot to indicate that:
#
# 1) It is "reactive" and therefore should be automatically
# re-executed when inputs change
# 2) Its output type is a plot
output$distPlot <- renderPlot({
ggplot(faithful, aes(x = waiting)) +
geom_histogram(bins = input$bins, colour = "white")
})
}
shinyApp(ui = ui, server = server,
options = list(height = 500))
Below are tables of the possible input/output and render functions available to construct reactive objects in the app with.
Shiny widgets are web elements that enable your user to interact with the app. There are several standard widgets available in Shiny. You can play with them at the Shiny widgets gallery.
input function | widget |
---|---|
actionButton | Action Button |
checkboxGroupInput | A group of check boxes |
checkboxInput | A single check box |
dateInput | A pair of calendars for selecting a date range |
fileInput | A file upload control wizard |
helpText | Help text that can be added to an input form |
numericInput | A field to enter numbers |
radioButtons | A set of radio buttons |
selectInput | A box with choices to select from |
sliderInput | A slider bar |
submitButton | A submit button |
textInput | A field to enter text |
The first two arguments to a widget function is always
The rest of the arguments depend on the function of the widget. For example, the sliderInput
from the earlier example requires the setting of the minimum and maximum value of the slider, as well as the value it should be set as default when the user loads the app.
sliderInput(inputId = "bins",
label = "Number of bins:",
min = 1,
max = 50,
value = 30)
output function | creates |
---|---|
htmlOutput | raw HTML |
imageOutput | image |
plotOutput | plot |
tableOutput | table |
textOutput | text |
uiOutput | raw HTML |
verbatimTextOutput | text |
The one required argument to an output function is the outputId
, which helps connect it to the render function on the server side that generates the output.
On the ui side:
plotOutput(outputId = "distPlot")
On the server side:
output$distPlot <- renderPlot({
[...put your plotting code here...]
})
render function | creates |
---|---|
renderImage | images |
renderPlot | plots |
renderPrint | any printed output |
renderTable | data frame, matrix, other table like structures |
renderText | character strings |
renderUI | a Shiny tag object or HTML |
One of the more powerful render functions is renderUI() - it enables us to generate dynamic UI, i.e. construct reactive objects that depend on user input. We illustrate how it works with a toy example that requires this function.
Let’s say we are selecting colors for plotting in R, and we want to help the user narrow down the choices through color families.
If the user wants a red color, then the drop-down menu can display only the red options. We can’t put a selectInput
drop down menu in ui when we can’t populate the choices argument with the correct list of colors that depend on what the user chooses in the radioButtons
. This means that we need to dynamically generate this selectInput
inside renderUI
(on the server side!) based on the user-chosen color family input$color_family
.
library(shiny)
color_list <- list(red = c("tomato", "violetred", "firebrick"),
green = c("chartreuse", "forestgreen", "olivedrab"),
blue = c("navy", "dodgerblue", "torquoise"))
ui <- fluidPage(
radioButtons(inputId = "color_family",
label = "Choose a color family",
choices = c("red", "green", "blue")),
uiOutput(outputId = "color_selector"),
verbatimTextOutput(outputId = "color_chosen")
)
server <- function(input, output) {
output$color_selector <- renderUI({
selectInput(inputId = "color",
label = "Select color",
choices = color_list[input$color_family])
})
output$color_chosen <- renderText({
paste("You chose this color:", input$color)
})
}
shinyApp(ui = ui, server = server,
options = list(height = 300))
Designing the layout of the user interface is a critical component of app development. The goal is to make an app that is intuitive and friendly to use so people will actually want to use it! This requires thoughtful planning and lot of iterations as you test different layouts on your users.
library(shiny)
ui <- fluidPage(
titlePanel("title panel"),
sidebarLayout(
sidebarPanel( "sidebar panel"),
mainPanel("main panel")
)
)
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
Another nice way to organize a layout is through tabs:
library(shiny)
ui <- fluidPage(
titlePanel("title panel"),
sidebarLayout(
sidebarPanel( "sidebar panel"),
mainPanel(tabsetPanel(
tabPanel("Plot", "put plot here!"),
tabPanel("Summary"),
tabPanel("Table")
)
)
)
)
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
A popular and easy way to create dashboard apps is with shinydashboard.
You need to specify three parts:
Here’s the basic skeleton of how to specify these parts:
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody()
)
server <- function(input, output) { }
shinyApp(ui, server)
Here’s an example with some parts filled out:
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(title = "My Dashboard"),
dashboardSidebar(
sidebarMenu(
menuItem("About", tabName = "about_tab", icon = icon("info"))
)
),
dashboardBody("This is the body!")
)
server <- function(input, output) { }
shinyApp(ui, server)
Now that we know how to set up an app, what are some ways to interact with data?
Data can be loaded from files in a Shiny specific file called global.R. Anything run in that file will be available in the global environment, which means that both ui.R and server.R can access the variables and functions.
Things that you can typically include in global.R:
my_data <- read_csv("data/my_data_file.csv")
library(dplyr)
source(my_utils.R)
my_colors <- c("orange", "blue")
calculate_percent <- function(...)
db_conn <- dplyr::src_postgres(...db_credentials...)
Reactive expressions are a way for your app to update efficiently when users interact with it. They are a bit nuanced to use - here’s the RStudio tutorial on them https://shiny.rstudio.com/tutorial/lesson6/.
In general, any code you put in a render
function will continually be updated, even if you don’t necessarily need them to be rerun every single time. This will slow down your app considerably. If you want to use data inside a render
function that does not need to be updated every time if input
doesn’t change, then you can wrap your data inside a reactive expression that can be called inside render
. The reactive expression will only recalculate if input
has changed.
library(shiny)
ui <- fluidPage(
sliderInput(inputId = "rate",
label = "Choose rate",
min = 0,
max = 1,
value = 0.5),
textOutput(outputId = "pct_std"),
textOutput(outputId = "pct_half")
)
server <- function(input, output) {
my_percent <- reactive({
input$rate * 100
})
my_half_percent <- reactive({
my_percent() / 2
})
output$pct_std <- renderText({
paste0("This is my percent: ", my_percent(), "%")
})
output$pct_half <- renderText({
paste0("This is my percent by half: ", my_half_percent(), "%")
})
}
shinyApp(ui = ui, server = server,
options = list(height = 200))
input
values or values from other reactive expressions.reactive({})
.()
.reactive
or render
function.There are several well-developed packages for generating interactive graphics in a Shiny app:
suppressMessages(library(plotly))
library(shiny)
ui <- fluidPage(
plotlyOutput("plot"),
verbatimTextOutput("hover"),
verbatimTextOutput("click")
)
server <- function(input, output) {
output$plot <- renderPlotly({
plot_ly(x = rnorm(10), y = rnorm(10), z = rnorm(10), type = "scatter3d")
})
output$hover <- renderPrint({
d <- event_data("plotly_hover")
if (is.null(d)) "Hover events appear here (unhover to clear)" else d
})
output$click <- renderPrint({
d <- event_data("plotly_click")
if (is.null(d)) "Click events appear here (double-click to clear)" else d
})
}
shinyApp(ui, server,
options = list(height = 800))
The Javascript library DataTables provides very powerful functionality to working with data tables in a web browser. Shiny apps can tap this through the R package DT that interfaces with DataTables.
Some of the useful things implemented are:
Here’s a minimal example that you can run in the R console:
suppressMessages(library(DT))
datatable(iris)
Here’s how that table gets into a Shiny app:
library(shiny)
ui <- fluidPage(
DT::dataTableOutput('tbl')
)
server <- function(input, output) {
output$tbl = DT::renderDataTable(
iris
)
}
shinyApp(ui, server)
You can enable users to dynamically compile Rmarkdown into documents that he/she can then download. For example, a scientist can choose an experiment from a table in an app, look at the results in different kinds of plots, and then select certain plots to be included in an official report generated by the app.
Here’s a working example that takes a name and uses that as the author of a report.
You’ll notice that it needs a rmarkdown template to fill in the user inputs and compile. The header of the file report.Rmd
looks like:
---
title: "My Report"
author: "`r input$user_name`"
date: "`r format(Sys.time(), '%B %d, %Y')`"
output: html_document
---
library(shiny)
library(rmarkdown)
ui <- fluidPage(
textInput(inputId = 'user_name',
label = 'Name'),
radioButtons(inputId = 'format',
label = 'Run Report Format',
c('PDF', 'HTML', 'Word'),
inline = TRUE),
downloadButton('generate_report')
)
server <- function(input, output) {
output$generate_report <- downloadHandler(
filename = function() {
paste0("my_report", ".",
switch(
input$format, PDF = 'pdf', HTML = 'html', Word = 'docx'
))
},
content = function(file) {
markdownFile <- 'report.Rmd'
src <- normalizePath(markdownFile)
owd <- setwd(tempdir())
on.exit(setwd(owd))
file.copy(src, markdownFile, overwrite = TRUE)
out <- render(markdownFile, switch(
input$format,
PDF = pdf_document(), HTML = html_document(), Word = word_document()
))
file.rename(out, file)
}
)
}
shinyApp(ui = ui, server = server,
options = list(height = 300))
Large and complex Shiny apps will have many many lines of code. Having it all in just one or two files can get very disorganized and unreadable. Just like how we modularize reusable R code into functions, we can modularize Shiny code into Shiny modules!
You can construct a Shiny module with two functions - one for the UI and one for the server.
This lecture won’t go into detail on how it works - it can get complicated but here’s a simple module that will function if you run it.
Some key points:
linkedScatter
is the module - linkedScatterUI()
specifies the UI, linkedScatter()
specifies the server part of the module (this requires the session
argument after input
and output
).callModule()
to call the module in the main server part.NS()
) in the module so that it enforces unique ids across the entire app.library(shiny)
linkedScatterUI <- function(id) {
ns <- NS(id)
fluidRow(
column(6, plotOutput(ns("plot1"), brush = ns("brush"))),
column(6, plotOutput(ns("plot2"), brush = ns("brush")))
)
}
linkedScatter <- function(input, output, session, data, left, right) {
# Yields the data frame with an additional column "selected_"
# that indicates whether that observation is brushed
dataWithSelection <- reactive({
brushedPoints(data(), input$brush, allRows = TRUE)
})
output$plot1 <- renderPlot({
scatterPlot(dataWithSelection(), left())
})
output$plot2 <- renderPlot({
scatterPlot(dataWithSelection(), right())
})
return(dataWithSelection)
}
scatterPlot <- function(data, cols) {
ggplot(data, aes_string(x = cols[1], y = cols[2])) +
geom_point(aes(color = selected_)) +
scale_color_manual(values = c("black", "#66D65C"), guide = FALSE)
}
ui <- fixedPage(
h2("Module example"),
linkedScatterUI("scatters"),
textOutput("summary")
)
server <- function(input, output, session) {
df <- callModule(linkedScatter, "scatters", reactive(mpg),
left = reactive(c("cty", "hwy")),
right = reactive(c("drv", "hwy"))
)
output$summary <- renderText({
sprintf("%d observation(s) selected", nrow(dplyr::filter(df(), selected_)))
})
}
shinyApp(ui, server,
options = list(height = 600))
Once you’ve made your awesome Shiny app, how do you share it with world or at least the people you’ve designed it for? Remember that apps are great because the user does not need to download and install R or know any R code whatsoever to be able to use them. But this means that the app has to be hosted somewhere that does have R, shiny, and all the required packages installed.
The easiest way to host your app is to have RStudio host it for you at https://www.shinyapps.io/. The pros are that you don’t have to worry about any IT infrastructure in maintaining your own server; but if your app processes sensitive or confidential data or you don’t want the public to be able to access it, then you either have to pay RStudio for more capabilities over the free version or deploy apps yourself with Shiny Server - locally or on the cloud.