Chapter 9 Building Apps

9.1 Summary

This chapter will introduce the development of data visualisation applications using the R-based Shiny web framework. Shiny allows you to rapidly design, build and deploy highly customised and advanced interactive data visualisations using R and RStudio.

9.1.1 Learning Objectives

The learning objectives associated with this chapter are:

  • Understand how data visualisation applications are designed, built and deployed through Shiny and RStudio.
  • Develop your own data visualisation apps using a Shiny framework. This includes the following tasks:
    • Create a Shiny web application project in RStudio
    • Create a ui.R and server.R file
    • Edit and customise the layout and appearance of ui.R
    • Add inputs and plot outputs to ui.R
    • Load packages and perform the statistical computations in server.R required by the application.
    • Map inputs from ui.R to changing parameters in server.R
    • Bundle output in server.R to display as output in ui.R
    • Run and debug your app in RStudio
  • Deploy your app publicly on the shinyapps.io Shiny cloud server

9.2 Apps

Chapter 8 covered the basics of adding functional interactive features to data visualisations using Plotly. However, we learnt that Plotly does not manipulate data and interactivity can be somewhat limited. This chapter will explore the use of Shiny, which allows almost unlimited scope for interactivity when coupled with Plotly.

Data visualisation apps can be incredibly powerful. You only have to look at the amazing Gapminder app to have a sense of what can be achieved. Here is a list of some of the common interactive features that you can deploy using applications:

  • Add slider bars to change parameters used in the data visualisation.
  • Add a play button to transition the data visualisation based on a time variable.
  • Add drop-down menus for the viewer to change the variables used in the visualisations.
  • Add buttons to turn features on or off.
  • Add input boxes for people to add their own code or data.
  • Add multiple tabs that can be selected to change the type of data visualisation displayed.
  • Add selection boxes to choose colour palettes or map additional variables to aesthetics.

Being able to program applications will take your data visualisation skills to the next level. There are many ways to develop applications. However, without strong web-programming skills, many approaches present significant barriers. Fortunately, RStudio has developed Shiny, which allows R coders to jump over this barrier.

9.3 Shiny

Shiny is an open source, web-based framework for rapidly developing powerful R-based applications. Think of it like a mini-version of R sitting on a server in order to drive a user interface. Shiny integrates with RStudio and opens the computational power of R to help you develop cutting-edge interactive data visualisations without the need to understand HTML, CSS and JavaScript. Shiny can run locally through RStudio or be deployed on a Shiny server in order to share with others. As Shiny is web-based, your apps will run on any device with a modern web-browser.

Shiny works by loading a streamlined version of R, with all the required packages to run your app, on a server. You build a user interface that interacts with the server to run statistical computations, manipulate data and create data visualisations. The server then feeds output back into the user interface for the viewer to see.

The best way to learn Shiny is by example. Shiny is very powerful and there is lots to learn. You won’t learn everything all at once. This chapter has been designed for you to learn using examples. However, afterwards, I encourage you to continue learning because it’s really useful and lots of fun. The Shiny website has many great resources.

Have a go at the following examples first and then get stuck into your own apps. You always learn more working on your own projects.

9.4 shinyapps.io

Shinyapp.io provides cloud-based Shiny servers to host your apps. A free account is limited to 5 apps and 25 active hours. You can sign-up for a free account here. You will need this service to host your apps in the cloud. Alternatively, you can deply your own Shiny Server.

9.5 Building Your First App

The following slideshow will take you through the steps of developing your first app and hosting it in the cloud. Later sections contain all the code you need to follow along and replicate the example. You can view a downloadable version of the presentation here.

9.6 Histograms App

Here is the Histograms app covered in the slideshow.

The ui.R and server.R code for the Histogram app is shown below:

9.6.1 ui.R

# Histograms Shiny App

library(shiny)

shinyUI(fluidPage(
  
  # App title
  titlePanel("Histograms - Exploring the Effect of Sample Size and Bin Number"),
  
  # Sidebar with a sample button and slider input for number of bins
  sidebarLayout(
    
    sidebarPanel(
      
      p("Select a sample size to randomly sample. 
        Click sample to draw. If you draw multiple samples 
        of the same sampl size, you can see how histograms 
        can vary from sample to sample."),
      
      sliderInput("n",
                  "Sample size:",
                  min = 10,
                  max = 10000,
                  value = 5),
      
      actionButton("SampleButton", "Sample"),
      
      p("Adjust the number of bins to see how 
        it impacts the histogram's appearance."),
      
      sliderInput("bins",
                  "Number of bins:",
                  min = 1,
                  max = 100,
                  value = 30)
    ),
    
    # Show a plot of the generated distribution
    
    mainPanel(
      
      p("The red line is the target population 
distribution and the blue line is 
the sample density estimate."),

plotOutput("distPlot"),

p("Histograms perform best for large samples that can support many bins.")
    )
  )
))

9.6.2 server.R

# Histograms Shiny App

library(shiny)
library(ggplot2)

shinyServer(function(input, output) {
  
  # Monitor 'Sample' button. When pressed, generate a random sample of normal data
  # based on selected sample size from slider input.
  
  df <- eventReactive(input$SampleButton, {
    data.frame(x=rnorm(input$n,0,1))
  })
  
  # Using random data, and slider input for number of histogram bins, generate 
  # histogram. Overlay theoretical and density estimate of distribution
  
  output$distPlot <- renderPlot({
    m <- ggplot(df(), aes(x=x))
    m + geom_histogram(bins=input$bins,aes(y = ..density..),colour = "white") + 
      geom_density(colour="blue") +
      stat_function(fun = dnorm, colour = "red") +
      scale_x_continuous(limits=c(-3,3)) + scale_y_continuous(limits=c(0,1))
  })
  
})

9.7 Population Pyramid App

The following app will replicate the basic functionality of the Australian Bureau of Statistics (ABS) Population Pyramid app. The app shows how the age and gender distribution of the Australian population has grown and changed over time.

9.7.1 Data

The aus_age_gender_hist dataset was extracted from the ABS catalogue number 3101.0 - Australian Demographic Statistics, Sep 2015 data cubes. The dataset contains the frequency distribution of Australia by age category and gender from 1921 to 2011.

Here is a preview of the dataset.

Here is the Population Pyramid app:

Here are the ui.R and server.R code.

9.7.2 ui.R

# Australian Population Pyramid

# Load required packages
library(shiny)

shinyUI(fluidPage(
  
  # Application title
  titlePanel("Population Pyramid - Australia 1921 - 2011"),
  
  # Sidebar with a slider input for year
  sidebarPanel(
    sliderInput("year", label = "Year", min = 1921, sep="", 
                max = 2011, value = 1921, 
                animate = animationOptions(interval = 500, loop = TRUE))
  ), 
  
  # Population Pyramid
  mainPanel(
    plotOutput("pyramid")
  )
))

9.7.3 server.R

# Australian Population Pyramid

# Load required packages

library(shiny)
library(ggplot2)
library(dplyr)
library(tidyr)
library(stringr)

# Thanks to https://rpubs.com/walkerke/pyramids_ggplot2!
# Import data
ABS <- read.csv("data/aus_age_gender_hist.csv",stringsAsFactors = TRUE)

# Label Age_cat factor
ABS$Age_Cat<- factor(ABS$Age_Cat,
                     labels = c("0 to 4","5 to 9","10 to 14",
                                "15 to 19","20 to 24","25 to 29",
                                "30 to 34","35 to 39","40 to 44", 
                                "45 to 49","50 to 54","55 to 59",
                                "60 to 64","65 to 69","70 to 74",
                                "75 to 79","80 to 84","85 and over"))

# Convert ABS to long format
ABS_long <- gather(ABS,"Year","Population",X1901:X2011)

#Strip x from "x1901" in year variable

ABS_long$Year <- as.numeric(str_replace(ABS_long$Year,"\\w",""))

# Now we define the shinyServer function

shinyServer(function(input, output) {
  
  output$pyramid <- renderPlot({
    p1 <- ggplot(ABS_long, aes(x = Age_Cat, y = Population, fill = Gender))
    p1 + geom_bar(data = filter(ABS_long,Gender == "Females" & Year == input$year), 
                  stat = "identity") +
      geom_bar(data = filter(ABS_long,Gender == "Males" & Year == input$year),
               aes(y=Population*(-1)), stat = "identity") +
      scale_y_continuous(breaks = seq(-1000000, 1000000, 250000), 
                         limits = c(-1000000, 1000000),
                         labels = paste0(as.character(abs(seq(-1, 1,.25))), "M")) +
      labs(x = "Age Category", y = "Population (Million)")+
      coord_flip()
  })
})

9.8 Shiny Apps II

In the following sections, we will look at improving coding efficiency, improving appearance and adding advanced features to Shiny apps. Specifically, we will consider the following:

  • Single file apps
  • Building a ui quickly
  • Tabsets and navbars
  • Plotly and Shiny
  • Reactivity
  • Upload data and preview data tables
  • Shiny themes

You will need to install the relevant packages and run the apps in RStudio. Study each example closely and ensure you understand the gist of the code.

9.8.1 Single File Apps

Shiny apps can be created as a single file named app.R. For small apps, this can make it easier to code and share. However, with larger apps, the separation of the ui.R and server.R files may be more efficient. Here is an example of a single file app:

## Single file Shiny app.R

# Load packages and prepare data

library(ggplot2)
library(shiny)
library(dplyr)
library(lubridate)

texas <- ggplot2::txhousing

texas_sum <- txhousing %>% group_by(year,month) %>% 
  summarise(sales = sum(sales,na.rm = TRUE),
            volume = sum(volume,na.rm = TRUE),
            median = sum(median,na.rm = TRUE),
            listings = sum(listings,na.rm = TRUE),
            inventory = sum(inventory,na.rm = TRUE))

texas_sum <- texas_sum %>% ungroup()

texas_sum$date <- ymd(paste0(texas_sum$year,"-",texas_sum$month,"-1"))

# Assign server function

server <- function(input, output) {
  output$distPlot <- renderPlot({
   ggplot(data = texas_sum, aes(x = date, y = get(input$var))) +
      geom_line() + labs(y = input$var)
  })
}

# Create ui

ui <- fluidPage(
  titlePanel("Texas Housing Data"),
  sidebarLayout(
    sidebarPanel(
      selectInput("var", "Variable", 
                  colnames(texas_sum[3:7]), selected = colnames(texas_sum[3]))
    ),
    mainPanel(plotOutput("distPlot"))
  )
)

# Deploy app

shinyApp(ui = ui, server = server)

9.8.2 Building a ui Quickly

Building a ui can be tedious. However, there are shortcuts. We can use a data.frame’s colnames or a factor’s levels to create lists that can be fed into to the ui. For example, if we wanted a list of factors included in the MGP dataset, we could write:

MPG <- ggplot2::mpg
names(MPG)[ sapply(MPG, is.character) | sapply(MPG, is.factor) ]
## [1] "manufacturer" "model"        "trans"        "drv"          "fl"          
## [6] "class"

Using this list, we can automatically label widgets in the ui.

# Load packages and prepare data

library(ggplot2)
library(shiny)

MPG <- ggplot2::mpg

MPG$year <- MPG$year %>% as.factor()
MPG$cyl <- MPG$cyl %>% as.factor()

# Generate variables lists for ui

factors <- names(MPG)[ sapply(MPG, is.character) | sapply(MPG, is.factor) ]
quantitative <- names(MPG)[ sapply(MPG, is.numeric)]

# Assign server function

server2 <- function(input, output) {
  output$distPlot <- renderPlot({
    ggplot(data = MPG, aes(x = factor(get(input$var1)), y = get(input$var2))) +
      geom_boxplot() + labs(x = input$var1, y = input$var2)
  })
}

# Create ui

ui2 <- fluidPage(
  titlePanel("Side-by-side Comparisons"),
  sidebarLayout(
    sidebarPanel(
      selectInput("var1", "Grouping Variable", 
                  choices = factors, selected = factors[1]),
      
      selectInput("var2", "Response Variable", 
                  choices = quantitative, selected = quantitative[1])
      
    ),
    mainPanel(plotOutput("distPlot"))
  )
)

# Deploy app

shinyApp(ui = ui2, server = server2)

9.8.3 Tabsets and Navbars

Tabsets (Clickable tabs at the top of a page that link to another panel sitting within a page) and navbars (clickable menus that navigate to different pages in an app) are an effective way to organise visualisations in an app. Here is an example of a tabset displaying different types of visualisations for the same plot:

# Load packages and prepare data

library(ggplot2)
library(shiny)

MPG <- ggplot2::mpg

MPG$year <- MPG$year %>% as.factor()
MPG$cyl <- MPG$cyl %>% as.factor()
MPG$displ <- MPG$displ %>% as.factor()

# Generate variables lists for ui

factors <- names(MPG)[ sapply(MPG, is.character) | sapply(MPG, is.factor) ]
quantitative <- names(MPG)[ sapply(MPG, is.numeric)]

# Assign server function

server3 <- function(input, output) {
  output$dotPlot <- renderPlot({
    ggplot(data = MPG, aes(x = factor(get(input$var1)), y = get(input$var2))) +
      geom_dotplot(stackdir = "center", dotsize = .5, binaxis = "y") + 
      labs(x = input$var1, y = input$var2)
  })
  
  output$boxPlot <- renderPlot({
    ggplot(data = MPG, aes(x = factor(get(input$var1)), y = get(input$var2))) +
      geom_boxplot() + labs(x = input$var1, y = input$var2)
  })
  
  output$violinPlot <- renderPlot({
    ggplot(data = MPG, aes(x = factor(get(input$var1)), y = get(input$var2))) +
      geom_violin() + labs(x = input$var1, y = input$var2)
  })
}

# Create ui

ui3 <- fluidPage(
  titlePanel("Exploring Different Plot Types"),
  sidebarLayout(
    sidebarPanel(
      selectInput("var1", "Grouping Variable", 
                  choices = factors, selected = factors[1]),
      
      selectInput("var2", "Response Variable", 
                  choices = quantitative, selected = quantitative[1])
    ),
    
    mainPanel(
      tabsetPanel(
        tabPanel("Dot Plot", plotOutput("dotPlot")),
        tabPanel("Box Plot", plotOutput("boxPlot")),
        tabPanel("Violin Plot", plotOutput("violinPlot"))
      )
    )
  )
)

# Deploy app

shinyApp(ui = ui3, server = server3)

Navbars can be used alternatively or in addition to tabsets. Navbars allow the user to navigate to different “pages” within the app. They act in the same way as a website navigation menu.

# Load packages and prepare data

library(ggplot2)
library(shiny)

MPG <- ggplot2::mpg

MPG$year <- MPG$year %>% as.factor()
MPG$cyl <- MPG$cyl %>% as.factor()
MPG$displ <- MPG$displ %>% as.factor()

# Generate variable lists for ui

factors <- names(MPG)[ sapply(MPG, is.character) | sapply(MPG, is.factor) ]
quantitative <- names(MPG)[ sapply(MPG, is.numeric)]

# Assign server function

server4 <- function(input, output) {
  output$dotPlot <- renderPlot({
    ggplot(data = MPG, aes(x = factor(get(input$var1)), y = get(input$var2))) +
      geom_dotplot(stackdir = "center", dotsize = .5, binaxis = "y") + 
      labs(x = input$var1, y = input$var2)
  })
  
  output$boxPlot <- renderPlot({
    ggplot(data = MPG, aes(x = factor(get(input$var1_2)), y = get(input$var2_2))) +
      geom_boxplot() + labs(x = input$var1, y = input$var2)
  })
  
  output$violinPlot <- renderPlot({
    ggplot(data = MPG, aes(x = factor(get(input$var1_3)), y = get(input$var2_3))) +
      geom_violin() + labs(x = input$var1, y = input$var2)
  })
}

# Create ui

ui4 <- navbarPage("Exploring Different Plot Types",
                  tabPanel("Dot Plot",
                           sidebarLayout(
                             sidebarPanel(
                               selectInput("var1", "Grouping Variable", 
                                           choices = factors, selected = factors[1]),
                               selectInput("var2", "Response Variable", 
                                           choices = quantitative, selected = quantitative[1])),
                             
                             mainPanel(
                               plotOutput("dotPlot")
                             )
                           )
                  ),
                  tabPanel("Box Plot",
                           sidebarLayout(
                             sidebarPanel(
                               selectInput("var1_2", "Grouping Variable", 
                                           choices = factors, selected = factors[1]),
                               selectInput("var2_2", "Response Variable", 
                                           choices = quantitative, selected = quantitative[1])),
                             
                             mainPanel(
                               plotOutput("boxPlot")
                             )
                           )
                  ),
                  tabPanel("Violin Plot",
                           sidebarLayout(
                             sidebarPanel(
                               selectInput("var1_3", "Grouping Variable", 
                                           choices = factors, selected = factors[1]),
                               selectInput("var2_3", "Response Variable", 
                                           choices = quantitative, selected = quantitative[1])),
                             
                             mainPanel(
                               plotOutput("violinPlot")
                             )
                           )
                  )
)

# Deploy app

shinyApp(ui = ui4, server = server4)

9.8.4 Plotly and Shiny

Plotly and Shiny work seamlessly and provide designers with the best of both worlds - the raw power of R and the interactivity of Plotly.

# Load packages and prepare data

library(ggplot2)
library(shiny)
library(plotly)
library(gapminder)
library(RColorBrewer)

gapminder_filt <- gapminder::gapminder %>% filter(country != "Kuwait") # remove outlier!

# Assign server function

server5 <- function(input, output) {
  output$gapminder <- renderPlotly({
    
    gapminder_filt$highlight <- ifelse(gapminder_filt$country == input$highlight,
                                       input$highlight,"Other")
    
    gapminder_filt$highlight <- gapminder_filt$highlight %>% 
      factor(levels = c(input$highlight,"Other"))
    
    col <- ifelse(input$highlight == "continent", "continent",
                  "highlight")
    
    plot_ly(gapminder_filt, x = ~gdpPercap, y = ~lifeExp, color = ~get(col), colors = "Set1",
            frame = ~year, alpha = 1, type = "scatter", mode = "markers") %>% 
      
      layout( title = "GDP Per Capita vs. Life Expectancy by Year",
              yaxis = list(zeroline = FALSE, title = "Life Expectancy"),
              xaxis = list(zeroline = FALSE, title = "GDP Per Capita"))
  })
}

# Create ui

ui5 <- fluidPage(
  titlePanel("Plotly & Shiny"),
  sidebarLayout(
    sidebarPanel(
      selectInput("highlight", "Track Country", 
                  choices =  c("continent",levels(gapminder$country)), 
                  selected = "continent")
    ),
    mainPanel(plotlyOutput("gapminder"))
  )
)

# Deploy app

shinyApp(ui = ui5, server = server5)

9.8.5 Reactive Events

Reactive output is a great way to speed up apps. Reactive output will only execute changes on the Shiny server when the user changes an input. This is useful when you have resource-hungry applications. By default, Shiny will update with any change to an input. However, using advanced reactive observers, such as the eventReactive, you can delay these updates by linking to an actionButton input. The following example shows the use of an actionButton.

library(shiny)
library(ecodist)

ui6 <- fluidPage(
  titlePanel("Guess the Correlation"),
   sidebarLayout(
    sidebarPanel(
      actionButton("go", "Generate random correlation"),
      
      actionButton("answer", "Show answer"),
      verbatimTextOutput("answer")),
    
    mainPanel(plotOutput("plot"))
)
)

server6 <- function(input, output) {
  
  rcor <- eventReactive(input$go, {
    data.frame(corgen(50, r=runif(1,-1,1), population = FALSE, epsilon = 0))
  })
  
  r <- eventReactive(input$answer, {
    cor(rcor()$x, rcor()$y)
  })

  output$plot <- renderPlot({
    qplot(data = rcor(), x = x, y = y, geom = "point") +
      scale_y_continuous(limits = c(-3,3)) +
      scale_x_continuous(limits = c(-3,3)) 
  })
  
  output$answer <- renderText({
    paste0("Answer: r = ", round(r(),2))
  })

}

shinyApp(ui = ui6, server = server6)

The following RShiny re-make of the classic “Guess that Correlation” game was developed by Arion Barzoucas-Evans, a very talented former student of the course. I have to thank Arion for giving me permission to show his code here. I’ve had the opportunity to build upon Arion’s work. Specifically, I have simplified the generation of the correlated data, fixed the plot size and constrained the axis limits.

library(shiny)
library(ecodist)
library(shinyjs)

ui7 <- fluidPage(
  
  useShinyjs(),
  
  # Application title
  titlePanel("Guess the correlation game"),
  
  sidebarLayout(
    sidebarPanel(
      HTML(
        paste("<b><i>INSTRUCTIONS:<br>1. Click on the 'Generate Scatter Plot' 
              button to generate", "a new scatter plot.<br>2. Guess the 
              correlation present in the scatter plot (a value between -1 and 1) 
              and enter the value", "in the 'Guess the correlation box'.
              <br>3. Click the 'Submit' button to submit your guess.",
              "<br><br>RULES:<ul><br><li>Guess within 0.05 of the true correlation: 
              +1 life and +5 points.</li>","<br><li>Guess within 0.10 of the true 
              correlation: +1 point.</li>","<br><li>Guess within >0.10 of the true 
              correlation: -1 life.</li>","<br><li>You will also receive bonus 
              points if you make good guesses in a row!</li>")),
      
      HTML("<br><br><br>"),
      
      tags$head(
        tags$style(
          HTML("#scatterButton{background-color:green}"),
          HTML("#scatterButton{color:white}"),
          HTML("#guessButton{background-color:green}"),
          HTML("#guessButton{color:white}"),
          HTML("#resetButton{background-color:blue}"),
          HTML("#resetButton{color:white}"),
          HTML("#highscore{color:blue}")
        )
      ),
      
      actionButton("scatterButton",HTML("<b>Generate Scatter Plot")),
      HTML("<br><br><br>"),
      
      numericInput("guess","Guess the correlation:", 
                   value = 0, min = -1, max = 1, step = 0.01),
      
      actionButton("guessButton",HTML("<b>Submit")),
      
      conditionalPanel("output.lives < 0", 
                       actionButton("resetButton",HTML("<b>Reset")))
      
    ),
    
    
    mainPanel(
      fluidRow(
        column(12,verbatimTextOutput("highscore")),
        column(12,plotOutput("distPlot")),
        column(12,verbatimTextOutput("progress")),
        column(12,conditionalPanel("output.corr_diff <= 0.1", tags$head(
          tags$style(
            HTML("#wintext{color:green}"))),
          verbatimTextOutput("wintext"))),
        column(12,conditionalPanel("output.corr_diff > 0.1", tags$head(
          tags$style(
            HTML("#losetext{color:red}"))),
          verbatimTextOutput("losetext")))
      )
    )
  )
)

server7 <- function(input, output) {
  
  # reactive variables
  vars <- reactiveValues(streaks = 0, counter = 0, guess_sum = 0, 
                         corr_diff = 0, score = 0, lives = 3, highscore = 0)
  
  # random scatter plot generation
  df <- eventReactive(input$scatterButton, {
    data.frame(corgen(runif(1, 10, 500), r=runif(1,-1,1), population = FALSE, 
                      epsilon = 0))
  })
  
  # game progress text output
  progress <- eventReactive(input$guessButton, {
    vars$counter <- vars$counter + 1
    vars$corr_diff <- abs(input$guess - cor(df()$x,df()$y))
    vars$guess_sum <- vars$guess_sum + vars$corr_diff
    if(vars$corr_diff <=0.05 && vars$streaks >= 2){
      vars$streaks <- vars$streaks + 1
      vars$score <- vars$score + 8
      vars$lives <- vars$lives + 1
    }else if(vars$corr_diff <=0.05){
      vars$streaks <- vars$streaks + 1
      vars$score <- vars$score + 5
      vars$lives <- vars$lives + 1
    }else if(vars$corr_diff <=0.1 && vars$streaks >= 2){
      vars$streaks <- vars$streaks + 1
      vars$score <- vars$score + 4
    }else if(vars$corr_diff <=0.1){
      vars$streaks <- vars$streaks + 1
      vars$score <- vars$score + 1
    }else{
      vars$streaks <- 0
      vars$lives <- vars$lives - 1
    }
    text <- paste("Streak:",vars$streaks, 
                  "\nMean Error:", round(vars$guess_sum/vars$counter,2),
                  "\nScore:",vars$score,"\nLives Left:",vars$lives)
    
  })
  
  # win text display
  wintext <- eventReactive(input$guessButton, {
    
    text <- paste("True Correlation:",
                  round(cor(df()$x,df()$y),2),"\nGuessed Correlation:",input$guess,
                  "\nDifference:", round(vars$corr_diff,2))
   
  })
  
  # lose text display
  losetext <- eventReactive(input$guessButton, {
    
    text <- paste("True Correlation:",round(cor(df()$x,df()$y),2),
                  "\nGuessed Correlation:",input$guess,
                  "\nDifference:", round(vars$corr_diff,2))
    
  })
  
  # highscore display
  highscore <- eventReactive(input$guessButton, {
    
    if(vars$score > vars$highscore){
      vars$highscore <- vars$score
    }
    
    text <- paste("High Score:", vars$highscore)
  })
  
  # assigning outputs
  
  # reactive outputs to use in UI
  output$lives <- reactive({vars$lives})
  output$corr_diff <- reactive({vars$corr_diff})
  
  # text output
  output$progress <- renderText(progress())
  output$wintext <- renderText(wintext())
  output$losetext <- renderText(losetext())
  output$highscore <- renderText(highscore())
  
  # make reactive variables available in UI
  outputOptions(output, "lives", suspendWhenHidden = FALSE)
  outputOptions(output, "corr_diff", suspendWhenHidden = FALSE)
  
  # hide game attempt
  observeEvent(input$scatterButton, {
    hide("losetext")
    hide("wintext")
    show("progress")
    })
  
  # show game attempt
  observeEvent(input$guessButton, {
    show("progress")
    show("losetext")
    show("wintext")})
  
  # reset game
  observeEvent(input$resetButton, {
  vars$lives <- 3
  vars$streaks <- 0
  vars$counter <- 0
  vars$guess_sum <- 0
  vars$corr_diff <- 0
  vars$score <- 0
  hide("progress")
  hide("wintext")
  hide("losetext")})

  # plot output
  output$distPlot <- renderPlot({
    
    if(vars$lives >= 0){
      ggplot(data = df(), aes(x = x, y = y)) + geom_point(size = 3) + 
        theme_classic() + scale_y_continuous(limits = c(-3, 3)) +
        scale_x_continuous(limits = c(-3,3))
    }else{
      ggplot() + geom_text(aes(x= "", y = "", 
                               label = "GAME OVER :'(\nPLEASE RESET THE GAME"), 
                           size = 14, colour = "RED") + 
        theme_classic() + scale_y_continuous(limits = c(-3, 3)) +
        scale_x_continuous(limits = c(-3,3))
    }
  }, height = 500, width = 600)

}

shinyApp(ui = ui7, server = server7)

9.9 Uploading and Viewing Data

Shiny can also be used to upload user data and preview it. Allowing a user to view (and even download) the data is good practice (provided you have permission…). Here is an example of an app where the user can upload and preview a .csv dataset.

library(shiny)
library(DT)

ui8 <- fluidPage(
  titlePanel("Uploading and Viewing Data"),
  sidebarLayout(
    sidebarPanel(

      fileInput("file", "Choose CSV Dataset",
                multiple = TRUE,
                accept = c("text/csv",
                         "text/comma-separated-values,text/plain",
                         ".csv"))
    ),

    mainPanel(
      dataTableOutput("contents")
    )

  )
)

# Define server logic to read selected file ----
server8 <- function(input, output) {

  output$contents <- renderDataTable({

    req(input$file)

    df <- read.csv(input$file$datapath)
  })

}

# Deploy app
shinyApp(ui = ui8, server = server8)

9.10 Shiny Themes

You can change Shiny themes for a different look. First install and load shinythemes. Themes, based on the Bootstrap Themes, include the following: cerulean, cosmo, cyborg, darkly, flatly, journal, lumen, paper, readable, sandstone, simplex, slate, spacelab, superhero, united and yeti. Here is an example of the dark cyborg theme. Notice how we can change the theme by adding a theme = option to the ui.

library(shiny)
library(DT)
library(shinythemes)

ui9 <- fluidPage(theme = shinytheme("cyborg"),
  titlePanel("Uploading, Viewing and Downloading Data"),
  sidebarLayout(
    sidebarPanel(

      fileInput("file", "Choose CSV Dataset",
                multiple = TRUE,
                accept = c("text/csv",
                         "text/comma-separated-values,text/plain",
                         ".csv"))
    ),

    mainPanel(
      dataTableOutput("contents")
    )

  )
)

# Define server logic to read selected file ----
server9 <- function(input, output) {

  output$contents <- renderDataTable({

    req(input$file)

    df <- read.csv(input$file$datapath)
  })

}

shinyApp(ui = ui9, server = server9)

9.11 Avoiding Common Errors in Shiny

Building Shiny apps can be a time consuming process due to the difficulty of debugging. Shiny’s error messages are often vague and unhelpful. However, with a bit of experience and know-how, you will become more efficient at identifying and addressing bugs. The following tips will help you to avoid the most common issues:

9.11.1 Load your Packages and Data

Ensure all your packages and data are loaded in the app. When the app runs on a server, it needs to load the packages and data required for the app to run. If these are not specified in the app, the app won’t be able to locate the data or functions required for the app to load.

9.11.2 Remove the Unnecessary

Only include packages, data and code that are required for the app to run. Loading unnecessary packages will often create function conflicts and increase the risk of issues installing packages on the Shiny server. Loading unnecessary data and including unnecessary code will decrease app performance and increase the chances of errors.

9.11.3 Use Current Versions

Use current versions of packages from CRAN. This minimises the risk of issues created by outdated functions and package bugs. You can use packages from GitHub by loading Devtools v. > 1.4.

9.11.4 Use Relative Paths

Place your dataset in your app directory and load using a relative path. When the app is hosted on a Shiny server, it must access files from the server. The app will no longer have access to local paths on your computer. For example, you might load data.csv from your local computer by directing to your C: drive.

data <- read.csv("C:/folder/subfolder/data.csv")

This will work when you run the app locally because the Shiny server has access to your local folders. However, when hosted online, the app won’t have access. Instead, place the dataset in the same directory as your app file and load using a relative path. For example…

# Right
data <- read.csv("data.csv")

This tells the app that data.csv is located in the same folder as app.R.

9.11.5 Build One Feature at a Time

Build your app one feature at a time. Don’t try to incorporate all your interactive features in one go. It will make debugging very slow. List the interactive features you want to include. Pick the most important. Build it. Test it. Refine it. Then move onto the next feature. This will make it much easier to identify the point in your code that is breaking the app.

9.11.6 Check Logs

Shiny includes detailed logs which can provide helpful information about bugs. Check for error clues using rsconnect::showLogs() after you have closed the app following an error.

9.11.7 Avoid Shiny-cide Functions

There are many functions that will kill a Shiny app. Here are some common examples:

  • install.packages(): This function installs a package. Shiny does not use this function to install packages on the Shiny Server. When an app is loaded on a Shiny server, there are functions that extract a list of the packages loaded using library() or require() and then uses this list to install the required packages with the help of the rsconnect package.
  • View(): This function opens a spreadsheet-like view of a dataset on a local machine. It was not intended for a Shiny server.
  • setwd(): This function specifies a local directory to efficiently load and save data and other objects in R. It will not work on a Shiny server because the server won’t have access to your local directories. Use relative paths instead (see above)

9.11.8 Publishing Your App

When you publish your Shiny app to a Shiny server, ensure you avoid spaces or unusual characters in the app name. Also, don’t forget to upload your data with the app!

9.12 Conclusion

This chapter started by introducing the basics of Shiny applications. Part II considered efficient ways to code user-interfaces, how to incorporate useful functionality, change themes and integrate Plotly. In the next chapter, we will take another look at Shiny and how it can be used to design engaging and informative dashboards.