# 1. Load the data straight from the source
# Crime Statistics Agency (CSA) - Recorded offences by LGA, year ending June 2026.
data_url   <- "https://files.crimestatistics.vic.gov.au/2026-09/Data_Tables_LGA_Recorded_Offences_Year_Ending_June_2026.xlsx"
local_file <- "Data_Tables_LGA_Recorded_Offences_Year_Ending_June_2026.xlsx"

if (!file.exists(local_file)) {
  options(timeout = 600)
  download.file(data_url, local_file, mode = "wb")
}

# Find the sheets by name
sheets   <- excel_sheets(local_file)
sheet_01 <- sheets[str_detect(sheets, "01")][1]
sheet_02 <- sheets[str_detect(sheets, "02")][1]

# Helper: make column names simple (lower case, underscores)
clean_names_simple <- function(df) {
  names(df) <- names(df) %>% tolower() %>%
    str_replace_all("[^a-z0-9]+", "_") %>% str_remove_all("^_|_$")
  df
}

t1 <- read_excel(local_file, sheet = sheet_01) %>% clean_names_simple()
t2 <- read_excel(local_file, sheet = sheet_02) %>% clean_names_simple()

# 2. Give the columns short, standard names 
rename_by_pattern <- function(df, pattern, new_name, exclude = NULL) {
  hit <- str_detect(names(df), pattern)
  if (!is.null(exclude)) hit <- hit & !str_detect(names(df), exclude)
  names(df)[which(hit)[1]] <- new_name
  df
}

t1 <- t1 %>%
  rename_by_pattern("^year$", "year") %>%
  rename_by_pattern("government_area", "lga") %>%
  rename_by_pattern("region", "region") %>%
  rename_by_pattern("offence_count", "offences") %>%
  rename_by_pattern("rate", "rate") %>%
  mutate(year = as.integer(year),
         offences = suppressWarnings(as.numeric(offences)),
         rate = suppressWarnings(as.numeric(rate)))

t2 <- t2 %>%
  rename_by_pattern("^year$", "year") %>%
  rename_by_pattern("government_area", "lga") %>%
  rename_by_pattern("division", "division", exclude = "sub") %>%
  rename_by_pattern("subdivision", "subdivision") %>%
  rename_by_pattern("subgroup", "subgroup") %>%
  rename_by_pattern("offence_count", "offences") %>%
  mutate(year = as.integer(year),
         offences = suppressWarnings(as.numeric(offences)),
         justice = str_detect(division, "^E"))   # E = Justice procedures offences

# Helper: remove the CSA code at the start of a name, e.g. "E23 Breach bail conditions"
strip_code <- function(x) str_squish(str_remove(x, "^([A-Z][0-9]+|[0-9]+)\\s+"))

# 3. Population for each LGA (worked out from the CSA rate)
# rate = offences / population * 100,000  ->  population = offences / rate * 100,000
lga_pop <- t1 %>%
  filter(!is.na(rate), rate > 0, !is.na(region)) %>%
  mutate(pop = offences / rate * 1e5) %>%
  select(year, lga, region, pop)

first_yr <- min(t2$year)
last_yr  <- max(t2$year)

# 4. Statewide totals
state <- t2 %>%
  group_by(year) %>%
  summarise(all     = sum(offences, na.rm = TRUE),
            justice = sum(offences[justice], na.rm = TRUE),
            .groups = "drop") %>%
  mutate(other = all - justice,
         justice_share = justice / all * 100)

get <- function(col, yr) state[[col]][state$year == yr]

# Key numbers used in the text
total_first <- get("all", first_yr);      total_last <- get("all", last_yr)
just_first  <- get("justice", first_yr);  just_last  <- get("justice", last_yr)
other_first <- get("other", first_yr);    other_last <- get("other", last_yr)
total_chg <- (total_last / total_first - 1) * 100
just_chg  <- (just_last / just_first - 1) * 100
other_chg <- (other_last / other_first - 1) * 100
share_first <- get("justice_share", first_yr)
share_last  <- get("justice_share", last_yr)

# Text helpers
n_fmt  <- function(x) comma(round(x))
p_fmt  <- function(x) paste0(ifelse(x > 0, "+", ""), round(x, 1), "%")
up_dn  <- function(x) ifelse(x >= 0, "went up", "went down")

# Common chart settings
base_font <- list(family = "Arial", size = 14, color = "#222222")
cfg <- function(p) config(p, displaylogo = FALSE,
                          modeBarButtonsToRemove = c("lasso2d", "select2d"))

Why I looked at this

Every few months, we hear news about crime increasing in Victoria. According to Victoria Police (2026), breaches of orders were the biggest contributor to recorded crime in the year ending June 2026. This made me curious about which types of crime were actually increasing. After looking at the official data, I found that the biggest contributor was not burglary, assault, or car theft. Instead, it was offences related to breaking the rules of the justice system, such as breaching intervention orders, breaking bail conditions, and failing to appear in court. This report looks more closely at these offences and what the data tells us about crime in Victoria.

How to use this report: All charts are interactive. Hover over or tap the chart to see the exact numbers. Click on the legend to show or hide data, and use the buttons above each chart to change the view.


1. The headline: “crime is up”

In the year ending June 2017, Victoria Police recorded 529,764 offences. By the year ending June 2026, this number was 620,155, a change of +17.1%. That is the number most headlines use.

But look what happens when we split it in two. Offences against the justice system (breaches of orders, bail breaches, failing to appear and similar) went up by +45%. Everything else changed by just +12.9%. Press “% change since 2017” to see the gap clearly.

# ---- Chart 1: total crime vs justice procedures vs everything else ------------
c1 <- state %>%
  select(year, all, other, justice) %>%
  pivot_longer(-year, names_to = "series", values_to = "offences") %>%
  group_by(series) %>%
  mutate(change = (offences / offences[year == first_yr] - 1) * 100) %>%
  ungroup() %>%
  mutate(series = recode(series,
    all     = "All recorded offences",
    other   = "Everything except justice offences",
    justice = "Justice procedures offences"))

c1_cols   <- c("All recorded offences" = "#555555",
               "Everything except justice offences" = "#0072B2",
               "Justice procedures offences" = "#D55E00")
c1_dash   <- c("dot", "solid", "solid")   # line style, so colour is not the only cue
c1_symbol <- c("circle", "square", "diamond")

p1 <- plot_ly()
for (i in seq_along(c1_cols)) {          # traces 1-3: number of offences
  s <- names(c1_cols)[i]
  p1 <- p1 %>% add_trace(
    data = filter(c1, series == s), x = ~year, y = ~offences,
    type = "scatter", mode = "lines+markers", name = s, visible = TRUE,
    line = list(color = c1_cols[[s]], width = 3, dash = c1_dash[i]),
    marker = list(color = c1_cols[[s]], size = 8, symbol = c1_symbol[i]),
    hovertemplate = paste0("<b>", s, "</b><br>Year ending June %{x}<br>",
                           "%{y:,.0f} offences<extra></extra>"))
}
for (i in seq_along(c1_cols)) {          # traces 4-6: % change since first year
  s <- names(c1_cols)[i]
  p1 <- p1 %>% add_trace(
    data = filter(c1, series == s), x = ~year, y = ~change,
    type = "scatter", mode = "lines+markers", name = s, visible = FALSE,
    line = list(color = c1_cols[[s]], width = 3, dash = c1_dash[i]),
    marker = list(color = c1_cols[[s]], size = 8, symbol = c1_symbol[i]),
    hovertemplate = paste0("<b>", s, "</b><br>Year ending June %{x}<br>",
                           "%{y:+.1f}% compared with ", first_yr, "<extra></extra>"))
}

p1 %>%
  layout(
    title = list(text = paste0("<b>", ifelse(just_chg > other_chg,
                                 "Justice offences grew much faster than other crime",
                                 "Justice offences vs other crime"), "</b><br>",
                               "<sup>Recorded offences in Victoria, years ending June ",
                               first_yr, "-", last_yr, "</sup>"),
                 x = 0, xanchor = "left"),
    font = base_font,
    xaxis = list(title = "Year ending June", dtick = 1),
    yaxis = list(title = "Recorded offences", tickformat = ",", rangemode = "tozero"),
    legend = list(orientation = "h", y = -0.2, yanchor = "top"),
    margin = list(t = 110, b = 90),
    hovermode = "closest",
    updatemenus = list(list(
      type = "buttons", direction = "right", x = 1, xanchor = "right", y = 1.17,
      buttons = list(
        list(label = "Number of offences", method = "update",
             args = list(list(visible = c(TRUE, TRUE, TRUE, FALSE, FALSE, FALSE)),
                         list(yaxis = list(title = "Recorded offences", tickformat = ",",
                                           rangemode = "tozero")))),
        list(label = paste0("% change since ", first_yr), method = "update",
             args = list(list(visible = c(FALSE, FALSE, FALSE, TRUE, TRUE, TRUE)),
                         list(yaxis = list(title = paste0("% change since ", first_yr),
                                           ticksuffix = "%", zeroline = TRUE,
                                           rangemode = "normal")))))))) %>% cfg()

Source: Crime Statistics Agency (2026), Recorded offences by LGA, year ending June 2026.

Chart summary: justice procedures offences made up 13.1% of all recorded offences in 2017 and 16.2% in 2026.


2. Inside the “justice” offences

What do these offences actually mean? The chart below shows the main types. Most involve breaking rules set by the court or police, such as intervention orders or bail conditions. Each time someone breaks these rules, it is recorded as a new offence, even if no one is physically hurt.

Use “Stacked” to see the total build up, or “Separate lines” to compare each type on its own.

# ---- Chart 2: types of justice procedures offences over time -------------------
c2_raw <- t2 %>%
  filter(justice) %>%
  mutate(type = strip_code(subgroup)) %>%
  group_by(year, type) %>%
  summarise(offences = sum(offences, na.rm = TRUE), .groups = "drop")

# Keep the five biggest types (in the latest year); group the rest as "Other"
top_types <- c2_raw %>% filter(year == last_yr) %>%
  slice_max(offences, n = 5, with_ties = FALSE) %>% pull(type)

c2 <- c2_raw %>%
  mutate(type = if_else(type %in% top_types, type, "Other justice offences")) %>%
  group_by(year, type) %>%
  summarise(offences = sum(offences), .groups = "drop") %>%
  mutate(type = factor(type, levels = c(top_types, "Other justice offences")))

c2_cols <- c("#D55E00", "#E69F00", "#CC79A7", "#0072B2", "#009E73", "#999999")
types   <- levels(c2$type)
k       <- length(types)

biggest_type     <- top_types[1]
biggest_type_n   <- c2_raw %>% filter(year == last_yr, type == biggest_type) %>% pull(offences)
biggest_type_old <- c2_raw %>% filter(year == first_yr, type == biggest_type) %>% pull(offences)
if (length(biggest_type_old) == 0) biggest_type_old <- NA

p2 <- plot_ly()
for (i in seq_along(types)) {   # stacked areas
  p2 <- p2 %>% add_trace(
    data = filter(c2, type == types[i]), x = ~year, y = ~offences,
    type = "scatter", mode = "lines", stackgroup = "one", name = types[i],
    legendgroup = types[i], visible = TRUE,
    line = list(color = c2_cols[i], width = 1), fillcolor = c2_cols[i],
    hovertemplate = paste0("<b>", types[i], "</b><br>Year ending June %{x}<br>",
                           "%{y:,.0f} offences<extra></extra>"))
}
for (i in seq_along(types)) {   # separate lines
  p2 <- p2 %>% add_trace(
    data = filter(c2, type == types[i]), x = ~year, y = ~offences,
    type = "scatter", mode = "lines+markers", name = types[i],
    legendgroup = types[i], visible = FALSE,
    line = list(color = c2_cols[i], width = 3), marker = list(color = c2_cols[i], size = 7),
    hovertemplate = paste0("<b>", types[i], "</b><br>Year ending June %{x}<br>",
                           "%{y:,.0f} offences<extra></extra>"))
}

p2 %>%
  layout(
    title = list(text = paste0("<b>Most justice offences are breaches of rules set by courts and police</b><br>",
                               "<sup>Justice procedures offences by type, Victoria</sup>"),
                 x = 0, xanchor = "left"),
    font = base_font,
    xaxis = list(title = "Year ending June", dtick = 1),
    yaxis = list(title = "Recorded offences", tickformat = ","),
    legend = list(orientation = "h", y = -0.2, yanchor = "top"),
    margin = list(t = 110, b = 110),
    hovermode = "x unified",
    updatemenus = list(list(
      type = "buttons", direction = "right", x = 1, xanchor = "right", y = 1.17,
      buttons = list(
        list(label = "Stacked", method = "restyle",
             args = list("visible", as.list(c(rep(TRUE, k), rep(FALSE, k))))),
        list(label = "Separate lines", method = "restyle",
             args = list("visible", as.list(c(rep(FALSE, k), rep(TRUE, k))))))))) %>% cfg()

Source: Crime Statistics Agency (2026), Recorded offences by LGA, year ending June 2026.

Chart summary: the biggest single type in 2026 was “Breach family violence order”, with 60,938 offences (it was 38,500 in 2017).


3. Which crimes really added to the rise?

Percentages can sometimes be misleading. For example, if a crime increases from 10 to 20 cases, it has doubled, but the actual increase is only 10 cases. This chart shows the number of extra offences for each crime type between 2017 and 2026. Orange bars represent justice-related offences, while blue bars show all other types of crime.

Press “% change” to see the same crimes as percentages instead. Only crime types with at least 1,000 offences in 2017 are shown, so small numbers do not give a false picture.

# ---- Chart 3: change in offences by crime type (subdivision) -------------------
c3 <- t2 %>%
  mutate(sub = strip_code(subdivision)) %>%
  group_by(year, sub, justice) %>%
  summarise(offences = sum(offences, na.rm = TRUE), .groups = "drop") %>%
  filter(year %in% c(first_yr, last_yr)) %>%
  pivot_wider(names_from = year, values_from = offences, names_prefix = "y") %>%
  rename(before = paste0("y", first_yr), after = paste0("y", last_yr)) %>%
  filter(!is.na(before), !is.na(after), before >= 1000) %>%
  mutate(abs_change = after - before,
         pct_change = (after / before - 1) * 100,
         group = if_else(justice, "Justice procedures offences", "All other crime types")) %>%
  arrange(abs_change)

top_grower <- c3 %>% slice_max(abs_change, n = 1)
order_c3 <- c3$sub   # fixed order (smallest to largest extra offences)
c3_cols  <- c("Justice procedures offences" = "#D55E00", "All other crime types" = "#0072B2")
grp      <- names(c3_cols)

hover_c3 <- paste0("<b>%{y}</b><br>", first_yr, ": %{customdata[0]:,.0f}<br>",
                   last_yr, ": %{customdata[1]:,.0f}<br>",
                   "Change: %{customdata[2]:+,.0f} (%{customdata[3]:+.1f}%)<extra></extra>")

p3 <- plot_ly()
for (g in grp) {   # traces 1-2: number of extra offences
  d <- filter(c3, group == g)
  p3 <- p3 %>% add_trace(
    data = d, x = ~abs_change, y = ~sub, type = "bar", orientation = "h",
    name = g, legendgroup = g, visible = TRUE, marker = list(color = c3_cols[[g]]),
    customdata = as.matrix(d[, c("before", "after", "abs_change", "pct_change")]),
    hovertemplate = hover_c3)
}
for (g in grp) {   # traces 3-4: % change
  d <- filter(c3, group == g)
  p3 <- p3 %>% add_trace(
    data = d, x = ~pct_change, y = ~sub, type = "bar", orientation = "h",
    name = g, legendgroup = g, visible = FALSE, marker = list(color = c3_cols[[g]]),
    customdata = as.matrix(d[, c("before", "after", "abs_change", "pct_change")]),
    hovertemplate = hover_c3)
}

p3 %>%
  layout(
    title = list(text = paste0("<b>\"", top_grower$sub, "\" added the most offences of any crime type</b><br>",
                               "<sup>Change in recorded offences by crime type, ",
                               first_yr, " to ", last_yr, "</sup>"),
                 x = 0, xanchor = "left"),
    font = base_font,
    barmode = "relative",
    xaxis = list(title = "Extra offences (number)", tickformat = ",", zeroline = TRUE),
    yaxis = list(title = "", categoryorder = "array", categoryarray = order_c3,
                 tickfont = list(size = 12)),
    legend = list(orientation = "h", x = 0, y = 1.02, yanchor = "bottom"),
    margin = list(t = 150, b = 60, l = 10),
    updatemenus = list(list(
      type = "buttons", direction = "right", x = 1, xanchor = "right", y = 1.13,
      buttons = list(
        list(label = "Number of extra offences", method = "update",
             args = list(list(visible = c(TRUE, TRUE, FALSE, FALSE)),
                         list(xaxis = list(title = "Extra offences (number)",
                                           tickformat = ",", ticksuffix = "")))),
        list(label = "% change", method = "update",
             args = list(list(visible = c(FALSE, FALSE, TRUE, TRUE)),
                         list(xaxis = list(title = "% change", tickformat = "",
                                           ticksuffix = "%")))))))) %>% cfg()

Source: Crime Statistics Agency (2026), Recorded offences by LGA, year ending June 2026.

Chart summary: the crime type that added the most offences was “Theft” (53,146 more offences, +29.3%).


4. Where does the system feed itself?

Justice offences vary across different areas of Victoria. In this chart, each bubble represents a local council area (LGA). Areas further to the right have more other crimes per person, while areas higher up have more justice offences per person. Bigger bubbles represent areas with larger populations.

Press ▶ Play (or drag the slider) to watch the bubbles move year by year. Hover over a bubble to see the council name. Both axes use a log scale, so each grid line is a big step up.

# ---- Chart 4: animated LGA bubble chart (rates per 100,000 people) --------------
c4 <- t2 %>%
  group_by(year, lga) %>%
  summarise(just_n  = sum(offences[justice], na.rm = TRUE),
            other_n = sum(offences[!justice], na.rm = TRUE), .groups = "drop") %>%
  inner_join(lga_pop, by = c("year", "lga")) %>%
  filter(pop >= 1000) %>%
  mutate(just_rate  = just_n / pop * 1e5,
         other_rate = other_n / pop * 1e5,
         region = strip_code(region)) %>%
  filter(just_rate > 0, other_rate > 0) %>%
  arrange(year, lga)

region_cols <- c("#0072B2", "#E69F00", "#009E73", "#CC79A7", "#56B4E9", "#D55E00")
region_cols <- setNames(region_cols[seq_along(unique(c4$region))], sort(unique(c4$region)))

plot_ly(c4, x = ~other_rate, y = ~just_rate, frame = ~year, ids = ~lga,
        color = ~region, colors = region_cols,
        type = "scatter", mode = "markers",
        marker = list(size = ~pmax(sqrt(pop) / 18, 6), sizemode = "diameter",
                      opacity = 0.75, line = list(color = "white", width = 1)),
        customdata = ~pop, text = ~lga,
        hovertemplate = paste0("<b>%{text}</b><br>",
                               "Justice offences: %{y:,.0f} per 100,000 people<br>",
                               "Other offences: %{x:,.0f} per 100,000 people<br>",
                               "Population: %{customdata:,.0f}<extra></extra>")) %>%
  layout(
    title = list(text = paste0("<b>Some areas record far more justice offences per person</b><br>",
                               "<sup>Victorian LGAs, offences per 100,000 people</sup>"),
                 x = 0, xanchor = "left"),
    font = base_font,
    xaxis = list(title = "Other offences per 100,000 people (log)", type = "log"),
    yaxis = list(title = "Justice offences per 100,000 (log)", type = "log"),
    legend = list(title = list(text = "<b>Police region</b>")),
    margin = list(t = 120, b = 80, l = 90)) %>%
  animation_opts(frame = 900, transition = 500, redraw = FALSE) %>%
  animation_slider(currentvalue = list(prefix = "Year ending June ", xanchor = "right",
                                       font = list(size = 16, color = "#222222")),
                   pad = list(t = 70)) %>%
  animation_button(x = 0, xanchor = "left", y = -0.5) %>% cfg()

Source: Crime Statistics Agency (2026), Recorded offences by LGA, year ending June 2026.

Chart summary: in 2026, among councils with at least 10,000 people, Greater Shepparton had the highest rate of justice offences: about 4,656 per 100,000 people.


5. Find your own area

Has the share of justice offences increased in your local area? Select your council from the “Highlight a council” list at the top right of the chart. Your selected council will be circled and labelled. Choose “(none selected)” to remove the highlight.

  • Bubbles above the dashed line have a higher share of justice offences than in 2017.
  • Bubbles below the dashed line have a lower share.
# ---- Chart 5: share of each LGA's crime that is justice offences, then vs now ---
c5 <- t2 %>%
  filter(year %in% c(first_yr, last_yr)) %>%
  group_by(year, lga) %>%
  summarise(share = sum(offences[justice], na.rm = TRUE) /
                    sum(offences, na.rm = TRUE) * 100, .groups = "drop") %>%
  pivot_wider(names_from = year, values_from = share, names_prefix = "s") %>%
  rename(share_first = paste0("s", first_yr), share_last = paste0("s", last_yr)) %>%
  inner_join(lga_pop %>% filter(year == last_yr) %>% select(lga, region, pop), by = "lga") %>%
  filter(!is.na(share_first), !is.na(share_last), pop >= 1000) %>%
  mutate(region = strip_code(region))

above_line <- sum(c5$share_last > c5$share_first)
max_axis   <- ceiling(max(c(c5$share_first, c5$share_last)) / 5) * 5

# Dropdown: pick a council and it is circled and labelled on the chart.
# (Built with plotly's own dropdown, so it needs no extra JavaScript libraries.)
n_regions <- n_distinct(c5$region)
hl_trace  <- n_regions + 1   # trace number (counting from 0): regions, then the line, then the highlight

pick_buttons <- c(
  list(list(label = "Highlight a council (none selected)", method = "restyle",
            args = list(list(x = list(list()), y = list(list()), text = list(list())),
                        list(hl_trace)))),
  lapply(sort(c5$lga), function(l) {
    r <- filter(c5, lga == l)
    list(label = l, method = "restyle",
         args = list(list(x = list(list(r$share_first)), y = list(list(r$share_last)),
                          text = list(list(l))),
                     list(hl_trace)))
  }))

plot_ly(c5, x = ~share_first, y = ~share_last, color = ~region, colors = region_cols,
        type = "scatter", mode = "markers",
        marker = list(size = ~pmax(sqrt(pop) / 18, 6), sizemode = "diameter",
                      opacity = 0.75, line = list(color = "white", width = 1)),
        text = ~lga, customdata = ~pop,
        hovertemplate = paste0("<b>%{text}</b><br>",
                               "Share in ", first_yr, ": %{x:.1f}%<br>",
                               "Share in ", last_yr, ": %{y:.1f}%<br>",
                               "Population: %{customdata:,.0f}<extra></extra>")) %>%
  add_trace(x = c(0, max_axis), y = c(0, max_axis), type = "scatter", mode = "lines",
            inherit = FALSE, name = "No change", hoverinfo = "skip",
            line = list(color = "#555555", dash = "dash", width = 1.5)) %>%
  add_trace(x = c(NA), y = c(NA), text = "", type = "scatter", mode = "markers+text",
            inherit = FALSE, name = "Selected council", showlegend = FALSE,
            hoverinfo = "skip", textposition = "top center",
            textfont = list(size = 15, color = "#000000"),
            marker = list(size = 34, color = "rgba(0,0,0,0)",
                          line = list(color = "#000000", width = 3))) %>%
  layout(
    title = list(text = paste0("<b>", ifelse(above_line > nrow(c5) / 2, "In most councils", "In some councils"),
                               ", a bigger share of crime is now justice offences</b><br>",
                               "<sup>Justice offences as a % of all recorded offences, by LGA</sup>"),
                 x = 0, xanchor = "left"),
    font = base_font,
    xaxis = list(title = paste0("Share in ", first_yr, " (%)"), range = c(0, max_axis), ticksuffix = "%"),
    yaxis = list(title = paste0("Share in ", last_yr, " (%)"), range = c(0, max_axis), ticksuffix = "%"),
    legend = list(title = list(text = "<b>Police region</b>")),
    margin = list(t = 140, b = 80),
    updatemenus = list(list(
      type = "dropdown", active = 0, buttons = pick_buttons,
      x = 0, xanchor = "left", y = 1.03, yanchor = "bottom",
      bgcolor = "#FFFFFF", bordercolor = "#555555"))) %>%
  cfg()

Source: Crime Statistics Agency (2026), Recorded offences by LGA, year ending June 2026.

Chart summary: 65 of 79 council areas are above the line, meaning justice offences now make up a bigger part of their crime than in 2017.


What this means

When we hear that crime is increasing, we often think that more people are becoming victims. However, the data shows another side of the story. A large and growing part of recorded crime in Victoria comes from people breaking rules set by the justice system, such as bail conditions, intervention orders and court orders.

This does not mean these offences are harmless. Breaking a family violence intervention order, for example, can put someone in serious danger. However, it is important to understand what the numbers really show.

  1. Headlines do not tell the whole story. Total crime figures include both offences that cause new harm and breaches of rules set by the justice system.
  2. The same person can be counted more than once. Someone on bail with several conditions may commit multiple offences if they break those conditions.
  3. We need to ask better questions. Instead of only asking whether crime is increasing, we should also ask whether people are being harmed more often and whether intervention orders and bail conditions are working as intended.

Notes about the data

  • Data source: Crime Statistics Agency Victoria, Recorded offences by LGA, year ending June 2026, released on 24 September 2026. The data covers financial years ending on 30 June. No other data sources were used.
  • Justice procedures offences: These are classified as Offence Division “E” by the Crime Statistics Agency. They include breaching orders, breaking bail conditions, failing to appear in court, resisting police and similar offences.
  • Population estimates: The population for each council area was calculated using the agency’s offence counts and rates (population = offences ÷ rate × 100,000).
  • Understanding the data: Recorded offences include crimes reported to or detected by police. Changes in the numbers may also reflect changes in police activity or laws, not just changes in people’s behaviour.
  • Excluded areas: Areas with fewer than 1,000 people and records that could not be linked to a local government area (LGA), such as some justice institutions, were excluded from Charts 4 and 5.
  • Charts and colours: All charts were created in R using plotly. The colour-blind-friendly Okabe-Ito palette was used, along with different line styles, so the charts are easier to understand without relying on colour alone.

Acknowledgements

Data © State of Victoria (Crime Statistics Agency), used under a Creative Commons Attribution 4.0 International licence.

I acknowledge the use of Claude (Anthropic, https://claude.ai) for assistance with R Markdown code and refining the report wording. I selected the final story focus, ran the code in RStudio, verified the results against the Crime Statistics Agency data, and reviewed and edited the final report before submission.

References

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

Crime Statistics Agency. (2026). Recorded offences by LGA – year ending June 2026 [Data set]. DataVic. https://discover.data.vic.gov.au/dataset/data-tables-recorded-offences

Crime Statistics Agency. (n.d.). Glossary and data dictionary. Retrieved October 19, 2026, from https://www.crimestatistics.vic.gov.au/about-the-data/glossary-and-data-dictionary

Okabe, M., & Ito, K. (2008). Color universal design (CUD): How to make figures and presentations that are friendly to colorblind people. J*FLY. https://jfly.uni-koeln.de/color/

R Core Team. (2026). R: A language and environment for statistical computing [Computer software]. R Foundation for Statistical Computing. https://www.R-project.org/

Sievert, C. (2020). Interactive web-based data visualization with R, plotly, and shiny. Chapman and Hall/CRC. https://plotly-r.com

Victoria Police. (2026). Release of Crime Statistics Agency data – year ending June 2026 [Media release]. https://www.police.vic.gov.au/release-crime-statistics-agency-data-year-ending-june-2026

Wickham, H., Averick, M., Bryan, J., Chang, W., McGowan, L. D., François, R., Grolemund, G., Hayes, A., Henry, L., Hester, J., Kuhn, M., Pedersen, T. L., Miller, E., Bache, S. M., Müller, K., Ooms, J., Robinson, D., Seidel, D. P., Spinu, V., … Yutani, H. (2019). Welcome to the tidyverse. Journal of Open Source Software, 4(43), 1686. https://doi.org/10.21105/joss.01686