This document provides a quick example of one way to make choropleth maps out of the congressional districts shapefiles found at http://cdmaps.polisci.ucla.edu/ using R. Of course, as with most things in R, there are many packages that you could use to this. Here we are doing things Hadley Wickham’s tidy way and relying on the sf and ggmap packages (see here and here for tutorials on these packages).

Required packages

We begin by loading up up the necessary packages. Of course, you will have to install these first for this to work.

require(tidyverse)
## Loading required package: tidyverse
## Loading tidyverse: ggplot2
## Loading tidyverse: tibble
## Loading tidyverse: tidyr
## Loading tidyverse: readr
## Loading tidyverse: purrr
## Loading tidyverse: dplyr
## Conflicts with tidy packages ----------------------------------------------
## filter(): dplyr, stats
## lag():    dplyr, stats
require(ggplot2)
require(sf)
## Loading required package: sf
## Linking to GEOS 3.4.2, GDAL 2.1.2, proj.4 4.9.1
require(ggmap)
## Loading required package: ggmap
## Google Maps API Terms of Service: http://developers.google.com/maps/terms.
## Please cite ggmap if you use it: see citation("ggmap") for details.

Loading up the geographic data

Next I provide a function that returns the district boundaries for the congressional districts from a specific congress. You have have be connected to the internet for this work.

get_congress_map <- function(cong=113) {
  tmp_file <- tempfile()
  tmp_dir  <- tempdir()
  zp <- sprintf("http://cdmaps.polisci.ucla.edu/shp/districts%03i.zip",cong)
  download.file(zp, tmp_file)
  unzip(zipfile = tmp_file, exdir = tmp_dir)
  fpath <- paste(tmp_dir, sprintf("districtShapes/districts%03i.shp",cong), sep = "/")
  st_read(fpath)
}

Then for our example, we load the districts from the 114th congress:

cd114 <- get_congress_map(114)
## Reading layer `districts114' from data source `/private/var/folders/l_/f53v6nlj6cgdfpybr4d1vqf80000gn/T/RtmpxV0Tnp/districtShapes/districts114.shp' using driver `ESRI Shapefile'
## Simple feature collection with 436 features and 15 fields (with 1 geometry empty)
## geometry type:  MULTIPOLYGON
## dimension:      XY
## bbox:           xmin: -179.1473 ymin: 18.91383 xmax: 179.7785 ymax: 71.35256
## epsg (SRID):    4269
## proj4string:    +proj=longlat +datum=NAD83 +no_defs

Selecting specfic districts

Manipulating data the tidy way, let’s select just the districts from New Jersey:

cd114_nj <- cd114 %>% 
            filter(STATENAME=="New Jersey") %>%
            mutate(DISTRICT = as.character(DISTRICT)) %>%
            select(DISTRICT)

Added data to plot

Now we can add in new district-level data like this:

trump_nj <- tibble(DISTRICT=as.character(1:12),
                   `Trump Vote`=c(36.1, 50.6, 51.4, 55.8, 
                                    48.8, 40.6, 47.5, 21.5,
                                    33.1, 12.8, 48.8, 31.8))
cd114_nj <- cd114_nj %>% left_join(trump_nj, by="DISTRICT")
cd114_nj
## Simple feature collection with 12 features and 2 fields
## geometry type:  MULTIPOLYGON
## dimension:      XY
## bbox:           xmin: -75.55979 ymin: 38.92852 xmax: -73.90267 ymax: 41.35742
## epsg (SRID):    4269
## proj4string:    +proj=longlat +datum=NAD83 +no_defs
##    DISTRICT Trump Vote                       geometry
## 1         1       36.1 MULTIPOLYGON (((-75.0258737...
## 2         2       50.6 MULTIPOLYGON (((-75.5426929...
## 3         3       51.4 MULTIPOLYGON (((-75.031005 ...
## 4         4       55.8 MULTIPOLYGON (((-74.752681 ...
## 5         5       48.8 MULTIPOLYGON (((-75.1355228...
## 6         6       40.6 MULTIPOLYGON (((-74.519208 ...
## 7         7       47.5 MULTIPOLYGON (((-75.2037114...
## 8         8       21.5 MULTIPOLYGON (((-74.254068 ...
## 9         9       33.1 MULTIPOLYGON (((-74.206197 ...
## 10       10       12.8 MULTIPOLYGON (((-74.305014 ...
## 11       11       48.8 MULTIPOLYGON (((-74.78921 4...
## 12       12       31.8 MULTIPOLYGON (((-74.942909 ...

These data on Trump vote shares in the 2016 Presidential general election are from the Daily Kos and are used only for the purposes of this example. I would suggest going to an authoritative source if you are studying presidential vote by congressional district.

Render the map

Finally, we can render a map using ggmap which places our map on top of a Google Maps (tm) baselayer:

dm <- get_map(location="New Jersey",zoom=8)
## Source : https://maps.googleapis.com/maps/api/staticmap?center=New+Jersey&zoom=8&size=640x640&scale=2&maptype=terrain&language=en-EN
## Source : https://maps.googleapis.com/maps/api/geocode/json?address=New%20Jersey
ggmap(dm) + 
  geom_sf(data=cd114_nj,aes(fill=`Trump Vote`),inherit.aes=FALSE,alpha=0.9) + 
  scale_fill_gradient(low = "blue", high = "red", limits=c(20,80)) +
  theme(axis.title.x=element_blank(),
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.title.y=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank()) 

Or, we can just skip the base layer and fancy colors:

ggplot() + 
  geom_sf(data=cd114_nj,aes(fill=`Trump Vote`),inherit.aes=FALSE,alpha=0.9) + 
  scale_fill_gradient(low = "white", high = "black", limits=c(20,80)) +
  theme_void() +
  theme(axis.title.x=element_blank(),
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.title.y=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank()) 

That’s about it! Happy mapping.