The Shiny package is used to easily build interactive web applications whose primary purpose is to explore and/or present a data set. The package is installed in a standard way.
Eleven examples showing how Shiny works have been added to the package. Each example is a standalone application.
Other examples:
runExample ("02_text") # tables and data frames
runExample ("03_reactivity") # a reactive expression
runExample ("04_mpg") # global variables
runExample ("05_sliders") # slider bars
runExample ("06_tabsets") # tabbed panels
runExample ("07_widgets") # help text and submit buttons
runExample ("08_html") # Shiny app built from HTML
runExample ("09_upload") # file upload wizard
runExample ("10_download") # file download wizard
runExample ("11_timer") # an automated timer
Shiny applications are contained in a single app.R
script. If the script is placed in the my_app
folder, the
application will be launched by running the command:
The app.R
script contains three parts:
ui
function defining the user interface,server
function containing instructions constituting
the application engine,shinyApp
function.Below is the content of the script that runs the Hello Shiny! example.
library(shiny)
# Define UI for app that draws a histogram ----
ui <- fluidPage (
# App title ----
titlePanel("Hello Shiny!"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input: Slider for the number of bins ----
sliderInput(inputId = "bins",
label = "Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Histogram ----
plotOutput(outputId = "distPlot")
)
)
)
# Define server logic required to draw a histogram ----
server <- function (input, output) {
# Histogram of the Old Faithful Geyser Data ----
# with requested number of bins
# This expression that generates a histogram is wrapped in a call
# to renderPlot to indicate that:
#
# 1. It is "reactive" and therefore should be automatically
# re-executed when inputs (input$bins) change
# 2. Its output type is a plot
output$distPlot <- renderPlot({
x <- faithful$waiting
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = "#007bc2", border = "white",
xlab = "Waiting time to next eruption (in mins)",
main = "Histogram of waiting times")
})
}
# Run the app
shinyApp (ui = ui, server = server)
The bare minimum needed to run a blank application is as follows:
Shiny uses the fluidPage
function to adjust
automatically to the user’s browser window size. The two most common
elements added to fluidPage
are titlePanel
and
sidebarLayout
. The first one contains, of course, the title
of the application, while the second one takes at least two arguments:
sidebarPanel
and mainPanel
.
ui <- fluidPage (
titlePanel ("title panel"),
sidebarLayout (
sidebarPanel ("sidebar panel"),
mainPanel ("main panel")
)
)
sidebarPanel
is located on the left side by
default, but this can be changed using the position
parameter:
ui <- fluidPage (
titlePanel ("title panel"),
sidebarLayout (position = "right",
sidebarPanel ("sidebar panel"),
mainPanel ("main panel")
)
)
titlePanel
and sidebarLayout
generate
the basic look for the Shiny application, but a different layout can be
used. Using fluidRow
and column
allows you to
build applications based on any grid, while using
navbarPage
you can get multiple tabs placed in the
navigation bar. These layouts are discussed in the Shiny
Application Layout Guide.
The content inside functions from the *Panel
family can
be formatted using commands known from the HTML language:
Function | HTML5 tag | Action |
---|---|---|
p |
<p> | New paragraph of text |
h1 |
<h1> | First level heading |
h2 |
<h2> | Second level heading |
h3 |
<h3> | Third level heading |
h4 |
<h4> | Fourth level heading |
h5 |
<h5> | Fifth level heading |
h6 |
<h6> | Sixth level heading |
a |
<a> | Hyperlink |
br |
<br> | Linebreak |
div |
<div> | A section of text with a uniform style |
span |
<span> | Inline section of text without style |
pre |
<pre> | Fixed-width preformatted text |
code |
<code> | Source code format |
img |
<img> | Image |
strong |
<strong> | Bold |
em |
<em> | Italic |
HTML |
Directly passes the string as HTML |
ui <- fluidPage (
titlePanel ("My Star Wars App"),
sidebarLayout (
sidebarPanel (),
mainPanel (
h6("Episode IV", align = "center"),
h6("A NEW HOPE", align = "center"),
h5("It is a period of civil war.", align = "center"),
h4("Rebel spaceships, striking", align = "center"),
h3("from a hidden base, have won", align = "center"),
h2("their first victory against the", align = "center"),
h1("evil Galactic Empire.")
)
)
)
ui <- fluidPage (
titlePanel ("My Shiny App"),
sidebarLayout (
sidebarPanel (),
mainPanel (
p("A new p() command starts a new paragraph. Supply a style attribute to change the format
of the entire paragraph.", style = "font-family: 'times'; font-si16pt"),
strong("strong() makes bold text."),
em("em() creates italicized (i.e, emphasized) text."),
br(),
code("code displays your text similar to computer code"),
div("div creates segments of text with a similar style. This division of text is all blue
because I passed the argument 'style = color:blue' to div", style = "color:blue"),
br(),
p("span does the same thing as div, but it works with",
span("groups of words", style = "color:blue"),
"that appear inside a paragraph.")
)
)
)
The img (src = "my_image.png")
function is responsible
for adding images to the application, the first argument of which is the
name of the file containing the graphics that we want to place in the
application. Within the function, it is also possible to manipulate the
height and width of the image (which are given in pixels).
The file location is an important issue. The src
argument should not contain a path, only a name. The file should be
placed in the www
folder, which in turn should be placed in
the same folder as the app.R
file.
Widgets, i.e., control components, are used to retrieve numerical or text data from the user. It is through them that the user interacts with the application. In the Shiny package, we can choose from many different controls presented in the image below.
Controls are placed in the panel using appropriate functions:
Function | Widget |
---|---|
actionButton |
Button |
checkboxGroupInput |
Checkbox group |
checkboxInput |
Single checkbox |
dateInput |
Calendar for selecting a single date |
dateRangeInput |
A pair of calendars to choose a time period |
fileInput |
File selection window |
helpText |
Supporting text |
numericInput |
Field to enter a number |
radioButtons |
Radiobuttons group |
selectInput |
Dropdown list |
sliderInput |
Slider |
submitButton |
Submit button |
textInput |
Field to enter a text |
Each of the above functions may require several arguments, including two:
""
.The remaining arguments depend on the specific widget and include, for example, initial values or ranges. The following code calls the application from the image above.
library(shiny)
# Define UI
ui <- fluidPage(
titlePanel("Basic widgets"),
fluidRow(
column(3,
h3("Buttons"),
actionButton("action", "Action"),
br(),
br(),
submitButton("Submit")),
column(3,
h3("Single checkbox"),
checkboxInput("checkbox", "Choice A", value = TRUE)),
column(3,
checkboxGroupInput("checkGroup",
h3("Checkbox group"),
choices = list("Choice 1" = 1,
"Choice 2" = 2,
"Choice 3" = 3),
selected = 1)),
column(3,
dateInput("date",
h3("Date input"),
value = "2014-01-01"))
),
fluidRow(
column(3,
dateRangeInput("dates", h3("Date range"))),
column(3,
fileInput("file", h3("File input"))),
column(3,
h3("Help text"),
helpText("Note: help text isn't a true widget,",
"but it provides an easy way to add text to",
"accompany other widgets.")),
column(3,
numericInput("num",
h3("Numeric input"),
value = 1))
),
fluidRow(
column(3,
radioButtons("radio", h3("Radio buttons"),
choices = list("Choice 1" = 1, "Choice 2" = 2,
"Choice 3" = 3),selected = 1)),
column(3,
selectInput("select", h3("Select box"),
choices = list("Choice 1" = 1, "Choice 2" = 2,
"Choice 3" = 3), selected = 1)),
column(3,
sliderInput("slider1", h3("Sliders"),
min = 0, max = 100, value = 50),
sliderInput("slider2", "",
min = 0, max = 100, value = c(25, 75))
),
column(3,
textInput("text", h3("Text input"),
value = "Enter text..."))
)
)
# Define server logic
server <- function (input, output) {
}
# Run the app
shinyApp (ui = ui, server = server)
All controls and their code have been collected in the Shiny Widgets Gallery.
Placing widgets in the user interface is only the first of three
steps to building a fully functional application. Values from the
controls are stored in a special input
list, from where
they can be retrieved inside the server()
function. There,
they are used in functions from the render*()
family, which
generate charts, tables, images, etc. What the render*()
functions return must be saved to a special output
list and
then used in functions from the *Output()
family, which are
placed inside ui()
, most often in the main application
panel.
Let’s carefully follow all the steps that need to be taken to create a working Shiny application.
This step was discussed above in the section Widgets.
An example implementation of this step might look like this:
server <- function(input, output) {
output$selected_var <- renderText({
paste("You have selected", input$var)
})
}
In the above example, the value from the var
control is
used inside the renderText
function, and the created object
is added to the output
list under the name
selected_var
. Shiny will automatically make the object
reactive if it uses a value stored in the input
list.
Whenever the user changes the value of the control, Shiny will call the
rendering function to update the object. Other rendering functions:
Rendering function | Returned object |
---|---|
renderDataTable |
DataTable |
renderImage |
Image |
renderPlot |
Plot |
renderPrint |
Content to print or save to a file |
renderTable |
Data frame, matrix, other tabular structures |
renderText |
Text |
renderUI |
Shiny or HTML tag |
Referring to the example from the previous step, we can add the
textOutput()
function to the main panel:
Each *Output()
function takes a single string argument,
the name of the reactive element given inside the server()
function. In the example above, it is selected_var
. List of
all functions from the *Output()
family:
Function | Displayed object |
---|---|
dataTableOutput |
DataTable |
imageOutput |
Image |
plotOutput |
Plot |
htmlOutput |
Raw HTML code |
tableOutput |
Data frame, matrix, other tabular structures |
textOutput |
Text |
verbatimTextOutput |
Text |
Sample application containing the above code snippets:
library(shiny)
ui <- fluidPage(
titlePanel("censusVis"),
sidebarLayout(
sidebarPanel(
helpText("Create demographic maps with
information from the 2010 US Census."),
selectInput("var",
label = "Choose a variable to display",
choices = c("Percent White",
"Percent Black",
"Percent Hispanic",
"Percent Asian"),
selected = "Percent White"),
sliderInput("range",
label = "Range of interest:",
min = 0, max = 100, value = c(0, 100))
),
mainPanel(
textOutput("selected_var"),
textOutput("min_max")
)
)
)
server <- function(input, output) {
output$selected_var <- renderText({
paste("You have selected", input$var)
})
output$min_max <- renderText({
paste("You have chosen a range that goes from",
input$range[1], "to", input$range[2])
})
}
shinyApp(ui, server)
Other R packages, built-in or external data sets, or finally, our
functions are often used to build applications. It is crucial where in
the app.R
script we place the code responsible for this.
Three places in the code differ significantly in execution
frequency:
ui()
and server()
functions is always executed only once after starting the application on
the server,server()
function is run every time a new user
connects to the application,render*()
functions runs whenever
the user changes the value of the associated widget.Therefore, the best place to place code to be executed only once is
at the beginning of the app.R
script, preferably before the
ui()
function. Additionally, suppose we use multiple
proprietary functions to avoid unnecessarily lengthening the
app.R
script. In that case, placing them in an external
script in the same folder as app.R
is best. We run such an
additional script with the source()
command.
If the application needs to read data from a file, placing it in the
data
subfolder is best.
We enable additional packages in the normal way, preferably
immediately after calling the shiny
package. This is what
the app.R
script looks like using additional packages,
proprietary functions, and an external data set.
library(shiny)
library(maps)
library(mapproj)
source("helpers.R")
counties <- readRDS("data/counties.rds")
ui <- fluidPage(
titlePanel("censusVis"),
sidebarLayout (
sidebarPanel (
helpText ("Create demographic maps with
information from the 2010 US Census."
),
selectInput ("var",
label = "Choose a variable to display",
choices = c("Percent White", "Percent Black",
"Percent Hispanic", "Percent Asian"),
selected = "Percent White"),
sliderInput ("range",
label = "Range of interest:",
min = 0, max = 100, value = c(0, 100))
),
mainPanel (
plotOutput ("map")
)
)
)
server <- function(input, output) {
output$map <- renderPlot({
data <- switch (input$var,
"Percent White" = counties$white,
"Percent Black" = counties$black,
"Percent Hispanic" = counties$hispanic,
"Percent Asian" = counties$asian)
color <- switch (input$var,
"Percent White" = "darkgreen",
"Percent Black" = "black",
"Percent Hispanic" = "darkorange",
"Percent Asian" = "darkviolet")
legend <- switch (input$var,
"Percent White" = "% White",
"Percent Black" = "% Black",
"Percent Hispanic" = "% Hispanic",
"Percent Asian" = "% Asian")
percent_map (var = data,
color = color,
legend.title = legend,
max = input$range[2],
min = input$range[1])
})
}
shinyApp (ui = ui, server = server)
The switch()
function can easily convert the output
value from a control to anything else. Here, it turns a text into a
vector from a data frame, color, or other text.
Shiny applications respond quickly to user commands, but what if the
application sometimes has to perform many tedious calculations? Using
reactive expressions, we can precisely control which elements of the
application should be updated at a given moment, thus avoiding
unnecessary calculations that slow down the application. An example is
the application below, located in the stockVis
folder.
# Load packages ----
library(shiny)
library(quantmod)
# Source helpers ----
source("helpers.R")
# User interface ----
ui <- fluidPage(
titlePanel("stockVis"),
sidebarLayout(
sidebarPanel(
helpText("Select a stock to examine.
Information will be collected from Yahoo finance."),
textInput("symb", "Symbol", "SPY"),
dateRangeInput("dates",
"Date range",
start = "2013-01-01",
end = as.character(Sys.Date())),
br(),
br(),
checkboxInput("log", "Plot y axis on log scale",
value = FALSE),
checkboxInput("adjust",
"Adjust prices for inflation", value = FALSE)
),
mainPanel(plotOutput("plot"))
)
)
# Server logic
server <- function(input, output) {
output$plot <- renderPlot({
data <- getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
chartSeries(data, theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
}
# Run the app
shinyApp(ui, server)
The application in the form above is not fully functional. In
particular, the Adjust prices for inflation checkbox does not
work. The application relies mainly on two functions from the
quantmod
package:
it uses getSymbols()
to retrieve data directly from
websites such as Yahoo Finance
and Federal Reserve Bank
of St. Louis,
by using the chartSeries()
function, prices are
displayed transparently.
An adjust()
function in the helpers.R
script corrects the data for inflation.
if (!exists(".inflation")) {
.inflation <- getSymbols('CPIAUCNS', src = 'FRED',
auto.assign = FALSE)
}
# adjusts Google finance data with the monthly consumer price index
# values provided by the Federal Reserve of St. Louis
# historical prices are returned in present values
adjust <- function(data) {
latestcpi <- last(.inflation)[[1]]
inf.latest <- time(last(.inflation))
months <- split(data)
adjust_month <- function(month) {
date <- substr(min(time(month[1]), inf.latest), 1, 7)
coredata(month) * latestcpi / .inflation[date][[1]]
}
adjs <- lapply(months, adjust_month)
adj <- do.call("rbind", adjs)
axts <- xts(adj, order.by = time(data))
axts[ , 5] <- Vo(data)
axts
}
However, there is an optimization problem here. Each time the user
checks the Plot y axis on log scale checkbox, all contents of
the renderPlot()
function must be executed, meaning the
data is re-downloaded. This is unnecessary because the user did not want
new data but only wanted to change the presentation of already
downloaded data. The answer to this problem is the
reactive()
function, which, similarly to functions from the
render*()
family, will be executed every time the user
changes the value of the control. We can place any expression or
multiple expressions inside the reactive()
function. What
reactive()
returns is called a reactive expression
and is executed in the same way as a regular function. This is what the
improved server()
function will look like for the above
example:
server <- function(input, output) {
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
output$plot <- renderPlot({
chartSeries(dataInput(), theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
}
It may seem that the above change won’t do much because the
dataInput
expression is called every time inside
renderPlot
. Does this mean that the data will be downloaded
every time anyway? No, because a reactive expression is smarter than a
function and remembers the last value returned. He also knows whether
its value is still valid. Regarding aesthetic changes to the chart, the
dataInput
expression does not change its value and will
return its last value without executing any code. What if we change the
value of input$symp
, used in the reactive expression but
not in the rendering function? Will Shiny know that after changing this
control, not only dataInput
but also
renderPlot
should be called? Yes, it will be. Shiny knows
which values in the output
list depend on which reactive
expressions and when to update them.
Now, let’s try to fix the Adjust prices for inflation checkbox. A possible solution is below, but it has a drawback.
server <- function (input, output) {
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
output$plot <- renderPlot({
data <- dataInput()
if (input$adjust) data <- adjust(dataInput())
chartSeries(data, theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
}
Calling the adjust()
function inside
renderPlot()
will cause this function to execute every time
the user changes the y-axis to logarithmic. Again, this can be avoided
by using a regular expression.
server <- function(input, output) {
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
finalInput <- reactive({
if (!input$adjust) return(dataInput())
adjust(dataInput())
})
output$plot <- renderPlot({
chartSeries(finalInput(), theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
}
After writing the application, we would naturally like to present it
to the world. If the application users have R and Shiny installed, pack
the archive with the script and additional files (for example, the
www
or data
folder) and send it the standard
way. After unpacking, the application will be immediately ready to run,
like when working on it, i.e., with the
runApp (path_to_dir)
command or the button in RStudio.
Additionally, R has three built-in commands that make it easy to run
applications directly from the hosting server:
runUrl ("<weblink>"
- will work for
applications compressed into a zip archive. The command downloads,
unpacks, and runs the application.
runGitHub ("<your repository name>","<your user name>")
- downloads and runs the application posted on GitHub.
runGist ("<gist number>")
- downloads and runs
an application posted anonymously on GitHub Gist. The
<gist number>
argument means the number at the end of
the page URL where the project is placed. For example:
Most often, however, users who want to use our application do not have R and Shiny installed. In this case, we must make our application available as a website. We can do this in one of three ways:
Via the shinyapps.io portal - this is the simplest option and requires the least effort. This method is discussed in more detail below.
Using Shiny Server. It is a free, open-source program on GitHub that allows you to run a server hosting the Shiny application on your computer.
RStudio Connect is a commercial solution for companies. A server enables remote work in R for employees, sharing reports, creating applications, and authentication.
rsconnect
package.Create an account on the website shinyapps.io. It is possible to use a Google or GitHub account.
Configure rsconnect
. This process takes place in two
steps:
setAccountInfo
function to
connect rsconnect
to your shinyapps.io account.deployApp()
function. Alternatively, you can use the
Publish button in RStudio in the upper right corner of the running
application. After sending the application, your browser should
automatically open a window with the application running on the
server.For more information on working with shinyapps.io, please refer to the user manual.
Visit https://shiny.rstudio.com/tutorial for further steps in improving your application writing skills in Shiny. The intermediate course can be found here. In addition to the course, the website includes a link to a free book, videos on YouTube and additional workshops conducted by the most renowned Shiny developers.