shiny
101- Building a
shiny
app - The user interface (UI)
- The server
- Take-aways
08-12-2021
shiny
101shiny
appshiny
?R
Markdown document (like this one!);R
workflows:
shiny
?R
package for building interactive web apps.R
users who have zero experience with web development;shiny
app?We have two options:
A. Create a file called app.R
and add shiny
components (file name and components are non-negotiable!)
shiny
app need?You can see this in the default shiny
structure:
library(shiny) ui <- fluidPage() server <- function(input, output) {} shinyApp(ui = ui, server = server)
Data on products sold by BC Liquor Store (source: OpenDataBC).
## Rows: 6,132 ## Columns: 7 ## $ Type <chr> "WINE", "WINE", "WINE", "WINE", "WINE", "WINE", "WINE"~ ## $ Subtype <chr> "TABLE WINE RED", "TABLE WINE WHITE", "TABLE WINE RED"~ ## $ Country <chr> "CANADA", "CANADA", "CANADA", "CANADA", "UNITED STATES~ ## $ Name <chr> "COPPER MOON - MALBEC", "DOMAINE D'OR - DRY", "SOMMET ~ ## $ Alcohol_Content <dbl> 14.0, 11.5, 12.0, 11.0, 13.5, 11.0, 12.5, 12.0, 11.5, ~ ## $ Price <dbl> 30.99, 32.99, 29.99, 33.99, 36.99, 34.99, 119.00, 32.9~ ## $ Sweetness <int> 0, 0, 0, 1, 0, 0, 0, 0, 0, NA, 0, NA, NA, 0, 0, 2, 0, ~
We’re going to create a shiny
app to visualize these data interactively.
Let’s start with the empty shiny
structure:
library(shiny) ui <- fluidPage() server <- function(input, output) {} shinyApp(ui = ui, server = server)
R
can find the data for your app (e.g., load it on the server side, or let the user upload a dataset)!ui <- fluidPage( titlePanel("BC Liquor Store prices") )
The shiny
function titlePanel()
To render text in our app, we can just add character/string objects inside fluidPage()
:
ui <- fluidPage( titlePanel("BC Liquor Store prices"), "BC Liquor Store", "prices" )
For formatted text, shiny
has many functions that are wrappers around HTML tags. For example:
h1()
: top-level header;h2()
: secondary header;strong()
: bold text;em()
: italicized text;br()
: line break;img()
: image;a()
: hyperlink, etc.Let’s replace the UI part of our code with the following:
ui <- fluidPage( titlePanel("BC Liquor Store prices"), h2("BC"), "Liquor", br(), em("Store"), strong("prices") )
The simple sidebar layout:
We’ll replace the formatted text by a sidebar layout:
ui <- fluidPage( titlePanel("BC Liquor Store prices"), sidebarLayout( sidebarPanel("[inputs]"), mainPanel("[outputs]") ) )
Inputs allow users to interact with a shiny
app.
We’ve seen two types already:
selectInput()
creates a dropdown menu (e.g., number of bins in the template app);sliderInput()
creates a numeric scale (e.g., bandwidth adjustment in the template app).Can you guess what kind of element these input functions will create?
textInput()
;dateInput()
;checkboxInput()
.
If we want to add an input element for the variable price in our app, which function should be use?
radioButtons()
: choosing a specific number;sliderInput()
: choosing a range of values on the slider.sliderInput( inputId = "priceInput", label = "Price", min = 0, max = 100, value = c(25, 40), pre = "$" )
All input functions have the same first two arguments:
inputId
, the name by which shiny
will refer to this input when you want to retrieve its current value;label
, which specifies the text displayed right above the input element.These argument names are typically dropped from the ...Input()
function call:
sliderInput("priceInput", "Price", min = 0, max = 100, value = c(25, 40), pre = "$")
inputId
; the app will not work properly otherwise! So keep your inputId
s simple and sensible.The resulting UI code looks like:
ui <- fluidPage( titlePanel("BC Liquor Store prices"), sidebarLayout( sidebarPanel( sliderInput("priceInput", "Price", min = 0, max = 100, value = c(25, 40), pre = "$") ), mainPanel("[outputs]") ) )
Let’s create input elements for the variables country and product type as well. Which input function(s) should we use if we want to restrict the user to only a few choices?
We’ll use a dropdown list for the countries Canada, France and Italy with selectInput()
:
selectInput("countryInput", "Country", choices = c("CANADA", "FRANCE", "ITALY"))
And for product type, we’ll specify choices with radioButtons()
:
radioButtons("typeInput", "Product type", choices = c("BEER", "REFRESHMENT", "SPIRITS", "WINE"), selected = "WINE")
The full UI code is now:
ui <- fluidPage( titlePanel("BC Liquor Store prices"), sidebarLayout( sidebarPanel( sliderInput("priceInput", "Price", 0, 100, c(25, 40), pre = "$"), radioButtons("typeInput", "Product type", choices = c("BEER", "REFRESHMENT", "SPIRITS", "WINE"), selected = "WINE"), selectInput("countryInput", "Country", choices = c("CANADA", "FRANCE", "ITALY")) ), mainPanel("[outputs]") ) )
Outputs are shown in the UI, but created on the server side.
That’s why we add placeholders for the outputs in the UI.
Placeholders:
Let’s add a figure as output in our app:
mainPanel( plotOutput("coolplot") )
The placeholder doesn’t show anything, because we haven’t created any figure yet on the server side.
But first, let’s add another output element:
mainPanel( plotOutput("coolplot"), br(), br(), tableOutput("our_table") )
br()
between the two outputs, so that they aren’t crammed on top of each other.ui <- fluidPage( titlePanel("BC Liquor Store prices"), sidebarLayout( sidebarPanel( sliderInput("priceInput", "Price", 0, 100, c(25, 40), pre = "$"), radioButtons("typeInput", "Product type", choices = c("BEER", "REFRESHMENT", "SPIRITS", "WINE"), selected = "WINE"), selectInput("countryInput", "Country", choices = c("CANADA", "FRANCE", "ITALY")) ), mainPanel( plotOutput("coolplot"), br(), br(), tableOutput("our_table") ) ) )
library(shiny) ui <- fluidPage() server <- function(input, output) {} shinyApp(ui = ui, server = server)
The server function:
render...()
functions;Let’s use the exception to the rule to develop our server step-by-step.
Instead of input from the UI, we’ll use static data to fill in the placeholder plot:
server <- function(input, output) { output$coolplot <- renderPlot({ ggplot() + geom_histogram(aes(x = rnorm(100))) + ggtitle("Histogram of 100 random numbers (static)") }) }
To make the figure interactive, we have to link the server to the UI inputs.
Let’s use the price input to create some random data, interactively:
server <- function(input, output) { output$coolplot <- renderPlot({ ggplot() + geom_histogram(aes(x = rnorm(input$priceInput[2]))) + ggtitle(paste("Histogram of", input$priceInput[2], "random numbers (interactive)")) }) }
So whenever the maximum price input changes, the plot updates and shows the specified number of points.
Now that we’ve seen the basics of interactivity, let’s plot the case study data.
We’ll create a histogram of the percentage alcohol in the beverages.
Ultimately, the plot should match the input elements interactively. But first, we’ll plot a static version (briefly ignoring the inputs again):
server <- function(input, output) { output$coolplot <- renderPlot({ bcl %>% ggplot(aes(Alcohol_Content)) + geom_histogram() + ggtitle("Histogram of alcohol content (static)") }) }
To incorporate interactivity, we’re going to filter the data based on the values of priceInput
, typeInput
, and countryInput
:
server <- function(input, output) { output$coolplot <- renderPlot({ bcl %>% filter(Price >= input$priceInput[1], Price <= input$priceInput[2], Type == input$typeInput, Country == input$countryInput ) %>% ggplot(aes(Alcohol_Content)) + geom_histogram() + ggtitle("Histogram of alcohol content (interactive)") }) }
To complete our app we need to build some output for the table placeholder, and add it to the server:
output$our_table <- renderTable({ bcl %>% filter(Price >= input$priceInput[1], Price <= input$priceInput[2], Type == input$typeInput, Country == input$countryInput ) %>% select(c("Name", "Price", "Type", "Country", "Alcohol_Content")) })
Short break from our app to talk about a crucial concept in shiny
: reactivity.
Reactivity enables your outputs to react to changes in inputs.
On the most basic level, it means that when the value of a variable x
changes, anything that relies on x
(i.e. has x
in it) gets re-evaluated.
Consider the following code
x <- 5 y <- x + 1 x <- 10
What is the value of y
?
What is the value of y
?
x <- 5 y <- x + 1 x <- 10
In ordinary programming, the value of y
is still 6.
In reactive programming, however, x
and y
are reactive expressions. Now, the value of y
updates reactively, and becomes 11.
Reactivity is the foundation for the responsiveness of shiny
apps.
In our server, we implicitly use reactivity when we filter the data for our outputs:
bcl %>% filter(Price >= input$priceInput[1], Price <= input$priceInput[2], Type == input$typeInput, Country == input$countryInput )
Whenever one of the inputs changes, our outputs change with it. But, this part of code is duplicated, because we didn’t use a reactive variable.
We can avoid code duplication by:
render...()
functions.filtered <- reactive({ bcl %>% filter(Price >= input$priceInput[1], Price <= input$priceInput[2], Type == input$typeInput, Country == input$countryInput ) })
What is going on behind the scenes?
shiny
‘looks’ at the reactive(s) that depend on price →filtered()
is re-evaluated →shiny
‘looks’ at the reactive(s) that depend on filtered()
→render...()
functions are re-executed →This can be visualized in a dependency tree, to show what value depends on what other value.
server <- function(input, output) { filtered <- reactive({ bcl %>% filter(Price >= input$priceInput[1], Price <= input$priceInput[2], Type == input$typeInput, Country == input$countryInput ) }) output$coolplot <- renderPlot({ ggplot(filtered(), aes(Alcohol_Content)) + geom_histogram() + ggtitle("Histogram of alcohol content") }) output$our_table <- renderTable({ filtered() %>% select(c("Name", "Price", "Type", "Country", "Alcohol_Content")) }) }
Make your app more advanced:
update...()
functions;plotly
and datatable
.In general:
Before building a shiny
app, think about:
Build the front-end and the back-end separately.
Front-end:
Back-end:
data <- mtcars
instead of data <- reactive(...).
);Deploy your app on shinyapps.io:
If you want your Shiny app to last, you should make it robust:
golem
framework for production-grade shiny
apps (but decide up-front!).Check out these amazing resources:
And look for inspiration here:
shiny
contest;R
package hex stickers.shiny
allows you to build interactive (web) apps from R
;shiny
apps consist of two parts, the user interface (UI) and the server:
shiny
;