Created
August 25, 2022 19:04
-
-
Save tor-gu/3f9ab5d151e4bee3a41cbb70f72c954d to your computer and use it in GitHub Desktop.
Two party share of vote in NJ maps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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