Introduction

Understanding Seat Margins


National Shift Overview


Seats That Flipped


State by State Breakdown


Minor Parties and Independents


Voter Turnout


What’s Changed Since 2022?


Conclusion

References

---
title: "Changing Votes, Changing Outcomes: The 2025 Election Story"
author: "Anjalika Fernando"
output:
  flexdashboard::flex_dashboard:
    orientation: columns
    storyboard: true
    social: menu
    source_code: embed
---

```{r setup, include=FALSE}
library(tidyverse)
library(flexdashboard)
library(readr)
library(ggplot2)
library(DT)
library(scales)
library(forcats)
```

```{r data-load, include=FALSE}
# Load datasets 
prefsParty <- read_csv("data/HouseFirstPrefsByPartyDownload-31496.csv", skip = 1)
changedHands <- read_csv("data/HouseSeatsWhichChangedHandsDownload-31496.csv", skip = 1)
tppDivision <- read_csv("data/HouseTppByDivisionDownload-31496.csv", skip = 1)
tppState <- read_csv("data/HouseTppByStateDownload-31496.csv", skip = 1)
tpp_2022 <- read_csv("data/HouseTppByDivisionDownload-27966.csv", skip = 1)
tpp_2025 <- read_csv("data/HouseTppByDivisionDownload-31496.csv", skip = 1)
```

### Introduction {.storyboard}

- The 2025 Australian Federal Election featured tight races for a number of key seats.

- Small variations in the percentage of votes cast had a big impact on the result.

- This report examines how changes in voter support across the country influenced the final results.


### Understanding Seat Margins 

```{r}
tppDivision <- tppDivision %>%
  mutate(Margin = abs(as.numeric(Swing))) %>%
  mutate(MarginType = ifelse(Margin < 6, "Marginal", "Safe"))

margin_summary <- tppDivision %>%
  count(MarginType)

ggplot(margin_summary, aes(x = fct_rev(MarginType), y = n, fill = MarginType)) +
  geom_col(show.legend = FALSE) +
  geom_text(aes(label = n), vjust = -0.3) +
  scale_fill_manual(values = c("Safe" = "#008ac5", "Marginal" = "#00c698")) +
  labs(title = "2025 Seat Margin Breakdown", y = "Number of Seats", x = NULL) +
  theme_minimal()
```

*** 

- Seats are classified as safe or marginal according on how close the result was.

- A marginal seat is one where the winning party received less than 6% of the vote, increasing the likelihood of a change in power.

- This graph depicts how many seats fell into each category in the 2025 election.




### National Shift Overview

```{r}
topVotes <- tppDivision %>%
  select(DivisionNm, Swing) %>%
  mutate(Swing = as.numeric(Swing)) %>%
  slice_max(order_by = abs(Swing), n = 10)

ggplot(topVotes, aes(x = reorder(DivisionNm, Swing), y = Swing, fill = Swing)) +
  geom_col() +
  scale_fill_gradient2(low = "red", mid = "white", high = "blue", midpoint = 0) +
  coord_flip() +
  labs(title = "Top 10 Divisions by Vote Shift (%)", y = "% Vote Shift", x = "Division") +
  theme_minimal()
```

*** 

- This graph depicts the divisions with the greatest shifts in voter choice.

- A larger shift indicates that voters have shifted their support significantly from the last election.

- In several of these divisions, a single digit difference was enough to determine who won the seat.

- These top ten divisions experienced the most significant shifts, demonstrating how dynamic the 2025 election truly was.


### Seats That Flipped

```{r}
datatable(changedHands %>%
            select(StateAb, DivisionNm, PreviousPartyNm, PartyNm),
          colnames = c("State", "Division", "From Party", "To Party"))
```

***

- These are the divisions that changed hands in 2025.

- The table shows which party lost and which party gained the seat.

### State by State Breakdown 

```{r}
tppState <- tppState %>% mutate(VoteShift = as.numeric(Swing))

ggplot(tppState, aes(x = StateNm, y = VoteShift, fill = VoteShift)) +
  geom_col(width = 0.6) +
  scale_fill_gradient2(low = "#e6550d", mid = "white", high = "#31a354", midpoint = 0) +
  geom_text(aes(label = round(Swing, 1)),
            vjust = ifelse(tppState$VoteShift >= 0, -0.5, 1.5),
            size = 3.5) +
  labs(title = "Vote Shift by State (%)", y = "% Vote Shift", x = NULL) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 30, hjust = 1, size = 6),
    plot.margin = margin(t = 10, r = 15, b = 35, l = 15)
  )
```

***

- The graph represents the variance in voter support for the major parties among states in 2025.


### Minor Parties and Independents 

```{r}
minor_wins <- prefsParty %>%
  filter(!PartyAb %in% c("ALP", "LP", "LNP")) %>%
  select(PartyNm, Elected) %>%
  filter(Elected > 0) %>%               
  arrange(desc(Elected)) %>%
  slice_max(order_by = Elected, n = 10) 

ggplot(minor_wins, aes(x = reorder(PartyNm, Elected), y = Elected, fill = PartyNm)) +
  geom_col(show.legend = FALSE, width = 0.6) +
  geom_text(aes(label = Elected), vjust = -0.3, size = 4) +
  labs(title = "Minor Parties Seat Gains (2025)", y = "Seats Won", x = NULL) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 30, hjust = 1),
    plot.margin = margin(t = 10, r = 10, b = 35, l = 10)
  )

```

***

- Minor parties increased their seat count in 2025.
- This chart shows how many seats each non major party won.


### Voter Turnout 

```{r}
library(scales) 

ggplot(tppState, aes(x = reorder(StateNm, TotalVotes), y = TotalVotes)) +
  geom_col(fill = "#00c698", width = 0.6) +
  geom_text(aes(label = comma(TotalVotes)), vjust = -0.5, size = 3.5) +
  labs(title = "Voter Turnout by State", y = "Total Votes", x = NULL) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 30, hjust = 1),
    plot.margin = margin(t = 10, r = 10, b = 40, l = 10)
  )

```

***

- This chart provides the total number of votes given in each state.

### What’s Changed Since 2022? 

```{r}

# Prepare and join
voteCompare <- inner_join(
  tpp_2022 %>% select(DivisionNm, Swing) %>% mutate(Vote_2022 = as.numeric(Swing)) %>% select(-Swing),
  tpp_2025 %>% select(DivisionNm, Swing) %>% mutate(Vote_2025 = as.numeric(Swing)) %>% select(-Swing),
  by = "DivisionNm"
) %>%
  filter(abs(Vote_2025) < 20) %>%
  mutate(SwingDiff = abs(Vote_2025 - Vote_2022)) %>%
  slice_max(SwingDiff, n = 6) %>%
  pivot_longer(c(Vote_2022, Vote_2025), names_to = "Year", values_to = "Swing")

# Plot
ggplot(voteCompare, aes(x = DivisionNm, y = Swing, fill = Year)) +
  geom_col(position = "dodge") +
  labs(title = "Swing Comparison: 2022 vs 2025", y = "% Swing", x = NULL) +
  scale_fill_manual(values = c("Vote_2022" = "#bdbdbd", "Vote_2025" = "#756bb1")) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 30, hjust = 1))
```

*** 

- This graph compares vote shifts in critical divisions from the 2022 and 2025 elections. 

### Conclusion 

- Marginal seats had a significant influence on the 2025 result.

- Small swings changed the political landscape.

- Data visualisation helps make sense of how electoral power shifts.



### References {.storyboard}

- Australian Electoral Commission. (2025). *Tally Room 2025 Federal Election Results*. https://tallyroom.aec.gov.au

- Australian Electoral Commission. (2022). *Two-party-preferred results by division – House of Representatives*. https://results.aec.gov.au/27966/website/HouseTppByDivision-27966-NAT.htm

- RStudio. (n.d.). *Using flexdashboard Appearance Options*. Retrieved from https://rstudio.github.io/flexdashboard/articles/using.html#appearance