The registered crimes in Victoria have a wavering pattern between
2016 and 2025, with a notable decline around 2022 followed by a rebound
to the peak in 2025.
The trend likely reflects a post-pandemic recovery in reporting and
policing activity, and changes in social/economic factors influencing
crime.
The recent upsurge indicates renewed pressure on law enforcement and
community safety programs.
The highest incidence rates of family-violence in the regional LGAs such as Benalla, Northern Grampians and Central Goldfields and lowest in metropolitan LGAs such as Boroondara and Glen Eira. This contrasts with an imbedded socio-economic and access-to-services disparities between rural and metropolitan Victoria. Its findings emphasize the importance of prevention and support program on a geographically oriented manner.
In the four regions of police in Victoria, the rates of offences differ considerably with Western and Eastern regions having a number of the high rate LGAs than their metropolitan counterparts. North West Metro comprises of Melbourne and Brimbank areas that have dense population and are registered as having crime activity, compared to Southern Metro that is relatively stable. Such distribution implies that policing policies and resource allocations towards regions need to reflect the individual crime profile in the area.
There are three major themes in the crime landscape of Victoria, 2016-2025. First, total recorded offences fell dramatically around 2022 and recovered to record levels in 2025 indicating a post-pandemic rebound in the activity as well as reporting. Second, geographically focused prevention programs are necessary, with incidences of family-violence still being significantly disproportionately higher in regional LGAs than in metropolitan ones. And lastly, local variations in the level of recorded offences in police areas emphasize that strategies of crime prevention and resource distribution should not be functioning on global scale. The statistics indicate that the overall number of offences is increasing once again, but the spatial and socio-economic inequalities of the distribution of crime in Victoria still define the priorities in policing and the choice of policy interventions.
---
title: "Family-Violence Crime in Victoria (2018–2025)"
subtitle: "Rates, regional patterns, and context"
author: "Anuk Sasmitha Pallegangoda — S4145395"
output:
flexdashboard::flex_dashboard:
storyboard: true
theme: cosmo
self_contained: true
source_code: embed
social: menu
vertical_layout: fill
---
```{r setup-theme, include=FALSE}
# libraries needed by the theme
library(ggplot2)
# Shared minimalist ggplot theme used by Charts B & C
theme_csa <- function() {
theme_minimal(base_size = 13, base_family = "Segoe UI") +
theme(
plot.title = element_text(face = "bold", size = 14, color = "#003366", hjust = 0.5),
plot.subtitle = element_text(size = 11, color = "grey30", hjust = 0.5),
plot.caption = element_text(size = 9, color = "grey40", hjust = 0.5),
axis.title = element_text(size = 11, face = "bold", color = "#003366"),
axis.text = element_text(size = 10, color = "grey20"),
panel.grid.minor = element_blank(),
panel.grid.major.y= element_line(color = "grey85"),
panel.grid.major.x= element_line(color = "grey90"),
strip.text = element_text(face = "bold", color = "#003366"),
strip.background = element_rect(fill = "grey95", color = NA)
)
}
```
-----------------------------------------------------------------------
### Chart A — Recorded Offences (Victoria, by year)
```{r chart_a_interactive, echo=FALSE, message=FALSE, warning=FALSE}
library(tidyverse)
library(readxl)
library(janitor)
library(plotly)
# Load statewide table
p_vic_ts <- "CSA_Vic_Recorded_Offences_2025.xlsx"
vic_ts_raw <- read_xlsx(p_vic_ts, sheet = "Table 01") |> clean_names()
# Robust column detection
nm <- names(vic_ts_raw)
col_year <- nm[grepl("^year$|^year_ending$", nm, ignore.case = TRUE)][1]; if (is.na(col_year)) col_year <- "year"
col_total <- nm[grepl("total.*offence|recorded.*offence.*total|offence.*count|^total$|^count$",
nm, ignore.case = TRUE)][1]
if (is.na(col_total)) {
numc <- nm[vapply(vic_ts_raw, is.numeric, TRUE)]
cand <- numc[grepl("offence|count|total", numc, ignore.case = TRUE)][1]
col_total <- cand
}
state_ts <- vic_ts_raw |>
rename(year = all_of(col_year)) |>
mutate(
year = suppressWarnings(as.integer(year)),
count = suppressWarnings(as.numeric(.data[[col_total]]))
) |>
filter(!is.na(year), !is.na(count)) |>
group_by(year) |>
summarise(total_offences = sum(count, na.rm = TRUE), .groups = "drop")
# Interactive line + markers with range selector & slider
plot_ly(state_ts,
x = ~year, y = ~total_offences,
type = "scatter", mode = "lines+markers",
hovertemplate = "<b>%{x}</b><br>Total offences: %{y:,}<extra></extra>") |>
layout(
title = list(text = "Victoria — recorded offences (total) by year",
font = list(size = 16, color = "#003366")),
yaxis = list(title = "Total offences", tickformat = ",", color = "grey20"),
xaxis = list(
title = NULL, color = "grey20",
rangeselector = list(
buttons = list(
list(count = 3, label = "3y", step = "year", stepmode = "backward"),
list(count = 5, label = "5y", step = "year", stepmode = "backward"),
list(step = "all", label = "All")
)
),
rangeslider = list(visible = TRUE)
),
margin = list(l = 60, r = 20, t = 60, b = 40),
paper_bgcolor = "white",
plot_bgcolor = "white",
font = list(family = "Segoe UI", size = 12, color = "grey25"),
annotations = list(
list(xref = "paper", yref = "paper", x = 0, y = -0.18, showarrow = FALSE,
text = "Source: CSA Victoria — Recorded Offences (Table 01).",
font = list(size = 10, color = "grey40"))
)
)
```
### Chart A — Insights & Interpretation {.well data-height=150}
The registered crimes in Victoria have a wavering pattern between 2016 and 2025, with a
notable decline around 2022 followed by a rebound to the peak in 2025.
The trend likely reflects a post-pandemic recovery in reporting and policing activity, and
changes in social/economic factors influencing crime.
The recent upsurge indicates renewed pressure on law enforcement and community safety programs.
-----------------------------------------------------------------------
### Chart B — Top & Bottom LGAs (Family Incidents, latest year)
```{r, fig.height=7}
library(tidyverse)
library(readxl)
library(janitor)
library(plotly)
library(rlang) # for %||%
# Read the correct sheet directly
p_fam <- "CSA_LGA_Family_Incidents_2025.xlsx"
fam_raw <- read_xlsx(p_fam, sheet = "Table 01") |> clean_names()
# Detect columns
nm <- names(fam_raw)
col_year <- nm[grepl("^year$|financial_year|year_ending|^year_ending$", nm, ignore.case = TRUE)][1] %||% "year"
col_lga <- nm[grepl("lga|local.*government.*area", nm, ignore.case = TRUE)][1]
col_rate <- nm[grepl("rate.*100", nm, ignore.case = TRUE)][1]
col_cnt <- nm[grepl("family.*incident|incident.*count|offence.*count|^count$|^number$", nm, ignore.case = TRUE)][1]
stopifnot(!is.na(col_lga))
use_rate <- !is.na(col_rate)
# Tidy data
fam_tidy <- fam_raw |>
rename(year = all_of(col_year), lga = all_of(col_lga)) |>
mutate(
year = suppressWarnings(as.integer(year)),
lga = lga |> stringr::str_squish() |> stringr::str_to_sentence(),
value = if (use_rate)
suppressWarnings(as.numeric(.data[[col_rate]]))
else
suppressWarnings(as.numeric(.data[[col_cnt]]))
) |>
filter(!is.na(year), !is.na(lga), !is.na(value))
latest_year <- max(fam_tidy$year, na.rm = TRUE)
rank_tbl <- fam_tidy |>
filter(year == latest_year) |>
arrange(desc(value)) |>
mutate(rank = row_number())
# Top & Bottom 10
top_n <- 10
sel <- bind_rows(
rank_tbl |> slice(1:top_n) |> mutate(group = "Top 10"),
rank_tbl |> slice((n() - top_n + 1):n()) |> mutate(group = "Bottom 10")
) |>
mutate(
group = factor(group, levels = c("Top 10", "Bottom 10")), # lock facet order
order_key = ifelse(group == "Top 10", -value, value),
lga = forcats::fct_reorder(lga, order_key, .desc = TRUE)
)
# Plot (with shared theme + Option 3 spacing)
p_rank <- ggplot(sel, aes(
x = lga, y = value, fill = group,
text = paste0("<b>", lga, "</b><br>",
if (use_rate) "Rate per 100k: " else "Incidents: ",
scales::comma(value))
)) +
geom_col() +
coord_flip() +
facet_wrap(~ group, ncol = 1, scales = "free_y") +
scale_y_continuous(labels = scales::comma) +
guides(fill = "none") +
labs(
title = paste0("Family incidents — Top & Bottom LGAs in ", latest_year),
x = NULL,
y = if (use_rate) "Rate per 100,000 population" else "Incidents (count)",
caption = "Source: CSA Victoria — Table 01, Family Incidents by LGA (2016–2025)."
) +
theme_csa() +
theme(
axis.text.y = element_text(size = 9),
strip.text = element_text(face = "bold", size = 11),
panel.grid.major.y = element_blank(),
panel.spacing.y = grid::unit(1.2, "lines") # add vertical space between Top 10 & Bottom 10
)
# Make interactive
plotly::ggplotly(p_rank, tooltip = "text")
```
### Chart B — Insights & Interpretation {.well data-height=150}
The highest incidence rates of family-violence in the regional LGAs such as Benalla, Northern Grampians and Central Goldfields and lowest in metropolitan LGAs such as Boroondara and Glen Eira.
This contrasts with an imbedded socio-economic and access-to-services disparities between rural and metropolitan Victoria.
Its findings emphasize the importance of prevention and support program on a geographically oriented manner.
-----------------------------------------------------------------------
### Chart C — Recorded offences by LGA (rate per 100k, latest year; faceted by Police Region)
```{r, message=FALSE, warning=FALSE, fig.height=8}
library(tidyverse)
library(readxl)
library(janitor)
library(plotly)
p_lga_off <- "CSA_LGA_Recorded_Offences_2025.xlsx"
off_raw <- read_xlsx(p_lga_off, sheet = "Table 01") |> clean_names()
nm <- names(off_raw)
col_year <- nm[grepl("^year$|^year_ending$", nm, ignore.case = TRUE)][1]; if (is.na(col_year)) col_year <- "year"
col_lga <- nm[grepl("lga|local.*government.*area", nm, ignore.case = TRUE)][1]
col_region<- nm[grepl("police.*region", nm, ignore.case = TRUE)][1]
col_rate <- nm[grepl("rate.*100", nm, ignore.case = TRUE)][1]
col_cnt <- nm[grepl("recorded.*offence|offence.*count|^count$|^number$", nm, ignore.case = TRUE)][1]
# Prefer rate if available
use_rate <- !is.na(col_rate)
off_tidy <- off_raw |>
rename(year = all_of(col_year),
lga = all_of(col_lga),
region = all_of(col_region)) |>
mutate(
year = suppressWarnings(as.integer(year)),
lga = stringr::str_squish(stringr::str_to_sentence(lga)),
region = stringr::str_squish(stringr::str_to_sentence(region)),
value = if (use_rate) suppressWarnings(as.numeric(.data[[col_rate]]))
else suppressWarnings(as.numeric(.data[[col_cnt]]))
) |>
filter(!is.na(year), !is.na(lga), !is.na(region), !is.na(value))
latest_year <- max(off_tidy$year, na.rm = TRUE)
plot_dat <- off_tidy |>
filter(year == latest_year) |>
group_by(region) |>
# order LGAs within each region by value
mutate(lga_ord = forcats::fct_reorder(lga, value)) |>
ungroup()
# Lollipop by region (facets)
p_c <- ggplot(plot_dat, aes(x = lga_ord, y = value)) +
geom_segment(aes(xend = lga_ord, y = 0, yend = value), linewidth = 0.5, alpha = 0.6) +
geom_point(size = 2) +
coord_flip() +
facet_wrap(~ region, scales = "free_y") +
scale_y_continuous(labels = scales::comma) +
labs(
title = paste0("Recorded offences by LGA in ", latest_year, " — by Police Region"),
x = NULL,
y = if (use_rate) "Rate per 100,000 population" else "Recorded offences (count)",
caption = "Source: CSA Victoria — LGA Recorded Offences, Table 01"
) +
theme_csa() + # ← Apply your consistent dashboard theme
theme(
axis.text.y = element_blank(),
panel.grid.major.y = element_blank()
)
# Make interactive
plotly::ggplotly(p_c, tooltip = c("x","y"))
```
### Chart C — Insights & Interpretation {.well data-height=150}
In the four regions of police in Victoria, the rates of offences differ considerably with Western and Eastern regions having a number of the high rate LGAs than their metropolitan counterparts.
North West Metro comprises of Melbourne and Brimbank areas that have dense population and are registered as having crime activity, compared to Southern Metro that is relatively stable.
Such distribution implies that policing policies and resource allocations towards regions need to reflect the individual crime profile in the area.
### Overall Summary {.well data-height=220}
There are three major themes in the crime landscape of Victoria, 2016-2025.
First, total recorded offences fell dramatically around 2022 and recovered to record levels in 2025 indicating a post-pandemic rebound in the activity as well as reporting.
Second, geographically focused prevention programs are necessary, with incidences of family-violence still being significantly disproportionately higher in regional LGAs than in metropolitan ones.
And lastly, local variations in the level of recorded offences in police areas emphasize that strategies of crime prevention and resource distribution should not be functioning on global scale.
The statistics indicate that the overall number of offences is increasing once again, but the spatial and socio-economic inequalities of the distribution of crime in Victoria still define the priorities in policing and the choice of policy interventions.