Introduction

Australia’s cost of living crisis is often discussed in terms of rising prices. But the bigger question is whether Australian’s wages have kept up, and how households have responded

For years, wage growth outpaced living costs. That changed around 2023, when household living costs overtook wages. Yet not all households experienced the same pressures, and different expenses rose at different rates

Beyond rising prices, household consumption has shifted, while savings fell sharply from their COVID-19 highs before recovering. Although inflation has slowed, living costs remain high, raising questions about the lasting impacts on household finances

1. Is Household Spending Growing Or Shrinking?

sp_cols   <- c("sp_food", "sp_tobacco", "sp_alcohol", "sp_clothing", "sp_rent", "sp_energy",
               "sp_furnish", "sp_health", "sp_vehicles_buy", "sp_vehicles_op", "sp_transport", "sp_comms",
               "sp_recreation", "sp_education", "sp_hotels", "sp_insurance", "sp_other")
sp_labels <- c("Food", "Cigarettes and tobacco", "Alcoholic beverages", "Clothing and footwear",
               "Rent and other dwelling services", "Electricity, gas and other fuel",
               "Furnishings and household equipment", "Health", "Purchase of vehicles", "Operation of vehicles",
               "Transport services", "Communications", "Recreation and culture", "Education services",
               "Hotels, cafes and restaurants", "Insurance and other financial services", "Other goods and services")


latest <- as.numeric(plot_data[n_q, sp_cols])
since <- (latest / as.numeric(plot_data[1,        sp_cols]) - 1) * 100
since19 <- (latest / as.numeric(plot_data[n_q - 24, sp_cols]) - 1) * 100
one <- (latest / as.numeric(plot_data[n_q - 4,  sp_cols]) - 1) * 100

g1 <- data.frame(label = sp_labels, since = fmt(since), since19 = fmt(since19), one = fmt(one))


g1 <- arrange(g1, since)
g1$label <- factor(g1$label, levels = g1$label)

label_since   <- paste0("Since ", plot_data$quarter_label[1])
label_since19 <- paste0("Since ", plot_data$quarter_label[n_q - 24])
label_one     <- paste0("dates between, ", plot_data$quarter_label[n_q - 4], " to ", plot_data$quarter_label[n_q])


fig1 <- plot_ly(g1, y = ~label) %>%
  add_trace(x = ~since, type = "bar", orientation = "h",
            marker = list(color = ifelse(g1$since >= 0, "#009E73", "#D55E00")),
            text = paste0("<b>", g1$label, "</b><br>", label_since, "<br>", g1$since, "%"),
            hoverinfo = "text", textposition = "none", visible = TRUE) %>%
  add_trace(x = ~since19, type = "bar", orientation = "h",
            marker = list(color = ifelse(g1$since19 >= 0, "#009E73", "#D55E00")),
            text = paste0("<b>", g1$label, "</b><br>", label_since19, "<br>", g1$since19, "%"),
            hoverinfo = "text", textposition = "none", visible = FALSE) %>%
  add_trace(x = ~one, type = "bar", orientation = "h",
            marker = list(color = ifelse(g1$one >= 0, "#009E73", "#D55E00")),
            text = paste0("<b>", g1$label, "</b><br>", label_one, "<br>", g1$one, "%"),
            hoverinfo = "text", textposition = "none", visible = FALSE)

updatemenus <- list(
  list(
    type = "dropdown", x = 0, y = 1.1,
    buttons = list(
      list(label = label_since, method = "update", args = list(list(visible = c(TRUE, FALSE, FALSE)))),
      list(label = label_since19, method = "update", args = list(list(visible = c(FALSE, TRUE, FALSE)))),
      list(label = label_one, method = "update", args = list(list(visible = c(FALSE, FALSE, TRUE))))
    )
  )
)

fig1 <- fig1 %>%
  layout(title = "Real Household Spending by Category",
         margin = list(t = 110),
         showlegend = FALSE,
         xaxis = list(title = "Change in real spending (%)", zeroline = TRUE),
         yaxis = list(title = ""),
         updatemenus = updatemenus)

fig1

Use the drop-down to compare spending changes across three periods, Hover for the exact percentages. Green indicates growth and orange indicates declines, sorted from the largest decrease to the largest increase

Source: ABS (2026), Australian National Accounts: National Income, Expenditure and Product, Cat. no. 5206.0, Table 8.

2. Are Wages Keeping Up With Living Costs?

base_label <- plot_data$quarter_label[1]

growth <- data.frame(quarter = plot_data$quarter,
                     quarter_label = plot_data$quarter_label,
                     wages = plot_data$wpi_r - 100,
                     prices = plot_data$employee_r - 100,
                     gap = plot_data$real_wage - 100)


growth_long <- gather(growth, key = "series", value = "growth", wages:prices)
growth_long$series <- factor(growth_long$series, levels = c("wages", "prices"),
                             labels = c("Wages", "Employee living cost index"))

# Hover text
growth_long$text <- paste0("<b>", growth_long$series, "</b><br>", growth_long$quarter_label,
                           "<br>Growth since ", base_label, ": ", fmt(growth_long$growth), "%",
                           "<br>Wages compared with prices: ", fmt(growth_long$gap), "%")

sd2 <- SharedData$new(growth_long, key = ~quarter_label, group = "quarters")

fig2 <- plot_ly(sd2, x = ~quarter, y = ~growth, color = ~series, colors = c("#1F4E79", "#D55E00"),
                type = "scatter", mode = "lines+markers", text = ~text, hoverinfo = "text") %>%
  layout(
    title = "Cumulative Growth in Wages and Living Costs Since 2015",
    margin = list(t = 110),
    xaxis = list(
      title = "",
      rangeselector = list(x = 0, y = 1.02, yanchor = "bottom", buttons = list(
        list(count = 1, label = "1y", step = "year", stepmode = "backward"),
        list(count = 3, label = "3y", step = "year", stepmode = "backward"),
        list(count = 5, label = "5y", step = "year", stepmode = "backward"),
        list(step = "all", label = "All")
      )),
      rangeslider = list(type = "date")
    ),
    yaxis = list(title = paste0("Cumulative change since ", base_label, " (%)"), ticksuffix = "%",
                 rangemode = "tozero", dtick = 10),
    legend = list(orientation = "h", x = 0, y = -0.35)
  )

highlight(fig2, on = "plotly_click", color = "darkgrey")

Use the time buttons or slider to explore different periods. Hover to compare wage and living cost frowth, and use the legend to show or hide lines.

Sources: ABS (2026), Wage Price Index, Australia, Cat. no. 6345.0, Table 1; ABS (2026), Selected Living Cost Indexes, Australia, Cat. no. 6467.0, Table 1.

3. What Expenses Are Getting More Expensive?

group_cols   <- c("employee", "food", "alcohol", "clothing", "housing", "furnish", "health",
                  "transport", "comms", "recreation", "education", "insurance")
group_labels <- c("<b>All spending combined</b>", "Food and non-alcoholic beverages", "Alcohol and tobacco",
                  "Clothing and footwear", "Housing", "Furnishings, household equipment and services",
                  "Health", "Transport", "Communication", "Recreation and culture", "Education",
                  "Insurance and financial services")


dec_now <- seq(8, n_q, by = 4)
dec_prev <- dec_now - 4
rise <- (plot_data[dec_now, group_cols] / plot_data[dec_prev, group_cols] - 1) * 100
rise$gap <- NA                                 
rise$period <- plot_data$quarter_label[dec_now]

# One row per group per year
heat <- gather(rise, key = "group", value = "rise", 1:13)
heat$group  <- factor(heat$group, levels = c(group_cols, "gap"), labels = c(group_labels, " "))
heat$period <- factor(heat$period, levels = rise$period)

# Order of the groups by their average annual rise, with the overall index and the blank row at the bottom
avg <- heat %>% filter(!is.na(rise), group != group_labels[1]) %>% group_by(group) %>% summarise(mean = mean(rise))
heat$group <- factor(heat$group, levels = c(group_labels[1], " ", as.character(arrange(avg, mean)$group)))

fig3 <- plot_ly(heat, x = ~period, y = ~group, z = ~rise, type = "heatmap",
                colors = "RdYlBu", reversescale = TRUE, zmin = -10, zmax = 10, hoverinfo = "text",
                text = ~ifelse(is.na(rise), NA, paste0("<b>", group, "</b><br>", period, "<br>Prices up ", fmt(rise), "%"))) %>%
  colorbar(title = "Rise (%)") %>%
  layout(title = "Annual Price Changes by Expenditure Category",
         margin = list(t = 80),
         xaxis = list(title = "Year (December)", showgrid = FALSE),
         yaxis = list(title = "", showgrid = FALSE, ticks = ""))

fig3

Hover each square to view annual price changes. Red indicates riding and blue indicates falling prices.

Source: ABS (2026), Selected Living Cost Indexes, Australia, Cat. no. 6467.0, Table 2.

4. How Are Different Households Affected?

now  <- 5:n_q
prev <- 1:(n_q - 4)

rise_wide <- data.frame(
  quarter        = plot_data$quarter[now],
  quarter_label  = plot_data$quarter_label[now],
  employee       = (plot_data$employee[now]/ plot_data$employee[prev] - 1) * 100,
  age_pensioner  = (plot_data$age_pensioner[now]/ plot_data$age_pensioner[prev] - 1) * 100,
  other_transfer = (plot_data$other_transfer[now]/ plot_data$other_transfer[prev] - 1) * 100,
  self_funded    = (plot_data$self_funded[now]/ plot_data$self_funded[prev] - 1) * 100,
  pension_benef  = (plot_data$pension_benef[now]/ plot_data$pension_benef[prev] - 1) * 100
)


long4 <- gather(rise_wide, key = "type", value = "rise", employee:pension_benef)

long4$type <- factor(long4$type,
                     levels = c("employee", "age_pensioner", "other_transfer", "self_funded", "pension_benef"),
                     labels = c("Employed", "Age pensioner", "Other government transfer recipient",
                                "Self-funded retiree", "Pensioner and beneficiary"))

long4$text <- paste0("<b>", long4$type, " households</b><br>", long4$quarter_label,
                     "<br>", fmt(long4$rise), "% on a year earlier")


sd4 <- SharedData$new(long4, key = ~quarter_label, group = "quarters")


p4 <- ggplot(sd4, aes(x = quarter, y = rise, group = type, text = text)) +
  geom_line(colour = "#1F4E79") +
  geom_point(colour = "#1F4E79", size = 1) +
  facet_wrap(~type, ncol = 2, scales = "free_x") +
  labs(x = "", y = "Annual change in living costs (%)") +
  theme_minimal() +
  theme(panel.spacing = unit(2.5, "lines"))

fig4 <- ggplotly(p4, tooltip = "text", height = 900) %>%
  layout(title = ("Annual Living-Cost Changes by Household Type"),
         margin = list(t = 80, l = 70))

highlight(fig4, on = "plotly_click", color = "black")

Hover to see annual living-cost changes for each household types.

Source: ABS (2026), Selected Living Cost Indexes, Australia, Cat. no. 6467.0, Table 1.

5. What Has Happened to Household Savings?

# Hover text
plot_data$saving_hover <- paste0("<b>", plot_data$quarter_label, "</b>",
                                 "<br>Household saving ratio: ", fmt(plot_data$saving_ratio), "% of income")


events <- plot_data[c(which.max(plot_data$saving_ratio), which.min(plot_data$saving_ratio), n_q), ]
events$event <- c("Pandemic saving peak", "Lowest saving ratio", "Saving recovers")
events$hover <- paste0("<b>", events$event, "</b><br>", events$quarter_label,
                       "<br>Household saving ratio: ", fmt(events$saving_ratio), "% of income")


plot_data$saving_hover[plot_data$quarter_label %in% events$quarter_label] <- events$hover

sd5 <- SharedData$new(plot_data, key = ~quarter_label, group = "quarters")

fig5 <- plot_ly(sd5, x = ~quarter, y = ~saving_ratio,
                type = "scatter", mode = "lines+markers",
                line = list(color = "navy", width = 2),
                marker = list(size = 6, color = "navy"),
                text = ~saving_hover, hoverinfo = "text") %>%
  add_trace(data = events, x = ~quarter, y = ~saving_ratio, inherit = FALSE,
            type = "scatter", mode = "markers",
            marker = list(size = 13, color = "orange"), hoverinfo = "skip") %>%
  layout(
    title = "Australia's Household Saving Ratio Since 2015?",
    margin = list(t = 110),
    showlegend = FALSE,
    xaxis = list(title = "",
      rangeselector = list(x = 0, y = 1.02, yanchor = "bottom", buttons = list(
        list(count = 1, label = "1y", step = "year", stepmode = "backward"),
        list(count = 3, label = "3y", step = "year", stepmode = "backward"),
        list(count = 5, label = "5y", step = "year", stepmode = "backward"),
        list(step = "all", label = "All")
      ))
    ),yaxis = list(title = "Household saving ratio (%)", ticksuffix = "%")
  )

highlight(fig5, on = "plotly_click", color = "black")

Use the time buttons to explore different periods and gover to view quaterly saving ratios

Source: ABS (2026), Australian National Accounts: National Income, Expenditure and Product, Cat. no. 5206.0, Table 20.

So, Are Australians Keeping Up?

Not quite. Since 2015, wages have grown by about 32%, while employee households living costs have risen by 36%. Wages were ahead for much of the decade, but living costs overtook them in late 2022

Australians are spending, but growth has slowed. Total real household spending has increased by 24% since 2015, although it grew by only 2.5% in the past year. Spending patterns have also shifted, with some categories growing while others have declined

Living-cost increases have varied over time. Employee households faced a peak of 9.7% in mid-2023, while pensioners now face faster increases. Housing is currently among the fastest-rising costs for employees.

Household saving tells another story. The saving ratio fell from a COVID-19 peak of 23% to just 1.8% in 2023, before recovering to around 7%, above its pre-pandemic level.

So the story is not simply that Australians are spending less or saving less. Living costs have outpaced wage growth, spending patterns have shifted, and financial pressures have changed over time. Although inflation has eased and saving has recovered, the higher cost of living has not disappeared

Notes on the data

-Source: The data comes from three ABS releases: the Wage Price index (Table 1), the seclted living cost indexes (Tables 1 and 2), and National Income, Expenditure and Produce (Tables 8 and 20).

-Period: The charts cover quarterly data from Mar-15 to Dec-25. The files run to June 2026, but later quarters are not used

-Calculations: Growth, annual changes, real wages (wage index divded by price index) and the saving ratio (net saving divided by net disposable income) were calculated all in R from the ABS tables

-Wages and Prices: Wages are the wage Price Index, which measures pay for a fixed set of jobs rather than average earnings. Living costs are the index for employee households (Graphs 2 and 3), and it is not the CPI

-Adjustment: Spending is in chain volume measures, so price changes are removed. Spending and saving are seasonally adjusted but the wage and price indexes are not, so annual changes compare the same quarter a year earlier.

-Limits: Dec-19 serves as the pre-pandemic reference point in Graph 1. Recent household saving figures remain subject to ABS revisions, and the graph illustrate trends rather than establish the cause.

-Colour: The chosen Colours was to help readers with colour blindness, Graph 1 and 2 use orange paired with green or blue

Acknowledgements

I acknowledge the use of Claude (Anthropic, 2026) to help me with debugging code issues during the creation of the visualization. The story, interpretation and the data selection was all done by me

References

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

Australian Bureau of Statistics. (2026a, September 2). Australian national accounts: National income, expenditure and product, June 2026 [Data set]. https://www.abs.gov.au/statistics/economy/national-accounts/australian-national-accounts-national-income-expenditure-and-product/jun-2026

Australian Bureau of Statistics. (2026b, August 5). Selected living cost indexes, Australia, June 2026 [Data set]. Retrieved October 23, 2026, from https://www.abs.gov.au/statistics/economy/price-indexes-and-inflation/selected-living-cost-indexes-australia/latest-release

Australian Bureau of Statistics. (2026c, August 19). Wage price index, Australia, June 2026 [Data set]. Retrieved October 23, 2026, from https://www.abs.gov.au/statistics/economy/price-indexes-and-inflation/wage-price-index-australia/latest-release

Cheng, J., & Sievert, C. (2025). crosstalk: Inter-widget interactivity for HTML widgets (Version 1.2.2) [Computer software]. https://doi.org/10.32614/CRAN.package.crosstalk

Müller, K. (2025). here: A simpler way to find your files (Version 1.0.2) [Computer software]. https://doi.org/10.32614/CRAN.package.here

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. (2025). readxl: Read Excel files (Version 1.4.5) [Computer software]. https://doi.org/10.32614/CRAN.package.readxl

Wickham, H., François, R., Henry, L., Müller, K., & Vaughan, D. (2026). dplyr: A grammar of data manipulation (Version 1.2.1) [Computer software]. https://doi.org/10.32614/CRAN.package.dplyr

Wickham, H., Vaughan, D., & Girlich, M. (2025). tidyr: Tidy messy data (Version 1.3.2) [Computer software]. https://doi.org/10.32614/CRAN.package.tidyr