Overview

Row

Total Votes by Party

Average Swing by Party

Row

Number of Candidates by Party

Elected vs Not Elected Candidates

Divisions

Row

Total Votes by Division

Top 5 Divisions by Votes

Row

Party Vote Share in Top 5 Divisions (Stacked)

Insights

Row

Top 10 Candidates by Ordinary Votes

Row

Distribution of Votes by Party (Boxplot)

Row

Reference

Australian Electoral Commission. (2025). First preferences by candidate by polling place – VIC [Data set]. https://tallyroom.aec.gov.au/HouseDefault-31496.htm

---
title: "Election 2025 (VIC): A Dashboard of Preferences and Swings"
output:
  flexdashboard::flex_dashboard:
    orientation: rows
    vertical_layout: fill
    theme: cosmo
    source_code : embed
  html_document:
    df_print: paged
---

```{r, include=FALSE}

library(readr)
library(dplyr)
library(ggplot2)
library(janitor)
library(plotly)
library(flexdashboard)

# Load and clean data
vic_data <- read_csv("ElectionVIC.csv") |>
  clean_names()

```

Overview 
=======================================================================

Row 
-----------------------------------------------------------------------
### Total Votes by Party



```{r, include=FALSE}


total_party_votes <- vic_data %>%
  group_by(party_ab) %>%
  summarise(total_votes = sum(ordinary_votes, na.rm = TRUE), .groups = "drop")

```


```{r}
# Plot interactively
p1 <- ggplot(total_party_votes, aes(x = reorder(party_ab, -total_votes), y = total_votes)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(title = "Total Votes by Party", x = "Party", y = "Votes") +
  theme_minimal(base_size = 10)

ggplotly(p1)



```

### Average Swing by Party

```{r}

avg_swing <- vic_data %>%
  group_by(party_ab) %>%
  summarise(avg_swing = mean(swing, na.rm = TRUE))

ggplot(avg_swing, aes(x = reorder(party_ab, -avg_swing), y = avg_swing)) +
  geom_bar(stat = "identity", fill = "darkred") +
  labs(title = "Average Swing by Party", x = "Party", y = "Swing (%)") +
  theme_minimal(base_size = 10)
```

Row 
-----------------------------------------------------------------------

### Number of Candidates by Party



```{r}

candidate_counts <- vic_data |>
  group_by(party_ab) |>
  summarise(candidate_count = n_distinct(candidate_id))

ggplotly(
  ggplot(candidate_counts, aes(x = reorder(party_ab, -candidate_count), y = candidate_count)) +
    geom_bar(stat = "identity", fill = "orange") +
    labs(title = "Number of Candidates per Party", x = "Party", y = "Count") +
    theme_minimal(base_size = 10)
)

```


### Elected vs Not Elected Candidates

```{r}

elected_summary <- vic_data |>
  count(elected) |>
  mutate(label = ifelse(elected == "Y", "Elected", "Not Elected"))

plot_ly(
  elected_summary,
  labels = ~label,
  values = ~n,
  type = 'pie',
  hole = 1.5,
  textinfo = "label+percent"
) %>%
  layout(
    title = list(text = "Elected vs Not Elected", x = 0.5),
    showlegend = TRUE,
    margin = list(l = 10, r = 10, b = 10, t = 30, pad = 4)
  )
```



Divisions
================================================================================

Row 
-----------------------------------------------------------------------

### Total Votes by Division

```{r}

total_votes_division <- vic_data |>
  group_by(division_nm) |>
  summarise(total_votes = sum(ordinary_votes, na.rm = TRUE)) |>
  arrange(desc(total_votes))

ggplotly(
  ggplot(total_votes_division, aes(x = reorder(division_nm, total_votes), y = total_votes)) +
    geom_bar(stat = "identity", fill = "lightblue") +
    labs(title = "Total Votes by Division", x = "Division", y = "Votes") +
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1, size = 9),  # Rotate x labels
      plot.title = element_text(size = 14, face = "bold"),
      axis.title = element_text(size = 12)
    )
)


```


### Top 5 Divisions by Votes

```{r}

top5_divisions <- head(total_votes_division, 5)$division_nm

votes_top5 <- vic_data |>
  filter(division_nm %in% top5_divisions) |>
  group_by(division_nm, party_ab) |>
  summarise(votes = sum(ordinary_votes, na.rm = TRUE), .groups = "drop")

ggplotly(
  ggplot(votes_top5, aes(x = division_nm, y = votes, fill = party_ab)) +
    geom_bar(stat = "identity", position = "dodge") +
    labs(title = "Votes by Party in Top 5 Divisions", x = "Division", y = "Votes") +
    theme_minimal()
)

```


Row 
-----------------------------------------------------------------------

### Party Vote Share in Top 5 Divisions (Stacked)

```{r}

ggplotly(
  ggplot(votes_top5, aes(x = division_nm, y = votes, fill = party_ab)) +
    geom_bar(stat = "identity") +
    labs(title = "Stacked Party Vote Share - Top 5 Divisions") +
    theme_minimal()
)


```

Insights
================================================================================

Row 
-----------------------------------------------------------------------

### Top 10 Candidates by Ordinary Votes

```{r}

top_candidates <- vic_data |>
  mutate(full_name = paste(given_nm, surname, "(", party_ab, ")")) |>
  group_by(full_name) |>
  summarise(votes = sum(ordinary_votes, na.rm = TRUE)) |>
  arrange(desc(votes)) |>
  head(10)

ggplotly(
  ggplot(top_candidates, aes(x = votes, y = reorder(full_name, votes))) +
    geom_bar(stat = "identity", fill = "purple") +
    labs(title = "Top 10 Candidates by Votes", x = "Votes", y = "Candidate") +
    theme_minimal()
)

```

Row 
-----------------------------------------------------------------------
### Distribution of Votes by Party (Boxplot)

```{r}

ggplotly(
  ggplot(vic_data, aes(x = party_ab, y = ordinary_votes)) +
    geom_boxplot(fill = "lightgreen", outlier.colour = "red") +
    labs(title = "Distribution of Ordinary Votes by Party", x = "Party", y = "Votes") +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
)


```

Row 
-----------------------------------------------------------------------

### Reference 

Australian Electoral Commission. (2025). First preferences by candidate by polling place – VIC [Data set]. https://tallyroom.aec.gov.au/HouseDefault-31496.htm