Data: Recycling Victoria (2026), licensed under Creative Commons Attribution 4.0 International (CC BY 4.0).

data <- read_excel("victorian-local-government-waste-data-release-notes-2025.03.xlsx",
                   sheet = "VLGAS v2025.03")

data$council <- trimws(data$council)
data$start_year <- as.numeric(substr(data$financial_year, 1, 4))
latest_year <- max(data$financial_year)

data$freq <- ifelse(data$garbage_service_frequency == "Weekly", "Weekly",
                    ifelse(data$garbage_service_frequency == "Fortnightly", "Fortnightly",
                           "Other or changed"))

data$fogo_raw <- trimws(as.character(data$kerbside_organics_fogo_included))
data$fogo_status <- ifelse(is.na(data$fogo_raw), "FOGO status not recorded",
                           ifelse(data$fogo_raw == "Yes", "FOGO included",
                                  ifelse(data$fogo_raw == "No", "Organics without FOGO", "Other")))

data$garbage_kg_per_resident   <- 1000 * data$garbage_collected_total_tonnes / data$Population
data$recycling_kg_per_resident <- 1000 * data$kerbside_recycling_total_collected_tonnes / data$Population
data$organics_kg_per_resident  <- 1000 * data$kerbside_organics_collected_tonnes / data$Population

Is the red bin really just as important as the green one?

The bins that are lined up outside the front door are quite alike from one street to another, yet councils in Victoria operate them in different ways (Recycling Victoria, 2026). There are councils collecting food and garden organics (FOGO), there are fortnightly collections of the rubbish bin, and there are those still collecting it weekly. I wanted to examine whether these factors go with differences in the amount of rubbish councils collect per resident.

The big picture: three bins, three different paths

trend <- data %>%
  group_by(financial_year, start_year) %>%
  summarise(
    rubbish_kg   = median(garbage_kg_per_resident, na.rm = TRUE),
    recycling_kg = median(recycling_kg_per_resident, na.rm = TRUE),
    organics_kg  = median(organics_kg_per_resident, na.rm = TRUE),
    rubbish_n    = sum(!is.na(garbage_kg_per_resident)),
    recycling_n  = sum(!is.na(recycling_kg_per_resident)),
    organics_n   = sum(!is.na(organics_kg_per_resident))
  )

trend_long <- rbind(
  data.frame(financial_year = trend$financial_year, start_year = trend$start_year,
             stream = "Rubbish", kg = trend$rubbish_kg, councils = trend$rubbish_n),
  data.frame(financial_year = trend$financial_year, start_year = trend$start_year,
             stream = "Mixed recycling", kg = trend$recycling_kg, councils = trend$recycling_n),
  data.frame(financial_year = trend$financial_year, start_year = trend$start_year,
             stream = "Organics", kg = trend$organics_kg, councils = trend$organics_n)
)

rec_peak      <- round(max(trend$recycling_kg, na.rm = TRUE))
rec_peak_year <- trend$financial_year[which.max(trend$recycling_kg)]
rec_last      <- round(trend$recycling_kg[nrow(trend)])
org_n_first   <- trend$organics_n[1]
org_n_last    <- trend$organics_n[nrow(trend)]

r_first   <- round(trend$rubbish_kg[1])
r_last    <- round(trend$rubbish_kg[nrow(trend)])
org_first <- round(trend$organics_kg[1])
org_last  <- round(trend$organics_kg[nrow(trend)])


p1 <- ggplot(trend_long, aes(x = start_year, y = kg, colour = stream,
                             linetype = stream, group = stream,
                             text = paste0(stream, "<br>", financial_year,
                                           "<br>Median: ", round(kg), " kg per resident",
                                           "<br>Councils reporting: ", councils))) +
  annotate("rect", xmin = 2018.5, xmax = 2021.5, ymin = 0, ymax = 230,
           fill = "grey80", alpha = 0.5) +
  annotate("text", x = 2020, y = 225, label = "Pandemic years", size = 3.5) +
  geom_line(linewidth = 1) +
  geom_point(size = 1.5) +
  scale_colour_manual(values = c("Rubbish" = "#D55E00",
                                 "Mixed recycling" = "#0072B2",
                                 "Organics" = "#009E73")) +
  scale_x_continuous(breaks = seq(2001, 2023, 2)) +
  scale_y_continuous(limits = c(0, 230)) +
  labs(x = "Financial year (starting)", y = "Median kg per resident",
       colour = "Waste stream", linetype = "Waste stream") +
  theme_minimal(base_size = 13)

ggplotly(p1, tooltip = "text") %>%
  layout(title = list(
    text = paste0("Rubbish per resident has fallen and recycling has slipped since ",
                  rec_peak_year, ";<br>organics is the only stream still growing",
                  "<br><sup>Recycling peaked at ", rec_peak, " kg in ", rec_peak_year,
                  " and is now ", rec_last, " kg.",
                  "<br>Organics medians come from ", org_n_first, " councils in 2001-02 and ",
                  org_n_last, " in 2023-24.</sup>"),
    x = 0.02, xanchor = "left", font = list(size = 16)),
    font = list(size = 13),
    margin = list(t = 140),
    legend = list(orientation = "h", y = -0.3, font = list(size = 12)))

Each point is the median across councils reporting that stream. Early organics values come from fewer councils (hover to see how many). Shaded band: pandemic years (2019-20 to 2021-22). Source: Own calculations from (Recycling Victoria, 2026).

Text description: Three lines depict the median kilograms collected per resident in councils from 2001-02 to 2023-24. Rubbish decreased from about 213 kg to 170 kg. Mixed recycling reached the peak at about 109 kg in 2008-2009, after which it has decreased to 72 kg. Organics increased from about 21 kg to 120 kg, and the number of councils reporting organics also increased from 31 to 66,so part of the rise reflects reporting. The band of the pandemic years is shown with the shade.

Rubbish fell over time period and mixed recycling has fallen since peak, whereas organics increased partially because the number of councils reporting it increased.So who actually has a green bin?

Who gets a green bin?

# Years from 2017-18 only (earlier FOGO flags are mostly blank or have odd values)
sub <- data %>%
  filter(start_year >= 2017, fogo_status != "Other")

sub$size_band <- cut(sub$Population,
                     breaks = c(0, 20000, 50000, 100000, Inf),
                     labels = c("Under 20,000", "20,000-50,000",
                                "50,000-100,000", "Over 100,000"))

sub$fogo_status <- factor(sub$fogo_status,
                          levels = c("FOGO included", "Organics without FOGO", "FOGO status not recorded"))

# Count councils in each year, size band and FOGO status
counts <- data.frame(table(sub$start_year, sub$size_band, sub$fogo_status))
names(counts) <- c("year", "size_band", "status", "councils")
counts$year <- as.numeric(as.character(counts$year))
counts$financial_year <- paste0(counts$year, "-", counts$year + 1)

# Numbers for the title (taken from the data)
latest_size <- sub %>% filter(financial_year == latest_year)
n_big     <- sum(latest_size$size_band == "Over 100,000", na.rm = TRUE)
fogo_big  <- sum(latest_size$size_band == "Over 100,000" &
                   latest_size$fogo_status == "FOGO included", na.rm = TRUE)
n_small    <- sum(latest_size$size_band == "Under 20,000", na.rm = TRUE)
fogo_small <- sum(latest_size$size_band == "Under 20,000" &
                    latest_size$fogo_status == "FOGO included", na.rm = TRUE)
band_totals <- table(latest_size$size_band)

p2 <- ggplot(counts, aes(x = year, y = councils, colour = status,
                         linetype = status, shape = status, group = status,
                         text = paste0(financial_year, "<br>", size_band, " residents",
                                       "<br>", status, "<br>Councils: ", councils))) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  facet_wrap(~ size_band) +
  scale_colour_manual(values = c("#0072B2", "#B35900", "#7F7F7F")) +
  scale_shape_manual(values = c(16, 17, 15)) +
  scale_x_continuous(breaks = seq(2017, 2023, 2)) +
  labs(x = "Financial year (starting)", y = "Number of councils",
       colour = "FOGO status", linetype = "FOGO status", shape = "FOGO status") +
  theme_minimal(base_size = 13)

ggplotly(p2, tooltip = "text") %>%
  layout(title = list(
    text = paste0("By 2023-24, ", fogo_big, " of ", n_big,
                  " councils with over 100,000 residents had FOGO,<br>compared with ",
                  fogo_small, " of ", n_small, " councils with under 20,000 residents"),
    x = 0.02, xanchor = "left", font = list(size = 16)),
    font = list(size = 13),
    margin = list(t = 100, l = 80),
    legend = list(orientation = "h", y = -0.2, font = list(size = 12)))

Each panel shows one council size group (by number of residents). In 2023-24 the four groups had 22, 17, 10 and 30 councils, so counts are not directly comparable between panels. In 2023-24, every council with no FOGO status recorded also has no organics tonnes. Before 2021-22 many councils left the FOGO field blank, so part of the rise in FOGO reflects better recording. Councils with a missing population value in a year are left out of that year. Source: Own calculations from Recycling Victoria (2026).

Text description: In 2023-24, 28 of 30 councils with over 100,000 residents had FOGO, compared with 12 of 22 councils with under 20,000 residents. Each panel shows lines for councils with FOGO, organics without FOGO, and no FOGO status recorded, from 2017-18 to 2023-24. Almost all large councils have FOGO (28 of 30), however about half of smallest do (12 of 22). Does having FOGO tell us how much rubbish a council collects?

Where councils stand today

snap <- data %>%
  filter(financial_year == latest_year,
         freq %in% c("Weekly", "Fortnightly"),
         !is.na(garbage_kg_per_resident))

snap$status <- ifelse(snap$fogo_status == "FOGO included", "FOGO included",
                      ifelse(snap$fogo_status == "Organics without FOGO",
                             "Organics without FOGO", "FOGO status not recorded"))
snap$status <- factor(snap$status,
                      levels = c("FOGO included", "Organics without FOGO", "FOGO status not recorded"))

snap$freq_label <- ifelse(snap$freq == "Weekly", "Rubbish collected weekly",
                          "Rubbish collected fortnightly")
snap$freq_label <- factor(snap$freq_label,
                          levels = c("Rubbish collected weekly", "Rubbish collected fortnightly"))

med_fort <- round(median(snap$garbage_kg_per_resident[snap$freq == "Fortnightly" &
                                                        snap$status == "FOGO included"]))
med_week <- round(median(snap$garbage_kg_per_resident[snap$freq == "Weekly" & snap$status == "FOGO included"]))
top_kg <- round(max(snap$garbage_kg_per_resident, na.rm = TRUE))

p3 <- ggplot(snap, aes(x = status, y = garbage_kg_per_resident,
                       colour = status, shape = status, size = Population,
                       text = paste0(council, "<br>Rubbish: ",
                                     round(garbage_kg_per_resident), " kg per resident",
                                     "<br>Population: ", format(Population, big.mark = ","),
                                     "<br>Collection: ", freq, "<br>", status))) +
  geom_jitter(width = 0.2, alpha = 0.7) +
  facet_wrap(~ freq_label) +
  scale_colour_manual(values = c("#0072B2", "#B35900", "#7F7F7F")) +
  scale_shape_manual(values = c(16, 17, 15)) +
  scale_size(range = c(1.5, 6)) +
  scale_x_discrete(labels = c("FOGO", "Organics,\nno FOGO", "FOGO status\nnot recorded")) +
  labs(x = "", y = "Rubbish per resident (kg)") +
  theme_minimal(base_size = 13) +
  theme(legend.position = "none")

ggplotly(p3, tooltip = "text") %>%
  layout(title = list(
    text = paste0("Among FOGO councils in 2023-24, fortnightly collection goes with<br>about ",
                  med_fort, " kg per resident, weekly collection with about ", med_week, " kg",
                  "<br><sup>Each dot is a council; larger dots have more residents</sup>"),
    x = 0.02, xanchor = "left", font = list(size = 16)),
    font = list(size = 13),
    margin = list(t = 120, b = 80))

Each dot is One council in 2023-24, larger dots have more residents. Councils shown as “FOGO status not recorded” also report no organics tonnes, which may mean no service or no reporting. Only 3 councils without FOGO collect rubbish fortnightly 9 2 with organics but no FOGO, 1 with no FOGO status recorded), so the fortnightly panel has very few of them. The highest dots are small rural councils, where per-resident figures can be inflated by visitors and non-residential collections. One council that changed frequency mid-year is left out of this chart. Source: My own calculation from (Recycling Victoria, 2026).

Text description: Every council is represented by each dot in 2023-24. Councils with FOGO that collect rubbish fortnightly cluster around 122 kg per resident, whereas FOGO councils that collect weekly cluster around 175 kg. Weekly councils without FOGO are a little higher than weekly FOGO councils, and weekly councils with no FOGO status recorded are the highest, with the top council at about 376 kg.

The fortnightly councils are lowest here, but this compares different councils. What happens when the same council changes?

The year the bin schedule changed

# start_year + 1 shifts each year forward so last year's value lines up with this year
prev <- data %>%
  mutate(start_year = start_year + 1) %>%
  select(council, start_year,
         kg_prev = garbage_kg_per_resident,
         freq_prev = freq,
         est_prev = garbage_collected_total_tonnes_estimated)

data2 <- left_join(data, prev, by = c("council", "start_year"))
data2$step_change <- data2$garbage_kg_per_resident - data2$kg_prev

# Exclude years where either figure was an estimate
data2 <- data2 %>%
  filter(is.na(garbage_collected_total_tonnes_estimated) |
           garbage_collected_total_tonnes_estimated != "Yes",
         is.na(est_prev) | est_prev != "Yes")

switch_year <- data2 %>%
  filter(freq == "Fortnightly", freq_prev == "Weekly", start_year >= 2020)
stay_weekly <- data2 %>%
  filter(freq == "Weekly", freq_prev == "Weekly", start_year >= 2020)

switch_year$group <- "Moved to fortnightly"
stay_weekly$group <- "Stayed weekly"
step_data <- rbind(switch_year, stay_weekly)
step_data$group <- factor(step_data$group,
                          levels = c("Stayed weekly", "Moved to fortnightly"))
step_data$fogo_status <- factor(step_data$fogo_status,
                                levels = c("FOGO included", "Organics without FOGO", "FOGO status not recorded"))

n_stay        <- sum(!is.na(stay_weekly$step_change))
n_switch      <- sum(!is.na(switch_year$step_change))
med_stay      <- round(median(stay_weekly$step_change, na.rm = TRUE))
med_switch    <- round(median(switch_year$step_change, na.rm = TRUE))
n_switch_fogo <- sum(switch_year$fogo_status == "FOGO included" & !is.na(switch_year$step_change))


# Weekly councils' median change in each year
st_by_year <- stay_weekly %>%
  group_by(start_year) %>%
  summarise(median_stay = median(step_change, na.rm = TRUE))

# Each switcher's change minus the same year's weekly median
switch2 <- left_join(switch_year, st_by_year, by = "start_year")
adj_median <- round(median(switch2$step_change - switch2$median_stay, na.rm = TRUE))


x_labels <- c(
  "Stayed weekly" = paste0("Stayed weekly\n", n_stay,
                           " council-years, median ", med_stay, " kg"),
  "Moved to fortnightly" = paste0("Moved to fortnightly\n", n_switch,
 
                                  
                                                                   " council-years, median ", med_switch, " kg"))
# chart4
p4 <- ggplot(step_data, aes(x = group, y = step_change)) +
  geom_hline(yintercept = 0, linetype = "dashed", colour = "grey40") +
  geom_boxplot(outlier.shape = NA, fill = NA, colour = "grey30", width = 0.5) +
  geom_jitter(aes(colour = fogo_status, shape = fogo_status,
                  text = paste0(council, "<br>", financial_year,
                                "<br>Change: ", round(step_change), " kg per resident",
                                "<br>Rubbish collection: ", freq,
                                "<br>", fogo_status)),
              width = 0.3, alpha = 0.7, size = 2) +
  scale_colour_manual(values = c("#0072B2", "#B35900", "#7F7F7F")) +
  scale_shape_manual(values = c(16, 17, 15)) +
  scale_x_discrete(labels = x_labels) +
  labs(x = "", y = "Change from previous year\n (kg per resident)",
       colour = "FOGO status", shape = "FOGO status") +
  theme_minimal(base_size = 13)

ggplotly(p4, tooltip = "text", height = 620) %>%
  layout(title = list(
    text = paste0("Rubbish per resident typically fell in the year councils<br>moved to fortnightly bins",
                  "<br><sup>Compared with weekly councils in the same year, the extra drop was about ",
                  abs(adj_median), " kg.<br>Nearly all of these councils also had FOGO.</sup>"),
    x = 0.02, xanchor = "left", font = list(size = 16)),
    font = list(size = 13),
    margin = list(t = 140, l = 90),
    legend = list(orientation = "h", y = -0.35, font = list(size = 12)))

Every data point indicates one particular council for one specific year. The boxes indicate the interquartile range of changes observed. When the median change in rubbish per resident among the weekly councils in the same year is stripped out,councils that moved to fortnightly collection of rubbish fell by a median of about 43kg more. For 20 council-years, the gap was larger in every year,but only few councils switched in each of the first two years. From 20 council-years, 18 had FOGO (food and garden organics), so the two changes can not be separated. Years explicitly flagged as estimated are excluded; the estimate flag is blank from 2022-23. These are associations and not proven effects. Source: Own calculations from (Recycling Victoria, 2026).

Text description:: The councils which continued with weekly collection (referred to as 202 council-years) showed a median change of around -2 kilograms per resident The councils which switched to fortnightly collection (referred to as 20 council-years) showed a median change of approximately -46 kilograms. After accounting for the difference in median change of weekly councils during the same year, the extra reduction was found to be 43 kilograms. It must be understood that most of these councils also had FOGO.

For each year, between 2020-21 and 2023-24, the councils which switched to fortnightly collection showed larger reductions compared to the councils which continued with weekly collection. The degree to which the difference occurred varied widely, with only 20 council-years being included in the analysis. Since most of these councils also had FOGO, it is not possible to ascertain which change mattered more.

Find your council

snap5 <- data %>%
  filter(financial_year == latest_year,
         !is.na(garbage_kg_per_resident), Population > 0)

snap5$status <- ifelse(snap5$fogo_status == "FOGO included", "FOGO included",
                       ifelse(snap5$fogo_status == "Organics without FOGO",
                              "Organics without FOGO", "FOGO status not recorded"))
snap5$status <- factor(snap5$status,
                       levels = c("FOGO included", "Organics without FOGO", "FOGO status not recorded"))

snap5$freq_label <- factor(snap5$freq,
                           levels = c("Weekly", "Fortnightly", "Other or changed"))

vic_median <- round(median(snap5$garbage_kg_per_resident))

shared5 <- SharedData$new(snap5, key = ~council, group = "Search for your council")

p5 <- ggplot(shared5, aes(x = Population, y = garbage_kg_per_resident,
                          colour = status, shape = freq_label, group = council,
                          text = paste0(council,
                                        "<br>Rubbish: ", round(garbage_kg_per_resident), " kg per resident",
                                        "<br>Population: ", format(Population, big.mark = ","),
                                        "<br>Rubbish collection: ", freq_label,
                                        "<br>", status))) +
  geom_hline(yintercept = vic_median, linetype = "dashed", colour = "grey40") +
  geom_point(size = 2.5, alpha = 0.8) +
  scale_x_log10(
    breaks = c(3000, 10000, 30000, 100000, 300000),
  labels = scales::label_comma()
  ) +
  scale_colour_manual(values = c("#0072B2", "#B35900", "#7F7F7F")) +
  scale_shape_manual(values = c(18, 4, 3)) +
  labs(x = "No. of Residents (log scale)", y = "Rubbish per resident (kg)",
       colour = "FOGO status", shape = "Rubbish collection") +
  theme_minimal(base_size = 13)

gg5 <- ggplotly(p5, tooltip = "text", height = 750) %>%
  layout(title = list(
    text = paste0("Find your council: where does it sit?",
                  "<br><sup>Dashed line = median across councils (", vic_median,
                  " kg per resident, 2023-24)</sup>"),
    x = 0.02, xanchor = "left", font = list(size = 16)),
    font = list(size = 13),
    margin = list(t = 120, b = 300),
    legend = list(orientation = "v", x = 0, y = -0.3, yanchor = "top", font = list(size = 12)))
highlight(gg5, selectize = TRUE, on = "plotly_click")

A dot stands for each council in the year 2023-24. Utilize the search function in order to find your own council. The X-axis shows the population in logarithmic scale. The dashed line stands for the median across councils. Data source: Own calculations based on (Recycling Victoria, 2026).

Text description: Each data point is a council, corresponding to 2023-24, with its population depicted on a logarithmic scale on the x-axis and its rubbish per resident on the y-axis. Color shows FOGO status and shape shows how often rubbish is collected (diamond: weekly, cross:fortnightly, plus:other or changed). There is a dashed line that indicates the median value of 170 kg per resident. There is a search bar available above the graph which enables the user to highlight any selected council.

Find your council and what to ask

By using the search option, find your council and compare it with other councils of similar size. If your council is still collecting rubbish every week or has no FOGO status recorded in the data, you could check their waste plan to see whether anything is changing. The covers until June2024, so the council may have changed since.

What i found

What this data can and can’t tell us

Method notes

What I did

Choices I made, and why

Acknowledgement of generative AI

I used ChatGPT (OpenAI, 2026) to help me with grammar and writing style of my own writing. Also used AI for my research work with publishing visualisations online.

References