Module 5.2

Exploring Reactivity

Prework
  • Get a FRED API key
  • Install fredr, read about its basic usage and have a look at the FRED website
  • Install ecm, which we will use to build our recession shading helper script
  • Install shinyWidgets and familiarize yourself with its basic functions
  • Start a new Shiny project for this lesson. Go to File, select New Directory and then Shiny App. Browse to where you want to save the app, give the directory a name and click Create Project.

Overview

A fundmental concept in R Shiny is reactivity. Reactivity refers to the automatic responsiveness and dynamic behavior of the application based on user input and data changes. It allows the application to update and re-render specific parts of the user interface (UI) in response to changes in input values, data updates, or other reactive triggers.

All Shiny apps have an element of reactivity. In a basic Shiny app like we say in the last module, reactivity occurs when user input is fed to the server function through functions like renderPlot() or renderTable(). But we might also want to add additional elements of reactivity by using reactive functions like reactive() or observe(). In this lesson, we are going to look at how to use the reactive() function to control two separate reactive inputs to a line chart: the indicator the user wishes to view and the date range that the way to view it for. Here is the app that we are going to be building:

Setup

In the setup portion of our we want to start by loading the packages we will need to build the app. For this app, we are going to be using the fredr package to download data pertaining to the overall health of the economy from the St. Louis Fed’s Federal Reserve Economic Data (FRED)[https://fred.stlouisfed.org/] API. So here we will also set our FRED API key and assign the codes for the indicators that we want to download to objects.

Next, we will use the as.Date() function to set the start date of our line series to Januar 1, 1970 and the end date as today’s date (system date). We will also create a list of variable names for our UI dropdown (vars) and relate them to the objects containing the indicator codes.

Finally, we are going to be using a helper function to generate recession shading for our charts. Scroll down to the bottom of this page to see the code for the helper function. Take this and save it in an R file and put it in your app folder. In this chunk, we are going to call it with the source function, e.g. source(helper.R).

# Load packages
library(shiny)
library(fredr)
library(dplyr)
library(ggplot2)

# Set Fred API key 
fredr_set_key("YOUR FRED API KEY") 

# Assign FRED series to objects
cci <- "CSCICP03USM665S" # consumer confidence
bci <- "BSCICP03USM665S" # business confidence
cli <- "USALOLITONOSTSAM" # composite lead indicator
unemp_rate <- "UNRATE" # unemployment rate
growth <- "A191RL1Q225SBEA" # growth rate

# set start and end date
start_date <- as.Date("1970-01-01")
end_date <- as.Date(Sys.Date())

# Create list of named values for the input selection
vars <- c("Consumer Confidence" = cci, 
          "Business Confidence" = bci, 
          "Composite Indicator" = cli, 
          "Unemployment Rate" = unemp_rate,
          "Growth Rate" = growth)

# Load helper script
source("helper.R") # scroll down, code pasted below

UI

Now we can get started on developing the UI for our app. For this app, we are going to have a title panel and two main display elements. The first is a panel where the user can select the indicator that they want to chart and the second is a plot with a slider where users can select the years they want to view. So let’s divide the UI into two sections using the fluidRow() and column()

The fluidRow() function creates horizontal containers while the column() function is used to create vertical containers. Since we our app is going to display a single row, we will have just one fluidRow() call. Then we can divide that row into two columns using the column() function. The first argument in column()is the column width. Since column widths in Shiny are based on the Boots Bootstrap 12-wide grid system, our column widths must add up to 12. So let’s make our panel for selecting the indicator 4 units wide and the area where we will display the plot 8 units wide.

From there, we can define the panel as a wellPanel() to give it an inset look and a grey background. We include selectInput() to get our dropdown where users can select an indicator from the vars list. Let’s also use the helpText() function to display some instructions regarding how to use the app.

For the main display section, we are going to have our plot out along with the slider input. We are going to call this input “range” and leave the label blank. Then we need to define a min value, a max value and a range for the slider. For min and max, we will use start_date and end_date, which we defined earlier in the setup and the combination of these to define the range (value). Then we set width to 100% because we want the slider to expand to fit the entire width of the plot. `

ui <- fluidPage(

    # Application title
    titlePanel("FRED Data App"),
    
    fluidRow(
      
      # 12 columns on one row: this panel will take 1/3 of it
      column(4, wellPanel(
        selectInput("indicator", "Indicator:", vars)
        ),
      helpText("Select an indicator, choose a date range and view the trend. 
               The grey bars represent economic recessions. 
               The data for this app comes from the St. Louis Fed's 
               FRED database. The consumer confidence, business confidence and 
               lead composite indicators are OECD data downloaded through FRED.")
      ), 
      
      # Remaining 2/3 occupied by plot
      column(8,
        plotOutput("lineChart"),     
        sliderInput(
          "range",
          "",
          min = start_date,
          max = end_date, 
          value = c(start_date, end_date), 
          width = "100%"
        )
      )
    )
)

Server

For our server function, we are going to define two separate reactive() functions. This is how we are going to dynamically update the plot based on two different user inputs. First, we will define an input for the indicator where input$indicator takes the user input from the dropdown menu to perform a fresh API call whenever the selected indicator changes. Then, we take that input and filter it based on the input from the slider, e.g. input$range. Then we render the plot based on these updated data.

Notice that whenever we want to use the stored data from the reactive calls we need to add parentheses after the objects, e.g. fred_indicator() in the second reactive function or fred_data() in the ggplot call. This is to ensure that the reactive expression is evaluated and its current value is used as the input data for the plot.

Finally, we are going to use the add_rec_shade() helper function to add the recession shading to the chart. We again use the inputs from the two reactive functions to define the start date and end date of the shading as well as the y-min and y-max values of the shaded rectangles.

server <- function(input, output) {
  
    # Download data from FRED with reactive function. 
    # Only updates when user selects new indicator
    fred_indicator <- reactive({
      fredr(series_id = input$indicator,
        observation_start = start_date,
        observation_end = end_date)
    })
  
    # Filter data according to chosen years 
    # Only updates when user selects new data range
    fred_data <- reactive({
      fred_indicator() |>
      filter(between(date, input$range[1],input$range[2])) 
   })

    # Render line chart
    output$lineChart <- renderPlot({
      
      # Build plot with ggplot2
      ggplot(fred_data(), aes(x = date, y = value)) + 
        geom_line(color = "navyblue") +
        labs(
          x = "", 
          y =  names(vars[which(vars == input$indicator)])
        ) +
        theme_minimal() +
        # add recession shading
        add_rec_shade(st_date = input$range[1], 
                      ed_date = input$range[2], 
                      shade_color = "darkgrey",
                      y_min = min(fred_data()$value),
                      y_max = max(fred_data()$value))
    })
}

Call to Shiny app

Once we have our UI and server functions defined we are ready to go. But don’t forget to include the call to the Shiny app or the app won’t run! Once this is in place, you can click “Run App” in the RStudio IDE to view the app locally. Optionally, right now, you can try setting up an account on shinyapps.io and try publishing your app on their server.

# See above for the definitions of ui and server
ui <- ...

server <- ...

# Run the application 
shinyApp(ui = ui, server = server)

Helper script

This is the helper script for shaded recession rectangles. Save in a file called helper.R in same folder as your app.R file. See this post for more details.

library(ecm) # forlagpad

# define add_rec_shade function
add_rec_shade<-function(st_date,ed_date,shade_color, y_min, y_max) {
  
  # download NBER recession indicators, peak through trough
  recession<- fredr(series_id = "USRECD",
                    observation_start = as.Date(st_date), 
                    observation_end = as.Date(ed_date))
  
  #code 1 for 1st day of recession, -1 for 1st day after it ends
  recession$diff<-recession$value-lagpad(recession$value,k=1)
  
  #drop 1st N.A. value
  recession<-recession[!is.na(recession$diff),] 
  
  #create vector of recession start dates
  recession.start<-recession[recession$diff==1,]$date 
  
  #create vector of recession end dates
  recession.end<-recession[recession$diff==(-1),]$date 
  
  # if there are more dates listed in recession.start than recession.end
  if(length(recession.start)>length(recession.end))
  # then enter system date for last date in recession.end
  {recession.end<-c(recession.end,Sys.Date())} 
  
  # if there are more dates listed in recession.end than recession.start
  if(length(recession.end)>length(recession.start))       
  # then enter the earliest date in recession$date as first date in recession.start  
  {recession.start<-c(min(recession$date),recession.start)} 
  
  # make a dataframe out of recession.start and recession.end
  recs<-as.data.frame(cbind(recession.start,recession.end))
  
  # convert recession.start into a date
  recs$recession.start<-as.Date(
    as.numeric(recs$recession.start),
    origin=as.Date("1970-01-01")) 

  # convert recession.end into a date
  recs$recession.end<-as.Date(
    recs$recession.end,
    origin=as.Date("1970-01-01")) 
  
  # if the number of rows in recs > 0
  if(nrow(recs)>0) 
  # draw the rectangle  
  {rec_shade<-geom_rect(data=recs, 
                         # inherit.aes=F overrides default aesthetics
                         inherit.aes=F, 
                         aes(xmin=recession.start, 
                         xmax=recession.end, 
                         ymin=y_min, ymax=y_max), 
                         fill=shade_color, alpha=0.5)
    return(rec_shade)
  }
}