This project is a relatively simple map demonstration using Oregon election precincts and results from the 2020 general election. The map uses tmap’s Leaflet implementation and let’s users drill into a display that shows address-level two party election result with color coding and vote detail. This example displays percentages of the two party presidential election results.
The challenge is consolidating precinct data that isn’t consolidated at the state level by the Oregon secretary of state office. Each county reports its own data at the precinct level, and consolidated map files are hard to find.
This list and sequence should be cleaned up. I’m using the easypackages function libraries.
This chunk can produce an address marker using the geocode_OSM function from tmap. An example is shown here. In the next version this will use a text input for Shiny.
Easiest to find old fashioned shapefiles with all the headaches described by Lovelace. I read them as sf objects using st_read function. In this case, I combine an Oregon shapefile for counties with Clark County in Washington, included because Clark County is part of the Portland Metro area.
I added labels from the USMAP fips lookup. Is there a better lookup table provided by the Census bureau from tidycensus?
Notice that I transform the sf objects into a coordinate reference system of NAD83, although they appear to have been read with that CRS. The bind_rows function doesn’t appear to work otherwise.
I need to add a state boundary, unless Oregon decides to annex Clark County.
[Need to add a section with sources for all data and shapefiles]
Oregon boundaries from https://opendata.imspdx.org/dataset/november-2018-election-oregon-results-by-precinct
oregon_counties_sf <- st_read("shapefiles/oregon_counties/counties.shp", quiet = TRUE) %>%
st_transform(or_counties, crs = "NAD83") %>%
select(!COUNTY)
washington_state_counties_sf <-
st_read("shapefiles/washington_counties/WA_County_Boundaries.shp", quiet = TRUE) %>%
st_transform(crs = "NAD83") %>%
mutate(STFID = as.character(JURISDIC_5))
washington_state <- st_union(washington_state_counties_sf)
clark_county_sf <- washington_state_counties_sf %>%
filter(JURISDIC_2 == "Clark")
oregon_counties_and_clark_sf <- bind_rows(oregon_counties_sf, clark_county_sf) %>%
st_transform(crs = "NAD83")
oregon_fips <- fips_info(oregon_counties_and_clark_sf$STFID) %>%
mutate(county = gsub(" County", "", county))
oregon_counties_and_clark_sf <- left_join(oregon_counties_and_clark_sf, oregon_fips, by= c("STFID" = "fips"))
oregon_state <- st_union(oregon_counties_sf)
oregon_bb <- st_bbox(oregon_counties_and_clark_sf)
tmap_mode("plot")
## tmap mode set to plotting
tm_shape(oregon_counties_and_clark_sf, bbox = oregon_bb) +
tm_text("county", size = 0.5) +
tm_polygons(alpha = 0, id = "county") +
tm_shape(oregon_state) +
tm_borders(col = "orange", lwd = 2.5)
Metro Portland the rest of the state are processed separately.
metro_portland_precinct_sf <- st_read("shapefiles/metro_portland_precinct/precinct.shp", quiet = TRUE) %>%
select(PRECINCTID, COUNTY, geometry) %>%
mutate(COUNTY = case_when(
COUNTY == "W" ~ "Washington",
COUNTY == "M" ~ "Multnomah",
COUNTY == "C" ~ "Clackamas",
TRUE ~ "Other"
)
) %>%
st_transform(crs = "NAD83")
clark_wa_precincts_sf <- st_read("shapefiles/clark_precinct_shapefiles/Precinct.shp", quiet = TRUE) %>%
select(PRECINCTID = PRECINCT, geometry) %>%
mutate(PRECINCTID = str_c("K", as.character(PRECINCTID)), COUNTY = "Clark") %>%
st_transform(crs = "NAD83")
metro_portland_precinct_sf <- bind_rows(metro_portland_precinct_sf,
clark_wa_precincts_sf)
tm_shape(metro_portland_precinct_sf) + tm_polygons()
or_precinct_sf <- st_read("shapefiles/oregon_precincts/OregonPrecinctsNov2018.shp", quiet = TRUE) %>%
filter(!County %in% c("Multnomah", "Washington", "Clackamas")) %>%
st_transform("NAD83")
tm_shape(or_precinct_sf) + tm_polygons()
This section processes the various files with precinct level election results.
Lane County vote totals, hand tabulated from county pdf
lane_votes <- read_csv("data/votes/lane.csv", col_types = cols(.default = "d", precinct = "c")) %>%
mutate(county = "Lane") %>%
rename(DEM = Biden, REP = Trump)
Marion, Polk, and Yamhill County voter data. Note these use the OpenElections standard format. Portland Metro counties generated further down
Future: use purrr::map_dfr to produce a single data table
open_election_col_types = cols(.default = "c", votes = "d")
open_election_single_county_votes <- function(csv, coltypes = open_election_col_types) {
county_df <- read_csv(csv, col_types = coltypes) %>%
filter(office == "President") %>%
mutate(party = case_when (
str_detect(candidate,"Biden") ~ "DEM",
str_detect(candidate,"Trump") ~ "REP",
TRUE ~ party
)
) %>%
filter(party %in% c("DEM", "REP")) %>%
select(!candidate) %>%
pivot_wider(names_from = party, values_from = votes) %>%
select(county, precinct, REP, DEM)
return(county_df)
}
marion_votes <- open_election_single_county_votes("data/votes/20201103__or__general__marion__precinct.csv")
polk_votes <- open_election_single_county_votes("data/votes/20201103__or__general__polk__precinct.csv")
yamhill_votes <- open_election_single_county_votes("data/votes/20201103__or__general__yamhill__precinct.csv")
Tillamook County, hand tabulated from the published county pdf
tillamook_votes <- read_csv("data/votes/tillamook_votes.csv", col_types = cols(.default = "c", DEM = "d", REP = "d")) %>%
rename(precinct = precinct_votefile) %>%
select(county, precinct, DEM, REP)
Consolidate the five additional counties with or_precincts_votes
or_precincts_votes <- bind_rows(or_precincts_votes,
tillamook_votes,
lane_votes,
polk_votes,
marion_votes,
yamhill_votes
)
read in a lookup table that matches vote file precinct names and shape file precinct names
lookup <- read_csv("data/votes_shapefile_lookup.csv",
col_types = cols(.default = "c")) %>%
mutate(county = str_to_title(tolower(county)))
combine lookup table and shapefile
Two columns have white space in the text and needs to be trimmed. Could replace regular expressions with simpler tidy version.
or_precinct_sf$precinct_votefile <- gsub('\\s+', '', or_precinct_sf$precinct_votefile)
or_precincts_votes$precinct <- gsub('\\s+', '', or_precincts_votes$precinct)
or_precinct_sf <- left_join(or_precinct_sf, or_precincts_votes,
by = c("County" = "county",
"precinct_votefile" = "precinct")) %>%
select(county = County, precinct = precinct_votefile, REP, DEM, geometry) %>%
st_transform(crs = "NAD83")
Multnomah, Clackamas, and Washington, plus Clark County WA
Complicated because the Clackamas County reports combine several precincts. This chunk consolidates the affected precincts
The vote data was generated by hand before it became available in the Open Elections extract.
Voting tables and sf objects joined here
metro_portland_precinct_sf <- inner_join(metro_portland_precinct_sf,
metro_portland_votes,
by=("PRECINCTID")) %>%
select(county = COUNTY, precinct, DEM = Biden, REP = Trump)
or_precinct_sf <- bind_rows(or_precinct_sf,
metro_portland_precinct_sf
)
##The Leaflet Map
Some additional formatting variables added here.
or_precinct_sf <- or_precinct_sf %>%
mutate(vpct = DEM/(DEM + REP)) %>%
mutate(pwinner = ifelse(vpct >=.5,
"DEM",
"REP"),
DPR = DEM + REP) %>%
mutate(pct_lbl = ifelse(pwinner == "DEM",
str_c(pwinner, " ", format(vpct*100, digits = 3)),
str_c(pwinner, " ", format(100 - vpct*100, digits = 2)))) %>%
mutate(hover = str_c(county," ",precinct, ": ", pct_lbl," ",DPR)) %>%
dplyr::select(county, precinct, vpct, DEM, REP, hover)
The final or_precinct_votes table has this structure:
or_precinct_df <- st_drop_geometry(or_precinct_sf)
or_precinct_df %>%
filter(county=="Multnomah" & precinct == "3605") %>%
kbl() %>%
kable_styling()
| county | precinct | vpct | DEM | REP | hover |
|---|---|---|---|---|---|
| Multnomah | 3605 | 0.875155 | 2117 | 302 | Multnomah 3605: DEM 87.52 2419 |
Draw the map
tmap_mode("view")
## tmap mode set to interactive viewing
rwb <- colorRampPalette(c("red", "white", "blue"))(256)
pctiles <- c(0,.1,.3,.45,.5,.55,.7,.9,1)
bb_or <- bb(or_precinct_sf)
tmap_options(basemaps = c(Canvas = "OpenStreetMap"))
or_precincts_tmap <-
tm_shape(or_precinct_sf, bbox = bb_or) +
tm_polygons(col = "vpct",
n=length(5),
id = "hover",
style = "fixed",
breaks = pctiles,
alpha = 0.7,
palette = rwb) +
tm_shape(oregon_counties_and_clark_sf) +
tm_borders(lwd = 1, col = "green") +
tm_shape(address_sf) +
tm_dots()
or_precincts_tmap