Skip to content

Instantly share code, notes, and snippets.

@tor-gu
Created August 25, 2022 19:04
Show Gist options
  • Select an option

  • Save tor-gu/3f9ab5d151e4bee3a41cbb70f72c954d to your computer and use it in GitHub Desktop.

Select an option

Save tor-gu/3f9ab5d151e4bee3a41cbb70f72c954d to your computer and use it in GitHub Desktop.
Two party share of vote in NJ maps
library(tidyverse)
library(tigris)
library(njmunicipalities)
library(njelections)
county_names <- njmunicipalities::counties |> pull(county)
# Get the map of NJ
options(tigris_use_cache = TRUE)
nj_municipality_map <- county_names %>%
map_df( ~ county_subdivisions("NJ", county = ., class = "sf")) |>
filter(ALAND > 0)
# We will use Republican share of two-party share of vote
# (TPSOV) as our basic metric.
# Get statewide TPSOV by six-year half-cycle
tpsov_sw <- election_statewide |>
filter(party %in% c("Democratic", "Republican")) |>
select(year, party, office, vote_sw = vote) |>
# Aggregate party vote over half-cycles
mutate(half_cycle = as.integer(year/6)) |>
group_by(half_cycle, party) |>
summarize(vote_sw=sum(vote_sw), .groups="drop") |>
# Compute TPSOV
group_by(half_cycle) |>
mutate(tpsov_sw = vote_sw/sum(vote_sw)) |>
ungroup() |>
# Select just the Republican
filter(party == "Republican")
# Now do the same for municipalities
muni_tpsov <- election_by_municipality |>
filter(party %in% c("Democratic", "Republican")) |>
select(year, office, GEOID, party, vote) |>
# Aggregate the Princetons, and account for changing
# names and GEOIDs
mutate(GEOID = if_else(GEOID == PRINCETON_TWP_GEOID,
PRINCETON_BORO_GEOID,
GEOID)) |>
left_join(get_geoid_cross_references(2021, 2004:2021),
by=c("year", "GEOID")) |>
left_join(get_municipalities(2021),
by=c("GEOID_ref"="GEOID")) |>
select(-GEOID, GEOID=GEOID_ref) |>
#group_by(year, office, GEOID, party) |>
#summarize(vote = sum(vote), .groups = "drop") |>
# Aggregate party vote over half-cycles
mutate(half_cycle = as.integer(year/6)) |>
group_by(half_cycle, GEOID, party) |>
summarize(vote=sum(vote), .groups="drop") |>
mutate(half_cycle_name = paste0(half_cycle * 6, "-", half_cycle * 6 + 5)) |>
# Compute TPSOV
group_by(half_cycle, GEOID) |>
mutate(tpsov = vote/sum(vote)) |>
ungroup() |>
# Select just the Republican
filter(party == "Republican") |>
# Get rid of the middle half-cycle
filter(half_cycle_name != "2010-2015") |>
# Add in municipal names, and statewide TPSOV
left_join(get_municipalities(2021), by="GEOID") |>
left_join(tpsov_sw) |>
mutate(tpsov_delta = tpsov - tpsov_sw)
# Also create a table of the change in vote from
# 2004-2009 to 2012-2021
muni_tpsov_delta <- muni_tpsov |>
arrange(half_cycle) |>
group_by(GEOID, party) |>
mutate(tpsov_delta = tpsov - lag(tpsov)) |>
ungroup() |>
filter(!is.na(tpsov_delta))
# Now glue our election data to the map
map_with_values <- nj_municipality_map |>
left_join(muni_tpsov, by="GEOID")
# Let's get a label for the the most extreme
# municipalities
labels <- map_with_values |>
split(map_with_values$half_cycle) |>
map(filter, vote/tpsov > 1000) |>
map(~bind_rows(
slice_min(.x, n=5, order_by=tpsov),
slice_max(.x, n=5, order_by=tpsov)
)
) |>
map_df(arrange, desc(INTPTLAT)) |>
# Filter so we don't put the same label on both maps
group_by(GEOID) |>
filter(row_number() == 1) |>
ungroup()
# Plot our first map
plot_tpsov <- map_with_values |>
ggplot() +
geom_sf(aes(fill=tpsov_delta, geometry=geometry, size=.2)) +
ggrepel::geom_label_repel(
data = labels,
mapping = aes(label = NAME, geometry = geometry),
stat = "sf_coordinates",
nudge_x = -.2,
nudge_y = .2,
) +
scale_size_identity() +
scale_fill_gradient2(
na.value = "lightgrey",
low = "blue",
high = "red",
mid = "lightgrey",
midpoint = 0,
limits = c(-.4,.4),
oob = scales::squish,
breaks = c(-.4, 0, .4),
labels=c("More Democratic", "State Average", "More Republican"),
name="Margin"
) +
theme(
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
panel.background = element_rect(fill = "lightblue"),
panel.grid.major = element_line(color = "lightblue"),
legend.position = "bottom",
legend.key.width = unit(2, "cm"),
legend.title = element_blank(),
) +
facet_wrap("half_cycle_name") +
labs(title=str_wrap("Two party share of vote in NJ, relative to statewide average"),
subtitle = "Elections for Governor, US Senate and US President",
caption="Source: NJ Division of Elections")
# Now glue our election data to the map
map_with_values_delta <- nj_municipality_map |>
left_join(muni_tpsov_delta, by="GEOID")
# Get labels for extreme values
labels_delta <- map_with_values_delta |>
filter(vote/tpsov > 1000) |>
slice_max(n=10, order_by=abs(tpsov_delta)) |>
arrange(desc(INTPTLAT))
# We will get the statewide average shift for centering
sw_shift <- tpsov_sw |>
filter(half_cycle != 335) |>
pull(tpsov_sw) |>
diff()
# Plot our map
plot_tpsov_delta <- map_with_values_delta |>
ggplot() +
geom_sf(aes(fill=tpsov_delta, geometry=geometry, size=.2)) +
ggrepel::geom_label_repel(
data = labels_delta,
mapping = aes(label = NAME, geometry = geometry),
stat = "sf_coordinates",
nudge_x = -.2,
nudge_y = .2,
) +
scale_size_identity() +
scale_fill_gradient2(
na.value = "lightgrey",
low = "blue",
high = "red",
mid = "lightgrey",
midpoint = sw_shift,
limits = c(-.2, .2) + sw_shift,
oob = scales::squish,
breaks = c(-.2, 0, .2) + sw_shift,
labels=c("Democratic shift", "Average shift (Democrats +.02)", "Republican shift"),
) +
theme(
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
panel.background = element_rect(fill = "lightblue"),
panel.grid.major = element_line(color = "lightblue"),
legend.position = "bottom",
legend.key.width = unit(2, "cm"),
legend.title = element_blank(),
) +
labs(title=str_wrap("Shift in share of two-party vote within New Jersey, 2004-2009 to 2016-2021", width=50),
subtitle = "Elections for Governor, US Senate and US President",
caption="Source: NJ Division of Elections")
# Save plots
# ggsave("tpsov.png", plot = plot_tpsov)
# ggsave("tpsov_delta.png", plot_tpsov_delta)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment