I built this app as an exercise to practice R, R markdown, and Shiny. It was an exercise in joining WHO homicide, UN population and UN GDP data, and in getting a lightweight world polygon geodata file with iso alpha3 country codes to use with leaflet.

Data masking in dplyr

I learned the dplyr grammar has some quirks, like having to use .data[[var]] or {{var}} when supplying a variable through a character vector or function argument. It’s documented here. The following code outputs an empty frame without an error as if the condition was not met.

library(dplyr)
df <- data.frame(V1 = c(.2,.1,.11,.15,.4), V2 = c(30,21,43,65,29))
svar <- "V2"

df %>% 
  filter(svar < 30)
## [1] V1 V2
## <0 rows> (or 0-length row.names)

It seems the problem stems from drawing an equivalence between the data-variable df$V2 and the env-variable svar. They are not equivalent. Finding the data-variable in the current data frame solves the problem.

df %>% 
  filter(.data[[svar]] < 30)
##    V1 V2
## 1 0.1 21
## 2 0.4 29

The following also works, but it seems bang-bang precedes .data. Not sure.

df %>% 
  filter(!!sym(svar) < 30)
##    V1 V2
## 1 0.1 21
## 2 0.4 29

This also works and it saves some typing if you have to call the variable a few times.

svar <- sym("V2")
df %>% 
  filter(!!svar < 30)
##    V1 V2
## 1 0.1 21
## 2 0.4 29

I was confused since other dplyr functions do not need such wrapping.

df %>% 
  select(svar)
##   V2
## 1 30
## 2 21
## 3 43
## 4 65
## 5 29

This works as is. Seems inconsistent to me.

Dependent dropdowns in Shiny

I learned there are many ways to build contextual dropdown menus in Shiny. After trying the “update” and “dynamic UI” options, I chose the update method. Here is a simple example.

library(shiny)
df <- data.frame(state = c("MN","CA","CA","MN","MN","CA"),
                 city = c("Deluth","San Diego","Los Angeles","St. Paul","Deluth","Oakland"))
ui <- basicPage(
  selectInput("state", "Select State",
              choices = unique(df$state)),
  selectInput("city", "Select City", 
              choices = NULL)
)

server <- function(input, output, session) {
  observeEvent(input$state, {
    cities <- df$city[df$state == input$state]
    updateSelectInput(session, "city", choices = unique(cities))
  })
}

shinyApp(ui, server)

Note that session must be added to the server and update functions.

ISO3-friendly polygons for Leaflet

For leaflet choropleths world maps you need a data object that has the polygon information of each country. There are many ways to get this. I settled for using the geodata package to get an RSD file from GADM.

geodata::world(resolution = 5, level=0, path="data")

This function is meant to be run once. It saves a file in the path you indicate. I don’t include this in the app. In the app, I read the file, transform it into a SpatVector object, then into an sf object. The object is then ready to be fed to leaflet.

world_sf <- readRDS("data/gadm36_adm0_r5_pk.rds") %>% 
  terra::vect() %>% 
  sf::st_as_sf() %>% 
  sf::st_transform(crs = "+proj=longlat +datum=WGS84")

In this example, the world_sf object has two variables, GID_0 the ISO3 alpha country codes, and NAME_0 the country name. You can use the ISO3 country codes to easily merge data that has ISO3 codes.

Suppose you have a dataframe with a variable rate that has the homicide rates, and a variable iso3 that has the ISO3 country codes. You can join the data like this:

rates <- df %>% 
  select(iso3,rate)
    
world_sf <- left_join(world_sf, rates, by = c("GID_0" = "iso3"))

Then just feed this joined data to leaflet. The result is a beautiful choropleth map.


2022 Ingrid Lagos