A hot day is not the same for everyone

Shade is a public good, but it is not evenly spread. Using satellite measurements of ground temperature and tree cover for 10,113 small areas across metropolitan Melbourne (ABS Statistical Area Level 1, or SA1), this story asks one question: are the places with the least shade also the places where people are least able to cope with heat?

The short answer is mostly yes, with an important twist. Areas with the highest heat vulnerability score average about 10.3 degrees C above the non-urban baseline and 6.4% tree cover. The least vulnerable average about 4.3 degrees C and 25.8%.

1. The map: heat, shade and vulnerability line up

Choose a layer at the top right. Switch between heat, tree cover and heat vulnerability and watch which parts of the map change together. Click any area for details.

Chart description: a map of metropolitan Melbourne. Heat and low tree cover are highest in the west and north-west and in a south-eastern patch. Tree cover is highest in the east and north-east. Heat vulnerability scores are highest in the west, north-west and along the south-east corridor.

2. Where trees are scarce, it is hotter

Each dot below is one SA1 area. Areas with less tree cover tend to run hotter (correlation r = -0.72), and the darkest dots, the most vulnerable areas, cluster in the hot, low-tree corner. Click a legend item to hide or show a vulnerability group.

Chart description: a scatter plot of tree cover (horizontal) against heat (vertical) for about 9,900 areas. Heat falls as tree cover rises. Dark, high-vulnerability points sit at the top left, and pale, low-vulnerability points at the bottom right.

The pattern is not perfect. Some places with very little tree cover are not hot. Hover over Docklands, for example. It has low tree cover but a low heat reading, which may be because it sits beside water, which satellite surface temperature reads as cooler. Trees are not the only thing that matters: roads, roofs and industrial land also drive heat.

3. Shade is a vulnerability story too

Grouping areas by how much tree cover they have makes the gap clear. In areas with under 5% tree cover, about 67% score 4 or 5 for heat vulnerability. In areas with over 30% tree cover, only about 2.5% do.

Chart description: five stacked bars, one per tree cover band. The bar for under 5% tree cover is mostly dark (scores 4 and 5). The bar for over 30% is mostly light (scores 1 and 2).

4. The twist: shade alone does not decide who is at risk

Regions of Melbourne tell a more complicated story. Pick a measure and see whether the same regions come out worst each time. The order stays fixed, with the least shaded region at the top.

Chart description: horizontal bars for nine Melbourne regions, ordered from least to most tree cover. Buttons switch between tree cover, heat, and the share of areas with a high vulnerability score.

Melbourne - West has the least tree cover (about 3.9%) and the highest heat (about 9.8 degrees C), and about 69% of its areas score 4 or 5. Melbourne - Inner also has little tree cover (about 13.1%) and runs hot (8.1 degrees C), yet only about 9% of its areas score high. Melbourne - South East has more tree cover (about 16.6%), but about 58% of its areas score 4 or 5.

So exposure to heat and ability to cope with it are different things. The vulnerability score includes who lives in an area, not just the weather.

5. Find your suburb

Type a suburb name into the box (or click a dot) to see where it sits. Colour shows how many of its areas score 4 or 5.

Chart description: a scatter plot of about 300 suburb-sized areas by tree cover and heat, coloured by the share of areas with high vulnerability. A search box highlights a chosen suburb.

What this data cannot tell us

  • The data is from 2018. Melbourne has grown and changed since, so this is a baseline, not today’s picture.
  • Heat is satellite-measured surface temperature, compared with a non-urban baseline. It is not the air temperature people feel.
  • The vulnerability score is based on quintiles (1 to 5) and includes demographic factors, so it describes where vulnerable people live relative to heat and shade. It does not show that trees cause the temperature difference.
  • Averages are weighted by land area. Large, low-density outer areas count for more than small, dense inner ones, and the charts count areas, not people.
  • Suburb names are ABS SA2 areas, which are close to suburbs but not identical. Some combine two suburbs.
  • Regions differ. The Mornington Peninsula includes a lot of bush and farmland, so it is not directly comparable with built-up regions.

Data and method

Two open datasets from the Victorian Government were used, both licensed CC BY 4.0. Mesh Block values for heat and tree cover were averaged to SA1 level using land area as weights, then joined to the Heat Vulnerability Index on the SA1 code. Areas with a vulnerability score of 0 were treated as having no score (176 areas). Map shapes were simplified to keep the file small. The full preparation code is in the appendix.

The data come from two datasets published by the Department of Transport and Planning (2025, 2026). Analysis and charts were produced in R (R Core Team, 2025) using the sf (Pebesma, 2018; Pebesma & Bivand, 2023), dplyr (Wickham et al., 2023), readr (Wickham et al., 2024), leaflet (Cheng et al., 2025), plotly (Sievert, 2020) and crosstalk (Cheng & Sievert, 2023) packages. Code and text were developed with assistance from Claude (Anthropic, 2026).

I used Claude (Anthropic, 2026) to write and fix errors in my R code, to draft and correct the grammar of my text, and to understand R Markdown and how to publish my report. I chose the topic, downloaded the data, ran the code, and checked all results. I reviewed and checked everything Claude produced, and I take responsibility for the final report and submission.

References

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

Cheng, J., Schloerke, B., Karambelkar, B., Xie, Y., & Aden-Buie, G. (2025). leaflet: Create interactive web maps with the JavaScript ‘Leaflet’ library (R package version 2.2.3) [Computer software]. https://doi.org/10.32614/CRAN.package.leaflet

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

Department of Transport and Planning. (2025). Metropolitan Melbourne urban heat islands and urban vegetation 2018 [Data set]. https://discover.data.vic.gov.au/dataset/metropolitan-melbourne-urban-heat-islands-and-urban-vegetation-2018

Department of Transport and Planning. (2026). Metropolitan Melbourne heat vulnerability index 2018 [Data set]. https://discover.data.vic.gov.au/dataset/metropolitan-melbourne-heat-vulnerability-index-2018

Pebesma, E. (2018). Simple features for R: Standardized support for spatial vector data. The R Journal, 10(1), 439-446. https://doi.org/10.32614/RJ-2018-009

Pebesma, E., & Bivand, R. (2023). Spatial data science: With applications in R. Chapman and Hall/CRC. https://doi.org/10.1201/9780429459016

R Core Team. (2025). R: A language and environment for statistical computing. 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

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

Wickham, H., Hester, J., & Bryan, J. (2024). readr: Read rectangular text data (R package version 2.1.5) [Computer software]. https://doi.org/10.32614/CRAN.package.readr

Appendix: data preparation code

# Data preparation: Mesh Blocks to SA1, joined to the Heat Vulnerability Index
# Raw data: Victorian DataShare (Department of Transport and Planning), CC BY 4.0
#   HEAT_URBAN_HEAT_2018.shp          (Mesh Block level heat and vegetation)
#   HEAT_VULNERABILITY_INDEX_2018.shp (SA1 level vulnerability score)

library(sf)
library(dplyr)
library(readr)
library(janitor)
library(rmapshaper)

# Raw shapefiles were downloaded and saved locally
path_uhi <- "HEAT_URBAN_HEAT_2018.shp"
path_hvi <- "HEAT_VULNERABILITY_INDEX_2018.shp"
dir.create("clean", showWarnings = FALSE)

# ---- Load ----
uhi <- st_read(path_uhi, quiet = TRUE) |> clean_names()
hvi <- st_read(path_hvi, quiet = TRUE) |> clean_names()

# ---- Coordinate system: metres, so areas can be calculated ----
uhi_m <- st_transform(uhi, 7855)   # GDA2020 / MGA zone 55

# ---- Clean Mesh Block data ----
mb <- uhi_m |>
  mutate(area_m2 = as.numeric(st_area(geometry))) |>
  st_drop_geometry() |>
  transmute(
    mb_code = mb_code16, sa1 = sa1_main16,
    sa2_name = sa2_name16, sa4_name = sa4_name16, lga = lga, area_m2,
    heat_c = uhi18_m, tree_pct = peranytree,
    tree_3_10 = pertr03_10, tree_10_15 = pertr10_15, tree_15plus = pertr15pl,
    shrub_pct = pershrub, grass_pct = pergrass
  )

# ---- Aggregate Mesh Blocks to SA1 (area-weighted means) ----
sa1_env <- mb |>
  group_by(sa1) |>
  summarise(
    n_meshblocks = n(),
    area_km2     = sum(area_m2) / 1e6,
    heat_c       = weighted.mean(heat_c,      area_m2),
    tree_pct     = weighted.mean(tree_pct,    area_m2),
    tree_3_10    = weighted.mean(tree_3_10,   area_m2),
    tree_10_15   = weighted.mean(tree_10_15,  area_m2),
    tree_15plus  = weighted.mean(tree_15plus, area_m2),
    .groups = "drop"
  )

# ---- Vulnerability data (a score of 0 is treated as "no score") ----
hvi_clean <- hvi |>
  transmute(
    sa1 = sa1_main16, sa2_name = sa2_name16, sa3_name = sa3_name16,
    sa4_name = sa4_name16, pop_density = popu_dens, pct_care = per_care,
    hvi = na_if(hvi_index, 0), geometry
  )

# ---- Join on the SA1 code ----
sa1_all <- hvi_clean |>
  left_join(sa1_env, by = "sa1") |>
  mutate(
    hvi_label = factor(hvi, levels = 1:5,
                       labels = c("1 Lowest", "2", "3", "4", "5 Highest"),
                       ordered = TRUE),
    canopy_band = cut(tree_pct,
                      breaks = c(-Inf, 5, 10, 20, 30, Inf),
                      labels = c("Under 5%", "5-10%", "10-20%",
                                 "20-30%", "Over 30%"))
  )

# ---- Save outputs used by the charts ----
write_csv(st_drop_geometry(sa1_all), "clean/sa1_heat_canopy_hvi.csv")

sa1_map <- sa1_all |>
  st_transform(4326) |>
  ms_simplify(keep = 0.10, keep_shapes = TRUE)

saveRDS(sa1_map, "clean/sa1_map.rds")