2024-04-22
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.
https://github.com/rfordatascience/tidytuesday/tree/master/data/2023/2023-11-07
## ── 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.
## 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>
## 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
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
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.
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.
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.
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.
In this project we asked and answered three questions related to our data on the US House Elections using different types of graphs.