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
<- fluidPage(
ui
...
)
# "server logic"; calculate output from user input
<- function(input, output){
server
...
}
# "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)
<- fluidPage(
ui sliderInput(inputId = "n",
label = "Number of samples",
min = 1, max = 100, value = 10),
plotOutput(outputId = "hist")
)
## Server function connecting input and output
<- function(input, output){
server $hist <- renderPlot({
output<- rnorm(input$n) # draw n random values
x 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$
<- function(input, output){
server $mytext <- renderText({
outputpaste("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
<- fluidPage(
ui 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
<- function(input, output){
server $hist <- renderPlot({
output<- runif(input$n)
x 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
$div(
tags$p("This is the first paragraph"),
tags$p("This is meaningless placeholder text but",
tags$b("this"),
tags"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
<- fluidPage(
ui 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
<- function(input, output){
server $hist <- renderPlot({
output<- runif(input$n)
x 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 expression
Try to find the problem in the following app:
<- fluidPage(
ui fluidRow(
column(7,
sliderInput(inputId = "n",
label = "Number of samples",
min = 1, max = 100, value = 10),
verbatimTextOutput(outputId = "desc")
),column(5,
plotOutput(outputId = "hist")
)
)
)
<- function(input, output){
server $hist <- renderPlot( hist(rnorm(input$n), xlab = "x", main = "Histogram") )
output$desc <- renderPrint( summary(rnorm(input$n)) )
output }
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
<- function(input, output){
server <- reactive( rnorm(input$n) )
getdata $hist <- renderPlot( hist(getdata()) )
output$desc <- renderPrint( summary(getdata()) )
output }
eventReactive()
reacts only to selected
reactive values:
<- function(input, output){
server <- eventReactive(eventExpr = input$button, valueExpr = {
getdata # Perform calculations only when input$button changes
... # the reactive values in the valueExpr do not trigger invalidations.
})
... }
Reactive observers: observe()
,
observeEvent()
<- function(input, output){
server
...
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.<- fluidPage(
ui sliderInput(inputId = "n_sim",
label = "Number of samples",
min = 1, max = 10, value = 5),
actionButton(inputId = "sim_new", label = "Simulate data"),
verbatimTextOutput(outputId = "df")
)
<- function(input, output){
server # create list of reactive values n and data
<- reactiveValues(n = 5, data = data.frame(id = 1:5, x = NA ))
r
# simulate new data
observeEvent(input$sim_new, {
$n <- input$n_sim # overwrite rective values n and data
r$data <- data.frame(id = 1:r$n, x = rnorm(r$n))
r
})
# show data frame
$df <- renderPrint(r$data)
output
}
There is also reactiveVal()
to create a single reactive
value. Note that reading and writing are a bit special:
<- function(input, output){
server
...<- reactiveVal(5) # create reactive value x
x
observeEvent(input$button_add, {
<- x() + 1 # read reactive value x and add 1
new_val 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:
<- fluidPage(
ui sliderInput(inputId = "n_sample",
label = "Number of samples",
min = 1, max = 10, value = 5),
actionButton(inputId = "draw_plot", label = "Draw plot"),
plotOutput("ui_plot")
)
<- function(input, output, session){
server
$ui_plot <- renderPlot({
output$draw_plot # Dependency on the button
input<- rnorm(isolate(input$n_sample))
x 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
<- fluidPage(
ui sliderInput("a", "Number a", 1, 10, 5),
sliderInput("b", "Number b", 1, 10, 5),
textInput("t", "Text t"),
verbatimTextOutput("all_out"),
verbatimTextOutput("a_out")
)
<- function(input, output, session) {
server <- reactive({ input$a * input$b})
data
$all_out <- renderPrint({
outputpaste("Text t:", input$t, " a * b =", data())
})
$a_out <- renderPrint({
outputpaste("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.
<- fluidPage(
ui 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.
# !!!
<- function(input, output, session){
server
observeEvent(input$reset_slider,
updateSliderInput(session, "n", value = 5))
$slider_out <- renderPrint(input$n)
output }
conditionalPanel()
input.InputID
or
input["InputID"]
.Example:
<- fluidPage(
ui $p("Free drink with large pizzas (more than 20cm in diameter)."),
tagssliderInput(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")
)
<- function(input, output, session){
server
$order <- renderText({
output<- ifelse(input$d_pizza > 20,
drink $drink,
input"None")
paste("Your order:\n- Pizza's diameter:",
$d_pizza,
input"\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:
<- fluidPage(
ui checkboxInput("ui_type", "I prefer sliders."),
uiOutput("ui_numeric"),
verbatimTextOutput("number")
)
<- function(input, output, session){
server
$ui_numeric <- renderUI({
outputif(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)
}
})
$number <- renderText(input$num_chosen)
output
}
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)
<- fluidPage(
ui 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
<- function(input, output){
server $hist <- renderPlot({
output<- rnorm(input$n * 1000000) # draw n random values
x hist(x)
})
}
shinyApp(ui = ui, server = server)
withProgress()
inside reactive()
, observer()
or
render*()
library(shiny)
library(MASS)
set.seed(1606)
# simulate data
<- as.data.frame(mvrnorm( n = 200, mu = c(0, 50), Sigma = rbind(c(1, 0.5), c(0.5, 1))))
df1 names(df1) <- c("y", "x")
# User interface with slider (input) and plot (output)
<- fluidPage(
ui 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
<- function(input, output){
server
<- eventReactive(input$start_boot_sim, {
boot_rep ###### add Progress bar
withProgress(message = 'Bootstrap', value = 0, {
<- input$n
n_sim <- numeric(n_sim)
boot_sample for(i in 1:n_sim){
Sys.sleep(0.0005) # simulate a long computation
<- sample(1:nrow(df1), nrow(df1), replace = TRUE)
boot_idx
<- cor(df1[boot_idx, "x"], df1[boot_idx, "y"])
boot_sample[i]
#### 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
})
$hist <- renderPlot({
output<- round(quantile(boot_rep(), c(0.025, 0.975)), 3)
ci <- 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)
br 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:
$hist_mi <- renderPlot({
output
## check requirements -> stops silently if input$distr is missing
req(input$distr)
<- switch(input$disti, # draw n random values
x 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
$hist_mis <- renderPlot({
output
## 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.")
)##
<- switch(input$distr, # draw n random values
x 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
<- fluidPage(
ui actionButton("show_dialog", "Show modal dialog")
)
# Server function connecting input and output
<- function(input, output){
server
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)