# ---- 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 additive across offence groups 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

Before comparing neighbourhoods, we need to see whether the statewide total is moving steadily or changing direction. Explore the line across the full period: the timing of peaks and dips matters more than any one year’s headline figure.

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

A statewide increase or decrease can conceal offsetting changes across offence types. The bars below identify the subdivisions contributing the largest numerical movements between the two most recent reporting years. They show changes in recorded counts, not necessarily changes in underlying offending or victimisation.

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?

A statewide increase or decrease can conceal offsetting changes across offence types. The bars identify the subdivisions with the largest numerical movements between the two most recent reporting years; these are changes in recorded counts, not proof of changes in underlying victimisation.

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)

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 = ~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)),
        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

Two areas can appear near each other in a rate ranking while recording very different mixes of offences. This heatmap compares the percentage composition of recorded offences within each area; use the year control to see whether those patterns persist or shift. A larger share does not necessarily mean a higher offence rate.

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. [VERIFY AND EDIT: state that you spot-checked several counts against the official CSA workbook.] 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) to review and revise the R code and to fix data-loading and accessibility issues. I used [ChatGPT / other too, if applicable] for [purpose]. I did not use generative AI to choose the story, which is my own. All text, analysis and interpretation were checked and edited by me.”

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 (Sonnet 5.5 version) [Large language model]. https://claude.ai