# ---- Loading the dataset ---------------------------------------------------------------
data_file <- "abs-export-data-collation-june-2026.xlsx"

# All columns are read as text and then converted to the appropriate type.
raw <- read_excel(data_file, sheet = "Export Records", col_types = "text")

needed <- c("Financial Year", "Tonnes", "Value ($)", "Destination",
            "Material group", "Data scope", "State of origin",
            "AHECC code", "AHECC description")
stopifnot(all(needed %in% names(raw)))

# ---- Cleaning the dataset --------------------------------------------------------------
waste <- raw %>%
  transmute(
    fy       = `Financial Year`,
    year_end = as.integer(substr(fy, 1, 4)) + 1L,       # 2017-18 -> 2018
    tonnes   = as.numeric(Tonnes),
    value    = as.numeric(`Value ($)`),                  # declared value, nominal AUD
    dest     = recode(Destination,
                      "Korea, Republic of" = "South Korea",
                      "USA"                = "United States"),
    material = `Material group`,
    scope    = `Data scope`,
    ahecc    = as.character(`AHECC code`),               # customs product code
    ahecc_desc = `AHECC description`
  )

# Quality checks that must pass before anything is plotted
stopifnot(!anyNA(waste$tonnes), !anyNA(waste$value), !anyNA(waste$dest))

# ---- Scope --------------------------------------------------------------
core <- waste %>% filter(scope == "Core waste + ash")

first_fy <- min(core$fy)   # "2017-18"
last_fy  <- max(core$fy)   # "2025-26"

pal <- c("Metals"                  = "#4D4D4D",
         "Paper & cardboard"       = "#56B4E9",
         "Plastics"                = "#0072B2",
         "Glass"                   = "#009E73",
         "Textiles"                = "#E69F00",
         "Tyres"                   = "#D55E00",
         "Hazardous (excl. tyres)" = "#CC79A7",
         "Other core waste"        = "#999999")

source_note <- "Source: DCCEEW (2026), Waste Exports data collation (ABS customs data). Core waste + ash, nominal values."

# Left-aligned title and white background, used by every ggplotly() chart
left_title <- function(txt) list(text = paste0("<b>", txt, "</b>"), x = 0.01, xref = "container", xanchor = "left")

# Small formatting helpers used in the text and tooltips
fmt_t  <- function(x) paste0(comma(round(x)), " t")
fmt_m  <- function(x) paste0("$", comma(round(x)), "m")
fmt_pc <- function(x) paste0(ifelse(x >= 0, "+", ""), round(100 * x), "%")   
fmt_pp <- function(x) paste0(round(100 * abs(x)), "%")                      
# All numbers in the story are calculated here, so text and charts always agree.
annual <- core %>%
  group_by(fy, year_end) %>%
  summarise(tonnes = sum(tonnes), value = sum(value) / 1e6, .groups = "drop") %>%
  arrange(year_end) %>%
  mutate(tonnes_idx = 100 * tonnes / first(tonnes),
         value_idx  = 100 * value  / first(value))

by_mat_fy <- core %>%
  group_by(material, fy) %>%
  summarise(tonnes = sum(tonnes), value = sum(value) / 1e6, .groups = "drop") %>%
  mutate(vpt = value * 1e6 / tonnes)

mat_chg <- by_mat_fy %>%
  filter(fy %in% c(first_fy, last_fy)) %>%
  mutate(period = ifelse(fy == first_fy, "first", "last")) %>%
  select(-fy) %>%
  pivot_wider(names_from = period, values_from = c(tonnes, value, vpt)) %>%
  mutate(d_tonnes = tonnes_last - tonnes_first,
         d_value  = value_last  - value_first)

get <- function(m, col) mat_chg[[col]][mat_chg$material == m]

tonnes_pc <- last(annual$tonnes) / first(annual$tonnes) - 1
value_pc  <- last(annual$value)  / first(annual$value)  - 1

# Which product line in customs has the highest declared value in the latest year?
top_line <- core %>% filter(fy == last_fy) %>%
  group_by(ahecc, ahecc_desc) %>%
  summarise(tonnes = sum(tonnes), value = sum(value) / 1e6, .groups = "drop") %>%
  slice_max(value, n = 1)
line_txt   <- sub(" \\(excl\\..*$", "", top_line$ahecc_desc)       # short description
line_txt   <- trimws(line_txt)
line_txt   <- paste0(tolower(substring(line_txt, 1, 1)), substring(line_txt, 2))
line_by_fy <- core %>% filter(ahecc == top_line$ahecc) %>%
  group_by(fy) %>% summarise(tonnes = sum(tonnes), value = sum(value) / 1e6, .groups = "drop")
line_first <- line_by_fy$value[line_by_fy$fy == first_fy]
line_chg   <- top_line$value - line_first
annual_ex  <- core %>% filter(ahecc != top_line$ahecc) %>%
  group_by(fy) %>% summarise(tonnes = sum(tonnes), value = sum(value) / 1e6, .groups = "drop") %>% arrange(fy)
value_pc_ex  <- last(annual_ex$value)  / first(annual_ex$value)  - 1
tonnes_pc_ex <- last(annual_ex$tonnes) / first(annual_ex$tonnes) - 1

The bin is only the beginning

Our bins are emptied every week by trucks and they take off. Most people don’t question the next step in the journey of the material or if anyone profits from it.

The following is a tale of the export side of that trip. It uses Australia’s official records of waste and recovered materials leaving the country, from 2017-18 to 2025-26. Almost all of it is recovered material (RM) that is recycled or used for energy recovery (Department of Climate Change, Energy, the Environment and Water [DCCEEW], 2026). Your bin doesn’t have places for the data. It can reveal what the entire nation exports, what it imports and who imports it.

1. The hook: more money, barely more waste

Both lines begin at 100 in 2017-18, allowing you to make direct comparisons in growth. Hover over a point for the actual figures.

# Reshape the two indexed measures so they can share one line chart
annual_long <- annual %>%
  pivot_longer(cols = c(tonnes_idx, value_idx), names_to = "measure", values_to = "index") %>%
  mutate(measure = recode(measure, tonnes_idx = "Tonnes exported", value_idx = "Declared value"),
         actual  = ifelse(measure == "Tonnes exported", tonnes, value),
         tip     = ifelse(measure == "Tonnes exported",
                          paste0(fy, "<br>Tonnes exported: ", comma(round(actual)), " t"),
                          paste0(fy, "<br>Declared value: $", comma(round(actual)), " million")),
         tip     = paste0(tip, "<br>Index: ", round(index)))

g1 <- ggplot(annual_long, aes(x = year_end, y = index, colour = measure, group = measure, text = tip)) +
  geom_line(linewidth = 1.1) +
  geom_point(size = 2.5) +
  scale_colour_manual(values = c("Declared value" = "#1A1A1A", "Tonnes exported" = "#8C8C8C")) +
  scale_x_continuous(breaks = seq(2018, 2026, 2), labels = annual$fy[annual$year_end %in% seq(2018, 2026, 2)]) +
  scale_y_continuous(limits = c(80, 250), breaks = seq(100, 250, 50)) +
  labs(x = "Financial year", y = "Index (2017-18 = 100)", colour = "") +
  theme_minimal(base_size = 12) +
  theme(panel.grid.minor = element_blank())

ggplotly(g1, tooltip = "text") %>%
  layout(title = left_title(paste0("Tonnes up ", fmt_pp(tonnes_pc), ", declared value up ", fmt_pp(value_pc))),
         legend = list(orientation = "h", x = 0, y = -0.25),
         margin = list(t = 70, b = 110, l = 70),
         paper_bgcolor = "white", plot_bgcolor = "white") %>%
  plotly::config(displaylogo = FALSE)

Source: DCCEEW (2026), Waste Exports data collation (ABS customs data). Core waste + ash, nominal values.

So what? The tonnage exported from Australia increased by 11% over the period 2017-18 and 2025-26, while the declared value of the tonnage increased by 126%. Something other than “more bins” is driving the money. The next charts find out what.

Values are not profit, but nominal dollars (not inflation adjusted) as reported in the customs declaration. They never take into account collection, sorting and shipping expenses.

A caution about the value figure. One customs product line, slag, ash and residues containing mainly copper, was only 845 t in 2025-26 but was declared at $1,644m, up from $218m in 2017-18. The 126% is not a generalised increase without the single line, the value must have still gone up by 85% and tonnes by 11%, so the story does indeed stand true. Whether it is real high value material or reporting idiosyncrasies, the records do not provide any indication, and the workbook cautions that the codes and values of exporters may be incorrect.

2. Who gained and who lost

Each bar is the change between 2017-18 and 2025-26. Each bar is the change between 2017-18 and 2025-26. The bars to the right of the zero indicate rises and the bars to the left of the zero indicate falls. Panel A (top) shows the change in weight (tonnes). This represents the amount of money that changed (declared value in millions of dollars) in Panel B (bottom). Both panels are the same colour for each material.

# Put tonnes and dollars in one long table so they can be shown as two panels
mat_long <- mat_chg %>%
  select(material, d_tonnes, d_value) %>%
  pivot_longer(cols = c(d_tonnes, d_value), names_to = "measure", values_to = "change") %>%
  mutate(label = ifelse(measure == "d_tonnes",
                        paste0(ifelse(change >= 0, "+", "-"), comma(round(abs(change))), " t"),
                        paste0(ifelse(change >= 0, "+$", "-$"), comma(round(abs(change))), "m")),
         measure  = recode(measure, d_tonnes = "A. WEIGHT: change in tonnes exported",
                           d_value = "B. MONEY: change in declared value (A$ million)"),
         measure  = factor(measure, levels = c("A. WEIGHT: change in tonnes exported",
                                               "B. MONEY: change in declared value (A$ million)")),
         material = factor(material, levels = mat_chg$material[order(mat_chg$d_tonnes)]),
         tip      = paste0("<b>", material, "</b><br>", measure, "<br>", label))

g2 <- ggplot(mat_long, aes(x = change, y = material, fill = material, text = tip)) +
  geom_col() +
  geom_vline(xintercept = 0, colour = "#333333", linewidth = 0.4) +
  facet_wrap(~measure, ncol = 1, scales = "free_x") +
  scale_fill_manual(values = pal) +
  scale_x_continuous(labels = label_comma()) +
  labs(x = "", y = "") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none", panel.grid.minor = element_blank(),
        strip.text = element_text(face = "bold", size = 13, hjust = 0),
        panel.spacing.y = unit(4, "lines"))

ggplotly(g2, tooltip = "text") %>%
  layout(title = left_title("Tyres surged, plastics fell, and metals drove the value growth"),
         margin = list(t = 80, b = 60, l = 150),
         paper_bgcolor = "white", plot_bgcolor = "white") %>%
  plotly::config(displaylogo = FALSE)

Source: DCCEEW (2026), Waste Exports data collation (ABS customs data). Core waste + ash, nominal values.

So what? Tyres added 176,620 t, the second-largest tonnage increase, yet only $5m of value. Plastics fell by 143,731 t and paper by 210,869 t. The extra money came from metals ($2,374m) and from hazardous materials other than tyres ($1,449m). Most of that hazardous increase is the single copper-residue line described in Chart 1 ($1,426m).

3. Where the plastic went

Each panel is one destination, and every panel uses the same vertical scale so the heights can be compared. Hover over a point for the tonnes.

# Top six destinations by plastic tonnes over the whole period; everything else is grouped.
top_pl <- core %>% filter(material == "Plastics") %>%
  group_by(dest) %>% summarise(t = sum(tonnes)) %>% arrange(desc(t)) %>% slice_head(n = 6) %>% pull(dest)

pl <- core %>% filter(material == "Plastics") %>%
  mutate(panel = ifelse(dest %in% top_pl, dest, "All other destinations")) %>%
  group_by(panel, fy, year_end) %>% summarise(tonnes = sum(tonnes), .groups = "drop") %>%
  complete(panel, nesting(fy, year_end), fill = list(tonnes = 0)) %>%
  mutate(panel = factor(panel, levels = c(top_pl, "All other destinations")),
         tip = paste0("<b>", panel, "</b><br>", fy, "<br>", fmt_t(tonnes)))

pl_tot <- core %>% filter(material == "Plastics") %>% group_by(fy) %>% summarise(t = sum(tonnes))
pl_peak <- pl_tot %>% slice_max(t, n = 1)
pl_last <- pl_tot %>% filter(fy == last_fy)

g3 <- ggplot(pl, aes(x = year_end, y = tonnes, group = panel)) +
  geom_line(colour = pal["Plastics"], linewidth = 1.1) +
  geom_point(aes(text = tip), colour = pal["Plastics"], size = 2) +
  facet_wrap(~panel, ncol = 4) +
  scale_x_continuous(breaks = c(2018, 2022, 2026)) +
  scale_y_continuous(labels = label_comma(), limits = c(0, NA)) +
  labs(x = "Financial year ending", y = "Tonnes exported") +
  theme_minimal(base_size = 12) +
  theme(panel.grid.minor = element_blank(),
        panel.grid.major = element_line(colour = "#E8E8E8"),
        strip.text = element_text(face = "bold", hjust = 0),
        panel.spacing = unit(1.1, "lines"))

p3 <- ggplotly(g3, tooltip = "text") %>%
  layout(
    title = left_title(paste0("Plastic exports fell ", round(100 * (1 - pl_last$t / pl_peak$t)),
                              "% from their ", pl_peak$fy, " peak")),
    margin = list(t = 80, b = 70, l = 80),
    paper_bgcolor = "white", plot_bgcolor = "white"
  )
p3

Source: DCCEEW (2026), Waste Exports data collation (ABS customs data). Core waste + ash, nominal values.

So what? Recorded plastic exports peaked at 186,739 t in 2018-19 and were only 15,011 t in 2025-26. The collapse is across almost every destination, not one country. The data shows that plastic exports fell, not why. It does not show whether more was processed onshore, collected less or thrown away.

4. Money versus mass

Press play or drag the slider. Further right means more tonnes. Higher up means more declared dollars per tonne. Bigger bubbles mean more total declared value. Both axes are logarithmic, so each gridline is ten times the last.

# Same approach as the Gapminder animation in the course notes:
# x, y, colour and size, with the financial year as the animation frame
bub <- by_mat_fy %>%
  arrange(fy) %>%
  mutate(tip = paste0("<b>", material, "</b> (", fy, ")",
                      "<br>Tonnes: ", comma(round(tonnes)),
                      "<br>Declared value: ", fmt_m(value),
                      "<br>Value per tonne: $", comma(round(vpt))))

plot_ly(bub, x = ~tonnes, y = ~vpt, size = ~value, color = ~material, colors = pal,
        frame = ~fy, text = ~tip, hoverinfo = "text",
        type = "scatter", mode = "markers", sizes = c(15, 70)) %>%
  layout(title = left_title("The heaviest exports are not the most valuable per tonne"),
         xaxis = list(type = "log", title = "Tonnes exported (log scale)",
                      tickvals = c(100, 1000, 10000, 100000, 1000000),
                      ticktext = c("100", "1,000", "10,000", "100,000", "1 million"),
                      range = c(2.2, 6.8)),
         yaxis = list(type = "log", title = "Declared value per tonne (A$, log scale)",
                      tickvals = c(10, 100, 1000, 10000),
                      ticktext = c("$10", "$100", "$1,000", "$10,000"),
                      range = c(1.5, 4.7)),
         legend = list(title = list(text = "Material")),
         margin = list(t = 80, b = 160, l = 90),
         paper_bgcolor = "white", plot_bgcolor = "white") %>%
  animation_opts(frame = 1000, transition = 600) %>%
  animation_slider(currentvalue = list(prefix = "Financial year: "), pad = list(t = 70)) %>%
  plotly::config(displaylogo = FALSE)

Source: DCCEEW (2026), Waste Exports data collation (ABS customs data). Core waste + ash, nominal values. Bubble area shows total declared value.

So what? Tyres are very heavy (right) and very low down (low) in 2025-26: $95. However, hazardous materials other than tyres are considerably more expensive around $22,576 a tonne, which is attributable to only one line with copper residue. Plastics, paper and glass, the materials most people picture in a recycling bin, stay near the bottom. Together their declared value fell from $334m to $263m.

5. The twist: tyres and rubber scrap

Same layout as the plastics chart: one panel per destination on a shared scale. Hover over a bar for tonnes and declared value.

top_ty <- core %>% filter(material == "Tyres", fy == last_fy) %>%
  group_by(dest) %>% summarise(t = sum(tonnes)) %>% arrange(desc(t)) %>% slice_head(n = 5) %>% pull(dest)

ty <- core %>% filter(material == "Tyres") %>%
  mutate(panel = ifelse(dest %in% top_ty, dest, "All other destinations")) %>%
  group_by(panel, fy, year_end) %>%
  summarise(tonnes = sum(tonnes), value = sum(value) / 1e6, .groups = "drop") %>%
  complete(panel, nesting(fy, year_end), fill = list(tonnes = 0, value = 0)) %>%
  mutate(panel = factor(panel, levels = c(top_ty, "All other destinations")),
         tip = paste0("<b>", panel, "</b><br>", fy, "<br>", fmt_t(tonnes),
                      ifelse(tonnes > 0, paste0("<br>Declared value: ", fmt_m(value),
                                                "<br>About $", comma(round(value * 1e6 / tonnes)), " a tonne"), "")))

ty_tot <- core %>% filter(material == "Tyres") %>% group_by(fy) %>%
  summarise(t = sum(tonnes), v = sum(value) / 1e6)
ty_first <- ty_tot %>% filter(fy == first_fy)
ty_last  <- ty_tot %>% filter(fy == last_fy)
ty_chg   <- ty_last$t / ty_first$t - 1
ty_india <- ty %>% filter(panel == "India", fy == last_fy) %>% pull(tonnes)

g5 <- ggplot(ty, aes(x = year_end, y = tonnes)) +
  geom_col(aes(text = tip), fill = pal["Tyres"], width = 0.75) +
  facet_wrap(~panel, ncol = 3) +
  scale_x_continuous(breaks = c(2018, 2022, 2026)) +
  scale_y_continuous(labels = label_comma()) +
  labs(x = "Financial year ending", y = "Tonnes exported") +
  theme_minimal(base_size = 12) +
  theme(panel.grid.minor = element_blank(), panel.grid.major.x = element_blank(),
        panel.grid.major.y = element_line(colour = "#E8E8E8"),
        strip.text = element_text(face = "bold", hjust = 0),
        panel.spacing = unit(1.1, "lines"))

p5 <- ggplotly(g5, tooltip = "text") %>%
  layout(
    title = left_title(paste0("Tyre and rubber-scrap exports rose ", round(100 * ty_chg), "%, led by India")),
    margin = list(t = 80, b = 70, l = 80),
    paper_bgcolor = "white", plot_bgcolor = "white"
  )
p5
So what? Recorded tyre and rubber-scrap exports grew from 90,320 t to 266,940 t (+196%). India alone took 152,315 t in 2025-26. This is not an indication of an increase or decrease in recycling in Australia. Record this low figure: $25m for the entire load (approx. $95/tonne). It is not a common kerbside bin material, so this growth is predominantly not part of the yellow-bin story.

Source: DCCEEW (2026), Waste Exports data collation (ABS customs data). Core waste + ash, nominal values. Tyres material group. The customs classification codes changed over the period; see Methodology.

What to take from this

The yellow bin is the visible tip of a much bigger export system.

  • More money, not much more waste. Declared value rose 126% while tonnage rose 11%.
  • The extra money did not come from paper, plastics or glass. The gains came from metals and, above all, from one line of copper-bearing residues, while the declared value of paper, plastics and glass fell.
  • Outside metals, the biggest tonnage growth is almost worthless. Tyres and rubber scrap grew by 196% and earn very little per tonne.
  • Plastic exports fell sharply. It is evident from the records that it did happen, but why?

So when the next truck arrives ask, where does it go and who is paid to take it? Councils, recyclers and governments can answer that. The export data only covers the outward trip.

Methodology and limitations

scope_tbl <- waste %>%
  mutate(scope = ifelse(is.na(scope), "No scope recorded", scope)) %>%
  group_by(scope) %>%
  summarise(records = n(), tonnes_total = sum(tonnes), .groups = "drop") %>%
  mutate(tonnes_total = comma(round(tonnes_total)), records = comma(records))
knitr::kable(scope_tbl, col.names = c("Data scope in workbook", "Records", "Total tonnes (all years)"),
             align = "lrr")
Data scope in workbook Records Total tonnes (all years)
Core waste + ash 72,618 37,529,045
No scope recorded 25 667
Non-core waste 3,142 1,611,057
  • Data source. Waste Exports data collation from DCCEEW (2026) - collates Australian Bureau of Statistics customs records by financial year. Records cover July 2017 to June 2026. Exporters might be using the wrong codes or values in the workbook and values could be amended for up to six months after the workbook is published.
  • Scope. Only Core waste + ash records are used. The trend is inconsistent, as the “Non-core waste” records have spiked up significantly from 2023-24. Comparing like with like gives tonnes up 11% and value up 126%.
  • Materials. The material groups are taken from the “Material group” column of the workbook (only 8 groups). Used and retreaded tyres and waste, parings and scrap of rubber were previously classified in customs codes under “Tyres” but the material group maintains continuity of the series. Every other hazardous line is Hazardous (excl. tyres).
  • Value. Values are declared customs values, in nominal Australian dollars and not adjusted for inflation, and are not profit. Export revenue does not account for collection,sorting or shipping expenses.
  • What the data cannot show. There is no split between household and commercial uses, and it is not possible to trace any one bin. Changes were not reported as reasons (for instance, policy changes, onshore processing); they are only reported as the changes occurred.
  • One dominant line. A single customs product line (slag, ash and residues containing mainly copper) accounts for $1,644m of declared value in 2025-26. The headline value growth is reported with and without it.
  • Reproducibility. All charts are produced in R (R Core Team, 2025) from the workbook with the code shown on this page (use the “Code” buttons). The packages used were readxl (Wickham & Bryan, 2023), dplyr (Wickham et al., 2023), tidyr (Wickham et al., 2024), ggplot2 (Wickham, 2016) and plotly (Sievert, 2020), and the page was built with R Markdown (Xie et al., 2018).

Acknowledgements

• I used ChatGPT to offer feedback on spelling and grammar, to suggest references and online sources and to help with troubleshooting and understanding R code. I read through the comments and made changes as necessary and presented all final analysis, interpretation, decisions, code, and work submitted are my own.

• Country: I acknowledge the WurundjeriWoi-wurrung people of the Kulin Nation as the Traditional Custodians of the land on which RMIT University is located, and pay my respects to Elders past and present.

• University: I acknowledge RMIT University for providing the academic resources, learning environment and support required to complete this work.

• Datasets: I acknowledge the creators and maintainers of the publicly available datasets used in this analysis for making the data accessible for research and educational purposes.

References

Department of Climate Change, Energy, the Environment and Water. (2026, September). Waste exports data collation [Data set]. Commonwealth of Australia. https://www.dcceew.gov.au/environment/protection/waste/exports-data

Evergreen, S., & Emery, A. K. (2016). Updated data visualization checklist. https://stephanieevergreen.com/updated-data-visualization-checklist/

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

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

Segel, E., & Heer, J. (2010). Narrative visualization: Telling stories with data. IEEE Transactions on Visualization and Computer Graphics, 16(6), 1139–1148. https://doi.org/10.1109/TVCG.2010.179

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

Wickham, H. (2016). ggplot2: Elegant graphics for data analysis. Springer. https://ggplot2.tidyverse.org/

Wickham, H., & Bryan, J. (2023). readxl: Read Excel files (R package version 1.4.3) [Computer software]. https://readxl.tidyverse.org/

Wickham, H., François, R., Henry, L., & Müller, K. (2023). dplyr: A grammar of data manipulation (R package version 1.1.4) [Computer software]. https://dplyr.tidyverse.org/

Wickham, H., Vaughan, D., & Girlich, M. (2024). tidyr: Tidy messy data (R package version 1.3.1) [Computer software]. https://tidyr.tidyverse.org/

Xie, Y., Allaire, J. J., & Grolemund, G. (2018). R Markdown: The definitive guide. Chapman and Hall/CRC. https://bookdown.org/yihui/rmarkdown/