This week’s Lab Assignment focuses on creating interactive graphics and maps with both the plotly and tmap packages. The first part of the Lab Assignment uses the flexdashboard package because it leverages R Markdown which we have already built some familiarity with this semester (i.e., used in R Notebooks like these).
Cities are increasing creating public-facing dashboards as a means of tracking performance indicators and communicating basic information to the public. However, the use of these tools has faced criticism 😒 exemplified by this recently published journal article. Here are a few examples of dashboards maintained by local governments:
Let’s begin by accessing a dataset to work with. Most cities in the United States collect and publish information related to 311 calls. As described here, “311 is a non-emergency phone number that people can call in many cities to find information about services, make complaints, or report problems like graffiti or road damage.” We will access 311 service call data from the City of Chicago Open Data Portal for this Lab Assignment.
This portion of the Lab Assignment walks through the acquisition, preparation, and visualization of 311 service call data using flexdashboard. The function reference is available here. The first thing that we will need to do is to create a new R Markdown document that will serve as the basis for our dashboard.
From RStudio, click File -> New File
-> R Markdown as shown in the image below:
Then, in the window that appears (see image below), click From Template and Flex Dashboard as shown.
An in-depth discussion of how web-applications are created with
shiny is beyond the scope of this course, but the main
thing we need to do to achieve our goals is to modify the YAML header of the R
Markdown document we have used to create the static version
of the dashboard. As shown below, we have added an option called
runtime and set it to shiny, which adds
reactivity (e.g., the underlying data shown in the dashboard is
refreshed each time the dashboard is loaded).
We can control
the layout of the dashboard using the orientation
option and Level 2 Markdown headers --------------- are
used to define the rows or columns that make up the dashboard “panes”.
The vertical_layout option can be set to
scroll or fill (the default), but the
former is only recommended if there are many charts or plots to
view.
Setting the source_code option to embed
makes it possible for viewers of the dashboard to access the source code
directly from a web browser. We can also change the look-and-feel by
adding the theme option and specifying one of the alternatives shown
here.
---
title: "Dynamic Dashboard"
runtime: shiny
output:
flexdashboard::flex_dashboard:
orientation: rows
vertical_layout: scroll
source_code: embed
theme: cosmo
---
Please change the new R Markdown document you just
opened so that the header looks something like what you see above. Just
FYI, replacing orientation: columns with
orientation: rows means we arrange the plots within our
dashboard in rows rather than columns. Also, changing the
vertical_layout: portion of the header from
fill to scroll allows users to scroll down the
dashboard if there are many plots to view.
If you are uncomfortable hard-coding your Mapbox API token into your dashboard (and you should be…), you can read in the token, save it to an RData file, and upload that with your code instead ⚡ (more on that part later in this document). Alternatively, if you do not have a Mapbox API token you can create maps for this dashboard using the tmap package rather than the plotly package. We will see how to do that shortly 🕒
If you are using plotly for the dashboard map and have a Mapbox API token, you only need to run the commented out code below once and it should be done in the RStudio Console.
library(tidyverse)
#my.token <- read_lines("./bevs_api_key.txt")
#Sys.setenv("MAPBOX_TOKEN" = my.token)
#saveRDS(my.token, file = "bevs_mapbox_token.rds")
Afterwards, you could delete the above code chunk altogether. The first real code chunk in this R Notebook is where we will load all the packages and API tokens we will need. Modify the code chunk immediately after the YAML header in your new R Markdown document by inserting the code below AFTER 😩 you have exported your Mapbox token if you are taking that route:
Ultimately, we want to create an interactive map that shows the location of 311 service calls and so we will write the code to generate it inside a code chunk that is situated within one of dividers that breaks the dashboard into panes (refer to these diagrams for more detail.)
Add a new code chunk below the one that loads the packages and Mapbox
token, then modify it to match the code chunk shown below. First, we
retrieve the current date using Sys.Date because we want to
fetch
data from the API that was collected for the previous week only.
Next, we find the date that corresponds to one week prior by simply
subtracting seven days as shown.
After perusing the API documentation for this dataset, we craft the
baseurl shown below which allows us to filter out calls
that were made just to ask a question and also to limit the results to
only calls for service made within the prior week.
The data are returned in JSON format, but we can process it using a function from the jsonlite package. After removing records with missing data and converting the coordinates to numeric format, we are ready to create the interactive map. Note that a bug in the plotly.js JavaScript library that we are accessing from the R environment prevents us from using other symbols in the scattermapbox mode for the time being 😱 .
current.date <- Sys.Date()
prev.week <- (current.date - 7)
baseurl <- "https://data.cityofchicago.org/resource/v6vf-nfxy.json?$where=sr_type!='311 INFORMATION ONLY CALL' AND created_date > "
# Define a function that will fetch the 311 Service Calls data
# each time the dashboard page is loaded in a web browser...
load_remote_data <- function() {
fromJSON(readLines(URLencode(paste(baseurl, "'", prev.week, "'", "&$limit=50000", sep = "", collapse=""))))
}
df <- load_remote_data()
# Convert coordinates to numeric...
df <- df %>%
hablar::convert(
dbl(latitude),
dbl(longitude)
)
# Remove missing values...
df <- df %>%
filter(is.na(latitude) == FALSE)
In order to make the dashboard fully dynamic we included code (in the above chunk) that fetches the most recent 311 Service Calls data from the City of Chicago Open Data Portal each time the dashboard is loaded in a web browser. Although the dashboard was created using the flexdashboard package, we can leverage some of the functionality of shiny in order to make our dashboard an interactive document.
The important point about the load_remote_data
function is that it retrieves the 311 Service Calls data from the
Chicago Open Data Portal in real-time. The URLencode
function helps to properly format the http query we use to access the
data. As described in the documentation
page for this function, “Characters in a URL other than the
English alphanumeric characters and - _ . ~ should be encoded as % plus
a two-digit hexadecimal representation”.
Now, insert one of the two pieces of code below into the existing chunk under the Chart A header.
# Create the map using plotly...
#plot_mapbox(df, lat = ~latitude, lon = ~longitude, mode = 'scattermapbox') %>%
# add_markers(text = ~paste(sr_type, "\n", status),
# color = ~status, colors = c("purple", "green", "red"),
# size = 3000, hoverinfo = "text", alpha = 0.5) %>%
# layout(
# mapbox = list(zoom = 10, center = list(lat = ~median(df$latitude),
# lon = ~median(df$longitude))),
#legend = list(orientation = 'h',
# font = list(size = 8)))
# Create the map using tmap if you don't have a Mapbox API token...
calls_sf <- st_as_sf(df, coords = c("longitude","latitude"))
st_crs(calls_sf) <- 4326
tmap_mode("view")
## tmap mode set to interactive viewing
tm_shape(calls_sf, name = "Calls") +
tm_dots(col = "status", id = "sr_number",
palette = c("Completed" = "green4", "Open" = "red", "Canceled" = "yellow"),
alpha = 0.5,
popup.vars = c(
"This call was about: " = "sr_type",
"This call is now: " = "status"),
colorNA = "white",
textNA = "No complaints documented",
title = "Call Status") +
tm_layout(title = "311 Calls - Past 7 Days")