Annual operative temperature heatmap

Timeseries data
Simulation
R
Full-year hourly heatmap of operative temperature with overheating and underheating risk thresholds, from hourly timeseries data.
Code
library(tidyverse)
library(scales)

source(here::here("src", "R", "x_theme.R"))

# ------------------------------------------------------------
# DATA REQUIREMENTS
# ------------------------------------------------------------
# Your data should be a dataframe with:
#   - doy:        integer, day of year (1–365)
#   - hour_day:   integer, hour of day (0–23)
#   - living_oat: numeric, zone operative temperature [°C]
# ------------------------------------------------------------

# SAMPLE DATA (replace with your own hourly timeseries)
set.seed(42)
ep_synth <- expand.grid(doy = 1:365, hour_day = 0:23) %>%
  as_tibble() %>%
  mutate(
    annual     = -cos(2 * pi * doy / 365),
    diurnal    = sin(pi * hour_day / 12 - pi / 6),
    living_oat = 21 + 5.5 * annual + 2.5 * diurnal + rnorm(n(), 0, 0.6)
  )

# ------------------------------------------------------------
# PLOT
# ------------------------------------------------------------
ep_synth %>%
  ggplot(aes(x = doy, y = hour_day, fill = living_oat)) +
  geom_tile() +
  scale_fill_gradientn(
    colours = c("#2166AC", "#74ADD1", "#FEE090", "#F46D43", "#A50026"),
    name    = "OT",
    limits  = c(14, 32),
    oob     = squish,
    breaks  = seq(14, 32, by = 3),
    labels  = function(x) paste0(x, "°C / ", round(x * 9 / 5 + 32), "°F")
  ) +
  geom_tile(
    data   = . %>% filter(living_oat > 28),
    fill   = NA, colour = "#F5F3F3", linewidth = 0.1, alpha = 0.6
  ) +
  geom_tile(
    data   = . %>% filter(living_oat < 18),
    fill   = NA, colour = "#5C5C5F", linewidth = 0.1, alpha = 0.6
  ) +
  scale_x_continuous(
    breaks = c(0, cumsum(c(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31))),
    labels = c(month.abb, ""),
    expand = c(0, 0)
  ) +
  scale_y_continuous(breaks = seq(0, 23, 6), expand = c(0, 0)) +
  labs(
    title    = "Annual operative temperature (OT) heatmap — Living zone",
    subtitle = "White-outlined cells exceed 28 °C (overheating risk) · Grey-outlined cells below 18 °C (underheating risk)",
    x = "Month",
    y = "Hour of day"
  ) +
  theme_ep() +
  theme(
    panel.grid        = element_blank(),
    legend.position   = "right",
    legend.key.height = unit(1.5, "cm")
  )

Note

Code: View R code on GitHub · Added by C. Nambiar, T. Kramer