Comfort Plots#
Two comfort-related plots:
SIA 180 — Room temp vs 48h outdoor temp with SIA 180:2014 boundaries
Temperature vs Humidity — Scatter with comfort zone polygons
All plots are interactive — hover to see exact values.
Tip
How to read these plots: Reading the comfort compass (reading the compass, comparing rooms, comfort zone and warning bands, saving energy) and The h,x diagram in five minutes (reading the h,x diagram).
import plotly.io as pio
pio.renderers.default = "notebook_connected"
SIA 180 Thermal Comfort#
Room temperature vs 48-hour rolling mean outdoor temperature, with SIA 180:2014 comfort boundaries.
import pandas as pd
from importlib import resources
df_oa = pd.read_csv(
resources.files("pyedautils") / "data" / "outside_temp.csv", sep=";")
df_r = pd.read_csv(
resources.files("pyedautils") / "data" / "flat_temp.csv", sep=";")
from pyedautils.plots import plot_comfort_sia180
fig = plot_comfort_sia180(df_oa, df_r)
fig.show()
Temperature vs Humidity#
Temperature vs humidity with comfort zone polygons.
# Anonymised hourly room climate (a cool, humidity-controlled room).
df_th = pd.read_csv(
resources.files("pyedautils") / "data" / "room_climate_sample.csv",
parse_dates=["timestamp"])
df_th.head()
| timestamp | humidity | temperature | |
|---|---|---|---|
| 0 | 2025-05-01 00:00:00 | 51.0 | 19.1 |
| 1 | 2025-05-01 01:00:00 | 51.0 | 19.1 |
| 2 | 2025-05-01 02:00:00 | 51.0 | 19.1 |
| 3 | 2025-05-01 03:00:00 | 51.0 | 19.1 |
| 4 | 2025-05-01 04:00:00 | 50.2 | 19.1 |
from pyedautils.plots import plot_comfort_temp_humidity
fig = plot_comfort_temp_humidity(df_th)
fig.show()
Comfort donuts#
plot_comfort_donuts summarises how much time temperature and humidity spend below / within / above the comfort band.
from pyedautils.plots import plot_comfort_donuts
# Daily means so the slices count days (the CSV is hourly).
df_daily = df_th.set_index("timestamp").resample("D").mean()
fig = plot_comfort_donuts(df_daily, temp_range=(8, 18), hum_range=(40, 70))
fig.show()
Comfort compass#
plot_comfort_compass is an area-true polar glyph. The dashed circle is the area of 100 % of the days; the green disc and the arms always add up to exactly that area.
Green disc — its area is the share of days in range. It says so: the share plus the in-range word (e.g.
52%/in range). When the disc is too small for both lines only the share is printed, and when even that does not fit, nothing; without the legend a caption below the glyph then gives it (● 8% in range).Eight arms — the deviation directions (warm top, cold bottom, humid right, dry left, mixes diagonal), each split from the inside out into mild / moderate / severe. Severity is encoded as lightness on three fixed planes shared by all directions — mild is always the lightest shade, severe the darkest. A long arm is not “more than 100 %”: it is area moved out of the disc.
Labels — each direction label sits just beyond the end of its own arm, anchored outward.
Legend — each row breaks its share down by stage (e.g.
19% → 5/4/10%), with a grey mild/moderate/severe key underneath. Hover a segment for its count and share.
Comparing rooms. By default (scale="fixed", same as fixed_scale=True) the radial axis is framed to the largest possible arm, so the dashed 100 % circle is the same size in every chart and equal areas mean equal numbers of days — give the figures the same size. With scale="fit" each glyph is zoomed to itself and its labels so it fills the panel; the circle then differs from chart to chart, so compare such charts by their percentages only.
comfort.comfort_compass_distribution does the classification — by relative humidity, or relative or absolute humidity (the comfort-zone cap) via hum_abs_band.
Texts. Every visible text can be passed: title, names (legend/hover names and the in-range word), direction_labels, stage_names, count_label / count_label_singular and stats_text. The second example uses the German presets from pyedautils.plots.comfort.
from pyedautils.comfort import comfort_compass_distribution
from pyedautils.plots import plot_comfort_compass
# Anonymised hourly room climate; index by time so the compass counts days.
df_room = pd.read_csv(
resources.files("pyedautils") / "data" / "room_climate_sample.csv")
df_room["timestamp"] = pd.to_datetime(df_room["timestamp"])
df_room = df_room.set_index("timestamp")
# Classify against this room's own target band (a cool, humidity-controlled room)
dist = comfort_compass_distribution(df_room, temp_band=(8, 18), hum_band=(40, 70))
fig = plot_comfort_compass(dist, title="Comfort compass",
stats_text="T min 6.2 / mean 12.4 / max 18.9 °C")
fig.show()
from pyedautils.plots.comfort import (
_COMPASS_LABELS_DE, _COMPASS_NAMES_DE, _COMPASS_STAGE_NAMES_DE)
# Zoomed to this glyph, without legend, all texts in German.
fig = plot_comfort_compass(
dist, title="Komfort-Kompass", scale="fit", show_legend=False,
names=_COMPASS_NAMES_DE, direction_labels=_COMPASS_LABELS_DE,
stage_names=_COMPASS_STAGE_NAMES_DE,
count_label="Tage", count_label_singular="Tag")
fig.show()
Overheating analysis (SIA 180)#
The pyedautils.comfort module provides the SIA 180 adaptive boundary curves and overheating metrics. align_hourly joins room and outdoor temperature and adds the 48 h running mean used by the curves.
from pyedautils import comfort
aligned = comfort.align_hourly(df_r, df_oa)
total_h, threshold = comfort.overheating_hours(
aligned, method="adaptive", summer_only=True)
print(f"Overheating hours: {total_h:.0f}")
comfort.comfort_kpis(aligned, summer_only=True)
Overheating hours: 517
{'n_hours': 7937,
'h_cold': 0.0,
'h_warm': 517.0,
'h_ok': 7420.0,
'pct_ok': 93.5,
'overheating_h': 517.0,
'sia180_compliant': False,
'minergie_compliant': False}
Overheating charts#
plot_overheating_timeseries shows the room temperature with the comfort threshold and the overheating samples highlighted; plot_overheating_bar shows the overheating hours per month. Both mirror the interactive Streamlit overheating page.
from pyedautils.plots import (
plot_overheating_timeseries, plot_overheating_bar)
fig = plot_overheating_timeseries(aligned, method='adaptive', summer_only=True)
fig.show()
monthly = comfort.overheating_per_month(aligned, threshold)
fig = plot_overheating_bar(monthly)
fig.show()
Temperature & humidity over time#
plot_temp_humidity_timeseries plots temperature and humidity as two stacked time series. Each shows the hourly readings as faint dots (temperature red, humidity blue) and the daily mean as a black line (toggle with show_hourly / show_daily_mean), the daily line breaking at data gaps. On top it draws the green comfort target band (solid edge lines) plus the Moderate and Severe warning limits. The bands default to the Mollier h,x comfort zone (T 20–26 °C, φ 30–65 %, ±1 K/5 % and ±2.5 K/10 %); pass your own temp_band / hum_band etc. to override (here the organ room’s 8–18 °C / 40–70 %). Each subplot gets its own legend, and optional temp_title / hum_title headings. Hover labels use T / φ like the h,x-diagram.
from pyedautils.plots import plot_temp_humidity_timeseries
# This organ room is kept cool, so override the default h,x comfort zone
# with its own target band (8-18 °C / 40-70 %).
fig = plot_temp_humidity_timeseries(
df_th,
temp_band=(8, 18), hum_band=(40, 70),
temp_band_orange=(7, 19), hum_band_orange=(35, 75),
temp_band_red=(5.5, 20.5), hum_band_red=(30, 80),
temp_title="Temperature", hum_title="Humidity",
title="Room climate over time",
)
fig.show()