November 2nd will bring Pennsylvania’s municipal elections. In Philadelphia, Larry Krasner will certainly win. However, there are several state judicial races on the ballot, including for the Supreme Court. The court is represented currently by five Democrats and two Republicans, a balance that led to its anti-gerrymandering ruling.
The 2019 election reform bill that created no-excuse vote by mail (in Covid hindsight, a small miracle) also removed straight-party voting, in which a voter could vote for all Democratic or Republican candidates with the push of a single button.
This was expected to hurt Democratic judges downballot, as voters would vote for topline races but leave lower ones blank. An analysis by PennLive found that over 1/3 of voters used the straight-ticket button, and it was used disproportionately by Democrats.
We’ve had one general election since the law went into effect, so I thought I’d see if we could say anything about that ahead of next Tuesday’s race.
Downballot Results
In a typical judicial election, Philadelphia casts about 10% of the votes. Philadelphia’s four suburban counties–Bucks, Chester, Delaware, and Montgomery–cast an additional 26% in recent years. (I’m using Superior Court elections, which are more consistent that Supreme Court.)
View code
library(tidyverse)
devtools::load_all("../../admin_scripts/sixtysix/")
election_files <- list.files("../../data/pa_election_data/electionreturns.pa.gov", pattern = "PA_Official_[0-9]+_general", full.names = TRUE)
dfs <- list()
for(file in election_files){
dfs[[file]] <- read_csv(file)
}
df <- bind_rows(dfs)
names(df) <-
gsub(" ", "_",
gsub(" name$", "", tolower(names(df)))
)
df$year <- as.numeric(substr(df$election, 1, 4))
counties <- df %>%
filter(year == "2020", office == "President of the United States") %>%
select(county, candidate, votes) %>%
pivot_wider(names_from=candidate, values_from=votes) %>%
mutate(pdem_2020 = `BIDEN, JOSEPH ROBINETTE JR` / (`BIDEN, JOSEPH ROBINETTE JR` + `TRUMP, DONALD J.`)) %>%
mutate(cat = case_when(
county == "PHILADELPHIA" ~ "Philadelphia",
county %in% c("MONTGOMERY", "BUCKS", "CHESTER", "DELAWARE") ~ "Philadelphia Suburbs",
TRUE ~ "Rest of PA"
))
cat_colors <- c(
Philadelphia = colors_sixtysix()$strong_red,
`Philadelphia Suburbs` = colors_sixtysix()$strong_green,
`Rest of PA` = colors_sixtysix()$strong_blue
)
judge_results <- df %>%
filter(
office %in% c("Judge of the Superior Court")
# ifelse(
# year %% 4 == 1,
# office %in% c("Justice of the Supreme Court"),
# office %in% c("Judge of the Superior Court")
# )
) %>%
group_by(county, year) %>%
summarise(
ndem = length(unique(candidate[substr(party, 1, 3) == "Dem"])),
total_votes = sum(votes),
dem_votes = sum(votes * (substr(party, 1, 3) == "Dem")),
rep_votes = sum(votes * (substr(party, 1, 3) == "Rep"))
) %>%
left_join(counties)
judge_cats <- judge_results %>% group_by(cat, year) %>%
summarise(dem_votes=sum(dem_votes), rep_votes=sum(rep_votes)) %>%
mutate(
total_2party=dem_votes + rep_votes,
pdem = dem_votes/ total_2party
) %>%
group_by(year) %>%
mutate(prop_of_year = total_2party / sum(total_2party))
ggplot(
judge_cats,
aes(x = year, y=100*prop_of_year)
) +
geom_point(aes(color=cat), size=2)+
geom_line(aes(group=cat, color=cat), size=1) +
expand_limits(y=0) +
theme_sixtysix() +
geom_text(
data=tribble(
~x, ~y, ~cat,
2019, 7, "Philadelphia",
2019, 32, "Philadelphia Suburbs",
2019, 73, "Rest of PA"
),
aes(x=x, y=y, label=cat, color=cat),
hjust=1,
fontface="bold"
) +
scale_color_manual(guide=FALSE, values=cat_colors) +
labs(
title="Philadelphia casts about 10% of Judicial votes",
subtitle="Elections for Superior Court",
y="Percent of votes cast",
x=NULL
)
With that 10%, Philadelphia is by far more Democratic than the rest of the state. Democratic candidates receive over 80% of the vote here. The suburbs are significantly more Democratic recently, at 56% in recent years, and the rest of the state is becoming more Republican fast (53% in 2017, then 57% in 2019).
View code
ggplot(
judge_cats,
aes(x=year, y=100*pdem)
) +
geom_point(aes(color=cat), size=2)+
geom_line(aes(group=cat, color=cat), size=1) +
expand_limits(y=0) +
theme_sixtysix() +
geom_text(
data=tribble(
~x, ~y, ~cat,
2019, 78, "Philadelphia",
2019, 62, "Philadelphia Suburbs",
2019, 40, "Rest of PA"
),
aes(x=x, y=y, label=cat, color=cat),
hjust=1,
fontface="bold"
) +
scale_color_manual(guide=FALSE, values=cat_colors) +
labs(
title="Philadelphia votes over 80% Democratic",
subtitle="Elections for Superior Court",
y="Percent cast for Democrats",
x=NULL
)
I don’t have the actual straight-ticket use numbers. Instead, let’s look at downballot vote rates. Since 2011 Philadelphia has cast about 90% of the votes for Superior Court candidates as it has for topline races.
View code
df_phila <- readRDS("../../data/processed_data/df_major_type_20210118.Rds")
df_phila <- df_phila %>% filter(election_type=="general")
ndem <- df_phila %>%
filter(office == "JUDGE OF THE SUPERIOR COURT", substr(party, 1, 3) == "DEM") %>%
group_by(year) %>%
summarise(ndem = length(unique(candidate)))
vpv_phila <- df_phila %>%
filter(
asnum(year) %% 2 == 1,
is_topline_office | (office == "JUDGE OF THE SUPERIOR COURT")
) %>%
mutate(isjudge = ifelse(office == "JUDGE OF THE SUPERIOR COURT", "Sup Court", "Topline")) %>%
group_by(year, isjudge) %>%
summarise(votes=sum(votes)) %>%
pivot_wider(names_from=isjudge, values_from=votes) %>%
left_join(ndem) %>%
mutate(
votes_per_voter = `Sup Court` / (ndem * Topline)
)
ggplot(
vpv_phila %>% filter(year != 2005),
aes(x=asnum(year), y=100*votes_per_voter)
) +
geom_point() +
geom_path() +
labs(
title="90% of Philadelphian voters vote for judges",
y="Votes for Superior Court / \n(Topline Votes * N(Vacancies)) (%)",
x=NULL
) +
scale_y_continuous(breaks = seq(0, 100, 10)) +
expand_limits(y=50:100)+
theme_sixtysix()
I can’t create the same plot across the state because the PA results don’t provide municipal races. But here’s that plot for even years, using State Rep votes as the numerator.
View code
county_votes_presgov <- df %>%
filter(
office %in% c("President of the United States", 'Governor')
) %>%
group_by(year, county) %>%
summarise(votes_presgov=sum(votes))
county_votes_strep <- df %>%
filter(
office %in% c("Representative in the General Assembly")
) %>%
group_by(year, county) %>%
summarise(votes_strep=sum(votes))
votes_strep <- county_votes_strep %>%
left_join(county_votes_presgov) %>%
left_join(counties) %>%
group_by(cat, year) %>%
summarise(
across(c(votes_presgov, votes_strep), sum)
)
ggplot(
votes_strep,
aes(x=year, y=100*votes_strep / votes_presgov, color=cat)
) +
geom_point() +
geom_line(aes()) +
expand_limits(y=c(50,100)) +
scale_y_continuous(breaks = seq(0, 100, 10)) +
scale_color_manual(guide=FALSE, values=cat_colors) +
theme_sixtysix() +
geom_text(
data=tribble(
~x, ~y, ~cat,
2020, 85, "Philadelphia",
2020, 100, "Philadelphia Suburbs",
2020, 95, "Rest of PA"
),
aes(x=x, y=y, label=cat, color=cat),
hjust=1,
fontface="bold"
) +
labs(
title="Philadelphia's downballot rate dropped in 2020",
y="Votes for State Rep / Votes for Topline (%)",
x=NULL
)
There’s serious variance across years, as some elections draw only super-voters, while others draw only topline voters. But something big happened in 2020: Philadelphia’s ratio plummeted, while the rest of the state *increased*. It’s not entirely clear what might have caused this. It could certainly be the elimination of straight-ticket voting. It could also be an influx of lower-propensity voters, who are less likely to vote downballot, or even a signal of distaste for the Democratic party (a costless signal, since most of these races were non-competitive).
This change was completely different between mail and Election Day voters. Among Philadelphia’s mail voters, State Rep votes were at 91.5% of Presidential votes. Among Election Day voters, that was only 83%. There is obviously a ton of omitted variable bias in this difference–mail-in voters were different in many ways from Election Day voters–but it’s at least suggestive that maybe the physical experience of mail-in ballots makes voters more likely to vote downballot.
We might worry that this drop would not just hurt Philadelphia, but other Democratic counties. But there does not appear to be a broad correlation between Democratic vote share and changes in downballot vote rates.
View code
county_vpv_scatter <- county_votes_strep %>%
left_join(county_votes_presgov) %>%
left_join(counties) %>%
filter(year %in% c(2018, 2020)) %>%
mutate(vpv=votes_strep / votes_presgov) %>%
group_by(county) %>%
mutate(votes_20 = votes_presgov[year==2020]) %>%
select(year, county,cat, vpv, pdem_2020, votes_20) %>%
pivot_wider(
names_from=year, values_from=vpv
) %>%
mutate(ratio = `2020`/`2018`)
ggplot(county_vpv_scatter, aes(x=100*pdem_2020, y=ratio)) +
geom_smooth(weights="votes_20", color="grey60") +
geom_point(aes(size=votes_20, color=cat)) +
geom_hline(yintercept=1) +
theme_sixtysix() +
scale_size_area(guide=FALSE)+
scale_color_manual(guide=FALSE, values=cat_colors) +
labs(
title="No clear trend in downballot vote changes",
subtitle="Change in voting rates for State Rep, 2018-2020.",
y="2020 Votes per Voter / 2018 Votes per Voter\n(Votes per Voter = State Rep Votes / Topline Votes)",
x="2020 Percent for Biden"
)
Will Philadelphia’s drop continue this year?
This election is entirely different from last November, obviously. We’re entering the lowest turnout election in the four-year cycle, down from 2020’s highest. A surge in low-interest voters would have made this rate low in 2020, as those hard-to-get voters are also less likely to vote for non-Presidential races. Also, State Representative races are differentially competitive across the state. Add Covid, the novelty of mail-in voting, and Philadelphia’s new voting machines, and it’s not clear how generalizable 2020’s patterns will be to this year. (and as @Dan_UA points out, Supreme Court is not literally downballot ; so it depends if you think the mechanism is literal ballot position or voter interest.)
Suppose this trend did continue this year, though. Would it have mattered? In 2017, Republican Sally Mundy beat out Democrat Dwayne Woodruff by 95,000 votes. In that race, 200,000 Philadelphians voted. The 5% drop in downballot votes above would mean 10,000 votes, of which we’d expect Democrats to net 6,000. So it wouldn’t have affected 2017, but might in a much tighter race.