April 20, 2022

Mapping Voters

The Data

Robert Husseman @RHusseman on Twitter is running to represent Oregon District 21 in the Oregon House of Representatives. He has a platform that focuses on housing and livability in the state by making sensible allocations of public resources to public problems. He has an MBA; he was my student and his head and heart are very much in the right place to serve Oregon well.

His campaign has a voter list for the Democratic voters in Oregon 21. He shared access to this. Let me load them up from a saved Excel spreadsheet derived from a Google sheet.

Geocoding

I ran across a package called tidygeocoder to do the heavy lifting. Here is what I have to work with.

library(tidygeocoder)
names(RHCamp)
##  [1] "VOTER_ID"          "FIRST_NAME"        "MIDDLE_NAME"      
##  [4] "LAST_NAME"         "NAME_SUFFIX"       "BIRTH_DATE"       
##  [7] "CONFIDENTIAL"      "EFF_REGN_DATE"     "STATUS"           
## [10] "PARTY_CODE"        "PHONE_NUM"         "UNLISTED"         
## [13] "COUNTY"            "RES_ADDRESS_1"     "RES_ADDRESS_2"    
## [16] "HOUSE_NUM"         "HOUSE_SUFFIX"      "PRE_DIRECTION"    
## [19] "STREET_NAME"       "STREET_TYPE"       "POST_DIRECTION"   
## [22] "UNIT_TYPE"         "UNIT_NUM"          "ADDR_NON_STD"     
## [25] "CITY"              "STATE"             "ZIP_CODE"         
## [28] "ZIP_PLUS_FOUR"     "EFF_ADDRESS_1"     "EFF_ADDRESS_2"    
## [31] "EFF_ADDRESS_3"     "EFF_ADDRESS_4"     "EFF_CITY"         
## [34] "EFF_STATE"         "EFF_ZIP_CODE"      "EFF_ZIP_PLUS_FOUR"
## [37] "ABSENTEE_TYPE"     "PRECINCT_NAME"     "PRECINCT"         
## [40] "SPLIT"

A Great Thing about R

There is a vignette explaining the usage.

My use case is pretty easy though there is a lot of data. I want to try this with just the addresses. I have loaded the tidyverse and now I want to peel off just the data that I need in order to geocode it, and then process it with the Census geocoder. This results in 9443 queries.

GeoMe <- RHCamp %>% select(VOTER_ID, RES_ADDRESS_1, CITY, STATE) %>% geocode(street = RES_ADDRESS_1, city=CITY, state=STATE, method="census")
NewData <- GeoMe %>% left_join(., RHCamp)
save(NewData, file="~/Desktop/NewDataRH.RData")

This takes quite a long time to run – 667.1 seconds.

When it is complete, I will want to figure out how to map them in a way that is best fit for assisting a campaign. This seems like a good use for leaflet. Rather than taking 10 minutes or so to compile each time, I saved them off. I am loading them from a local file in the first line.

Leaflet Mapping

load("~/Desktop/NewDataRH.RData")

The leaflet package uses very powerful mapping tools that will be perfect for this use case because the maps have easy zoom features.

library(leaflet)
NewData %>% mutate(labMy = paste0(FIRST_NAME,LAST_NAME,RES_ADDRESS_1,sep="\n")) %>%
  leaflet(data = .) %>% 
  addTiles() %>%
  addCircleMarkers(~long, ~lat, label = ~labMy, fillOpacity = 0.1, stroke=FALSE, radius=1.6)
## Warning in validateCoords(lng, lat, funcName): Data contains 641 rows with
## either missing or invalid lat/lon values and will be ignored

That works but it isn’t quite right. Turns out that I will have to mess with html to get the labels quite right.

So, of course, I went to stackoverflow.

Building Better Labels

I found a post on how to do this.

The key piece of code seems to be:

labs <- lapply(seq(nrow(NewData)), function(i) {
  paste0( '<p>', NewData[i,"RES_ADDRESS_1"],'</p>' )
})
NewData %>%
  leaflet(data = .) %>% 
  addTiles() %>%
  addCircleMarkers(~long, ~lat, label = lapply(labs, htmltools::HTML), fillOpacity = 0.15, stroke=FALSE, radius=1.6)
## Warning in validateCoords(lng, lat, funcName): Data contains 641 rows with
## either missing or invalid lat/lon values and will be ignored