Introduction to Shiny for App Development in R

S3729C Wellness and Health Management - Lesson 12

Class Date : 10 September 2022

Learning Outcomes for Lesson 12

At the end of this lesson, students will be able to:

  1. Describe the architecture of a Shiny App with reference to the User Interface and Server functions.
  2. Specify the code that builds the UI of a Shiny App, by defining the layout, input and output controls.
  3. Develop an app that contains reactive programming, embedded in the UI inputs, render*() functions as well as UI outputs.

Loading the R Packages and Data Required for This Lesson

This is the code for installation of Pacman which is used to unpack all packages required in this lesson. Please copy, paste and run the code in your R Studio Cloud workspace.

install.packages("pacman",repos = "http://cran.us.r-project.org")

We will then proceed to load the packages required for this section

pacman::p_load(pacman, psych, rio, tidyverse, ggplot2, ggridges, devtools, vioplot, dplyr, ggwordcloud, ggforce, gridExtra, kableExtra, shiny)

For lessons 12 to 13, we will be using the movies dataset, which combines data from two websites: the Internet Movie Database, commonly known as IMDB, and Rotten Tomatoes. The observations are a random sample of 651 movies released in the US between 1970 and 2014.

GitHub Link for Movies Dataset (in csv) : https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

Check on the output and hence the integrity of the loaded data by reading the column names. You can also see that the data consists of the following fields

movies %>% colnames()
##  [1] "X"                "title"            "title_type"       "genre"           
##  [5] "runtime"          "mpaa_rating"      "studio"           "thtr_rel_date"   
##  [9] "thtr_rel_year"    "thtr_rel_month"   "thtr_rel_day"     "dvd_rel_date"    
## [13] "dvd_rel_year"     "dvd_rel_month"    "dvd_rel_day"      "imdb_rating"     
## [17] "imdb_num_votes"   "critics_rating"   "critics_score"    "audience_rating" 
## [21] "audience_score"   "best_pic_nom"     "best_pic_win"     "best_actor_win"  
## [25] "best_actress_win" "best_dir_win"     "top200_box"       "director"        
## [29] "actor1"           "actor2"           "actor3"           "actor4"          
## [33] "actor5"           "imdb_url"         "rt_url"

You can also use the following command to view a sample of 10 movies with all 35 fields in a table

kbl(movies[180:190, 1:35]) %>%
    kable_styling(fixed_thead = TRUE)
X title title_type genre runtime mpaa_rating studio thtr_rel_date thtr_rel_year thtr_rel_month thtr_rel_day dvd_rel_date dvd_rel_year dvd_rel_month dvd_rel_day imdb_rating imdb_num_votes critics_rating critics_score audience_rating audience_score best_pic_nom best_pic_win best_actor_win best_actress_win best_dir_win top200_box director actor1 actor2 actor3 actor4 actor5 imdb_url rt_url
180 180 Resident Evil Feature Film Action & Adventure 100 R Columbia Tristar Pictures 2002-03-15 05:00:00 2002 3 15 2002-07-30 04:00:00 2002 7 30 6.7 192052 Rotten 33 Upright 68 no no no no no no Paul W.S. Anderson Milla Jovovich Michelle Rodriguez Eric Mabius James Purefoy Martin Crewes http://www.imdb.com/title/tt0120804/ //www.rottentomatoes.com/m/resident_evil/
181 181 Top Gun Feature Film Drama 110 PG Paramount Pictures 1986-05-16 04:00:00 1986 5 16 1998-10-20 04:00:00 1998 10 20 6.9 211129 Rotten 54 Upright 83 no no no no no yes Tony Scott Tom Cruise Kelly McGillis Anthony Edwards Val Kilmer Tom Skerritt http://www.imdb.com/title/tt0092099/ //www.rottentomatoes.com/m/top_gun/
182 182 25th Hour Feature Film Drama 135 R Touchstone Pictures 2003-01-10 05:00:00 2003 1 10 2003-05-20 04:00:00 2003 5 20 7.7 146518 Certified Fresh 78 Upright 85 no no yes no no no Spike Lee Edward Norton Barry Pepper Philip Seymour Hoffman Rosario Dawson Anna Paquin http://www.imdb.com/title/tt0307901/ //www.rottentomatoes.com/m/25th_hour/
183 183 The Bad News Bears in Breaking Training Feature Film Comedy 100 PG Paramount Pictures 1977-07-08 04:00:00 1977 7 8 2002-02-12 05:00:00 2002 2 12 5.4 2239 Rotten 50 Spilled 39 no no no no no no Michael Pressman William Devane Jackie Earle Haley Clifton James Jimmy Baio Chris Barnes http://www.imdb.com/title/tt0075718/ //www.rottentomatoes.com/m/bad_news_bears_in_breaking_training/
184 184 Streets of Gold Feature Film Drama 95 R Live Home Video 1986-11-14 05:00:00 1986 11 14 NA NA NA NA 6.0 486 Rotten 31 Spilled 44 no no no no no no Joe Roth Klaus Maria Brandauer Adrian Pasdar Richard Pasdar Wesley Snipes Angela Molina http://www.imdb.com/title/tt0092022/ //www.rottentomatoes.com/m/streets_of_gold/
185 185 The Mod Squad Feature Film Drama 92 R MGM 1999-03-26 05:00:00 1999 3 26 1999-08-24 04:00:00 1999 8 24 4.1 7658 Rotten 4 Spilled 17 no no no no no no Scott Silver Claire Danes Giovanni Ribisi Omar Epps Josh Brolin Dennis Farina http://www.imdb.com/title/tt0120757/ //www.rottentomatoes.com/m/mod_squad/
186 186 Where the Heart Is Feature Film Drama 107 R Touchstone Pictures 1990-01-01 05:00:00 1990 1 1 2003-06-03 04:00:00 2003 6 3 6.1 1268 Rotten 11 Spilled 58 no no no no no no John Boorman Dabney Coleman Uma Thurman Joanna Cassidy Crispin Glover Suzy Amis http://www.imdb.com/title/tt0100924/ //www.rottentomatoes.com/m/1028561-where_the_heart_is/
187 187 Battlefield Earth Feature Film Action & Adventure 118 PG-13 Warner Bros. Pictures 2000-05-12 04:00:00 2000 5 12 2001-01-16 05:00:00 2001 1 16 2.4 64119 Rotten 3 Spilled 11 no no yes no no no Roger Christian John Travolta Barry Pepper Forest Whitaker Kim Coates Richard Tyson http://www.imdb.com/title/tt0185183/ //www.rottentomatoes.com/m/battlefield_earth/
188 188 Osmosis Jones Feature Film Comedy 95 PG Warner Bros. Pictures 2001-08-10 04:00:00 2001 8 10 2001-11-13 05:00:00 2001 11 13 6.2 24678 Rotten 55 Spilled 41 no no no no no no Bill Murray Bill Murray Chris Rock Laurence Fishburne David Hyde Pierce William Shatner http://www.imdb.com/title/tt0181739/ //www.rottentomatoes.com/m/osmosis_jones/
189 189 Never Cry Wolf Feature Film Drama 105 PG Disney 1983-01-01 05:00:00 1983 1 1 2000-02-22 05:00:00 2000 2 22 7.6 6114 Fresh 100 Upright 85 no no no no no no Carroll Ballard Charles Martin Smith Brian Dennehy Zachary Ittimangnaq Samson Jorah Hugh Webster http://www.imdb.com/title/tt0086005/ //www.rottentomatoes.com/m/never_cry_wolf/
190 190 A Good Woman Feature Film Drama 93 PG Lions Gate Films 2006-02-03 05:00:00 2006 2 3 2006-06-13 04:00:00 2006 6 13 6.5 9399 Rotten 37 Spilled 49 no no no yes no no Mike Barker Helen Hunt Scarlett Johansson Tom Wilkinson Stephen Campbell Moore Mark Umbers http://www.imdb.com/title/tt0379306/ //www.rottentomatoes.com/m/good_woman/

What is a Shiny App ?

Shiny is an R package that makes it easy to build interactive web apps straight from R. It combines the computational power of R with the interactivity of the modern web.

Here is an example of a Shiny App that you would build by the end of lessons 12 and 13, allowing end users to navigate the dataset on movies that you have just loaded.

This will contain the following :

How does R Shiny Work ?

Every Shiny app has a webpage that the user visits, and behind this webpage there is a computer that serves this webpage by running R.

When running your app locally, the computer serving your app is your computer.

When your app is deployed, the computer serving your app is a web server.

Each app is comprised of two components, a UI and a server.

The UI is ultimately built with HTML, CSS, and JavaScript. However, you as the Shiny developer do not need to know these languages. Shiny lets R users write user interfaces using a simple, familiar-looking API. However there are no limits to customization for advanced users who are familiar with these languages.

The server function contains the instructions to map user inputs to outputs.

Anatomy of a Shiny App

Lets start by taking a look at the anatomy of a Shiny App.

Here is the code for the development of a simple app. You can copy it into your R Studio IDE workspace, select all of the code, and press CTRL + Enter on your keypad to preview the output.

# Load packages ----------------------------------------------------------------

library(shiny)
library(ggplot2)

# Load data --------------------------------------------------------------------

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

# Define UI --------------------------------------------------------------------

ui <- fluidPage(
  
  sidebarLayout(
    
    # Inputs: Select variables to plot
    sidebarPanel(
      
      # Select variable for y-axis
      selectInput(
        inputId = "y",
        label = "Y-axis:",
        choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
        selected = "imdb_rating"
      ),
      # Select variable for x-axis
      selectInput(
        inputId = "x",
        label = "X-axis:",
        choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
        selected = "critics_score"
      )
    ),
    
    # Output: Show scatterplot
    mainPanel(
      plotOutput(outputId = "scatterplot")
    )
  )
)

# Define server ----------------------------------------------------------------

server <- function(input, output, session) {
  output$scatterplot <- renderPlot({
    ggplot(data = movies, aes_string(x = input$x, y = input$y)) +
      geom_point()
  })
}

# Create a Shiny app object ----------------------------------------------------

shinyApp(ui = ui, server = server)
## PhantomJS not found. You can install it with webshot::install_phantomjs(). If it is installed, please make sure the phantomjs executable can be found via the PATH variable.

We will now go into the User Interface (UI) and Server Function as they are applied in the development of this app.

User Interface

In this section we’ll build the user interface of a simple app.

However, before we get into the weeds of building a user interface, let’s revisit the anatomy of a Shiny app.

Example

For example, if your app features a plot, the code for building that plot lies in the server function. However, the setup for the user defined inputs for the plot, as well as information on where physically on the app the plot should appear, are defined in the UI.

Here is the app we’ll work with in this section and the code that builds the UI of that app.

Since this is too much code to parse, we’ll explore individual components of the UI one by one.

fluidPage()

At the outermost layer of our UI definition we begin with the fluidPage() function.

The fluidPage() function creates a fluid page layout consisting of rows and columns. Rows make sure that elements in them appear on the same line. Columns within these rows define how much horizontal space each element should occupy.

Fluid pages scale their components in realtime to fill all available browser width, which means you, the app developer, don’t need to worry about defining relative widths for individual app components.

As always, for more information on arguments to this function, you can view the R function help by typing ?fluidPage in your R console or visiting the function reference page on the package website here


Layout

Next, we define the layout of our app with sidebarLayout().

Shiny includes a number of options for laying out the components of an application. The default layout, the one we’re using in our example app, is a layout with a sidebar, that you can define with the sidebarLayout() function.

This is a simple layout with a narrow sidebar for inputs and a wider main area for output.

Under the hood, Shiny implements layout features available in Bootstrap 2, which is a popular HTML/CSS framework. However the nice thing about working in Shiny is that no prior experience with Bootstrap is necessary.

To learn more about various layouts, I recommend reviewing the Application Layout Guide article at https://shiny.rstudio.com/.


Input Controls

Next we define our sidebar panel containing input controls.

This panel contains two dropdown menus created with the selectInput() function.

Let’s take a look at one of the selectInput widgets a little more closely.

Main Panel

The final component of our UI is mainPanel().

In this example provided, the main panel contains only one component, a plot output. We’ll talk about how this plot is built later on in this lesson.

Server function

The key purpose of the server function is to define the relationship between inputs and outputs.

Here again is the app that we are working with in this module

In the earlier section, we showed you how to build the UI of this app, and also noted that each input was tagged with an inputId that can be used to refer to them in the server.

This is the server function code for this app

Once again there is a lot going on here to parse at once, so in the following sections we take a closer look at the function.

At the outermost layer

We define our server function which takes two arguments: an input and an output. Both of these are named lists.

The server function accesses inputs selected by the user to perform computations and specifies how outputs laid out in the UI should be updated.

output

Our simple app had only one output – a plot. So our server function contains the logic necessary to build this plot.

renderPlot()

The renderPlot() function specifies how the plot output should be updated. Let’s take a look at what is happening in the renderPlot() function first.

You may have noticed that this is none other than ggplot2 code which we covered in lesson 11.

One aspect of the syntax that might be new, however, is how the x and y variables are defined. They come from the input list that is built in the UI.

Inputs

Here is the relevant UI and server code.

Input x and y come from the selectInput() widgets, and map to the x and y arguments of the plot aesthetics.

Rules of server functions

There are three rules of building server functions:

  1. Always save objects to display to the named output list, i.e. something of the form output$xx, where xx is the plot you want to display.

  2. Always build objects to display with one of the render*() functions, like we built our plot with renderPlot().

  3. Use input values from the named input list, with input$xx.

Output types

Just like various inputs, Shiny also provides a wide selection of output types each of which works with a render function.

For example, in our app we used the renderPlot() function to build our reactive plot (we’ll get to what I mean by reactive in a second) and laid out the plot with the plotOutput() function.

Shiny knows to match these two together as they use the same outputID, scatterplot.

Reactivity

Let’s also briefly discuss reactivity, which we will go into further details in a subsequent section.

It’s easy to build interactive applications with Shiny, but to get the most out of it, you’ll need to understand the reactive programming scheme used by Shiny.

In a nutshell Shiny automatically updates outputs, such as plots, when inputs that go into them change.

Putting all the pieces together

The last component of a Shiny app is aptly named shinyApp() function, which puts the UI and the server pieces together to create a Shiny app object.

Adding a Third Dimension to the R Shiny App Scatterplot

Let us now take a look at a further development of the R Shiny App Scatterplot derived from the movies data, which now adds a third dimension to the variables, that being color (spelt the American way because R is coded this way)

Similar to what you did earlier, copy the code into your R Studio IDE, highlight and press CTRL + Enter to preview the output.

# Load packages ----------------------------------------------------------------

library(shiny)
library(ggplot2)

# Load data --------------------------------------------------------------------

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

# Define UI --------------------------------------------------------------------

ui <- fluidPage(
  sidebarLayout(
    
    # Inputs: Select variables to plot
    sidebarPanel(
      
      # Select variable for y-axis
      selectInput(
        inputId = "y",
        label = "Y-axis:",
        choices = c(
          "IMDB rating" = "imdb_rating",
          "IMDB number of votes" = "imdb_num_votes",
          "Critics score" = "critics_score",
          "Audience score" = "audience_score",
          "Runtime" = "runtime"
        ),
        selected = "audience_score"
      ),
      
      # Select variable for x-axis
      selectInput(
        inputId = "x",
        label = "X-axis:",
        choices = c(
          "IMDB rating" = "imdb_rating",
          "IMDB number of votes" = "imdb_num_votes",
          "Critics score" = "critics_score",
          "Audience score" = "audience_score",
          "Runtime" = "runtime"
        ),
        selected = "critics_score"
      ),
      
      # Select variable for color
      selectInput(
        inputId = "z",
        label = "Color by:",
        choices = c(
          "Title type" = "title_type",
          "Genre" = "genre",
          "MPAA rating" = "mpaa_rating",
          "Critics rating" = "critics_rating",
          "Audience rating" = "audience_rating"
        ),
        selected = "mpaa_rating"
      )
    ),
    
    # Output: Show scatterplot
    mainPanel(
      plotOutput(outputId = "scatterplot")
    )
  )
)

# Define server ----------------------------------------------------------------

server <- function(input, output, session) {
  output$scatterplot <- renderPlot({
    ggplot(data = movies, aes_string(
      x = input$x, y = input$y,
      color = input$z
    )) +
      geom_point()
  })
}

# Create a Shiny app object ----------------------------------------------------

shinyApp(ui = ui, server = server)

Reactive Flow

The concept of reactivity is key to Shiny App development.

Suppose you have a sliderInput in your app with the inputId of alpha. The value of this input is stored in input$alpha.

So when the user moves around the slider, the value of the alpha input is updated in the input list.

Reactivity 101

Reactivity automatically occurs when an input value is used to render an output object, i.e. in the server function below the plot is re-rendered when the value of input alpha changes based on user input. You, as the app developer, do not need to write code that says “Update the plot every time the value of input alpha changes”, Shiny automatically takes care of this for you in the render*() function.

Reactive flow

Here is a roadmap of the reactive flow in Shiny.

The user selects an input, this input goes through some expression in the server, and an output is rendered. Each time the user changes their input selection, the expression that generates the output will automatically re-execute, and the relevant output will be re-rendered based on the new value of the input.

In a Shiny application, there’s no need to explictly describe the relationships between inputs and outputs and tell R what to do when each input changes, Shiny automatically handles these details for you.

Build a Simple Reactive Wdiget

The following is code for a new input widget, a sliderInput, that controls the transparency of the plotted points.

This widget should have the ID alpha and its values should range between 0 and 1. You can decide what the displayed label and initial value of the slider should be.

Similar to before, copy the code into your R Studio IDE, highlight and press CTRL + Enter to preview the output.

# Load packages ----------------------------------------------------------------

library(shiny)
library(ggplot2)

# Load data --------------------------------------------------------------------

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

# Define UI --------------------------------------------------------------------

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput(
        inputId = "y",
        label = "Y-axis:",
        choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
        selected = "audience_score"
      ),
      
      selectInput(
        inputId = "x",
        label = "X-axis:",
        choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
        selected = "critics_score"
      ),
      
      sliderInput(
        inputId = "alpha",
        label = "Alpha:",
        min = 0, max = 1,
        value = 0.5
      )
    ),
    
    mainPanel(
      plotOutput(outputId = "scatterplot")
    )
  )
)

# Define server ----------------------------------------------------------------

server <- function(input, output, session) {
  output$scatterplot <- renderPlot({
    ggplot(data = movies, aes_string(x = input$x, y = input$y)) +
      geom_point(alpha = input$alpha)
  })
}

# Create the Shiny app object --------------------------------------------------

shinyApp(ui = ui, server = server)

UI Inputs

The goal of this section is to build familiarity with a few UI input functions.

Shiny provides a wide selection of input widgets. The Shiny cheatsheet is a great place to see a list of them all at once.

Once you know which one you want to use, you can find out more about it in the package documentation.

checkboxInput

Let’s start with a checkboxInput().

Suppose we want to add checkbox input to our app to specify whether the data plotted should be shown in a data table. We need to make three modifications to our app to accomplish this.

  1. In the ui: Add an input widget that the user can interact with to check/uncheck the box.
  2. In the ui: Add an output to the UI defining where the data table should appear.
  3. In the server function: Add a reactive expression that creates the data table if the checkbox is checked. We’ll go through these steps one by one.

1. ui: Add an input widget that the user can interact with to check/uncheck the box

# Show data table
checkboxInput(inputId = "show_data",
              label = "Show data table", 
              value = TRUE)

Watch for commas!

A cautionary tale before we move on – watch for your commas! Remember that this widget definition goes in the sidebarPanel(). In this panel we separate out widget with commas.

For example:

2. ui: Add an output to the UI defining where the data table should appear.

mainPanel(
  # Show scatterplot
  plotOutput(outputId = "scatterplot"), 
  # Show data table
  dataTableOutput(outputId = "movestable")
)

The second step was to add an output to the UI defining where the data table should appear. Note that for this we’re using the dataTableOutput() function.

This function takes one argument, the outputId. Again, you can define to be anything we want, but short and informative names are the best.

3. server: Add a reactive expression that creates the data table if the checkbox is checked.

# Print data table if checked 
output$moviestable <- renderDataTable({
  if(input$show_data){
    DT::datatable(data = movies %>% select(1:7),
                  options = list(pageLength = 10),
                  rownames = FALSE)
  } 
})

Lastly, in our server, we describe how this table should be calculated. We use the renderDataTable() function to build this table.

Note that the first line of code in the function is an if statement, telling the app to only do this if input$show_data is TRUE. We also specify some other arguments to datatable(), mostly for cosmetic reasons.

Here is the resulting app, with the box checked:

and box unchecked:

Here is the code. You may copy the code into your R Studio IDE, highlight and press CTRL + Enter to preview the output.

# Load packages ----------------------------------------------------------------

library(shiny)
library(ggplot2)
library(dplyr)
library(DT)

# Load data --------------------------------------------------------------------

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

# Define UI --------------------------------------------------------------------

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      # Select variable for y-axis 
      selectInput(inputId = "y", label = "Y-axis:",
                  choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
                  selected = "audience_score"),  
      # Select variable for x-axis
      selectInput(inputId = "x", label = "X-axis:",
                  choices = c("imdb_rating", "imdb_num_votes", "critics_score",  
                              "audience_score", "runtime"),
                  selected = "critics_score"),
      # Show data table 
      checkboxInput(inputId = "show_data",
                    label = "Show data table", value = TRUE)   
    ),
    
    mainPanel(
      # Show scatterplot
      plotOutput(outputId = "scatterplot"), 
      # Show data table
      dataTableOutput(outputId = "movestable")
    )
  )
)

# Define server ----------------------------------------------------------------

server <- function(input, output, session) {
  output$moviestable <- renderDataTable({
    if(input$show_data){
      DT::datatable(data = movies %>% select(1:7),
                    options = list(pageLength = 10),
                    rownames = FALSE)
    } 
  })
}

# Create the Shiny app object --------------------------------------------------

shinyApp(ui = ui, server = server)

render*() functions

The goal of this section is to build familiarity with a few rendering functions.

Shiny provides a wide selection of output types, each of which works with a render function. We looked at the renderPlot() function before.

Now let’s take a look at the renderTable() function.

This is the app you’ve seen numerous times so far that we will use as our starting point.

And this is the final app that we want to achieve, with a summary table beneath the plot.

renderTable

So we want to add a summary table beneath the plot displaying summary statistics for a new variable we’ll create: score_ratio, the ratio of audience scores to critics scores.

We need to make four modifications to our app to accomplish this.

  1. Calculate the new variable
  2. In the ui: Add an input widget that the user can interact with to check boxes for selected title types.
  3. In the ui: Add an output defining where the summary tables should appear.
  4. In the server function: Add a reactive expression that creates the summary table. We’ll go through these steps one by one.

1. Calculate the new variable.

# Create new variable:
# ratio of critics and audience scores
movies <- movies %>%
  mutate(score_ratio = audience_score / critics_score)

First is creating the new variable, which we can do outside of the ui and the server so it’s calculated once when our app launches.

There are many ways one can create the new variable in R. We do so here using the mutate() function from the dplyr package.

2. ui: Add an input widget that the user can interact with to check boxes for selected title types.

# Subset for title types
checkboxGroupInput(inputId = "selected_title_type", 
                   label = "Select title type:", 
                   choices = levels(moves$title_type),
                   selected = levels(movies$title_type))

3. ui: Add an output defining where the summary tables should appear.

mainPanel(
  # Show scatterplot
  plotOutput(outputId = "scatterplot"), 
  # Show data table
  tableOutput(outputId = "summarytable")
)

We use the tableOutput() function for this. This function takes one argument, the outputId.

4. server: Add a reactive expression that creates the summary table.

Lastly, in the server, we describe how this table should be calculated with the renderTable() function.

output$summarytable <- renderTable(
  {
    movies %>%
      filter(title_type %in% input$selected_title_type) %>%
      group_by(mpaa_rating) %>%
      summarise(mean_score_ratio = mean(score_ratio), SD = sd(score_ratio), n = n())
  },
  striped = TRUE,
  spacing = "l",
  align = "lccr",
  digits = 4,
  width = "90%",
  caption = "Score ratio (audience / critics' scores) summary statistics by MPAA rating."
)

Note that the name of the output created by the render function should match the name we used for the output in the ui (summarytable).

The first argument is the expression that returns an R object in tabular form.

  {
    movies %>%
      filter(title_type %in% input$selected_title_type) %>%
      group_by(mpaa_rating) %>%
      summarise(mean_score_ratio = mean(score_ratio), SD = sd(score_ratio), n = n())
  }

Note that we wrap the expression with curly braces. The expression first filters for the selected title types. Since this is a user selection, the information is in the input list generated in the ui.

Then, the expression groups the data by MPAA rating, and then calculates summary statistics like means, standard deviations, and sample sizes for each level of MPAA ratings.

If we stopped here and didn’t include any of the following arguments, the app would look something like this:

But we want our table to look like this:

In order to achieve this look, we add additional arguments to our render* function.

striped = TRUE, spacing = "l", align = "lccr", digits = 4, width = "90%",
caption = "Score ratio (audience / critics' scores) summary statistics by MPAA rating."

Add renderText()

In this app, the user selects x and y variables for the scatterplot. We will extend the app to also include a textOutput which prints the correlation between the two selected variables as well some informational text.

In this code, we will attempt to

  1. Create the text to be printed using the paste() function: “Correlation = ____. Note: If the relationship between the two variables is not linear, the correlation coefficient will not be meaningful.”
  2. Place the text within the renderText() function, and assign to output text .
# Load packages ----------------------------------------------------------------

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

# Load data --------------------------------------------------------------------

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

# Define UI --------------------------------------------------------------------

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput(
        inputId = "y",
        label = "Y-axis:",
        choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
        selected = "audience_score"
      ),
      
      selectInput(
        inputId = "x",
        label = "X-axis:",
        choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
        selected = "critics_score"
      )
    ),
    
    mainPanel(
      plotOutput(outputId = "scatterplot"),
      textOutput(outputId = "correlation")
    )
  )
)

# Define server ----------------------------------------------------------------

server <- function(input, output, session) {
  output$scatterplot <- renderPlot({
    ggplot(data = movies, aes_string(x = input$x, y = input$y)) +
      geom_point()
  })
  
  # Create text output stating the correlation between the two ploted
  output$correlation <- renderText({
    r <- round(cor(movies[, input$x], movies[, input$y], use = "pairwise"), 3)
    paste0(
      "Correlation = ", r,
      ". Note: If the relationship between the two variables is not linear, the correlation coefficient will not be meaningful."
    )
  })
}

# Create the Shiny app object --------------------------------------------------

shinyApp(ui = ui, server = server)

Run the script in your R studio IDE to view and manipulate the output. You should see something like this :

UI Outputs

We’ve seen UI outputs before, and in this section we dive deeper into the plotOutput() function to build an interactive graph.

Once again we’re going to start with this app.

And we’ll add functionality to the app so that movies corresponding to the selected points on the plot via brushing are displayed in a data table beneath the plot.

plotOutput()

We need to make three modifications to our app to accomplish this.

  1. ui: Add functionality to plotOutput to select points via brushing.
  2. ui: Add an output defining where the data table should appear.
  3. server: Add a reactive expression that creates the data table for the selected points. Let’s go through these steps one by one.

1. ui: Add functionality to plotOutput to select points via brushing.

2. ui: Add an output defining where the data table should appear.

3. server: Add a reactive expression that creates the data table for the selected points.

Brushing

Here is how to create the output that was described in the previous section, with a brush used to select entries which will appear on the data table.

# Load packages ----------------------------------------------------------------

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

# Load data --------------------------------------------------------------------

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

# Define UI --------------------------------------------------------------------

ui <- fluidPage(
  br(),
  
  sidebarLayout(
    sidebarPanel(
      selectInput(
        inputId = "y", label = "Y-axis:",
        choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
        selected = "audience_score"
      ),
      
      selectInput(
        inputId = "x", label = "X-axis:",
        choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
        selected = "critics_score"
      )
    ),
    
    mainPanel(
      plotOutput(outputId = "scatterplot", brush = "plot_brush"),
      DT::dataTableOutput(outputId = "moviestable"),
      br()
    )
  )
)

# Define server ----------------------------------------------------------------

server <- function(input, output, session) {
  
  output$scatterplot <- renderPlot({
    ggplot(data = movies, aes_string(x = input$x, y = input$y)) +
      geom_point()
  })
  
  output$moviestable <- renderDataTable({
    brushedPoints(movies, brush = input$plot_brush) %>%
      select(title, audience_score, critics_score)
  })
}

# Create the Shiny app object --------------------------------------------------

shinyApp(ui = ui, server = server)

Run the script in your R studio IDE to view and manipulate the output. You should see something like this :

Hovering

In addition to brushing, users can also interact with plots via hovering over them, which the following code will demonstrate.

# Load packages ----------------------------------------------------------------

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

# Load data --------------------------------------------------------------------

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

# Define UI --------------------------------------------------------------------

ui <- fluidPage(
  br(),
  
  sidebarLayout(
    sidebarPanel(
      selectInput(
        inputId = "y", label = "Y-axis:",
        choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
        selected = "audience_score"
      ),
      selectInput(
        inputId = "x", label = "X-axis:",
        choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
        selected = "critics_score"
      )
    ),
    
    mainPanel(
      plotOutput(outputId = "scatterplot", hover = "plot_hover"),
      dataTableOutput(outputId = "moviestable"),
      br()
    )
  )
)

# Define server ----------------------------------------------------------------

server <- function(input, output, session) {
  output$scatterplot <- renderPlot({
    ggplot(data = movies, aes_string(x = input$x, y = input$y)) +
      geom_point()
  })
  
  output$moviestable <- renderDataTable({
    nearPoints(movies, input$plot_hover) %>%
      select(title, audience_score, critics_score)
  })
}

# Create the Shiny app object --------------------------------------------------

shinyApp(ui = ui, server = server)

Run the script in your R studio IDE to view and manipulate the output. You should see something like this, with the data table displaying the selected movies and details as you hover over the section of the scatter plot :

Displaying Text Outputs

This code will develop an app where the user selects two variables and their relationship is visualized with a scatterplot, and averages of both variables are reported as well as the output of the linear regression predicting the variable on the y-axis from the variable in the x-axis.

# Load packages ----------------------------------------------------------------

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

# Load data --------------------------------------------------------------------

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

# Define UI --------------------------------------------------------------------

ui <- fluidPage(
  sidebarLayout(
    
    sidebarPanel(
      
      selectInput(inputId = "y",
                  label = "Y-axis:",
                  choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
                  selected = "audience_score"),
      
      selectInput(inputId = "x",
                  label = "X-axis:",
                  choices = c("imdb_rating", "imdb_num_votes", "critics_score", "audience_score", "runtime"),
                  selected = "critics_score")
      
    ),
    
    # Output(s)
    mainPanel(
      plotOutput(outputId = "scatterplot"),
      textOutput(outputId = "avg_x"), # avg of x
      textOutput(outputId = "avg_y"), # avg of y
      verbatimTextOutput(outputId = "lmoutput") # regression output
    )
  )
)

# Define server ----------------------------------------------------------------

server <- function(input, output, session) {
  
  output$scatterplot <- renderPlot({
    ggplot(data = movies, aes_string(x = input$x, y = input$y)) +
      geom_point()
  })
  
  output$avg_x <- renderText({
    avg_x <- movies %>% pull(input$x) %>% mean() %>% round(2)
    paste("Average", input$x, "=", avg_x)
  })
  
  output$avg_y <- renderText({
    avg_y <- movies %>% pull(input$y) %>% mean() %>% round(2)
    paste("Average", input$y, "=", avg_y)
  })
  
  output$lmoutput <- renderPrint({
    x <- movies %>% pull(input$x)
    y <- movies %>% pull(input$y)
    print(summary(lm(y ~ x, data = movies)), digits = 3, signif.stars = FALSE)
  })
  
}

# Create the Shiny app object --------------------------------------------------

shinyApp(ui = ui, server = server)

Run the script in your R studio IDE to view and manipulate the output. You should see something like this :

Download data with downloadButton()

This code will use the downloadHandler() function in the server and downloadButton() or downloadLink() function in the UI. This would allow users to select and download the data in the fields they want.

# Load packages ----------------------------------------------------------------

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

# Load data --------------------------------------------------------------------

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

# Define UI --------------------------------------------------------------------

ui <- fluidPage(
  sidebarLayout(
    
    sidebarPanel(
      
      radioButtons(inputId = "filetype",
                   label = "Select filetype:",
                   choices = c("csv", "tsv"),
                   selected = "csv"),
      
      checkboxGroupInput(inputId = "selected_var",
                         label = "Select variables:",
                         choices = names(movies),
                         selected = c("title"))
      
    ),
    
    mainPanel(
      HTML("Select filetype and variables, then hit 'Download data'."),
      downloadButton("download_data", "Download data")
    )
  )
)

# Define server ----------------------------------------------------------------

server <- function(input, output, session) {
  
  # Download file
  output$download_data <- downloadHandler(
    filename = function() {
      paste0("movies.", input$filetype)
    },
    content = function(file) { 
      if(input$filetype == "csv"){ 
        write_csv(movies %>% select(input$selected_var), file) 
      }
      if(input$filetype == "tsv"){ 
        write_tsv(movies %>% select(input$selected_var), file) 
      }
    }
  )
  
}

# Create the Shiny app object --------------------------------------------------

shinyApp(ui = ui, server = server)

Run the script in your R studio IDE to view and manipulate the output. You should see something like this :

Here’s a Sample Code with Multiple Reactive Elements

Scan through the code and run it to see if you can identify the various reactive elements that have been added to the features of this app.

# Load packages ----------------------------------------------------------------

library(shiny)
library(ggplot2)
library(tools)

# Load data --------------------------------------------------------------------

movies <- read.csv(file = "https://raw.githubusercontent.com/aaron-chen-angus/S3729C-Lesson-Files/6a58d56d3d42231fb011af462db7efc01537e515/movies.csv", header = TRUE, sep = ",")

# Define UI --------------------------------------------------------------------

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput(
        inputId = "y",
        label = "Y-axis:",
        choices = c(
          "IMDB rating" = "imdb_rating",
          "IMDB number of votes" = "imdb_num_votes",
          "Critics Score" = "critics_score",
          "Audience Score" = "audience_score",
          "Runtime" = "runtime"
        ),
        selected = "audience_score"
      ),
      
      selectInput(
        inputId = "x",
        label = "X-axis:",
        choices = c(
          "IMDB rating" = "imdb_rating",
          "IMDB number of votes" = "imdb_num_votes",
          "Critics Score" = "critics_score",
          "Audience Score" = "audience_score",
          "Runtime" = "runtime"
        ),
        selected = "critics_score"
      ),
      
      selectInput(
        inputId = "z",
        label = "Color by:",
        choices = c(
          "Title Type" = "title_type",
          "Genre" = "genre",
          "MPAA Rating" = "mpaa_rating",
          "Critics Rating" = "critics_rating",
          "Audience Rating" = "audience_rating"
        ),
        selected = "mpaa_rating"
      ),
      
      sliderInput(
        inputId = "alpha",
        label = "Alpha:",
        min = 0, max = 1,
        value = 0.5
      ),
      
      sliderInput(
        inputId = "size",
        label = "Size:",
        min = 0, max = 5,
        value = 2
      ),
      
      textInput(
        inputId = "plot_title",
        label = "Plot title",
        placeholder = "Enter text to be used as plot title"
      ),
      
      actionButton(
        inputId = "update_plot_title",
        label = "Update plot title"
      )
    ),
    
    mainPanel(
      plotOutput(outputId = "scatterplot")
    )
  )
)

# Define server ----------------------------------------------------------------

server <- function(input, output, session) {
  new_plot_title <- eventReactive(
    eventExpr = input$update_plot_title,
    valueExpr = {
      toTitleCase(input$plot_title)
    }
  )
  
  output$scatterplot <- renderPlot({
    ggplot(data = movies, aes_string(x = input$x, y = input$y, color = input$z)) +
      geom_point(alpha = input$alpha, size = input$size) +
      labs(title = new_plot_title())
  })
}

# Create the Shiny app object --------------------------------------------------

shinyApp(ui = ui, server = server)

Run the script in your R studio IDE to view and manipulate the output. You should see something like this :

Try It Out !

Use Your Dataset from Component 2 Survey on your Media Production

  1. Upload your dataset as a .csv file in GitHub
  2. Create a Shiny App involving the display of selected variables on a scatter plot
  3. Include some of the features covered in this lesson

Enjoy

References

Mastering Shiny: Build Interactive Apps, Reports, and Dashboards Powered by R 1st Edition

by Hadley Wickham
online version : https://mastering-shiny.org/

citation : Wickham, H. (2021). Mastering Shiny: Build Interactive Apps, Reports, and Dashboards Powered by R. (1st ed). O’Reilly