15 Day 13: Borders

15.1 Technologies/Techniques

  • Working with R Simple Features {sf}
  • Using {tigris} data
  • Working with external data
  • Generating maps for other uses

15.2 Data Source: U.S. House Voting Districts

Many organizations maintain up-to-date shapefiles of the latest U.S. Congressional voting districes. The GeoJSON used here comes from the Civil Services Accountability in Action GitHub50.

These virtual district borders are human-made creations that are supposed to help the democratic process be more fair. Alas, both major parties in the U.S. hack these shapes to suit themselves whenever power flips in Washington.

Since most folks outside of ultra-political circles almost never see these district shapes, I thought both a complete view and separate view of the 116th Congress borders would be interesting to show.

library(stringi)
library(tigris)
library(hrbrthemes)
library(tidyverse)

First we grab the GeoJSON file and normalize a GEOID column so we can then use data from {tigris} to get party representation:

jsonlite::fromJSON("https://github.com/CivilServiceUSA/us-house/raw/master/us-house/data/us-house.json") %>%
  as_tibble() %>%
  left_join(
    distinct(fips_codes, STATEFP=state_code, state_name)
  ) %>%
  mutate(district = ifelse(is.na(district), 0, district)) %>%
  mutate(GEOID = sprintf("%s%02s", STATEFP, district)) -> house

glimpse(house)
## Observations: 435
## Variables: 55
## $ state_name            <chr> "Alaska", "Alabama", "Alabama", "Alabama", "Ala…
## $ state_name_slug       <chr> "alaska", "alabama", "alabama", "alabama", "ala…
## $ state_code            <chr> "AK", "AL", "AL", "AL", "AL", "AL", "AL", "AL",…
## $ state_code_slug       <chr> "ak", "al", "al", "al", "al", "al", "al", "al",…
## $ district              <chr> "0", "1", "2", "3", "4", "6", "5", "7", "1", "2…
## $ at_large              <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,…
## $ vacant                <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
## $ bioguide              <chr> "Y000033", "B001289", "R000591", "R000575", "A0…
## $ thomas                <chr> "1256", "2197", "1986", "1704", "1460", "2221",…
## $ opensecrets           <chr> "N00007999", "N00035380", "N00030768", "N000247…
## $ votesmart             <chr> "26717", "27584", "71604", "5705", "441", "1462…
## $ fec                   <chr> "H6AK00045", "H4AL01123", "H0AL02087", "H2AL030…
## $ maplight              <chr> "525", "2054", "1408", "433", "127", "2064", "1…
## $ wikidata              <chr> "Q1239590", "Q4954892", "Q439117", "Q693238", "…
## $ google_entity_id      <chr> "kg:/m/024p1k", "kg:/m/09g6kty", "kg:/m/0drx5mb…
## $ title                 <chr> "representative", "representative", "representa…
## $ party                 <chr> "republican", "republican", "republican", "repu…
## $ name                  <chr> "Don Young", "Bradley Byrne", "Martha Roby", "M…
## $ name_slug             <chr> "don-young", "bradley-byrne", "martha-roby", "m…
## $ first_name            <chr> "Don", "Bradley", "Martha", "Mike", "Robert", "…
## $ middle_name           <chr> NA, NA, NA, NA, NA, NA, NA, "A.", NA, NA, NA, N…
## $ last_name             <chr> "Young", "Byrne", "Roby", "Rogers", "Aderholt",…
## $ name_suffix           <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ goes_by               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ pronunciation         <chr> "DAHN YUHNG", "BRADD-lee BURN", "MAR-thuh ROH-b…
## $ gender                <chr> "male", "male", "female", "male", "male", "male…
## $ ethnicity             <chr> "white-american", "white-american", "white-amer…
## $ religion              <chr> "episcopalian", "episcopalian", "presbyterian",…
## $ openly_lgbtq          <chr> "no", "no", "no", "no", "no", "no", "no", "no",…
## $ date_of_birth         <chr> "1933-06-09", "1955-02-16", "1976-07-27", "1958…
## $ entered_office        <chr> "1973-01-03", "2014-01-08", "2011-01-05", "2003…
## $ term_end              <chr> "2021-01-03", "2021-01-03", "2021-01-03", "2021…
## $ biography             <chr> "Don Young, a Representative from Alaska; born …
## $ phone                 <chr> "202-225-5765", "202-225-4931", "202-225-2901",…
## $ fax                   <chr> NA, "202-225-0562", "202-225-8913", "202-226-84…
## $ latitude              <chr> "38.8863235", "38.8863235", "38.8863235", "38.8…
## $ longitude             <chr> "-77.011373", "-77.011373", "-77.011373", "-77.…
## $ address_complete      <chr> "2436 Rayburn HOB, Washington, DC 20515", "2436…
## $ address_number        <chr> "2436", "2436", "2436", "2436", "2436", "2436",…
## $ address_prefix        <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ address_street        <chr> "Rayburn HOB", "Rayburn HOB", "Rayburn HOB", "R…
## $ address_sec_unit_type <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ address_sec_unit_num  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ address_city          <chr> "Washington", "Washington", "Washington", "Wash…
## $ address_state         <chr> "DC", "DC", "DC", "DC", "DC", "DC", "DC", "DC",…
## $ address_zipcode       <chr> "20515", "20515", "20515", "20515", "20515", "2…
## $ address_type          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ website               <chr> "https://donyoung.house.gov", "https://byrne.ho…
## $ contact_page          <chr> "http://donyoung.house.gov/contact/", "https://…
## $ facebook_url          <chr> "https://www.facebook.com/RepDonYoung", "https:…
## $ twitter_handle        <chr> "repdonyoung", "RepByrne", "RepMarthaRoby", "Re…
## $ twitter_url           <chr> "https://twitter.com/repdonyoung", "https://twi…
## $ photo_url             <chr> "https://cdn.civil.services/us-house/headshots/…
## $ STATEFP               <chr> "02", "01", "01", "01", "01", "01", "01", "01",…
## $ GEOID                 <chr> "0200", "0101", "0102", "0103", "0104", "0106",…
congressional_districts(TRUE, "20m", year = 2018, class="sf") %>%
  left_join(
    distinct(fips_codes, STATEFP=state_code, state_name)
  ) %>%
  filter(!(STATEFP %in% c("02", 15, 60:78))) %>%
  left_join(house) %>%
  filter(!is.na(party)) %>%
  mutate(party = stri_trans_totitle(party)) -> cd

glimpse(cd)
## Observations: 432
## Variables: 62
## $ STATEFP               <chr> "34", "42", "06", "12", "06", "12", "32", "12",…
## $ CD116FP               <chr> "10", "05", "12", "03", "45", "07", "01", "24",…
## $ AFFGEOID              <chr> "5001600US3410", "5001600US4205", "5001600US061…
## $ GEOID                 <chr> "3410", "4205", "0612", "1203", "0645", "1207",…
## $ LSAD                  <chr> "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2",…
## $ CDSESSN               <chr> "116", "116", "116", "116", "116", "116", "116"…
## $ ALAND                 <dbl> 196397289, 548506604, 100885569, 9232593580, 85…
## $ AWATER                <dbl> 12474852, 26310378, 211661576, 722972427, 51618…
## $ state_name            <chr> "New Jersey", "Pennsylvania", "California", "Fl…
## $ state_name_slug       <chr> "new-jersey", "pennsylvania", "california", "fl…
## $ state_code            <chr> "NJ", "PA", "CA", "FL", "CA", "FL", "NV", "FL",…
## $ state_code_slug       <chr> "nj", "pa", "ca", "fl", "ca", "fl", "nv", "fl",…
## $ district              <chr> "10", "5", "12", "3", "45", "7", "1", "24", "8"…
## $ at_large              <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
## $ vacant                <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
## $ bioguide              <chr> "P000604", "S001205", "P000197", "Y000065", "P0…
## $ thomas                <chr> "2097", NA, "905", "2115", NA, NA, "1940", "200…
## $ opensecrets           <chr> "N00034639", NA, "N00007360", "N00033220", "N00…
## $ votesmart             <chr> "90668", NA, "26732", "137622", "179393", "1734…
## $ fec                   <chr> "H2NJ10154", "H8PA07200", "H8CA05035", "H2FL061…
## $ maplight              <chr> "1687", NA, "408", "1749", NA, "2206", "800", "…
## $ wikidata              <chr> "Q1240224", "Q54611716", "Q170581", "Q3090476",…
## $ google_entity_id      <chr> "kg:/m/0jwr2v8", NA, "kg:/m/012v1t", "kg:/m/0l8…
## $ title                 <chr> "representative", "representative", "house-spea…
## $ party                 <chr> "Democrat", "Democrat", "Democrat", "Republican…
## $ name                  <chr> "Donald Payne", "Mary Gay Scanlon", "Nancy Pelo…
## $ name_slug             <chr> "donald-payne", "mary-gay-scanlon", "nancy-pelo…
## $ first_name            <chr> "Donald", "Mary Gay", "Nancy", "Ted", "Katie", …
## $ middle_name           <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ last_name             <chr> "Payne", "Scanlon", "Pelosi", "Yoho", "Porter",…
## $ name_suffix           <chr> "Jr.", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ goes_by               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "Tom", …
## $ pronunciation         <chr> "DAHN-uhld PAYN", "ME-ree-GAY SKAN-len", "NANN-…
## $ gender                <chr> "male", "female", "female", "male", "female", "…
## $ ethnicity             <chr> "african-american", "white-american", "white-am…
## $ religion              <chr> "baptist", "unspecified", "roman-catholic", "ch…
## $ openly_lgbtq          <chr> "no", "no", "no", "no", "no", "no", "no", "no",…
## $ date_of_birth         <chr> "1958-12-17", "1959-08-30", "1940-03-26", "1955…
## $ entered_office        <chr> "2012-11-15", "2019-01-03", "1987-01-06", "2013…
## $ term_end              <chr> "2021-01-03", "2021-01-03", "2021-01-03", "2021…
## $ biography             <chr> "Donald Payne, (son of Donald Milford Payne), a…
## $ phone                 <chr> "202-225-3436", "202-225-2011", "202-225-4965",…
## $ fax                   <chr> "202-225-4160", NA, "202-225-8259", "202-225-39…
## $ latitude              <chr> "38.8863235", "38.8863235", "38.8863235", "38.8…
## $ longitude             <chr> "-77.011373", "-77.011373", "-77.011373", "-77.…
## $ address_complete      <chr> "2436 Rayburn HOB, Washington, DC 20515", "2436…
## $ address_number        <chr> "2436", "2436", "2436", "2436", "222", "2436", …
## $ address_prefix        <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ address_street        <chr> "Rayburn HOB", "Rayburn HOB", "Rayburn HOB", "R…
## $ address_sec_unit_type <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ address_sec_unit_num  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ address_city          <chr> "Washington", "Washington", "Washington", "Wash…
## $ address_state         <chr> "DC", "DC", "DC", "DC", "DC", "DC", "DC", "DC",…
## $ address_zipcode       <chr> "20515", "20515", "20515", "20515", "20515", "2…
## $ address_type          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ website               <chr> "https://payne.house.gov", "https://scanlon.hou…
## $ contact_page          <chr> "http://payne.house.gov/contact/email-me", "htt…
## $ facebook_url          <chr> "https://www.facebook.com/DonaldPayneJr", "http…
## $ twitter_handle        <chr> "RepDonaldPayne", "housedemocrats", "NancyPelos…
## $ twitter_url           <chr> "https://twitter.com/RepDonaldPayne", "https://…
## $ photo_url             <chr> "https://cdn.civil.services/us-house/headshots/…
## $ geometry              <MULTIPOLYGON [°]> MULTIPOLYGON (((-74.30292 4..., MU…

15.3 Drawing the Map

The map is just a “standard” choropleth but using these alternate borders:

ggplot() +
  geom_sf(data = cd, aes(fill = party), color = "white", size = 0.125) +
  coord_sf(crs=albersusa::us_laea_proj, datum = NA) +
  scale_fill_manual(
    values = c(
      "Republican" = "#a50026",
      "Democrat" = "#313695"
    ), name = NULL
  ) +
  labs(
    x = NULL, y = NULL,
    title = "116th Congress District Borders",
    caption = "Data source: {tigris} • #30DayMapChallenge"
  ) +
  theme_ft_rc(grid="") +
  theme(legend.position = c(0.5, 0.95)) +
  theme(legend.position = "horizontal")

We’ll now make separate district maps for each state which can be used in other contexts (e.g. web sites):

map(unique(cd$state_name), ~{

  f <- filter(cd, state_name == .x)

  ggplot() +
    geom_sf(data = f, aes(fill = party), color = "white", size = 0.125) +
    scale_fill_manual(
      values = c(
        "Republican" = "#a50026",
        "Democrat" = "#313695"
      ), name = NULL
    ) +
    coord_sf(crs=albersusa::us_laea_proj, datum = NA) +
    labs(
      x = NULL, y = NULL,
      title = f$state_name[[1]]
    ) +
    theme_ipsum_rc(grid="") +
    theme(legend.position = "none") -> gg

  ggsave(
    here::here(sprintf("img/day-13/%s.png", tolower(f$state_name[[1]]))),
    plot = gg, width=250/72, height=250/72
  )

  gg

}) -> gd

We could have used purrr::walk() instead of map() but by using map() and returning the gg object each time we can inspect some of the maps:

15.4 In Review

We used today’s challenge to look at different kinds of borders than traditional state/county/country/continent borders. We also used external data to add some meaning to these new areas and also showed how to use {ggplot2} to make map files for use in other contexts.

15.5 Try This At Home

Use techniques from previous challenges to add a Congressional district map layer on a {mapdeck} interactive map, incorporating more data from the external JSON file to make the popups more useful.

Go to each state’s GIS site (most have them, e.g. Maryland51) and find the recommended coordinate reference system (CRS) for each state and use it when generating the individual output files.

Make SVGs vs PNGs.