Maps!?!

Making maps using tidycensus() and tigris()

Figuring out how to make fancy electoral maps in r and then documenting it so I don’t have to keep looking it up!

via GIPHY

Recently ran into a few situations where I needed to make some data-driven maps that reflected either congressional districts or census data or some combination of the two. Here are my notes and

Creating Congressional District Maps with ggplot2

For a project examining the Electoral College and voting behavior, we needed to make visually appealing maps of congressional districts. Our study focused on on four states: Maine, New Hampshire, Nebraska, and Kansas. Two of these states allocate their EC votes by congressional district (Nebraska & Maine) and the other two are winner-take-all states of comparable size/politics (Kansas & New Hampshire respectively). But this process could be used to create similar maps for any state in the U.S.

Setting Up Your Environment

First, let’s load the necessary libraries:

# load packages
library(sf)
library(tigris)
library(ggplot2)
library(dplyr)
library(ggfx)
library(ggtext)

Each package served a different purpose in the making of our maps:

  • sf: Handles spatial data
  • tigris: Accesses U.S. Census Bureau geographic data
  • ggplot2: Creates our visualizations
  • dplyr: Manipulates data
  • ggfx: Adds visual effects
  • ggtext: Enhances text rendering

In order to get the district-level map shape, we need to set some options for the tigris package:

options(tigris_class = "sf", tigris_use_cache = TRUE)

This ensures we’re using simple features (sf) class and caching data to speed up future runs.

Creating a Map: Nebraska as an Example

Let’s walk through the process of creating the Nebraska congressional district map:

Coordinates

Download the shape-file for each congressional district data using tigris::congressional_districts()

neb_districts <- tigris::congressional_districts(state = "NE", cb = TRUE, year = 2020)

You can also ask for the unique GEOID values for each district. This is needed to set the colors for the district.

print(unique(neb_districts$GEOID))
  1. Create the basic Nebraska map using ggplot2. We will want to include theme_void() to just get the map with not plot-background/axis/etc. We also want to use the GEOID information to set the colors using scale_fill_manual().
p <- ggplot(neb_districts) +
  geom_sf(aes(fill = as.factor(GEOID)), color = "#f5f1e7", lwd = 2) +
  theme_void() +
  scale_fill_manual(values = c("3103" = "#E81B23", "3101" = "#00AEF3", "3102" = "#a5228d"))

print(p)

We used the hex codes for the Republican #E81B23 and Democrat #00AEF3 parties. Also, purple #a5228d because NE-2 has operated as a swing-district over the last few Presidential elections.

Nebraska congressional districts - basic

Create a dataframe for district labels:

ne_label <- tibble(
  txt = c("**NE-1**", "**NE-2**", "**NE-3**"),
  lon = c(-97, -95.5, -101),
  lat = c(41.9, 41.3, 42.3)
)
  1. Create the plot using ggplot2:
p1 <- ggplot(neb_districts) +
  with_shadow(geom_sf(aes(fill = as.factor(GEOID)), color = "#f5f1e7", lwd = 2)) +  
  theme_void() +
  scale_fill_manual(values = c("3103" = "#E81B23", "3101" = "#00AEF3", "3102" = "#a5228d")) +
  with_shadow(geom_richtext(
    data = ne_label, 
    aes(x = lon, y = lat, label = txt), 
    size = 5.5, 
    label.size = 1
  )) +
  theme(legend.position = "none")

print(p1)

Let’s break down this code:

  • ggplot(neb_districts): Initializes the plot with our district data
  • with_shadow(geom_sf(...)): Draws the district boundaries with a shadow effect
  • theme_void(): Removes the default background grid
  • scale_fill_manual(): Sets custom colors for each district
  • with_shadow(geom_richtext(...)): Adds district labels with a shadow effect
  • theme(legend.position = "none"): Removes the legend
  1. Display and save the plot:

Nebraska congressional districts with labels
ggsave("nebraska_districts.png", width = 10, height = 6, units = "in")

Repeating for Other States

The process is similar for other states. Here’s how you’d create the Maine map:

  1. Download data and create labels:
maine_districts <- tigris::congressional_districts(state = "ME", cb = TRUE, year = 2020)

me_label <- tibble(
  txt = c("**ME-1**", "**ME-2**"),
  lon = c(-70.8, -69),
  lat = c(43.6, 46)
)
  1. Create the plot:
p3 <- ggplot(maine_districts) +
  with_shadow(geom_sf(aes(fill = as.factor(GEOID)), color = "#f5f1e7", lwd = 0.8)) +  
  theme_void() +
  scale_fill_manual(values = c("2301" = "#E81B23", "2302" = "#00AEF3")) +
  with_shadow(geom_richtext(
    data = me_label, 
    aes(x = lon, y = lat, label = txt),
    size = 5.5, 
    label.size = 1
  )) +
  theme(legend.position = "none")

print(p3)
  1. Display and save:

Maine congressional districts
ggsave("maine_districts.png", width = 10, height = 6, units = "in")

Customization Tips

  1. Colors: Adjust the scale_fill_manual() function to change district colors.
  2. Labels: Modify the txt, lon, and lat values in the label tibble to reposition or rename labels.
  3. Shadows: The with_shadow() function adds depth. Remove it for a flatter look.
  4. Boundaries: Change the color and lwd parameters in geom_sf() to adjust boundary appearance.

Conclusion

By following these steps, you can create congressional district maps for any state. The key is to use the tigris package to obtain the geographic data, and then leverage ggplot2’s powerful visualization capabilities to create informative and visually appealing maps.

Remember, you can apply these techniques to other types of geographic data as well. Yay maps!

Note

These maps all use the 2020 congressional district data

Back to top