Skip to content

Instantly share code, notes, and snippets.

@grantmcdermott
Last active October 28, 2025 17:00
Show Gist options
  • Save grantmcdermott/63c7e8737f50aba5877b115d7b041824 to your computer and use it in GitHub Desktop.
Save grantmcdermott/63c7e8737f50aba5877b115d7b041824 to your computer and use it in GitHub Desktop.
What is the minimum no. of votes that could have swung previous US elections?
# Context: https://bsky.app/profile/gmcd.bsky.social/post/3lzvu75zygk2f
library(rvest)
library(data.table)
elec_marg = function(year = 2020) {
wurl = paste0("https://en.wikipedia.org/wiki/", year, "_United_States_presidential_election")
ev = read_html(wurl) |>
html_element("table.wikitable:nth-child(1)") |>
html_table()
h = ev[1, ] |> as.vector()
h[1] = ""
names(h)[1] = "state"
idx = grepl("^state|Democratic$|Republican$", names(h)) & !grepl("%", h)
h = h[idx]
nms = c(
names(h)[1],
paste0(tolower(h[-1]), "_", gsub(".*(Democratic|Republican)", "\\1", names(h[-1])))
)
ev = as.data.table(ev)[-1, ..idx] |>
setnames(nms)
suppressWarnings(
ev[, names(.SD) := lapply(.SD, \(s) as.numeric(gsub(',', '', s))), .SDcols = -1]
)
setnafill(ev, type = "const", fill = 0, cols = c("ev_Democratic", "ev_Republican"))
# housecleaning (optional but nicer printing)
ev[, state := gsub(".*Tooltip |\\[.*", "", state)]
ev[, state := gsub("(\\w+)'s (\\d+)\\w+ congressional district", "\\1 \\2", state)]
# remove total case and any remaining NA cases (artifact of html tables)
ev = na.omit(ev[state != "Total"])
# remove special congression cases (handled directly)
ev = ev[!grepl("^Maine |^Nebraska ", state)]
# reshape (using the special measure(value.name) keyword to efficiently
# reshape and split across party-vote types, i.e. votes_<party>, ev_<party>.)
ev = melt(ev, measure.vars = measure(value.name, party, sep = "_"))
# get the total number of EVs and order by winning party
ev_tot = ev[, lapply(.SD, sum), .SDcols = c("votes", "ev"), by = party][order(-ev)]
# carry over to main dataset for ordering (needed for diff calc below)
ev[, party := factor(party, levels = ev_tot$party)]
setorder(ev, party)
ev_diff = ev[
order(state, party),
lapply(.SD, diff),
by = state,
.SDcols = c("votes", "ev")
]
# how many EVs are required to flip the result?
req_ev = 270 - ev_tot[, min(ev)]
# calculate min req votes based on most efficient potential gains (votes/ev)
flippable = ev_diff[ev < 0][order(abs(votes/ev))]
fidx = which(cumsum(abs(flippable$ev)) >= req_ev)[1]
min_votes = sum(abs(flippable$votes[1:fidx]))
min_states = flippable$state[1:fidx]
min_votes_perc_tot = paste0(signif(min_votes / sum(ev_tot$votes) * 100, 1), "%")
min_votes_perc_flip = paste0(signif(min_votes / ev[state %chin% min_states][, sum(votes)] * 100, 1), "%")
cat("Winner:", ev_tot$party[1], "\n")
cat("Min votes to flip ", ev_tot$party[2], ": ",
prettyNum(min_votes, big.mark = ","),
"\n", sep = "")
cat(" ", min_votes_perc_tot, " of total votes\n", sep = "")
cat(" ", min_votes_perc_flip, " of votes from ", fidx, " states",
" (", paste(flippable$state[1:fidx], collapse = ", "), ")",
"\n", sep = "")
return(invisible(min_votes))
}
## Examples
elec_marg(2016)
#> Winner: Republican
#> Min votes to flip Democratic: 77,744
#> 0.06% of total votes
#> 0.6% of votes from 3 states (Michigan, Pennsylvania, Wisconsin)
elec_marg(2020)
#> Winner: Democratic
#> Min votes to flip Republican: 123,473
#> 0.08% of total votes
#> 0.7% of votes from 4 states (Georgia, Arizona, Wisconsin, Pennsylvania)
elec_marg(2024)
#> Winner: Republican
#> Min votes to flip Democratic: 344,866
#> 0.2% of total votes
#> 2% of votes from 4 states (Wisconsin, Michigan, Pennsylvania, Georgia)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment