Dear Florida Democrats, where should we allocate resources this year to register new voters?
Using historical and public data from electoral records and the Census, we’re going to do a deep dive into recent trends in the Sunshine State’s demographics. This map uses Andrew Gillum’s gubernatorial run in 2018 as a benchmark of Democratic Party success.
You can follow along by downloading precinct-level results from the 2018 midterms. Download them to your directory (use getwd() and setwd() to check where your downloads are going!)
https://dos.myflorida.com/elections/data-statistics/elections-data/precinct-level-election-results/
Shapefiles are from https://dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/UBKYRU
library(tigris)
library(tidyr)
library(sf)
library(leaflet)
library(dplyr)
I suppose you could import the 67 text files that make up every county’s 2018 election results one at a time, but here’s a handy function to shortcut everything.
#this is your directory!
folder <- getwd()
#this will scan your directory of all the ".txt" files, so hopefully you cleaned it out recently
file_list <- list.files(path=folder, pattern="*.txt")
#this is a function that will import all of the files, and combine them into one gigantic file
gillum<-
do.call("rbind",
lapply(file_list,
function(x)
read.delim(paste(folder, x, sep='/'),
header = FALSE,
stringsAsFactors = FALSE)))
There’s no wrong way to clean data…just less messy ways until you learn another shortcut in the tidyverse that’s been there from the begining and you just feel stupid Googling stuff on StackOverflow.
#so these are the official column names from the readme of the election files, we're not going to use most of them but I feel like it's important to know what we're cutting from
colnames(gillum)<- c("County_Code","County_Name", "Election_Number", "Election_Date","Election_Name", "Unique_Precinct_Identifier","Precinct_Polling_Location", "Total_Registered_Voters", "Total_Registered_Republicans", "Total_Registered_Democrats", "Total_Registered_All_Other_Parties","Contest_Name", "District", "Contest_Code", "Candidate","Party","Vote_ID_Number","Candidate_Number", "Votes")
#the bulk of the cleaning revolved around consolidating duplicated precincts, checking for discrepanices and outliers, creating useful variables from the present data, and filtering it down to the relevant race (Gillum). I'm sure there's a more elegant and cleaner way of formatting my data, forgive me.
data<- gillum %>% filter(Contest_Name=="Governor") %>%
filter(Candidate != "OverVotes") %>%
mutate(Pct_std = paste(County_Code, sprintf("%04s", Unique_Precinct_Identifier), sep = "")) %>%
select(Pct_std, Candidate, Votes, Total_Registered_Voters) %>%
group_by(Pct_std) %>%
add_tally(Votes, name = "Total_Votes") %>%
filter(Candidate %in% c("Gillum / King")) %>% ungroup %>%
group_by(Pct_std, Candidate, Total_Votes) %>%
summarise_each(funs(sum)) %>%
mutate(Percentage = round(Votes/Total_Votes,4) * 100,
Voter_Turnout = round(Total_Votes/Total_Registered_Voters, 4)* 100) %>%
select(Pct_std, Candidate, Total_Registered_Voters, Total_Votes, Votes, Percentage, Voter_Turnout)
fl_shapefile<- st_read("fl_2018.shp") %>% st_transform(4326)
## Reading layer `fl_2018' from data source `/Users/timphan/Data/2020/dnc/question3/fl_2018.shp' using driver `ESRI Shapefile'
## Simple feature collection with 6073 features and 40 fields
## geometry type: MULTIPOLYGON
## dimension: XY
## bbox: xmin: -87.6349 ymin: 24.54405 xmax: -79.97431 ymax: 31.00097
## CRS: 4269
#this is the crucial join that merges the election results with the shapefile!
fl_shapefile<-geo_join(fl_shapefile, data, "Pct_std", "Pct_std") %>%
select(Pct_std, County, Precnct, G18GOVDGIL,Total_Registered_Voters, Total_Votes, Voter_Turnout, Votes, Percentage,geometry)
Leaflet is amazing at customization, feel free to edit the color palettes around to your convenience! We’re going with the obvious blue decor, I feel like it’s self-explanatory.
pal <- colorNumeric(palette = "Blues",
domain = fl_shapefile$Percentage)
popup<- paste0("<strong>Precinct ID: </strong>",
fl_shapefile$Pct_std,
"<br><strong>Gillum Percentage: </strong>",
fl_shapefile$Percentage,
"<br><strong>Voter Turnout: </strong>",
fl_shapefile$Voter_Turnout)
final<- leaflet() %>%
addProviderTiles(provider = "CartoDB.Positron") %>%
addPolygons(data = fl_shapefile,
group = "Gillum",
popup = popup,
stroke = FALSE,
smoothFactor = 0,
fillOpacity = 0.7,
color = ~ pal(Percentage))