A Shiny app is an interactive web page that is created using R and functions from the Shiny package (and the many packages Shiny depends on).
Getting help
A Shiny app consists of
Basic template
library(shiny) # install.packages("shiny")
# create web page
ui <- fluidPage(
...
)
# "server logic"; calculate output from user input
server <- function(input, output){
...
}
# "connect" ui with server logic and create app
shinyApp(ui = ui, server = server)Run it locally on your computer
For each app, create a new directory
Put app.R into this directory
Run it from inside R by
runApp("name-of-app-directory")or use RStudio’s Run App button
Run it on a web server
Histogram of random values
library(shiny)
## User interface with slider (input) and plot (output)
ui <- fluidPage(
sliderInput(inputId = "n",
label = "Number of samples",
min = 1, max = 100, value = 10),
plotOutput(outputId = "hist")
)
## Server function connecting input and output
server <- function(input, output){
output$hist <- renderPlot({
x <- rnorm(input$n) # draw n random values
hist(x)
})
}
shinyApp(ui = ui, server = server)Building blocks of an interactive web page are elements the user
interacts with; created by *Input() and
*Output() functions.
Every input needs a unique inputId, otherwise
the reference in the server function (see below) to the entered value is
not unique.
sliderInput(inputId = "id1",
label = "Slider input example",
min = 1, max = 10, value = 5)numericInput(inputId = "id2",
label = "Numeric input example",
min = 0, max = 100, value = 30, step = 1)selectInput(inputId = "id3",
label = "Select list input example",
choices = c("Choice 1",
"Choice 2",
"Choice 3"),
selected = "Choice 2",
multiple = TRUE)checkboxInput(inputId = "id4",
label = "Checkbox input example",
value = FALSE)See also checkboxGroupInput() to create a group of
checkboxes that can be used to toggle multiple choices
independently.
dateInput(inputId = "id5",
label = "Date input example",
value = "2020-10-20",
min = "2020-01-01", max = "2021-12-31")fileInput(inputId = "id6",
label = "File upload example",
multiple = FALSE,
accept = ".txt")radioButtons(inputId = "id7",
label = "Radio buttons example",
choices = c("Option 1",
"Option 2",
"Option 3"),
selected = "Option 3")textInput(inputId = "id8",
label = "Text input example",
placeholder = "Name")actionButton(inputId = "id9",
label = "Action button example")Outputs (often) respond to changes of input values. Just like inputs,
they need a unique identifier, called outputId.
textOutput(outputId = "myUniqueOutputId")plotOutput() # Plot output element
textOutput() # Text output element
verbatimTextOutput() # Verbatim text output element
tableOutput() # Table output element
dataTableOutput() # Data table output element
uiOutput() # User interface (html) element
downloadButton() # Download button
outputOptions() # Set options for an output object… connects inputs to outputs and (re-)calculates the outputs when changes in the inputs are detected (keyword: reactivity).
The server function takes two arguments
The names of the list elements must be identical to the input and output IDs in the user interface.
How to create elements in the output list
output$render*()input$server <- function(input, output){
output$mytext <- renderText({
paste("You entered this number:", input$x)
})
}A render*() function
*Output() function{})renderPlot() # Plot output <-> plotOutput()
renderText() # Text output <-> textOutput()
renderPrint() # Printable output <-> verbatimTextOutput()
renderTable() # Table output <-> tableOutput()
renderDataTable() # Data table output <-> dataTableOutput()
renderImage() # Image file output <-> imageOutput()
renderUI() # UI (HTML) output <-> uiOutput()
downloadHandler() # File downloads <-> downloadButton()Create an app with
n,custom_xlab andbox.The plot should be a boxplot showing n values drawn
randomly from an uniform distribution between 0 and 1
(runif()).
The x-axis label of the boxplot (argument xlab) shall
correspond to the text entered in custom_xlab.
Use the template found at the beginning of the script.
library(shiny)
# create web page
ui <- fluidPage(
sliderInput(inputId = "n", label = "Sample size",
min = 1, max = 100, value = 50),
textInput(inputId = "custom_xlab", label = "X-axis label"),
plotOutput(outputId = "hist")
)
# "server logic"; calculate output from user input
server <- function(input, output){
output$hist <- renderPlot({
x <- runif(input$n)
boxplot(x, xlab = input$custom_xlab)
})
}
# "connect" ui with server logic and create app
shinyApp(ui = ui, server = server)UI layouts (to be used inside fluidPage())
… using Shiny’s grid layout system (based on the Bootstrap 12-wide grid system)
Image source: Mastering Shiny
:
fluidRow( # custom layout with
column(6, ...), # two equal-width columns
column(6, ...) # width should add up to 12
# within fluidRow()
)… using tabsetPanel()
tabsetPanel(
tabPanel("NameTab1", ...),
tabPanel("NameTab2", ...),
tabPanel("NameTab3", ...)
)
Other layout types (e.g. with very many tabs
navlistPanel() or with a bootstrap “navbar”
navbarPage()) as well as an explanation of the grid system
can be found at https://shiny.rstudio.com/articles/layout-guide.html
The various Shiny functions for the user interface ultimately generate HTML code that is rendered by the browser. Therefore, it is possible to use HTML code directly to build the user interface without much effort.
HTML("<div>...</div>")tags list to
build a specific HTML tag, e.g.:
tags$div() to define a <div>
elementtags$video() to add a video to the UIExample:
fluidPage(
# Grid layout
fluidRow(
# column
column(12, # 12 = full width
# Custom HTML using "tags"-functions
tags$div(
tags$p("This is the first paragraph"),
tags$p("This is meaningless placeholder text but",
tags$b("this"),
"word seems to be very important. ")
),
# ... and some raw HTML
HTML("<p>Here you see raw HTML, <strong>bold</strong>,
<em>italic</em>, <u>underlined </u>and even formatted</p>
<ul>
<li>as</li>
<li>a</li>
<li>list.</li>
</ul>
<blockquote>
<p>Quotes</p>
</blockquote>
<p>as well as <a href='https://qhelp.eu/'>links </a>are of
course also possible.</p>"),
# Inputs: Slider
sliderInput(inputId = "bins",
label = "Number of bins:",
min = 1,
max = 50,
value = 30)
)
)
)This is the first paragraph
This is meaningless placeholder text but this word seems to be very important.
Here you see raw HTML, bold, italic, underlined and even formatted
Quotes
as well as links are of course also possible.
See also:
The MathJax library (https://www.mathjax.org/) typesets Latex-style expressions on the fly
Example:
fluidPage(
# load MathJax; only needs to be called once
withMathJax(),
# displayed and inline equation
"Here is an equation
$$\\alpha + \\frac{1}{2},$$
where \\(\\alpha = 2\\)."
)To dynamically adjust mathematical expressions, they can be generated
in the server function with renderUI({withMathJax(...)})
and inserted into the UI using uiOutput(...). In this case
it is necessary to call withMathJax(...) in each
renderUI() function. See this example: https://shiny.rstudio.com/gallery/mathjax.html
Select a (suitable) layout and customize the app that you programmed
in exercise 1.
Give the app a title using titlePanel() (see
?titlePanel for help).
library(shiny)
# create web page
ui <- fluidPage(
titlePanel("Boxplot of uniformly distributed values"),
sidebarLayout( # automatic layout with
sidebarPanel(
sliderInput(inputId = "n", label = "Sample size",
min = 1, max = 100, value = 50),
textInput(inputId = "custom_xlab", label = "X-axis label")
), # sidebar
mainPanel(
plotOutput(outputId = "hist")
) # main area
)
)
# "server logic"; calculate output from user input
server <- function(input, output){
output$hist <- renderPlot({
x <- runif(input$n)
boxplot(x, xlab = input$custom_xlab)
})
}
# "connect" ui with server logic and create app
shinyApp(ui = ui, server = server)Reactivity is a two-step process
In addition to the render*() functions there are other reactive constructs that allow for finer control over reactions.
reactive() # reactive expression
eventReactive() # delay reactions
observe() # reactive observer
observeEvent() # to run code on the server
reactiveValues() # object for storing reactive values
isolate() # non-reactive scope for an expressionTry to find the problem in the following app:
ui <- fluidPage(
fluidRow(
column(7,
sliderInput(inputId = "n",
label = "Number of samples",
min = 1, max = 100, value = 10),
verbatimTextOutput(outputId = "desc")
),
column(5,
plotOutput(outputId = "hist")
)
)
)
server <- function(input, output){
output$hist <- renderPlot( hist(rnorm(input$n), xlab = "x", main = "Histogram") )
output$desc <- renderPrint( summary(rnorm(input$n)) )
}Reactive expressions: reactive(),
eventReactive()
The solution to the problem with the app above:
# hist() and summary() work on same data
# getdata() is called like a function and returns
server <- function(input, output){
getdata <- reactive( rnorm(input$n) )
output$hist <- renderPlot( hist(getdata()) )
output$desc <- renderPrint( summary(getdata()) )
}eventReactive() reacts only to selected
reactive values:
server <- function(input, output){
getdata <- eventReactive(eventExpr = input$button, valueExpr = {
... # Perform calculations only when input$button changes
# the reactive values in the valueExpr do not trigger invalidations.
})
...
}Reactive observers: observe(),
observeEvent()
server <- function(input, output){
...
observe({
print(paste0("The value of input$x is now ", input$x))
})
observeEvent(input$savebutton, {
write.table(getdata(), file = "foo.txt")
})
}reactive() vs observe() reactive() / eventReactive() |
observe() / observeEvent() |
|---|---|
| Has to be called like a function | Cannot be called |
| Returns a value / no side effects | Side effects / no return value |
| Lazy (only executed if needed by other reactive object) | Eager (executed as soon as used input reactives change) |
| Cached | - |
Typical use cases:
|
Typical side effects:
|
... <- reactiveValues(...) inside the server
function to create a list of named reactive values.ui <- fluidPage(
sliderInput(inputId = "n_sim",
label = "Number of samples",
min = 1, max = 10, value = 5),
actionButton(inputId = "sim_new", label = "Simulate data"),
verbatimTextOutput(outputId = "df")
)
server <- function(input, output){
# create list of reactive values n and data
r <- reactiveValues(n = 5, data = data.frame(id = 1:5, x = NA ))
# simulate new data
observeEvent(input$sim_new, {
r$n <- input$n_sim # overwrite rective values n and data
r$data <- data.frame(id = 1:r$n, x = rnorm(r$n))
})
# show data frame
output$df <- renderPrint(r$data)
}There is also reactiveVal() to create a single reactive
value. Note that reading and writing are a bit special:
server <- function(input, output){
...
x <- reactiveVal(5) # create reactive value x
observeEvent(input$button_add, {
new_val <- x() + 1 # read reactive value x and add 1
x(new_val) # overwrite x with new_val
})
}isolate() can be used to prevent an object from being
recalculated when the reactive variable is changed.Example:
ui <- fluidPage(
sliderInput(inputId = "n_sample",
label = "Number of samples",
min = 1, max = 10, value = 5),
actionButton(inputId = "draw_plot", label = "Draw plot"),
plotOutput("ui_plot")
)
server <- function(input, output, session){
output$ui_plot <- renderPlot({
input$draw_plot # Dependency on the button
x <- rnorm(isolate(input$n_sample))
hist(x)
})
}
shinyApp(ui, server)To understand how a Shiny app reacts to changes in inputs, it is helpful to look at / construct the app’s reactive graph.
Create reactive graph automatically:
library(shiny)
options(shiny.reactlog = TRUE) # enable reactlog
ui <- fluidPage(
sliderInput("a", "Number a", 1, 10, 5),
sliderInput("b", "Number b", 1, 10, 5),
textInput("t", "Text t"),
verbatimTextOutput("all_out"),
verbatimTextOutput("a_out")
)
server <- function(input, output, session) {
data <- reactive({ input$a * input$b})
output$all_out <- renderPrint({
paste("Text t:", input$t, " a * b =", data())
})
output$a_out <- renderPrint({
paste("Number a:", input$a)
})
}
shinyApp(ui, server) # start app
# Press Ctrl-F3 (Command-F3) to launch the reactive graph in a new tab.
# Refresh the page to see the recent activities.In the beginning, it can be helpful to draw the reactive graph for various (simple) apps yourself.
*Input()-function is paired with an
update*()-functionsliderInput() <->
updateSliderInput()Example: Use an actionButton() to reset a slider.
ui <- fluidPage(
sliderInput(inputId = "n",
label = "Number of samples",
min = 1, max = 10, value = 5),
actionButton(inputId = "reset_slider", label = "Reset slider"),
verbatimTextOutput(outputId = "slider_out")
)
# !!!
# We need to include a session object as an argument to our server
# function so that shiny can "communicate" with the web page.
# !!!
server <- function(input, output, session){
observeEvent(input$reset_slider,
updateSliderInput(session, "n", value = 5))
output$slider_out <- renderPrint(input$n)
}conditionalPanel()
input.InputID or
input["InputID"].Example:
ui <- fluidPage(
tags$p("Free drink with large pizzas (more than 20cm in diameter)."),
sliderInput(inputId = "d_pizza",
label = "Diameter",
min = 10, max = 40, value = 15),
conditionalPanel(condition = "input.d_pizza > 20",
selectInput("drink",
label = "Choose your free drink",
choices = c("Water", "Beer",
"Coke", "Fanta"))),
verbatimTextOutput("order")
)
server <- function(input, output, session){
output$order <- renderText({
drink <- ifelse(input$d_pizza > 20,
input$drink,
"None")
paste("Your order:\n- Pizza's diameter:",
input$d_pizza,
"\n- Free drink:", drink)
})
}Free drink with large pizzas (more than 20cm in diameter.
*Input(), *Output(), etc.) or HTML code.conditionalPanel()…
renderUI() in the server function is paired with
uiOuput() in the ui object.tagList()
tagList(sliderInput(...), sliderInput(...))Example:
ui <- fluidPage(
checkboxInput("ui_type", "I prefer sliders."),
uiOutput("ui_numeric"),
verbatimTextOutput("number")
)
server <- function(input, output, session){
output$ui_numeric <- renderUI({
if(input$ui_type){ # slider
sliderInput("num_chosen",
"Choose a number",
0, 100, 0)
} else { # numeric input
numericInput("num_chosen",
"Choose a number",
0, 0, 100)
}
})
output$number <- renderText(input$num_chosen)
}
shinyApp(ui, server)For time-consuming calculations, the user should be given feedback on how far the calculation has progressed (or at least that the app has not crashed).
shinycssloaders::withSpinner() around
*Output()library(shiny)
library(shinycssloaders)
## User interface with slider (input) and plot (output)
ui <- fluidPage(
sliderInput(inputId = "n",
label = "Sample size [millions]",
min = 20, max = 30, value = 20),
withSpinner(plotOutput(outputId = "hist")) # add loading animation
)
## Server function connecting input and output
server <- function(input, output){
output$hist <- renderPlot({
x <- rnorm(input$n * 1000000) # draw n random values
hist(x)
})
}
shinyApp(ui = ui, server = server)withProgress()
inside reactive(), observer() or
render*()library(shiny)
library(MASS)
set.seed(1606)
# simulate data
df1 <- as.data.frame(mvrnorm( n = 200, mu = c(0, 50), Sigma = rbind(c(1, 0.5), c(0.5, 1))))
names(df1) <- c("y", "x")
# User interface with slider (input) and plot (output)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "n",
label = "Number of bootstrap replicates",
min = 100, max = 1000, value = 200, step = 10),
actionButton("start_boot_sim", "Start")),
mainPanel(
plotOutput(outputId = "hist") # add loading animation
)
))
# Server function connecting input and output
server <- function(input, output){
boot_rep <- eventReactive(input$start_boot_sim, {
###### add Progress bar
withProgress(message = 'Bootstrap', value = 0, {
n_sim <- input$n
boot_sample <- numeric(n_sim)
for(i in 1:n_sim){
Sys.sleep(0.0005) # simulate a long computation
boot_idx <- sample(1:nrow(df1), nrow(df1), replace = TRUE)
boot_sample[i] <- cor(df1[boot_idx, "x"], df1[boot_idx, "y"])
#### Increment progress bar in steps of n_sim/10
if(i %% floor(n_sim/10) == 0) # modulo operator %%
incProgress(floor(n_sim/10)/n_sim, detail = paste("Bootstrap replicates ", i))
}
})
boot_sample
})
output$hist <- renderPlot({
ci <- round(quantile(boot_rep(), c(0.025, 0.975)), 3)
br <- seq(ci[1], ci[2], length.out = 10)
br <- c(br[1] - diff(br)[1] * 15:1, br, br[length(br)] + diff(br)[1] * 1:15)
hist(boot_rep(), main = "Bootstrap distribution with 95%-CI",
xlab = "Correlation", xlim = c(0, 1), breaks = br,
col = ifelse(br >= ci[1] & br < ci[2], "grey50", "grey90"))
abline(v = ci)
})
}
shinyApp(ui = ui, server = server)input$...) exist.req(innput$...) can be
used. If no value is set for input$..., the calculation
will be aborted without a warning message.Example:
output$hist_mi <- renderPlot({
## check requirements -> stops silently if input$distr is missing
req(input$distr)
x <- switch(input$disti, # draw n random values
Normal = rnorm(input$n_mi),
Uniform = runif(input$n_mi))
hist(x)
})validate(need(...)) tests a condition and returns a
validation error if the test fails.In addition, the calculation is
aborted as with req(...).Artificial Example: (Bad UI/UX!!!)
Solution (Still bad UI/UX!):
# Server function connecting input and output
output$hist_mis <- renderPlot({
## Input validation
validate(
need(input$distr %in% c("normal", "uniform"),
"Only 'normal' and 'uniform' are supported distributions."),
need(input$n > 0,
"The number of values drawn must be greater than 0.")
)
##
x <- switch(input$distr, # draw n random values
normal = rnorm(input$n),
uniform = runif(input$n))
hist(x)
})(In this case, however, one should consider the restrictions earlier: set the limits differently for sliderInput() and use selectInput() instead of textInput().)
library(shiny)
# User interface
ui <- fluidPage(
actionButton("show_dialog", "Show modal dialog")
)
# Server function connecting input and output
server <- function(input, output){
observeEvent(input$show_dialog, {
showModal(
modalDialog(
title = "Important message",
HTML("This is an exemplary modal dialog. <br>
The dialogue can be terminated with cancel. <br>
<b>Don't press OK</b> if you hate Google and if your IP address is super secret."),
easyClose = TRUE,
footer = tagList(
modalButton("Cancel"),
actionButton("ok", "OK")
)
))
})
# css firework see: https://codepen.io/alvaromontoro/pen/MWrMEgW
observeEvent(input$ok, {
showModal(
modalDialog(
title = "HEHEHE",
HTML('<iframe width="816" height="459" src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ?controls=0&autoplay=1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>'),
easyClose = TRUE,
size = "l",
footer = tagList(
modalButton("Close")
)
))
})
}
shinyApp(ui = ui, server = server)You can see the result of an R Markdown document in front of you. This script was created with R Markdown, Shiny Apps and a little CSS and HTML magic. So you already have an impression of what is possible with R Markdown.
A Markdown-formatted document should be publishable as-is, as plain text, without looking like it’s been marked up with tags or formatting instructions. – John Gruber
There are many flavors of Markdown (GitHub Flavored Markdown, Markdown Extra)
R Markdown uses Pandoc’s Markdown an extended and slightly revised version of John Gruber’s Markdown syntax.
R Markdown supports many output formats including HTML, PDF, Beamer Presentations, HTML5 slides, books, dashboards, shiny applications, scientific articles…
R Markdown: The Definitive Guide by Yihui Xie, J. J. Allaire & Garrett Grolemund.
…. for more details about Pandoc’s Markdown visite: https://pandoc.org/MANUAL.html#pandocs-markdown.
R Markdown extends Markdown with R Code Chunks that can be executed and later displayed in the output format.
For example, it is possible to integrate the data analysis directly into a report and thus obtain a reproducible document (text + data analysis). In addition, Shiny Apps can also be integrated into the R Markdown document as long as the output format is HTML.
Download .Rmd example file
-> Open it with RStudio -> Click
Run Document
Which part of the code takes the most time to complete?
Use caching to improve speed: bindCache()
reactiveConsole(TRUE)