Last active
October 28, 2025 17:00
-
-
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?
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
| # 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