Below is the R code for producing the Electoral Vote Tracker and for downloading an initial data file with all electoral votes unallocated. Scroll down for a quick How does the tracker work? tutorial.

First, run the code for downloading the initial data file. Then, run the code for producing the tracker. Each time you run the code for producing the tracker, you will have an opportunity to edit and update the data file the tracker’s graphic represents.

Download the initial data file

(Note: Each time you download this file, any changes you have made to the file will be overwritten. So, run this code only if you have not run the tracker code before, or if you have but want to reset the tracker to show no votes allocated for either candidate.)

InitialDataFile <- read.csv("https://raw.githubusercontent.com/drkblake/Data/main/ElectoralVotesByState2024.csv")

write.csv(InitialDataFile,"ElectoralVotesByState2024.csv", row.names = FALSE)

rm(InitialDataFile)

Code for the tracker

The code below will produce the tracker. Each time you run the code, you will have an opportunity to edit the vote allocation. Any edits you make will be saved automatically.

Incorporating this code into an R Markdown document will give you a way to update and publish the tracker on RPubs.com, all in a single, repeatable process. You can save yourself some time and hassle by visiting this link:

The tracker, plus R Markdown code

… and pasting what you see there into a blank R markdown window in RStudio on your computer.

Meanwhile, here’s the code for just the tracker, with no R Markdown format codes included:

# Required packages

if (!require("tidyverse"))
  install.packages("tidyverse")
if (!require("plotly"))
  install.packages("plotly")
if (!require("DataEditR"))
  install.packages("DataEditR")

library(tidyverse)
library(plotly)
library(DataEditR)

# Loading the data from a local .csv file

AllData <- read.csv("ElectoralVotesByState2024.csv")
AllData <- AllData %>%
  arrange(State)

# Editing the data, and saving the edits

AllData <- data_edit(AllData)
write.csv(AllData,"ElectoralVotesByState2024.csv", row.names = FALSE)

# Formatting and transforming the data for plotting

MyData <- AllData %>%
  select(State, Votes.to.allocate,
         Unallocated, Harris, Trump) %>% 
  arrange(State)

MyData <- MyData %>%
  pivot_longer(cols=c(-State),names_to="Candidate")%>%
  pivot_wider(names_from=c(State)) %>%
  filter(Candidate == "Harris" |
           Candidate == "Trump" |
           Candidate == "Unallocated") %>%
  arrange(Candidate)

MyData <- MyData %>% 
  mutate(total = rowSums(.[2:52]))

# Formatting a horizontal line for the plot

hline <- function(y = 0, color = "darkgray") {
  list(
    type = "line",
    x0 = 0,
    x1 = 1,
    xref = "paper",
    y0 = y,
    y1 = y,
    line = list(color = color)
  )
}

# Producing the plot

fig <- plot_ly(
  MyData,
  x = ~ Candidate,
  y = ~ AK,
  legend = FALSE,
  marker = list(color = c("384B70", "B8001F", "gray")),
  type = 'bar',
  name = 'AK'
) %>% 
  add_annotations(
    visible = "legendonly",
    x = ~ Candidate,
    y = ~ (total + 20),
    text = ~ total,
    showarrow = FALSE,
    textfont = list(size = 50)
  ) 
fig <- fig %>% add_trace(y = ~ DE, name = 'DE')
fig <- fig %>% add_trace(y = ~ DC, name = 'DC')
fig <- fig %>% add_trace(y = ~ MT, name = 'MT')
fig <- fig %>% add_trace(y = ~ ND, name = 'ND')
fig <- fig %>% add_trace(y = ~ SD, name = 'SD')
fig <- fig %>% add_trace(y = ~ VT, name = 'VT')
fig <- fig %>% add_trace(y = ~ WY, name = 'WY')
fig <- fig %>% add_trace(y = ~ HI, name = 'HI')
fig <- fig %>% add_trace(y = ~ ID, name = 'ID')
fig <- fig %>% add_trace(y = ~ ME, name = 'ME')
fig <- fig %>% add_trace(y = ~ NH, name = 'NH')
fig <- fig %>% add_trace(y = ~ RI, name = 'RI')
fig <- fig %>% add_trace(y = ~ NE, name = 'NE')
fig <- fig %>% add_trace(y = ~ NM, name = 'NM')
fig <- fig %>% add_trace(y = ~ WV, name = 'WV')
fig <- fig %>% add_trace(y = ~ AR, name = 'AR')
fig <- fig %>% add_trace(y = ~ IA, name = 'IA')
fig <- fig %>% add_trace(y = ~ KS, name = 'KS')
fig <- fig %>% add_trace(y = ~ MS, name = 'MS')
fig <- fig %>% add_trace(y = ~ NV, name = 'NV')
fig <- fig %>% add_trace(y = ~ UT, name = 'UT')
fig <- fig %>% add_trace(y = ~ CT, name = 'CT')
fig <- fig %>% add_trace(y = ~ OK, name = 'OK')
fig <- fig %>% add_trace(y = ~ OR, name = 'OR')
fig <- fig %>% add_trace(y = ~ KY, name = 'KY')
fig <- fig %>% add_trace(y = ~ LA, name = 'LA')
fig <- fig %>% add_trace(y = ~ AL, name = 'AL')
fig <- fig %>% add_trace(y = ~ CO, name = 'CO')
fig <- fig %>% add_trace(y = ~ SC, name = 'SC')
fig <- fig %>% add_trace(y = ~ MD, name = 'MD')
fig <- fig %>% add_trace(y = ~ MN, name = 'MN')
fig <- fig %>% add_trace(y = ~ MO, name = 'MO')
fig <- fig %>% add_trace(y = ~ WI, name = 'WI')
fig <- fig %>% add_trace(y = ~ AZ, name = 'AZ')
fig <- fig %>% add_trace(y = ~ IN, name = 'IN')
fig <- fig %>% add_trace(y = ~ MA, name = 'MA')
fig <- fig %>% add_trace(y = ~ TN, name = 'TN')
fig <- fig %>% add_trace(y = ~ WA, name = 'WA')
fig <- fig %>% add_trace(y = ~ VA, name = 'VA')
fig <- fig %>% add_trace(y = ~ NJ, name = 'NJ')
fig <- fig %>% add_trace(y = ~ NC, name = 'NC')
fig <- fig %>% add_trace(y = ~ GA, name = 'GA')
fig <- fig %>% add_trace(y = ~ MI, name = 'MI')
fig <- fig %>% add_trace(y = ~ OH, name = 'OH')
fig <- fig %>% add_trace(y = ~ IL, name = 'IL')
fig <- fig %>% add_trace(y = ~ PA, name = 'PA')
fig <- fig %>% add_trace(y = ~ FL, name = 'FL')
fig <- fig %>% add_trace(y = ~ NY, name = 'NY')
fig <- fig %>% add_trace(y = ~ TX, name = 'TX')
fig <- fig %>% add_trace(y = ~ CA, name = 'CA')
fig <- fig %>% layout(yaxis = list(title = 'Electoral votes'),
                      barmode = 'stack',
                      showlegend = FALSE,
                      shapes = list(hline(270)))
# Showing the plot

fig

How does the tracker work?

Here’s an overview of what various sections of the tracker’s code do, and how.

Installing and loading required packages

The first chunk of code installs and activates various required R packages. The one most likely to be unfamiliar to you is the DataEditR package. This hand package allows you to edit a data file from within R instead of having to edit the data file in a separate application, like Excel.

The plotly package may be new to you, too. It’s a package for making interactive graphics.

# Required packages

if (!require("tidyverse"))
  install.packages("tidyverse")
if (!require("plotly"))
  install.packages("plotly")
if (!require("DataEditR"))
  install.packages("DataEditR")

library(tidyverse)
library(plotly)
library(DataEditR)

Reading the data

The next code block uses the read.csv() function to read the data file from your computer’s hard drive and put it into a data frame called AllData.

# Loading the data from a local .csv file

AllData <- read.csv("ElectoralVotesByState2024.csv")
AllData <- AllData %>%
  arrange(State)

Editing and saving the data

Here’s the code block that lets you edit the data on the fly, using the data_edit() function from the DataEditR package to open the AllData data frame in an editing window, using your computer’s default Web browser.

Saving your data edits. Once you have made your edits to the data - or even if you have made no edits at all to the data - it is critically important for you to click the editing window’s swirly “Synchronize” button, then click the blue “Done” button. They look like this:

Clicking these buttons saves your edits, if any, to the AllData data frame and allows R Markdown to continue generating the tracker. It’s a good idea to close the editing window after you have completed your edits and clicked these two buttons.

Preserving your edits for the tracker’s next run. Once you click the “Done” button, the code continues, automatically using the write.csv() function to save the data edits you made to the ElectoralVotesByState2024.csv data file stored on your computer’s hard drive. Without this step, the tracker would reload the original version of the .csv file the next time it runs, wiping out your data edits and setting all electoral vote allocations back to zero.

# Editing the data, and saving the edits

AllData <- data_edit(AllData)
write.csv(AllData,"ElectoralVotesByState2024.csv", row.names = FALSE)

Formatting the data for plotting

If you look at the AllData data frame, you’ll find that it is “long,” meaning that it has many more horizontal rows than vertical columns. The rows contain the data for each state. The columns contain each of the six variables. Here’s a glimpse of the first few rows:

Given the kind of plot we are making, Plotly expects the data to be “wide,” with a column for each state, and a row for Harris votes, Trump votes, and unallocated votes. Like this, which is what the first several columns of the MyData dataframe end up looking like:

“Flipping” a data file like this - so that rows become columns, and columns become rows - is what the pivot_longer() and pivot_wider() functions are good at. Both are available through the tidyverse package, which we loaded in the first code block.

The code also uses various tidyverse functions to sort, select, and filter the data, and put the total number of Harris, Trump, and unallocated votes in the flipped dataset’s last column.

# Formatting and transforming the data for plotting

MyData <- AllData %>%
  select(State, Votes.to.allocate,
         Unallocated, Harris, Trump) %>% 
  arrange(State)

MyData <- MyData %>%
  pivot_longer(cols=c(-State),names_to="Candidate")%>%
  pivot_wider(names_from=c(State)) %>%
  filter(Candidate == "Harris" |
           Candidate == "Trump" |
           Candidate == "Unallocated") %>%
  arrange(Candidate)

MyData <- MyData %>% 
  mutate(total = rowSums(.[2:52]))

Adding a “270” reference line

A candidate will need 270 or more electoral votes to win the presidency. This code creates and formats a horizontal line that the next block of code will add to the tracker at the 270-vote mark on the vertixal axis. The line will show the “finish line” for the electoral vote race.

# Formatting a horizontal line for the plot

hline <- function(y = 0, color = "darkgray") {
  list(
    type = "line",
    x0 = 0,
    x1 = 1,
    xref = "paper",
    y0 = y,
    y1 = y,
    line = list(color = color)
  )
}

Making and displaying the plot

We’re ready, at last, to make and show the plot. Most of the action takes place among the various arguments within the plot_ly() function, from the plotly package loaded in the first block of code. These include the type = argument, which tells R to use a bar chart rather than some other type of chart; the x = and y = arguments, which specify which variables will go on the x and y axes; and the marker = argument, which formats the chart’s bars and assigns their colors. The 384B70 and B8001F codes are HTML hexadecimal codes for the the shades of blue (for Harris) and red (for Trump) used in the chart. I could have used a similar code to define the gray shade used for the unallocated votes bar. But R can recognize “gray,” so I just went with that. You can find recommended color palettes all over the Web. I like to start with the free palettes at Coolers.com.

The plot_ly function and its arguments create the chart, but only for the first state in the data file - the state of Alaska. The code uses a series of add_trace() functions to add the rest of the states to the chart.

Finally, sending R the name of the chart, fig, tells R to display the chart.

# Producing the plot

fig <- plot_ly(
  MyData,
  x = ~ Candidate,
  y = ~ AK,
  legend = FALSE,
  marker = list(color = c("384B70", "B8001F", "gray")),
  type = 'bar',
  name = 'AK'
) %>% 
  add_annotations(
    visible = "legendonly",
    x = ~ Candidate,
    y = ~ (total + 20),
    text = ~ total,
    showarrow = FALSE,
    textfont = list(size = 50)
  ) 
fig <- fig %>% add_trace(y = ~ DE, name = 'DE')
fig <- fig %>% add_trace(y = ~ DC, name = 'DC')
fig <- fig %>% add_trace(y = ~ MT, name = 'MT')
fig <- fig %>% add_trace(y = ~ ND, name = 'ND')
fig <- fig %>% add_trace(y = ~ SD, name = 'SD')
fig <- fig %>% add_trace(y = ~ VT, name = 'VT')
fig <- fig %>% add_trace(y = ~ WY, name = 'WY')
fig <- fig %>% add_trace(y = ~ HI, name = 'HI')
fig <- fig %>% add_trace(y = ~ ID, name = 'ID')
fig <- fig %>% add_trace(y = ~ ME, name = 'ME')
fig <- fig %>% add_trace(y = ~ NH, name = 'NH')
fig <- fig %>% add_trace(y = ~ RI, name = 'RI')
fig <- fig %>% add_trace(y = ~ NE, name = 'NE')
fig <- fig %>% add_trace(y = ~ NM, name = 'NM')
fig <- fig %>% add_trace(y = ~ WV, name = 'WV')
fig <- fig %>% add_trace(y = ~ AR, name = 'AR')
fig <- fig %>% add_trace(y = ~ IA, name = 'IA')
fig <- fig %>% add_trace(y = ~ KS, name = 'KS')
fig <- fig %>% add_trace(y = ~ MS, name = 'MS')
fig <- fig %>% add_trace(y = ~ NV, name = 'NV')
fig <- fig %>% add_trace(y = ~ UT, name = 'UT')
fig <- fig %>% add_trace(y = ~ CT, name = 'CT')
fig <- fig %>% add_trace(y = ~ OK, name = 'OK')
fig <- fig %>% add_trace(y = ~ OR, name = 'OR')
fig <- fig %>% add_trace(y = ~ KY, name = 'KY')
fig <- fig %>% add_trace(y = ~ LA, name = 'LA')
fig <- fig %>% add_trace(y = ~ AL, name = 'AL')
fig <- fig %>% add_trace(y = ~ CO, name = 'CO')
fig <- fig %>% add_trace(y = ~ SC, name = 'SC')
fig <- fig %>% add_trace(y = ~ MD, name = 'MD')
fig <- fig %>% add_trace(y = ~ MN, name = 'MN')
fig <- fig %>% add_trace(y = ~ MO, name = 'MO')
fig <- fig %>% add_trace(y = ~ WI, name = 'WI')
fig <- fig %>% add_trace(y = ~ AZ, name = 'AZ')
fig <- fig %>% add_trace(y = ~ IN, name = 'IN')
fig <- fig %>% add_trace(y = ~ MA, name = 'MA')
fig <- fig %>% add_trace(y = ~ TN, name = 'TN')
fig <- fig %>% add_trace(y = ~ WA, name = 'WA')
fig <- fig %>% add_trace(y = ~ VA, name = 'VA')
fig <- fig %>% add_trace(y = ~ NJ, name = 'NJ')
fig <- fig %>% add_trace(y = ~ NC, name = 'NC')
fig <- fig %>% add_trace(y = ~ GA, name = 'GA')
fig <- fig %>% add_trace(y = ~ MI, name = 'MI')
fig <- fig %>% add_trace(y = ~ OH, name = 'OH')
fig <- fig %>% add_trace(y = ~ IL, name = 'IL')
fig <- fig %>% add_trace(y = ~ PA, name = 'PA')
fig <- fig %>% add_trace(y = ~ FL, name = 'FL')
fig <- fig %>% add_trace(y = ~ NY, name = 'NY')
fig <- fig %>% add_trace(y = ~ TX, name = 'TX')
fig <- fig %>% add_trace(y = ~ CA, name = 'CA')
fig <- fig %>% layout(yaxis = list(title = 'Electoral votes'),
                      barmode = 'stack',
                      showlegend = FALSE,
                      shapes = list(hline(270)))
# Showing the plot

fig