# ---- Load data from a local file downloaded to this device -------------------
# File: vic-recorded-offences-by-lga_2026-09-24.csv
# A reformatted copy of the Crime Statistics Agency (CSA) Victoria "Recorded offences by LGA"
# release of 24 September 2026. Keep the CSV in the same folder as this .Rmd file.
csv_file <- "vic-recorded-offences-by-lga_2026-09-24.csv"
if (!file.exists(csv_file)) stop("Put '", csv_file, "' in the same folder as this .Rmd file.")

raw <- read.csv(csv_file, stringsAsFactors = FALSE, check.names = FALSE)

required <- c("year", "year_ending", "lga", "offence_division", "offence_subdivision",
              "offence_subgroup", "offence_count", "lga_rate_per_100000")
stopifnot(all(required %in% names(raw)))

# ---- Preprocessing (documented) ---------------------------------------------
# 1. Keep rows with valid counts and rates; year = twelve months ending June.
# 2. Clean the offence labels by dropping the leading code (e.g. "A Crimes against the person"
#    becomes "Crimes against the person").
# 3. Rates per 100,000 residents are summed across distinct offence subgroups within an LGA, so group
#    rates are summed (they are never averaged).
dat <- raw %>%
  filter(year_ending == "June", !is.na(offence_count), !is.na(lga_rate_per_100000)) %>%
  mutate(year = as.integer(year),
         offence_count = as.numeric(offence_count),
         rate = as.numeric(lga_rate_per_100000),
         division = sub("^[A-Z] ", "", offence_division),
         subdivision = sub("^[A-Z][0-9]+ ", "", offence_subdivision))

latest <- max(dat$year)
first_year <- min(dat$year)

lga_year <- dat %>%
  group_by(lga, year) %>%
  summarise(offences = sum(offence_count), rate = sum(rate), .groups = "drop")

fmt <- function(x) scales::comma(round(x))
signed <- function(x) paste0(ifelse(x > 0, "+", ifelse(x < 0, "−", "")), scales::comma(abs(x)))
# Okabe-Ito colour-blind-safe palette
okabe <- c("#0072B2", "#D55E00", "#009E73", "#CC79A7", "#E69F00", "#56B4E9", "#000000", "#F0E442")

A ranking of places by crime can look like a straightforward answer to a difficult question: where is crime most common? But a single ranking hides the different ways offences change over time, the types of offences being counted, and the population used to calculate rates. This report starts with the statewide picture, then examines which categories are changing and why two local government areas with high recorded offence rates may have very different offence profiles. The aim is not to label communities as safe or unsafe, but to read the numbers more carefully.

Reading note: Recorded offences are police-recorded events, not a direct measure of victimisation or personal safety. Each year is the twelve months ending in June. Rates are offences per 100,000 residents.

1. Start with the trend, not the headline

Victoria recorded 634,156 offences in the year ending June 2025, followed by 620,155 in 2026: a fall of 14,001 (2.2%). The 2026 decline followed the highest recorded total in this ten-year dataset. Explore the line to see how the latest movement fits the longer trend.

annual <- lga_year %>% group_by(year) %>% summarise(offences = sum(offences), .groups = "drop")
plot_ly(annual, x = ~year, y = ~offences, type = "scatter", mode = "lines+markers",
        line = list(color = okabe[1], width = 3), marker = list(color = okabe[1], size = 8),
        hovertemplate = "Year ending June %{x}<br>Recorded offences: %{y:,.0f}<extra></extra>") %>%
  layout(title = list(text = "Recorded offences across Victoria"),
         xaxis = list(title = "Year ending June", dtick = 1, rangeslider = list(visible = TRUE)),
         yaxis = list(title = "Recorded offences", rangemode = "tozero", tickformat = ","),
         font = list(family = "Arial", size = 14))

Text description: the line shows statewide recorded offences each year from 2017 to 2026. The highest year is 2025 (634,156) and the lowest is 2022 (467,083). Drag the slider to zoom into a period. Source: Crime Statistics Agency (2026).

2. One total, several different trajectories

The statewide total masks different trajectories. These indexed lines compare how each offence division has changed relative to 2017 (set to 100). Click a legend item to focus on one division; the index compares relative change, not the number of offences.

by_div <- dat %>% group_by(year, division) %>%
  summarise(offences = sum(offence_count), .groups = "drop") %>%
  group_by(division) %>%
  mutate(index = 100 * offences / offences[year == first_year][1]) %>% ungroup()

plot_ly(by_div, x = ~year, y = ~index, color = ~division, colors = okabe,
        linetype = ~division, type = "scatter", mode = "lines+markers",
        text = ~paste0("Division: ", division, "<br>Year ending June ", year,
                       "<br>Offences: ", fmt(offences), "<br>Index (", first_year, " = 100): ", round(index, 1)),
        hoverinfo = "text") %>%
  layout(title = list(text = paste0("Offence divisions, indexed (", first_year, " = 100)")),
         xaxis = list(title = "Year ending June", dtick = 1),
         yaxis = list(title = paste0("Index (", first_year, " = 100)")),
         legend = list(title = list(text = "Offence division (click to toggle)")),
         font = list(family = "Arial", size = 14))

Text description: each line shows how one offence division moved relative to 2017, so divisions of very different size can be compared. Colour and line style both distinguish divisions. Source: Crime Statistics Agency (2026).

3. A high rate is not the whole story

Which Victorian local government areas have the highest recorded offence rates? The answer changes depending on the offence division selected.

Use the interactive dropdown to compare the top 15 LGAs. Rates are measured per 100,000 residents and do not represent an individual person’s probability of becoming a victim.

Text description: horizontal bars rank the 15 local government areas with the highest recorded offence rate for the selected offence division. Rates use resident population, so they can overstate risk to residents in areas with many visitors or workers and can swing widely in LGAs with small populations. Source: Crime Statistics Agency (2026).

4. What actually drove the latest change?

The 2026 statewide decline was not uniform: theft fell by 11,498 recorded offences and burglary/break and enter fell by 9,973, while deception rose by 4,076 and breaches of orders rose by 3,276. Explore the bars to compare the largest numerical changes; they do not establish why offences changed.

old <- latest - 1
chg <- dat %>% filter(year %in% c(old, latest)) %>%
  group_by(year, subdivision) %>% summarise(n = sum(offence_count), .groups = "drop") %>%
  tidyr::pivot_wider(names_from = year, values_from = n, values_fill = 0) %>%
  mutate(delta = .data[[as.character(latest)]] - .data[[as.character(old)]],
         dir = ifelse(delta > 0, "Increase", "Decrease")) %>%
  arrange(desc(abs(delta))) %>% slice_head(n = 15) %>%
  mutate(hover_label = paste0(subdivision, "<br>Year ending June ", old, ": ",
                              fmt(.data[[as.character(old)]]), "<br>Year ending June ",
                              latest, ": ", fmt(.data[[as.character(latest)]]),
                              "<br>Change: ", signed(delta)))

plot_ly(chg, x = ~delta, y = ~reorder(subdivision, delta), type = "bar", orientation = "h",
        color = ~dir, colors = c(Increase = okabe[2], Decrease = okabe[1]),
        text = ~signed(delta), textposition = "outside",
        hovertext = ~hover_label,
        hoverinfo = "text") %>%
  layout(title = paste0("Largest changes in recorded offences, ", old, " to ", latest),
         xaxis = list(title = "Change in recorded offences", tickformat = ","),
         yaxis = list(title = ""), margin = list(l = 260),
         legend = list(title = list(text = "Direction")),
         font = list(family = "Arial", size = 14))

Text description: bars show the 15 offence subdivisions with the largest absolute change between years ending June 2025 and 2026. Orange bars are increases, blue bars are decreases, and every bar is also labelled with a plus or minus sign. These are changes in counts, not percentage changes. Source: Crime Statistics Agency (2026).

5. Similar rankings, different offence mixes

Melbourne had the highest recorded offence rate in 2026 (approximately 22,860 offences per 100,000 residents), followed by Latrobe (17,928). But a rate ranking hides the composition of recorded offences. Compare the offence-division shares within the top 15 LGAs, and use the year control to explore changes. Shares are not rates.

top_lga <- lga_year %>% filter(year == latest) %>% arrange(desc(rate)) %>% slice_head(n = 15) %>% pull(lga)
heat <- dat %>% filter(lga %in% top_lga, year >= latest - 4) %>%
  group_by(year, lga, division) %>% summarise(n = sum(offence_count), .groups = "drop") %>%
  group_by(year, lga) %>% mutate(share = 100 * n / sum(n)) %>% ungroup() %>%
  mutate(lga = factor(lga, levels = rev(top_lga)),
         label = paste0(lga, "<br>", division, "<br>Offences: ", fmt(n),
                        "<br>Share of LGA: ", round(share, 1), "%"))

plot_ly(heat, x = ~division, y = ~lga, z = ~share, frame = ~year, text = ~label,
        type = "heatmap", colorscale = "Viridis", hoverinfo = "text",
        colorbar = list(title = "Share of LGA's offences (%)")) %>%
  layout(title = "Offence mix in the 15 LGAs with the highest offence rates",
         xaxis = list(title = "Offence division", tickangle = -35),
         yaxis = list(title = ""), margin = list(l = 140, b = 160),
         font = list(family = "Arial", size = 13)) %>%
  animation_slider(currentvalue = list(prefix = "Year ending June "))

Text description: each cell shows what percentage of an LGA’s recorded offences fall in an offence division; darker purple is a lower share and yellow is a higher share. The LGAs shown are those with the highest offence rate in year ending June 2026. Use the slider or play button to move between years. This shows composition, not crime rates. Source: Crime Statistics Agency (2026).

Conclusion: read beyond the league table

The central lesson is that no single crime statistic tells the whole story. Statewide totals show the scale of recorded offences, indexed trends reveal differences between offence categories, and local rates provide a population-adjusted comparison. Yet even among areas with high rates, the composition of recorded offences can vary considerably. These views should be considered together rather than treated as competing answers to which area is ‘worst’.

These data reflect offences recorded by police. They can be influenced by reporting behaviour, policing practices, population movements and classification rules. Resident-based rates are especially difficult to interpret for places with large daytime or visitor populations. The charts cannot establish why a trend changed, how much crime went unreported, or the risk faced by any particular person. A more responsible public conversation begins by asking what each measure includes—and what it leaves out.

Data and methods

Data: Crime Statistics Agency (CSA) Victoria, Recorded offences by LGA, released 24 September 2026 and licensed CC BY 4.0. The analysis file (vic-recorded-offences-by-lga_2026-09-24.csv) is a reformatted copy of the CSA release with offence counts, LGA names and rates per 100,000 population; it was loaded from a local file downloaded to my device. The supplied CSV was checked for missing values and duplicate year–police service area–LGA–offence subgroup records. Its 2025 and 2026 statewide totals were calculated from the CSV; an independent reconciliation against the official CSA workbook has not yet been completed. Annual periods are the twelve months ending in June. Offence counts are summed by year, local government area and offence classification, and rates per 100,000 residents (supplied in the file) are summed within LGA and offence group. No simulated data are used. All visualisations were created in R using plotly. Interactivity includes hover details, legend toggling, a range slider, a dropdown and a year slider.

Acknowledgements

I used Claude (Anthropic) and ChatGPT (OpenAI) to assist with reviewing and revising R code, troubleshooting interactive Plotly charts, improving explanatory text, and checking the dataset and calculations. I reviewed the outputs and remain responsible for the final analysis and submission. This acknowledgement should be edited if any detail does not accurately describe my use of these tools.

References

Crime Statistics Agency. (2026). Data tables: Recorded offences [Data set]. Victorian Government. https://discover.data.vic.gov.au/dataset/data-tables-recorded-offences

Anthropic. (2026). Claude [Large language model]. https://claude.ai/

OpenAI. (2026). ChatGPT [Large language model]. https://chatgpt.com/

Anthropic. (2026). Claude (Sonnet 5.5 version) [Large language model]. https://claude.ai