Monthly SET comfort breakdown

Timeseries data
Simulation
R
Stacked bar chart of occupied hours by Standard Effective Temperature (SET) comfort category across months.
Code
library(tidyverse)
library(scales)

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

# ------------------------------------------------------------
# DATA REQUIREMENTS
# ------------------------------------------------------------
# Your data should be a dataframe with:
#   - set:       numeric, Standard Effective Temperature [°C]
#   - month_num: integer, month number (1–12)
#   - occupied:  integer, 0/1 occupancy indicator
# ------------------------------------------------------------

# 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(
    month_num = as.integer(format(as.Date(doy - 1, origin = "2025-01-01"), "%m")),
    annual    = -cos(2 * pi * doy / 365),
    diurnal   = sin(pi * hour_day / 12 - pi / 6),
    set       = 23 + 2.5 * annual + 1.2 * diurnal + rnorm(n(), 0, 0.5),
    occupied  = as.integer(hour_day >= 7 & hour_day <= 22)
  )

# Monthly SET breakdown (ASHRAE 55 comfort range: 22.2–25.6 °C)
monthly_comfort <- ep_synth %>%
  filter(occupied > 0) %>%
  mutate(
    comfort_cat = factor(case_when(
      set < 22.2 ~ "Cool discomfort",
      set > 25.6 ~ "Warm discomfort",
      TRUE        ~ "Comfortable"
    ), levels = names(comfort_colours))
  ) %>%
  count(month_num, comfort_cat) %>%
  group_by(month_num) %>%
  mutate(pct = n / sum(n) * 100) %>%
  ungroup() %>%
  mutate(month = factor(month.abb[month_num], levels = month.abb))

# ------------------------------------------------------------
# PLOT
# ------------------------------------------------------------
ggplot(monthly_comfort, aes(x = month, y = pct, fill = comfort_cat)) +
  geom_col(width = 0.7) +
  scale_fill_manual(values = comfort_colours, name = NULL) +
  scale_y_continuous(labels = label_percent(scale = 1)) +
  labs(
    title    = "Monthly SET comfort breakdown — Living zone",
    subtitle = "% of occupied hours in each SET comfort category  |  ASHRAE 55 range: 22.2–25.6 °C",
    x = NULL,
    y = "% occupied hours"
  ) +
  theme_ep()

Note

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