Skip to content

Instantly share code, notes, and snippets.

@etachov
Created September 17, 2018 03:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save etachov/9342997b91efa5fd9f1b03780a4b74a8 to your computer and use it in GitHub Desktop.
Save etachov/9342997b91efa5fd9f1b03780a4b74a8 to your computer and use it in GitHub Desktop.
Dot density map of the 2018 NYC Democrat Governor Primary
library(rvest) # web scraping
library(tidyverse) # general data munging
library(sf) # spatial functions and stats
library(lwgeom) # not 100% sure why, but i needed to load this so `udunits` doesn't throw an error
## import data ------------
# import a geojson of assembly district outlines
nyc_ad <- st_read("http://services5.arcgis.com/GfwWNkhOj9bNBqoJ/arcgis/rest/services/nyad/FeatureServer/0/query?where=1=1&outFields=*&outSR=4326&f=geojson")
# scrape the table with assembly district level results from the Bureau of Elections
dem_gov <- read_html("https://enrweb.boenyc.us/CD22370AD0.html") %>%
html_node(".underline") %>%
html_table() %>%
select("AssemDist" = X1,
"NixonVotes" = X3,
"CuomoVotes" = X5,
"WriteInVotes" = X7) %>%
# clean up the table
filter(grepl("AD", AssemDist)) %>%
mutate(AssemDist = gsub("AD ", "", AssemDist)) %>%
mutate_all(as.numeric)
# join the geometry and the results. isn't `sf` grand?
nyc_ad_results <- left_join(nyc_ad, dem_gov) %>%
st_as_sf()
## calculating dots inside assembly districts --------------
# random rounding algo to prevent systemic bias in counting. credit: https://github.com/mountainMath/dotdensity/blob/master/R/dot-density.R
random_round <- function(x) {
v=as.integer(x)
r=x-v
test=runif(length(r), 0.0, 1.0)
add=rep(as.integer(0),length(r))
add[r>test] <- as.integer(1)
value=v+add
ifelse(is.na(value) | value<0,0,value)
return(value)
}
results_dots <- as.data.frame(nyc_ad_results) %>%
select(NixonVotes:WriteInVotes) %>%
# dividing results by 10 so that each dot represents 10 votes
mutate_all(funs(. / 10)) %>%
# use the random_round algot to prevent systemic bias
mutate_all(random_round)
# generate lat lon points inside assembly districts
# huge credit here to Paul Campbell's classic post: https://www.cultureofinsight.com/blog/2018/05/02/2018-04-08-multivariate-dot-density-maps-in-r-with-sf-ggplot2/
nyc_primary_dots <- map_df(names(results_dots),
~ st_sample(nyc_ad_results, size = results_dots[, .x], type = "random") %>%
st_cast("POINT") %>%
st_coordinates() %>%
as_tibble() %>%
setNames(c("lon","lat")) %>%
mutate(Candidate = .x)) %>%
slice(sample(1:n()))
# set the palette
pal <- c("CuomoVotes" = "#70147A", "NixonVotes" = "#78B943", "WriteInVotes" = "#FCBB30")
# build the plot
p <- ggplot() +
geom_sf(data = nyc_ad_results, fill = "transparent",colour = "white") +
geom_point(data = nyc_primary_dots, aes(lon, lat, colour = Candidate), alpha = .6, size = .7) +
scale_colour_manual(values = pal) +
theme_void() +
theme(panel.grid = element_line(color = "#141414"),
plot.background = element_rect(fill = "#141414", color = NA),
panel.background = element_rect(fill = "#141414", color = NA),
legend.background = element_rect(fill = "#141414", color = NA),
)
ggsave("nyc_primary_density.png", plot = p, dpi = 400, width = 90, height = 70, units = "cm")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment