Final Project

2024-04-22

Examining US House Election Votes and Results

Looking into the US House election results could show us trends for how people from different areas of the country voted in these elections. It could also show how different areas have different priorities when making decisions on who to vote for.

This data set shows the results of the elections dating back to 1976.

Downloading Data set, TidyVerse, and DPLYR

https://github.com/rfordatascience/tidytuesday/tree/master/data/2023/2023-11-07

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dplyr)

house <- read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2023/2023-11-07/house.csv')
## Rows: 32452 Columns: 20
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (8): state, state_po, office, district, stage, candidate, party, mode
## dbl (7): year, state_fips, state_cen, state_ic, candidatevotes, totalvotes, ...
## lgl (5): runoff, special, writein, unofficial, fusion_ticket
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Data Dictionary

str(house)
## spc_tbl_ [32,452 × 20] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ year          : num [1:32452] 1976 1976 1976 1976 1976 ...
##  $ state         : chr [1:32452] "ALABAMA" "ALABAMA" "ALABAMA" "ALABAMA" ...
##  $ state_po      : chr [1:32452] "AL" "AL" "AL" "AL" ...
##  $ state_fips    : num [1:32452] 1 1 1 1 1 1 1 1 1 1 ...
##  $ state_cen     : num [1:32452] 63 63 63 63 63 63 63 63 63 63 ...
##  $ state_ic      : num [1:32452] 41 41 41 41 41 41 41 41 41 41 ...
##  $ office        : chr [1:32452] "US HOUSE" "US HOUSE" "US HOUSE" "US HOUSE" ...
##  $ district      : chr [1:32452] "001" "001" "001" "002" ...
##  $ stage         : chr [1:32452] "GEN" "GEN" "GEN" "GEN" ...
##  $ runoff        : logi [1:32452] FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ special       : logi [1:32452] FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ candidate     : chr [1:32452] "BILL DAVENPORT" "JACK EDWARDS" "WRITEIN" "J CAROLE KEAHEY" ...
##  $ party         : chr [1:32452] "DEMOCRAT" "REPUBLICAN" NA "DEMOCRAT" ...
##  $ writein       : logi [1:32452] FALSE FALSE TRUE FALSE FALSE TRUE ...
##  $ mode          : chr [1:32452] "TOTAL" "TOTAL" "TOTAL" "TOTAL" ...
##  $ candidatevotes: num [1:32452] 58906 98257 7 66288 90069 ...
##  $ totalvotes    : num [1:32452] 157170 157170 157170 156362 156362 ...
##  $ unofficial    : logi [1:32452] FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ version       : num [1:32452] 20230706 20230706 20230706 20230706 20230706 ...
##  $ fusion_ticket : logi [1:32452] FALSE FALSE FALSE FALSE FALSE FALSE ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   year = col_double(),
##   ..   state = col_character(),
##   ..   state_po = col_character(),
##   ..   state_fips = col_double(),
##   ..   state_cen = col_double(),
##   ..   state_ic = col_double(),
##   ..   office = col_character(),
##   ..   district = col_character(),
##   ..   stage = col_character(),
##   ..   runoff = col_logical(),
##   ..   special = col_logical(),
##   ..   candidate = col_character(),
##   ..   party = col_character(),
##   ..   writein = col_logical(),
##   ..   mode = col_character(),
##   ..   candidatevotes = col_double(),
##   ..   totalvotes = col_double(),
##   ..   unofficial = col_logical(),
##   ..   version = col_double(),
##   ..   fusion_ticket = col_logical()
##   .. )
##  - attr(*, "problems")=<externalptr>
summary(house)
##       year         state             state_po           state_fips   
##  Min.   :1976   Length:32452       Length:32452       Min.   : 1.00  
##  1st Qu.:1988   Class :character   Class :character   1st Qu.:17.00  
##  Median :2000   Mode  :character   Mode  :character   Median :31.00  
##  Mean   :2000                                         Mean   :28.76  
##  3rd Qu.:2012                                         3rd Qu.:40.00  
##  Max.   :2022                                         Max.   :56.00  
##    state_cen        state_ic        office            district        
##  Min.   :11.00   Min.   : 1.00   Length:32452       Length:32452      
##  1st Qu.:23.00   1st Qu.:14.00   Class :character   Class :character  
##  Median :52.00   Median :40.00   Mode  :character   Mode  :character  
##  Mean   :50.95   Mean   :37.09                                        
##  3rd Qu.:74.00   3rd Qu.:52.00                                        
##  Max.   :95.00   Max.   :82.00                                        
##     stage             runoff         special         candidate        
##  Length:32452       Mode :logical   Mode :logical   Length:32452      
##  Class :character   FALSE:23788     FALSE:32362     Class :character  
##  Mode  :character   TRUE :8         TRUE :90        Mode  :character  
##                     NA's :8656                                        
##                                                                       
##                                                                       
##     party            writein            mode           candidatevotes   
##  Length:32452       Mode :logical   Length:32452       Min.   :     -1  
##  Class :character   FALSE:29722     Class :character   1st Qu.:   4324  
##  Mode  :character   TRUE :2730      Mode  :character   Median :  57328  
##                                                        Mean   :  66825  
##                                                        3rd Qu.: 112144  
##                                                        Max.   :1165136  
##    totalvotes      unofficial         version         fusion_ticket  
##  Min.   :     -1   Mode :logical   Min.   :20230706   Mode :logical  
##  1st Qu.: 162266   FALSE:32413     1st Qu.:20230706   FALSE:29812    
##  Median : 206983   TRUE :39        Median :20230706   TRUE :2640     
##  Mean   : 215165                   Mean   :20230706                  
##  3rd Qu.: 263386                   3rd Qu.:20230706                  
##  Max.   :2656104                   Max.   :20230706

Filtering for State and Party

To do this we first must split up the data and group it by state and party

state_party_votes <- house %>%
  group_by(state_po, party) %>%
  summarise(total_votes = sum(candidatevotes), .groups = "drop") %>%
  ungroup()

For this specific project we will only be using the main two political parties in the United States. Democratic and Republican Parties.

We must first filter the data for only these two parties

filtered_state_party_votes <- state_party_votes %>%
  filter(party %in% c("DEMOCRAT", "REPUBLICAN"))

Question 1: How did more rural states differ from more urban states voting patterns in the US House Elections?

Now that the data has been grouped by state and political party we can make a plot that shows us how the voting pattern differed.

ggplot(filtered_state_party_votes, aes(x = reorder(state_po, total_votes), y = total_votes/1000)) +
  geom_bar(stat = "identity", aes(fill = party), position = "dodge") +
  labs(title = "Distribution of Votes by State for Democrat and Republican Parties") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 5)) +  # Reduce font size of x-axis labels
  scale_x_discrete(name = "State") +
  scale_y_continuous(labels = scales::comma_format(scale = 1e-3), name = "Total Votes (Thousands)") +  # Format y-axis labels in thousands
  theme(
    axis.title.x = element_text(margin = margin(t = 10)),  # Adjust x-axis title margin
    plot.margin = margin(1, 1, 1, 1, "cm"), 
    legend.position = "none" 
  ) +
  scale_fill_manual(values = c("DEMOCRAT" = "blue", "REPUBLICAN" = "red"))

Here you can see the distribution of the ratio of Republican vs Democratic voters per state. As you can see most rural places such as Wyoming and Idaho are more dominated by the Republican party, while more populated places such as New York and California are dominated by the Democratic Party.

Question 2: How has the distribution of votes for the Democratic and Republican parties changed over time?

We first group the party votes by state postal code, party, and year. We then filter for the the Republican and Democratic parties

state_party_votes_by_year <- house %>%
  group_by(state_po, party,year) %>%
  summarise(total_votes = sum(candidatevotes), .groups = "drop") %>%
  ungroup()

state_party_votes_filtered <- state_party_votes_by_year %>%
  filter(party %in% c("DEMOCRAT", "REPUBLICAN"))

Now we want to add the total votes nationally for each party over time

total_votes_by_year_party <- state_party_votes_filtered %>%
  group_by(year, party) %>%
  summarise(total_votes = sum(total_votes),
            .groups = "drop")

Now we plot the graph that gives us a visual representation on the distribution of the votes over time.

ggplot(total_votes_by_year_party, aes(x = year, y = total_votes, color = party)) +
  geom_line() +
  geom_point() +
  labs(title = "Distribution of Votes for Democrat and Republican Parties Over Time",
       x = "Year",
       y = "Total Votes",
       color = "Party") +
  scale_y_continuous(labels = scales::comma_format()) +  # Format y-axis labels with commas
  scale_color_manual(values = c("DEMOCRAT" = "blue", "REPUBLICAN" = "red")) +  # Set color for each party
  theme(legend.position = "bottom")

As you can see as we begin from 1976 moving to around 1995 the Democratic party receives most of the votes and then it transitions to a period of Republican majority in the late 90s and mid 2000s. After that point it becomes a mix of both parties.

Question 3: What is the relationship between voter turnout and the margin of victory for the winning party in each state during the most recent election year(2022)?

We must first filter the data set and calculate the margin of victory for each state.

state_party_votes_2022 <- state_party_votes_by_year %>%
  filter(year == 2022)

# Calculate the margin of victory for each state
margin_of_victory <- state_party_votes_2022 %>%
  group_by(state_po) %>%
  summarise(margin_of_victory = max(total_votes) - min(total_votes))

Next we merge the margin of victory and the total votes for each state.

state_party_votes_margin <- left_join(state_party_votes_2022, margin_of_victory, by = "state_po")

Now we plot the relationship between the margin of victory and voter turnout.

ggplot(state_party_votes_margin, aes(x = total_votes/1000, y = margin_of_victory/1000)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Relationship between Voter Turnout and Margin of Victory (2022)",
       x = "Total Votes (Thousands)",
       y = "Margin of Victory (Thousands)") +
  scale_x_continuous(labels = scales::comma_format(scale = 1e-3)) +  # Format x-axis labels in thousands
  scale_y_continuous(labels = scales::comma_format(scale = 1e-3)) +  # Format y-axis labels in thousands
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

In this graph the data points each represent a state and where it is located represents how many voters turned out on the x-axis and the margin of victory on the y-axis. The fitted line helps us visualize the overall relationship between the two.

Conclusion

In this project we asked and answered three questions related to our data on the US House Elections using different types of graphs.