R Shiny is a package for R that allows users to create interactive HTML pages that can handle a wide range of applications. Due to the diverse array of applications for R Shiny Apps, there is no way to cover all of these in a single page. However, the goal of this page is to provide a primer on how to start building R Shiny Apps.
- Describe the difference between UI and Server and associated functions within R Shiny
- Create basic R Shiny apps
When creating an app within R Shiny the first place to start is by naming your app. It is a good idea to name your R script app.R
and place it within a directory that has a meaningful name. For example, your directory might be named Hello_world
and within this directory you have the R script, app.R
. This convention will be helpful later when you try to host your app online somewhere.
Next, we need to understand the difference between the user interface (UI) and server. The UI is the HTML rendering of your Shiny App and is referred to as your front-end since it is what users who interact with your app will see. The server is where the inputs from the UI are processed and output to the UI is returned from. The server is oftentimes referred to as the back-end.
The last component of the a Shiny app is the shinyApp
function which launches the app.
A typical Shiny App will have a structure that at bare minimum looks something like:
ui <- fluidPage(
[format]Input("input_variable"),
...
[format]Output("output_variable")
)
server <- function(input, output){
output$output_variable <- render[Format]({
input$input_variable ...
})
}
shinyApp(ui = ui, server = server)
Let's break this down a bit more and talk about each of these components:
First, let's talk about the UI side where everything gets assigned to the ui
object:
fluidPage()
is a function that is going to be used in the rendering of the HTML[format]Input("input_variable")
This is how you will provide your input. The"input_variable"
is called your inputID and this is how you will be calling this input on the server. When naming these inputIDs stay within R variable naming conventions. There are lots of different types of inputs, including:textInput()
This is for taking in a single line of texttextAreaInput()
This is for taking in text in a textbox stylenumericInput()
This is similar totextInput()
, but instead of text, you can provide it a numbersliderInput()
This provides an slider to select valuesselectInput()
This provides a list of values to choose from- Many more
[format]Output("output_variable")
This is your output that will be visualized. The"output_variable"
is your outputID and this is what you will name your output on the server side. Like inputIDs, outputIDs should follow R variable naming convetions. Some common functions here are:textOutput()
This returns text output and is usually paired withrenderText({})
on the server sideverbatimTextOutput()
This returns output styled exactly how output looks in R and is usually paired withrenderPrint({})
on the server sideplotOutput()
This returns a plot and is usually paired withrenderPlot({})
on the server sidedataTableOutput()
This returns a table and is usually paired withrenderDataTable({})
on the server side
Note: The table functions from the
DT
package,DTOutput()
andrenderDT()
, respectively, are the standard functions to use for rendering tables now.
Second, let's talk about the server side where everything is assigned to the server
object:
function(input, output){}
This is always the start to your server object and it opens a function to be processedoutput$output_variable <- render[Format]({})
This is what you are assigning the output to. Therender[Format]({})
function can have several forms:renderText({})
Prints text and is usually paired withtextOutput()
on the UI siderenderPrint({})
Prints text that way it comes from the R console and is usually paired withverbatimTextOutput()
on the UI siderenderPlot({})
Prints a plot and is usually paired withplotOutput()
on the UI siderenderDataTable({})
Prints a table and is usually paired withdataTableOutput()
on the UI side or when using theDT
package,renderDT()
and is usually paired withDTOutput()
on the UI side- The
{}
within therender
family of functions is only needed when there is more than one command within therender
function. However, it is fine to include it when there is only a single command as well. As a result, we will use it in every function so that it is consistent between our differerent apps. input$input_variable ...
This is just saying that we are going to do something with theinput_variable
Lastly, the UI and the server side are tied together and the app is launched with:
shinyApp(ui = ui, server = server)
Let's write a simple app that can help reinforce some of these principles. The goal of this app is simply to return text that you have provided as input. While this is an overly simplistic app, it will demonstrate many of the core ideas that are integral to an R Shiny App. Here is the code we will use:
library(shiny)
ui <- fluidPage(
textInput("input_text", "My input text"),
textOutput("output_text")
)
server <- function(input, output){
output$output_text <- renderText({
input$input_text
})
}
shinyApp(ui = ui, server = server)
Once we have copied and pasted this, we can hit Command + Shift + Return on a Mac or hit "Run App" in the top of your R Studio window. Your app should pop up looking like:
And when you type something in like "Hello World", "Hello World" should appear below:
NOTE: If you look at your console it is currently listening for input and you cannot use your console for other tasks. Before you can do anything else in R, you will need to close the app or hit the stop sign in the console.
Let's discuss what is happening here:
ui <- fluidPage()
Using afluidPage
function to to render an HTML and assigning it to theui
objectinputText("input_text", "My input text"),
This is the input text that we are going to give to the server and it is going to be assigned the variableinput$input_text
. The"My input text"
is what we want to title the text box to be. Also, note the use of a comma at the end of thisinputText()
function. Each function inside of thefluidPage()
function needs to be separated by a comma.outputText("output_text")
This is the output text from the server that we want displayed.
NOTE: The order of
inputText()
andoutputText()
will determine where they are placed on our HTML page. If we hadoutputText()
beforeinputText()
, then our returned text would be above our input text on the rendered HTML page.
-
server <- function(input, output){}
We are opening a function on the server side and assigning it to theserver
object -
output$output_text <- renderText({input$input_text})
Here, we are telling R to render the text output ofinput$input_text
and assign that to the variableoutput$output_text
-
shinyApp(ui = ui, server = server)
Run the Shiny App
The process of this app is outlined in the figure below:
While, we have written a simplistic app here, it has many of the core features that we are interested in utilizing. Armed with this basic understanding of some of the R Shiny syntax, let's tackle a slightly more complex R Shiny App.
Your Second R Shiny App: Using a slider to provide input and then performing an arithmetic function on it
In this app we are going to use a slider to select a value and then have our app return the squared value of it. The code for this app should look like:
library(shiny)
ui <- fluidPage(
sliderInput("number", "Select a number to square", min = 1, max = 10, value = 5),
textOutput("squared_number")
)
server <- function(input, output){
output$squared_number <- renderText({
input$number ** 2
})
}
shinyApp(ui = ui, server = server)
Run the app and the pop-up window should look like:
Let's take a closer look at the new parts to this code:
sliderInput("number", "Select a number to square", min = 1, max = 10, value = 5),
Instead of providing a text input, you are using a slider to gather our input."number"
is what we want the inputID to be,"Select a number to square"
is the title for the slider,min = 1
is setting the minimum value of the slider,max = 10
is setting the maximum value of the slider andvalue = 5
is setting the default value for the slider.textOutput("squared_number")
This is returning the squared number output- The code block below is taking
input$number
and squaring it, then assigning the text to be rendered into theoutput$squared_number
objectoutput$squared_number <- renderText({ input$number ** 2 })
Hopefully, at this point you are becoming slightly more comfortable with the syntax employed by R Shiny! Let's try another example to further our understanding.
Create an R Shiny App that does the following:
- Collects a numeric input
- Collects input from a slider ranging from 1 to 20
- Multiples the numeric input from 1) by the the slider numeric input from 2)
- Returns the product as text
Click here to see the answer
library(shiny)
ui <- fluidPage( numericInput("a", "First number to multiply", 5), sliderInput("b", "Second number to multiply", min = 1, max = 20, value = 5), textOutput("product") )
server <- function(input, output){ output$product <- renderText({ input$a * input$b }) }
shinyApp(ui = ui, server = server)
Your Third R Shiny App: Selecting a value from a dropdown menu for sample size and creating a random normal distribution from this sample size
In this app, we are going to select a value to use as our sample size from a dropdown menu and then have the app return a random normal distribution plot using this value as the sample size parameter. Copy and paste the code below:
library(shiny)
ui <- fluidPage(
selectInput("normal_sample_size", "What should be the sample size?", c(5, 25, 50, 100)),
plotOutput("normal_dist")
)
server <- function(input, output){
output$normal_dist <- renderPlot({
normal_random_numbers <- rnorm(input$normal_sample_size)
hist(normal_random_numbers)
})
}
shinyApp(ui = ui, server = server)
When we run this app is could look like:
Now we can click on the dropdown menu and select a new sample size and the histogram will update. If we were to select 100, then it could look like:
We have added a bit more complexity to this app, so let's take a look at it:
selectInput("normal_sample_size", "What should be the sample size?", c(5, 25, 50, 100)),
TheselectInput()
function allows you to have a dropdown menu with options. We will assign the selected option to the variableinput$normal_sample_size
. The text above the dropdown will be"What should be the sample size?"
and the vector containing the possible options to choose from for the sample size will bec(5, 25, 50, 100)
NOTE: You can also use the
multiple = TRUE
option withinselectInput()
if you would like to be able to select multiple values. For this context it isn't appropriate, but in other contexts it could be.
plotOutput("normal_dist")
Instead of having the output be text, now we are going useplotOutput()
to have the output be a plot and we will be reading in the plot from theoutput$normal_dist
object.- The code block below will use the
rnorm()
function in base R to randomly select a vector of values equal to the length specified frominput$sample_size
and assign it to a variable namednormal_random_numbers
. Then, we will create a histogram using thehist()
function from these random numbers and assign this plot to be rendered as the objectoutput$normal_dist
:output$normal_dist <- renderPlot({ normal_random_numbers <- rnorm(input$normal_sample_size) hist(normal_random_numbers) })
In this app, we are going to select between two possible built-in R data tables for Shiny to render. Importantly, you should use the DT
package when rendering data tables with R Shiny. The DT
package is the recommended package from the Shiny documentation to use when rendering tables.
library(shiny)
library(DT)
ui <- fluidPage(
radioButtons("dataset", "Select dataset", c("iris", "mtcars")),
DTOutput("table")
)
server <- function(input, output){
output$table <- renderDT({
if (input$dataset == "iris"){
iris
}
else if (input$dataset == "mtcars"){
mtcars
}
})
}
shinyApp(ui = ui, server = server)
When we run the app, it should look like:
Additionally, we can toggle the dataset we want and it will update in real-time and if we switched to the mtcars dataset it would look like:
Let's talk about how this app works:
radioButtons("dataset", "Select dataset", c("iris", "mtcars"))
These are going to provide what are called "radio buttons" or the circular buttons that you can select. We are going to have two choices,"iris"
or"mtcars"
. Note here that we are selecting from a character vector. These ARE NOT the actual mtcars and iris datasets!DTOutput("table")
This is the data table outputoutput$table <- renderDT({})
This is going to assign the rendered data table tooutput$table
.- Next, we have a pair of conditional statements:
if (input$dataset == "iris"){
iris
}
else if (input$dataset == "mtcars"){
mtcars
}
Here we are saying if our input$dataset
variable is equal to the "iris"
string, then we call the iris
table. If the input$dataset
is not equal to "iris"
, then we check to see if it is equal to "mtcars"
and if it is, then we call the mtcars
table.
In this app we will introduce checkboxes to select inputs and use an action button to control the reactivity of the app. Previous versions of R Shiny used the eventReactive() function to control actions, such as clicking of an action button. However, in Shiny 1.6 this has changed to using bindEvent(), so we will demonstrate it here.
library(shiny)
ui <- fluidPage(
checkboxGroupInput("values", "Select numbers to sum", c(1:10)),
actionButton("calculate", "Calculate!"),
textOutput("sum")
)
server <- function(input, output){
addition <- reactive(
sum(as.numeric(input$values))
) %>%
bindEvent(input$calculate)
output$sum <- renderText({addition()})
}
shinyApp(ui = ui, server = server)
This app should look like:
We can break apart this
checkboxGroupInput("values", "Select numbers to sum", c(1:10))
This provides checkbox based input, but it will be saved as a character object, so we will need to address this on the server side.actionButton("calculate", "Calculate!")
This creates the action button that can be clicked to execute calculation on the server side.textOutput("sum")
Renders our output sumaddition <- reactive() %>% bindEvent()
This syntax creates a reactive function calledaddition
and it is triggered when thebindEvent()
is triggered. When thebindEvent()
is triggered, which in this case is when the action button is clicked (input$calculate
), then the code withinreactive()
(sum(as.numeric(input$values))
), will be executed.output$sum <- renderText(addition())
Lastly, we will call thisaddition()
function within ourrenderText({})
function and assign it to our output variable.
We can customize our action button a bit using the class option within actionButton()
:
library(shiny)
ui <- fluidPage(
checkboxGroupInput("values", "Select numbers to sum", c(1:10)),
actionButton("calculate", "Calculate!", class = "btn-success"),
textOutput("sum")
)
server <- function(input, output){
addition <- reactive(
sum(as.numeric(input$values))
) %>%
bindEvent(input$calculate)
output$sum <- renderText({addition()})
}
shinyApp(ui = ui, server = server)
Now the app should look like:
To get the green action button, we just needed to add class = "btn-success"
toactionButton()
.
Other values of class that might be of interest are:
btn-warning
Creates an orange buttonbtn-danger
Creates a read buttonbtn-info
Creates a light blue buttonbtn-lg
Creates a larger buttonbtn-sm
Creates a smaller button
You can also combine the color and size options for class by using class = "btn-success btn-lg"
, which will create a larger, green button.
Now we are going to start formatting our UI a bit. In order to minimize the number of moving parts, let's go back to using our third app to do this. Copy and paste the following app:
library(shiny)
ui <- fluidPage(
sidebarPanel(
selectInput("normal_sample_size", "What should be the sample size?", c(5, 25, 50, 100))
),
mainPanel(
plotOutput("normal_dist")
)
)
server <- function(input, output){
output$normal_dist <- renderPlot({
normal_random_numbers <- rnorm(input$normal_sample_size)
hist(normal_random_numbers)
})
}
shinyApp(ui = ui, server = server)
This app should now look like:
You may need to widen the window or view it in a browser in order for it to render with the side panel on the the left side.
A few edits we've made to the UI to create this involves:
sidebarPanel()
This function wraps around the items we want in the sidebar panelmainPanel()
This function wraps around the items we want in the main panel
In the next app we are going to add tabs along the top of our app that allows us different distributions to look at:
library(shiny)
ui <- fluidPage(
navbarPage("Distributions",
tabPanel("Normal",
sidebarPanel(
selectInput("normal_sample_size", "What should be the sample size?", c(5, 25, 50, 100))
),
mainPanel(
plotOutput("normal_dist")
)
),
tabPanel("Uniform",
sidebarPanel(
selectInput("uniform_sample_size", "What should be the sample size?", c(5, 25, 50, 100))
),
mainPanel(
plotOutput("uniform_dist")
)
)
)
)
server <- function(input, output){
output$normal_dist <- renderPlot({
normal_random_numbers <- rnorm(input$normal_sample_size)
hist(normal_random_numbers)
})
output$uniform_dist <- renderPlot({
uniform_random_numbers <- runif(input$uniform_sample_size)
hist(uniform_random_numbers)
})
}
shinyApp(ui = ui, server = server)
This app should look like:
Below we explain the functions we added to this app:
navbarPage()
This function intiates a navigation bar across the top of the page that can be filled with tabs. This function needs to wrap around all of the tabs. The text (in this case"Distributions"
) will be placed on the left of the navigation bar.tabPanel()
This function initiates each tab of the navigation bar and it needs to wrap around everything in the tab. The text, in this case either"Normal"
or"Uniform"
, will be the name of the tab.
We have added symmetrical code for a uniform distribution on the UI-side and server-side.
Shiny has several themes to choose from but we first need to load the shinythemes
library to have them availible to us. We are going to go ahead and add the theme "cerulean" to our app, but you can pick any of the themes:
library(shiny)
library(shinythemes)
ui <- fluidPage(theme = shinytheme("cerulean"),
navbarPage("Distributions",
tabPanel("Normal",
sidebarPanel(
selectInput("normal_sample_size", "What should be the sample size?", c(5, 25, 50, 100))
),
mainPanel(
plotOutput("normal_dist")
)
),
tabPanel("Uniform",
sidebarPanel(
selectInput("uniform_sample_size", "What should be the sample size?", c(5, 25, 50, 100))
),
mainPanel(
plotOutput("uniform_dist")
)
)
)
)
server <- function(input, output){
output$normal_dist <- renderPlot({
normal_random_numbers <- rnorm(input$normal_sample_size)
hist(normal_random_numbers)
})
output$uniform_dist <- renderPlot({
uniform_random_numbers <- runif(input$uniform_sample_size)
hist(uniform_random_numbers)
})
}
shinyApp(ui = ui, server = server)
The app should now look like:
The only alteration to our code to add a theme was adding this line after opening fluidPage()
:
theme = shinytheme("cerulean")
This adds the "cerulean" theme to our app
If you would like to see the app visualized in a browser, you have two choices:
-
From the pop-up window you can left-click the "Open in Browser" button. Be aware that if you close the browser window with the app, you will still need to hit the stop sign above in the Console to stop the app or close the pop-up.
-
Alternatively, you can modify the
shinyApp
command to includeoptions = list(launch.browser = TRUE)
and it will automatically launch the app in your local browser. The full command would look like:
shinyApp(ui = ui, server = server, options = list(launch.browser = TRUE))
In this scenario, you would still need to hit the stop sign button in the console to stop the app from running, even after you've closed the browser window.
Consider the scenario where you want R Shiny to only evaluate some logic after some input has been provided. You could use an action button to accomplish this task as we've discussed before. However, you can also use the req()
function. The 'req()' function checks for required values before an evaluation of logic is done. Let's compare two examples to illustrate this:
library(shiny)
library(tidyverse)
ui <- fluidPage(
textInput("input_text", "What is the sum of 2 + 2?"),
textOutput("output_text"),
)
server <- function(input, output) {
output$output_text <- renderText({
if (input$input_text == "4"){
"Correct!"
}
else ("Wrong!")
})
}
# Run the application
shinyApp(ui = ui, server = server)
In this case, the word "Wrong!" appears by default because R Shiny evaluates the initial state of the input as blank and states that this is not equal to 4, so it returns "Wrong!". However, this is not what we want because the user hasn't even tried to to provide input yet, so stating that they are wrong prematurely seems incorrect. Thus, we will add a req()
function to require that in the input_text actually exists:
library(shiny)
library(tidyverse)
ui <- fluidPage(
textInput("input_text", "What is the sum of 2 + 2?"),
textOutput("output_text"),
)
server <- function(input, output) {
output$output_text <- renderText({
req(input$input_text, cancelOutput = TRUE)
if (input$input_text == "4"){
"Correct!"
}
else ("Wrong!")
})
}
# Run the application
shinyApp(ui = ui, server = server)
Now we can see that R Shiny only returns "Wrong!" or "Correct!" after we have provide the input that is required. However, you can see that once we have initally defined this variable and then deleted the text, the req()
condition is still being meet. Oftentimes, you would not want to use the req()
function in this way, but rather that an initial parameter, like a file being loaded (which we will explore in the next section), has been met.
One great aspect of Shiny Apps is that you can host them on internet for anyone to use. However, you will need a server that allows you to host R Shiny apps.
Currently, HMS is carrying out a pilot program for hosting R Shiny apps here called the Research Data Visualization Platform.
Another option that allows you to do this is shinyapps.io. You will need to make an account in order to host your apps here. A free account lets you host apps with low usage and limits the number of apps you can host. Of course, you can pay if you'd like to allow higher usage and more apps. Once you have your app written in an app.R
Rscript within a directory, you can configure your account and deploy it using these instructions.