This chart shows how rental stress has increased over time for the lowest-income Australians.
table12 <- read_excel(file, sheet = "Table 1.2", skip = 4) %>%
filter(!is.na(`1994–95`)) %>%
rename(category = 1)
## New names:
## • `` -> `...1`
## • `` -> `...2`
rent_costs_q1 <- table12 %>% filter(category == "Lowest quintile")
rent_costs_q2 <- table12 %>% filter(category == "Second quintile")
rent_costs_q1 <- rent_costs_q1 %>% mutate(across(-category, ~as.numeric(.)))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `across(-category, ~as.numeric(.))`.
## Caused by warning:
## ! NAs introduced by coercion
rent_costs_q2 <- rent_costs_q2 %>% mutate(across(-category, ~as.numeric(.)))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `across(-category, ~as.numeric(.))`.
## Caused by warning:
## ! NAs introduced by coercion
table13 <- read_excel(file, sheet = "Table 1.3", skip = 4) %>%
filter(!is.na(`1994–95`)) %>%
rename(category = 1)
## New names:
## • `` -> `...1`
## • `` -> `...2`
income_q1 <- table13 %>% filter(category == "Lowest quintile")
income_q2 <- table13 %>% filter(category == "Second quintile")
income_q1 <- income_q1 %>% mutate(across(-category, ~as.numeric(.)))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `across(-category, ~as.numeric(.))`.
## Caused by warning:
## ! NAs introduced by coercion
income_q2 <- income_q2 %>% mutate(across(-category, ~as.numeric(.)))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `across(-category, ~as.numeric(.))`.
## Caused by warning:
## ! NAs introduced by coercion
year_cols <- intersect(names(rent_costs_q1), names(income_q1))
stress_q1 <- (as.numeric(rent_costs_q1[1, year_cols]) /
as.numeric(income_q1[1, year_cols])) * 100
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
stress_q2 <- (as.numeric(rent_costs_q2[1, year_cols]) /
as.numeric(income_q2[1, year_cols])) * 100
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
stress_avg <- (stress_q1 + stress_q2) / 2
rent_stress <- tibble(
year = year_cols,
stress = stress_avg
)
fig1 <- plot_ly(
data = rent_stress,
x = ~year,
y = ~stress,
type = 'scatter',
mode = 'lines+markers',
line = list(color = '#D8352C', width = 3),
marker = list(color = '#D8352C', size = 6)
) %>%
layout(
title = "Approximate Official Rental Stress (Lowest 40% Income Households)",
xaxis = list(title = "Year"),
yaxis = list(title = "% of income spent on housing"),
width = 600
)
## Warning: Specifying width/height in layout() is now deprecated.
## Please specify in ggplotly() or plot_ly()
fig1
This multivariate chart compares rental stress across all income groups.
# ---- load table 1.2 (costs) ----
table12 <- read_excel(file, sheet = "Table 1.2", skip = 4)
## New names:
## • `` -> `...1`
## • `` -> `...2`
names(table12) <- names(table12) %>%
str_replace_all("\\(.*?\\)", "") %>%
str_replace_all("–", "-") %>%
str_trim()
table12 <- table12 %>%
filter(!is.na(`1994-95`)) %>%
rename(category = 1)
quintiles <- c(
"Lowest quintile",
"Second quintile",
"Third quintile",
"Fourth quintile",
"Highest quintile"
)
rent_costs <- table12 %>%
filter(category %in% quintiles)
# ---- load table 1.3 (income) ----
table13 <- read_excel(file, sheet = "Table 1.3", skip = 4)
## New names:
## • `` -> `...1`
## • `` -> `...2`
names(table13) <- names(table13) %>%
str_replace_all("\\(.*?\\)", "") %>%
str_replace_all("–", "-") %>%
str_trim()
table13 <- table13 %>%
filter(!is.na(`1994-95`)) %>%
rename(category = 1)
income <- table13 %>%
filter(category %in% quintiles)
# ---- strict year columns ----
years_rent <- names(rent_costs)[grepl("^[0-9]{4}-[0-9]{2}$", names(rent_costs))]
years_income <- names(income)[grepl("^[0-9]{4}-[0-9]{2}$", names(income))]
year_cols <- intersect(years_rent, years_income)
rent_costs[year_cols] <- lapply(rent_costs[year_cols], as.numeric)
income[year_cols] <- lapply(income[year_cols], as.numeric)
# ---- build stress_df (no purrr) ----
stress_df <- data.frame()
for (q in quintiles) {
rent_vals <- unlist(rent_costs[rent_costs$category == q, year_cols])
income_vals <- unlist(income[income$category == q, year_cols])
# skip if missing
if (length(rent_vals) == 0 || length(income_vals) == 0) next
# ensure equal length
n <- min(length(rent_vals), length(income_vals))
temp <- data.frame(
category = rep(q, n),
year = year_cols[1:n],
stress = (rent_vals[1:n] / income_vals[1:n]) * 100
)
stress_df <- rbind(stress_df, temp)
}
# ---- plot ----
fig2 <- plot_ly()
for (cat in unique(stress_df$category)) {
temp <- stress_df %>% filter(category == cat)
# convert year to numeric index
temp$year_num <- seq_along(temp$year)
# safe LOESS fit
smoothed <- tryCatch(
{
predict(loess(stress ~ year_num, data = temp))
},
error = function(e) {
# fallback: use raw values if smoothing fails
temp$stress
}
)
fig2 <- fig2 %>%
add_trace(
x = temp$year,
y = smoothed,
type = "scatter",
mode = "lines",
name = cat
)
}
fig2 <- fig2 %>%
layout(
title = "Rental Stress by Income Quintile (Smoothed)",
xaxis = list(title = "Year"),
yaxis = list(title = "% of income spent on housing"),
width = 600
)
## Warning: Specifying width/height in layout() is now deprecated.
## Please specify in ggplotly() or plot_ly()
fig2
This chart shows how different household types experience different housing cost pressures.
# -----------------------------
# 1. Define household categories
# -----------------------------
households <- c(
"Couple family with dependent children",
"One parent family with dependent children",
"Couple only",
"Other one family households",
"Multiple family households",
"Lone person households",
"Group households"
)
# -----------------------------
# 2. Filter rows
# -----------------------------
house_costs <- table12 %>%
filter(category %in% households)
# -----------------------------
# 3. Strict year detection
# -----------------------------
years_house <- names(house_costs)[grepl("^[0-9]{4}-[0-9]{2}$", names(house_costs))]
# -----------------------------
# 4. Convert to numeric safely
# -----------------------------
house_costs <- house_costs %>%
mutate(across(all_of(years_house), ~ as.numeric(as.character(.))))
# -----------------------------
# 5. Build long dataframe
# -----------------------------
house_df <- data.frame()
for (h in households) {
vals <- unlist(house_costs[house_costs$category == h, years_house])
temp <- data.frame(
household = h,
year = years_house,
cost = vals
)
house_df <- rbind(house_df, temp)
}
# -----------------------------
# 6. Plot Graph 3
# -----------------------------
fig3 <- plot_ly()
for (cat in unique(house_df$household)) {
temp <- house_df %>% filter(household == cat)
temp$year_num <- seq_along(temp$year)
smoothed <- tryCatch(
{
predict(loess(cost ~ year_num, data = temp))
},
error = function(e) {
temp$cost
}
)
fig3 <- fig3 %>%
add_trace(
x = temp$year,
y = smoothed,
type = "scatter",
mode = "lines",
name = cat
)
}
fig3 <- fig3 %>%
layout(
title = "Housing Costs by Household Type (Smoothed)",
xaxis = list(title = "Year"),
yaxis = list(title = "Weekly Housing Costs ($)"),
width = 600
)
## Warning: Specifying width/height in layout() is now deprecated.
## Please specify in ggplotly() or plot_ly()
fig3
This stacked area chart shows how the proportion of households in each stress category has changed.
# -----------------------------
# 1. Define rental stress categories
# -----------------------------
stress_cats <- c(
"25% or less",
"More than 25% to 30%",
"More than 30% to 50%",
"More than 50%"
)
# -----------------------------
# 2. Filter rows from Table 1.3
# -----------------------------
stress_data <- table13 %>%
filter(category %in% stress_cats)
# -----------------------------
# 3. Strict year detection
# -----------------------------
years_stress <- names(stress_data)[grepl("^[0-9]{4}-[0-9]{2}$", names(stress_data))]
# -----------------------------
# 4. Convert to numeric safely
# -----------------------------
stress_data <- stress_data %>%
mutate(across(all_of(years_stress), ~ as.numeric(as.character(.))))
# -----------------------------
# 5. Build long dataframe
# -----------------------------
stress_df <- data.frame()
for (s in stress_cats) {
vals <- unlist(stress_data[stress_data$category == s, years_stress])
temp <- data.frame(
stress_category = s,
year = years_stress,
percent = vals
)
stress_df <- rbind(stress_df, temp)
}
# -----------------------------
# 6. Plot Graph 4
# -----------------------------
# --- Auto-detect the category column (character/factor) ---
cat_col <- names(stress_df)[sapply(stress_df, is.character) | sapply(stress_df, is.factor)]
cat_col <- cat_col[cat_col != "year"][1] # exclude year
# --- Auto-detect the numeric column (percentage values) ---
num_col <- names(stress_df)[sapply(stress_df, is.numeric) & names(stress_df) != "year"][1]
# --- Build stacked area chart safely ---
fig4 <- plot_ly(
data = stress_df,
x = ~year,
y = as.formula(paste0("~`", num_col, "`")),
color = as.formula(paste0("~`", cat_col, "`")),
type = "scatter",
mode = "none",
stackgroup = "one"
) %>%
layout(
title = "Distribution of Households by Rental Stress Category (Stacked Area)",
xaxis = list(title = "Year"),
yaxis = list(title = "Percentage of Households (%)"),
width = 600
)
## Warning: Specifying width/height in layout() is now deprecated.
## Please specify in ggplotly() or plot_ly()
fig4
This chart compares long-term cost trends across dwelling types.
# -----------------------------
# 1. Dwelling categories that exist
# -----------------------------
dwellings <- c(
"Separate house",
"Semi-detached, row or terrace house, townhouse",
"Flat or apartment"
)
# -----------------------------
# 2. Filter rows
# -----------------------------
dwelling_costs <- table12 %>%
filter(category %in% dwellings)
# -----------------------------
# 3. Detect year columns
# -----------------------------
years_dwelling <- names(dwelling_costs)[grepl("^[0-9]{4}-[0-9]{2}$", names(dwelling_costs))]
# -----------------------------
# 4. Convert to numeric
# -----------------------------
dwelling_costs <- dwelling_costs %>%
mutate(across(all_of(years_dwelling), ~ as.numeric(as.character(.))))
# -----------------------------
# 5. Build long dataframe
# -----------------------------
dwelling_df <- data.frame()
for (d in dwellings) {
vals <- unlist(dwelling_costs[dwelling_costs$category == d, years_dwelling])
if (length(vals) == 0) next
base <- vals[1] # first year value
pct_change <- ((vals - base) / base) * 100
temp <- data.frame(
dwelling = rep(d, length(vals)),
year = years_dwelling,
pct_change = pct_change
)
dwelling_df <- rbind(dwelling_df, temp)
}
##
dwellings <- c(
"Separate house",
"Semi-detached, row or terrace house, townhouse",
"Flat or apartment"
)
dwelling_costs <- table12 %>%
filter(category %in% dwellings)
years_dwelling <- names(dwelling_costs)[grepl("^[0-9]{4}-[0-9]{2}$", names(dwelling_costs))]
dwelling_costs <- dwelling_costs %>%
mutate(across(all_of(years_dwelling), ~ as.numeric(as.character(.))))
dwelling_df <- data.frame()
for (d in dwellings) {
vals <- unlist(dwelling_costs[dwelling_costs$category == d, years_dwelling])
if (length(vals) == 0) next
temp <- data.frame(
dwelling = rep(d, length(vals)),
year = years_dwelling,
cost = vals
)
dwelling_df <- rbind(dwelling_df, temp)
}
# -----------------------------
# 6. Plot percentage-change graph
# -----------------------------
fig5 <- plot_ly()
for (cat in unique(dwelling_df$dwelling)) {
temp <- dwelling_df %>% filter(dwelling == cat)
temp$year_num <- seq_along(temp$year)
smoothed <- tryCatch(
{
predict(loess(cost ~ year_num, data = temp))
},
error = function(e) {
temp$cost
}
)
fig5 <- fig5 %>%
add_trace(
x = temp$year,
y = smoothed,
type = "scatter",
mode = "lines",
name = cat
)
}
fig5 <- fig5 %>%
layout(
title = "Housing Costs by Dwelling Type (Smoothed)",
xaxis = list(title = "Year"),
yaxis = list(title = "Weekly Housing Costs ($)"),
width = 600
)
## Warning: Specifying width/height in layout() is now deprecated.
## Please specify in ggplotly() or plot_ly()
fig5
ABS. (2023). Housing occupancy and costs, Australia.