Chapter 23 Shiny

This wonderful section on Shiny was co-written by Christian Luz (c.f.luz@umcg.nl)

Shiny is an R package that makes it easy to build interactive web apps straight from R. Shiny apps can be run in RStudio, embedded in RMarkdown documents, or deployed to a web server (e.g. shinyapps.io). It is a great way to interact with your data. The functionality and creativity is almost endless (check https://shiny.rstudio.com/gallery/). See also: https://www.showmeshiny.com/.

For today’s course we will build a small first shiny app to explore data from the gapminder package. Remember this code?

NL <- gapminder %>% filter(country == "Netherlands")

And this one?

ggplot(gapminder, aes(x = year, y = lifeExp)) +
  geom_line(aes(group = country)) +
  geom_line(stat = "summary", fun = "mean", colour = "blue", size = 2) +
  geom_line(data = NL, colour = "orange")

Wouldn’t it be great to have a small app to explore this data with a few simple clicks? You could also immediately communicate your findings with non-coders not as knowledgeable in R like you are now.

23.1 Creating the shiny app framework

First we need to download the shiny package.

install.packages("shiny")

RStudio has an easy way to initialize a shiny app. Go to File > New File > Shiny Web App… , give your app a name and select Multiple Files. Every shiny app exist of two components: the “front-end” ui.R where you define the design of your app and the “back-end” server.R where you code all your calculations, plots etc. These two components communicate with each other in a reactive way. That means they pass information from the ui (input) to the server and back (output). This fundamental difference to “normal” R scripts also requires us to write slightly different code.

When you have created the shiny template in RStudio you should have two open R scripts. Now delete all their content! :) (You could of course also just create two empty scripts and name them server.R and ui.R)

Like in every R script we need to load the packages we are going to use. Most importantly the shiny package. We also need to do this for every file (ui and server) separately.

library(shiny)
library(dplyr) # data transformation
library(ggplot2) # plotting
library(gapminder) # data source

# remember to load packages in each file: ui.R and server.R

Next we are defining the ui layout:

shinyUI(
    fluidPage(
      # here we are going to add our UI elements like checkboxes, sliders, plots, and tables
    )
)

Followed by preparing the server side (server.R):

shinyServer(function(input, output) {
  # this is were we are going to code all data transformation and output
})

Test the result by clicking the run app button on the top of one of the scripts or by running runApp("name_of_your_app") from the console.

Congrats! You have just built your first shiny app! (it’s empty though…)

23.2 Creating some content

We start with creating a typical gapminder plot on the server side of the app. We can just copy the code from above, but this time we want to send the output of the plot to the ui. We therefore need to create an output object output$plot. This object contains a call to render our plot. We use the renderPlot function from the shiny package. It takes an expression as first argument which will be our ggplot code.

# this is how your server side should look like
shinyServer(function(input, output) {
    
    NL <- gapminder %>% filter(country == "Netherlands")
    
    output$plot <- renderPlot(
        ggplot(gapminder, aes(x = year, y = lifeExp)) +
            geom_line(aes(group = country)) +
            geom_line(
              stat = "summary", fun = "mean", 
              colour = "blue", size = 2
            ) +
            geom_line(data = NL, colour = "orange")
    )
})

On the ui side we need a call to display this output in our app.

shinyUI(
  fluidPage(
    plotOutput("plot") # name of the plot as define by output$plot
  )
)

Great! Try to run the app again. You should be seeing the plot from above in your app. Just a static plot - let’s add some interactivity.

23.3 Reactive input

Before we interact with the data we are going to structure the design of our app. Typically, a web app or website contains some sort of sidebar and a main panel to display some content. The shiny package comes with two functions for that: sidebarLayout and mainPanel. Within the sidebar we also add a sidebar panel (sidebarPanel). This panel will list all our input functions. We also wrap our output in the mainPanel function.

shinyUI(
    fluidPage(
        sidebarLayout(
            sidebarPanel(
              # this is were we are going to define the input
            ),
            mainPanel(
                plotOutput("plot")
            )
        )
    )
)

Empty sidebar, time to add some input widgets (https://shiny.rstudio.com/tutorial/written-tutorial/lesson3/). This is where the creativity really begins. Let’s say we want to play around with different countries in our plot. We want some form of control over the country variable in our plot. The select box could be a good choice (selectInput). Add it to the sidebar panel:

# shinyUI(
#     fluidPage(
#         sidebarLayout(
#             sidebarPanel(
              selectInput(inputId = "country", # essential! give the input a name that you will need on the server side
                        label = "Select country", # text displayed in your app
                        choices = unique(gapminder$country), # input to choose from, here all gapminder countries
                        selected = "Netherlands") # define default
#             ),
#             mainPanel(
#                 plotOutput("plot")
#             )
#         )
#     )
# )

Looks good! Now we need to go back to the server side and make use of this input selection. The value of the selected input can be used on the server side by referring to with input$variable where variable is the inputId we defined on the ui side. So, for our app that is input$country.

Now, it gets a bit more tricky. In order to interpret this information on the server side we need to tell the server that this is reactive input. That means the input is dynamic and can change. We need to wrap the filter operation in the reactive function from the shiny package and replace "Netherlands" with the new input:

NL <- reactive(
  gapminder %>% 
    filter(country == input$country)
)

This will transform NL to a reactive object. From now on the ggplot function will no longer recognize NL. Any call for the new reactive object will need to be made like this: NL() (basically looks like a function now). Let’s change the plotting code.

We also change the plot a bit to better highlight the selected country.

    output$plot <- renderPlot(
        ggplot(gapminder, aes(x = year, y = lifeExp)) +
          geom_line(aes(group = country), colour = "lightgrey") +
          geom_line(data = NL(), colour = "red", size = 1.5) + 
          theme_minimal()
    )

Perfect! That’s an interactive shiny app!

Now, we are ready to add more functionality. How about some radio buttons (radioButtons) to select different variables? Just add this to your sidebar panel (don’t forget a comma to separate the input functions).

radioButtons(
  inputId = "variable",
  label = "Pick parameter",
  choiceNames = c("Life expectancy", "Population", "GDP per capita"), # display options
  choiceValues = c("lifeExp", "pop", "gdpPercap") # actual values passed to the server
)

Try to change the plotting code to use input$variable instead of lifeExp. Does it work?

No?

That’s tricky. We also need to change the aes to be able to use reactive variables. This does the trick (aes_string):

ggplot(gapminder, aes_string(x = "year", y = input$variable)) +

23.4 Bonus

From here you can go on and play around with shiny and make use of its enormous potential. Keep it simple or blow it up to something like this: https://ceefluz.shinyapps.io/radar/

Below some add-on code for this little gapminder app.

library(shiny)
library(gapminder)
library(DT)

shinyUI(
    fluidPage(

    # Application title
    titlePanel("SHINY GAPMINDER"),
    # Sidebar
    sidebarLayout(
        sidebarPanel(
            selectInput(inputId = "country",
                        label = "Select country",
                        choices = unique(gapminder$country),
                        multiple = TRUE,
                        selected = "Netherlands"),
            radioButtons(inputId = "variable",
                         label = "Pick parameter",
                         choiceNames = c("Life expectancy", "Population", "GDP per capita"),
                         choiceValues = c("lifeExp", "pop", "gdpPercap"))
        ),
        # Output
        mainPanel(
            plotOutput("plot"),
            checkboxInput(inputId = "continent",
                          label = "Show continents",
                          value = FALSE),
            dataTableOutput("table")
        )
    )
)
)
library(shiny)
library(tidyverse)
library(gapminder)
library(DT)

shinyServer(function(input, output) {
    
    NL <- reactive(
        gapminder %>% 
            filter(country == input$country)
    )

    output$plot <- renderPlot(
        ggplot(gapminder, aes_string("year", input$variable, 
                                     group = "continent", fill = "continent")) +
            {if(input$continent == TRUE) {
                geom_smooth(method = "lm", alpha = 0.3, colour = "lightgrey")
            } else {
                NULL
            }} +
            scale_fill_manual(values = c("#D8B70A", "#02401B", "#A2A475", 
                                         "#81A88D", "#972D15")) +
            geom_line(data = NL(), 
                      aes_string("year", input$variable, 
                                 group = "country", colour = "country"), 
                      size = 1.5) +
            scale_colour_manual(values = c("red", "orange", "blue")) +
            labs(fill = "Continent",
                 colour = "Selected country") +
            theme_minimal()
    )
    
    output$table <- renderDataTable(
        NL()
    )

})