comfort#
Thermal comfort and overheating metrics per SIA 180:2014 — adaptive boundary curves, summer-half-year filter, overheating hours and KPIs (including the Minergie and SIA 180 residential limits).
It also classifies samples for the comfort compass
(comfort_compass_distribution, with an optional absolute-humidity limit) and
turns its severity-stage limits into the ± offsets of the orange / red warning
bands of plot_mollier_hx (comfort_warning_bands), so the h,x diagram and
the compass share one definition.
See Comfort Plots for usage examples. The plotting
counterparts live in plots; the day counts of
comfort_compass_distribution are drawn by plot_comfort_compass (green
disc = share in range, arms = deviation direction and severity,
scale="fixed" or "fit").
API Reference#
Thermal comfort and overheating metrics per SIA 180:2014.
Provides the adaptive SIA 180 comfort boundary curves (lower heating setpoint and upper cooling limit as a function of the 48-hour running mean outdoor temperature), a summer-half-year filter, and overheating metrics (overheating hours, monthly breakdown, comfort KPIs) including the Minergie and SIA 180 residential limits.
The plotting counterparts live in pyedautils.plots.comfort.
- pyedautils.comfort.MINERGIE_OVERHEATING_LIMIT_H = 100.0#
max 100 h/year above the adaptive boundary.
- Type:
Minergie overheating limit
- pyedautils.comfort.SIA180_RESIDENTIAL_OVERHEATING_LIMIT_H = 400.0#
max 400 h/year.
- Type:
SIA 180 residential overheating limit
- pyedautils.comfort.FIXED_OVERHEATING_THRESHOLD = 26.5#
Fixed overheating threshold [°C] (Minergie / SIA 180 fixed criterion).
- pyedautils.comfort.sia180_max_temp(t_oa_48h) ndarray[source]#
SIA 180 adaptive maximum comfort temperature [°C].
Upper comfort boundary as a function of the 48-hour running mean outdoor temperature. Below
ADAPTIVE_MAX_T_OA_LOthe limit is constant atADAPTIVE_MAX_T_ROOM_LO, aboveADAPTIVE_MAX_T_OA_HIit is constant atADAPTIVE_MAX_T_ROOM_HI, and linear in between.- Parameters:
t_oa_48h – Scalar or array of 48-hour running mean outdoor temperatures [°C].
- Returns:
Maximum comfort temperature [°C], same shape as input.
- Return type:
np.ndarray
- pyedautils.comfort.sia180_min_temp(t_oa_48h) ndarray[source]#
SIA 180 adaptive minimum comfort (heating setpoint) temperature [°C].
Lower comfort boundary as a function of the 48-hour running mean outdoor temperature. Below
ADAPTIVE_MIN_T_OA_LOthe limit is constant atADAPTIVE_MIN_T_ROOM_LO, aboveADAPTIVE_MIN_T_OA_HIit is constant atADAPTIVE_MIN_T_ROOM_HI, and linear in between.- Parameters:
t_oa_48h – Scalar or array of 48-hour running mean outdoor temperatures [°C].
- Returns:
Minimum comfort temperature [°C], same shape as input.
- Return type:
np.ndarray
- pyedautils.comfort.is_summer_semester_sia180(timestamps) Series[source]#
Boolean mask for the SIA 180 summer half-year (16 Apr – 15 Oct).
- Parameters:
timestamps – Anything accepted by
pandas.to_datetime()(Series, DatetimeIndex, list of timestamps).- Returns:
Boolean Series,
Truefor timestamps within the summer half-year (inclusive).- Return type:
pd.Series
- pyedautils.comfort.align_hourly(room: DataFrame, outdoor: DataFrame, rolling_window: int = 48, min_periods: int = 12) DataFrame[source]#
Resample room and outdoor temperature to hourly means and join them.
Computes the 48-hour running mean of the outdoor temperature used by the adaptive SIA 180 boundary curves.
- Parameters:
room – DataFrame
[timestamp, value]with room temperature [°C].outdoor – DataFrame
[timestamp, value]with outdoor temperature [°C].rolling_window – Window length (hours) for the outdoor running mean. Default 48.
min_periods – Minimum number of observations in the rolling window. Default 12.
- Returns:
Indexed by hourly timestamp with columns
[t_room, t_oa, t_oa_48h]. Empty if either input is empty.- Return type:
pd.DataFrame
- pyedautils.comfort.comfort_kpis(aligned: DataFrame, summer_only: bool = False) Dict[str, float][source]#
Comfort key performance indicators from aligned hourly data.
Counts hours below the adaptive lower limit (too cold), above the adaptive upper limit (too warm / overheating), and within the comfort band, and checks the Minergie and SIA 180 residential overheating limits.
- Parameters:
aligned – DataFrame indexed by timestamp with columns
[t_room, t_oa, t_oa_48h](seealign_hourly()).summer_only – Restrict to the SIA 180 summer half-year. Default
False.
- Returns:
n_hours,h_cold,h_warm,h_ok,pct_ok,overheating_h,sia180_compliant,minergie_compliant. One sample equals one hour.- Return type:
dict
- pyedautils.comfort.overheating_hours(aligned: DataFrame, method: str = 'adaptive', summer_only: bool = True, business_hours_only: bool = False) Tuple[float, Series][source]#
Overheating hours and the per-hour threshold series.
- Parameters:
aligned – DataFrame indexed by timestamp with columns
[t_room, t_oa, t_oa_48h](seealign_hourly()).method –
"adaptive"uses the SIA 180 upper boundary curve (sia180_max_temp());"fixed"usesFIXED_OVERHEATING_THRESHOLD. Default"adaptive".summer_only – Restrict to the SIA 180 summer half-year. Default
True.business_hours_only – Restrict to 07:00–22:00. Default
False.
- Returns:
(total_hours, threshold_series)where threshold_series is the per-hour comfort threshold (useful for plotting). One exceeded sample equals one overheating hour.- Return type:
tuple
- pyedautils.comfort.overheating_per_month(aligned: DataFrame, threshold: Series) DataFrame[source]#
Overheating hours grouped by calendar month.
- Parameters:
aligned – DataFrame indexed by timestamp with a
t_roomcolumn.threshold – Per-hour comfort threshold (the second return value of
overheating_hours()).
- Returns:
Columns
[month, hours](month as 1–12).- Return type:
pd.DataFrame
- pyedautils.comfort.comfort_compass_categories() List[str][source]#
Ordered category keys of the comfort-compass distribution.
["ok"]followed by"<direction>_<stage>"for every direction in_COMPASS_DIRECTIONSand stage in_COMPASS_STAGES(25 keys). These are exactly the keys produced bycomfort_compass_distribution()and consumed bypyedautils.plots.comfort.plot_comfort_compass().
- pyedautils.comfort.comfort_compass_distribution(data: DataFrame, temp_band: Tuple[float, float], hum_band: Tuple[float, float], hum_abs_band: Tuple[float, float] | None = None, pressure: float = 101325.0, temp_col: str = 'temperature', hum_col: str = 'humidity', aggregate_daily: bool = True, temp_stage_limits: Tuple[float, float] = (1.0, 2.5), hum_stage_limits: Tuple[float, float] = (5.0, 10.0), hum_abs_stage_limits: Tuple[float, float] = (0.001, 0.0025)) Dict[str, int][source]#
Count samples per comfort-compass category (one count per day by default).
Each sample is classified against a fixed comfort band into one of nine states — the cross product of temperature {cold, ok, warm} and humidity {dry, ok, humid} — and graded into three severity stages from the exceedance over the band (temperature in K, humidity in %-points): mild
<= limit1, moderate<= limit2, severe otherwise (the worse axis wins). The result is the input ofpyedautils.plots.comfort.plot_comfort_compass().Humidity is judged by relative humidity against
hum_band. Passhum_abs_bandto also judge it by absolute humidity: a sample then counts as too humid / too dry if it exceeds the relative or the absolute limit (e.g. so a warm point above the comfort zone’s absolute-humidity cap reads as “warm + humid” even when its relative humidity is still in range). The absolute humidity is derived from temperature, relative humidity andpressure.By default the input is first averaged to daily means (one row per calendar day), so the counts are days — matching the per-day reading of the compass. Pass
aggregate_daily=Falseto count the rows as given (e.g. when the input is already daily).- Parameters:
data – DataFrame holding temperature and humidity columns. With
aggregate_daily=Trueit must have aDatetimeIndex(used to resample to daily means).temp_band –
(low, high)comfort temperature band [°C].hum_band –
(low, high)comfort relative humidity band [%rH].hum_abs_band – Optional
(low, high)comfort absolute humidity band [kg/kg]. When given, “too humid”/”too dry” is the relative or the absolute criterion.None(default) judges humidity by relative humidity only.pressure – Air pressure [Pa], used to derive absolute humidity when
hum_abs_bandis set. Default 101325.temp_col – Temperature column name [°C]. Default
"temperature".hum_col – Humidity column name [%rH]. Default
"humidity".aggregate_daily – Average to daily means before classifying, so the counts are days. Default
True.temp_stage_limits –
(light, marked)K over the band for the temperature severity stages. Default(1.0, 2.5).hum_stage_limits –
(mild, moderate)%-points over the band for the relative-humidity severity stages. Default(5.0, 10.0).hum_abs_stage_limits –
(mild, moderate)kg/kg over the band for the absolute-humidity severity stages (used only withhum_abs_band). Default(0.001, 0.0025)(1 and 2.5 g/kg).
- Returns:
dict mapping each key of
comfort_compass_categories()to the number of (daily) samples in that category (all zeros when there is no valid sample).
See also
Choosing the comfort zone and the stage limits, and reading the result with
pyedautils.plots.comfort.plot_comfort_compass(): https://retomarek.github.io/pyedautils/guides/comfort_compass.html#compass-comfort
- pyedautils.comfort.comfort_warning_bands(temp_stage_limits: Tuple[float, float] = (1.0, 2.5), hum_stage_limits: Tuple[float, float] = (5.0, 10.0), hum_abs_stage_limits: Tuple[float, float] = (0.001, 0.0025)) Dict[str, Dict[str, float]][source]#
Orange / red warning-band offsets matching the compass severity stages.
Translates the stage limits of
comfort_compass_distribution()(same parameter names and defaults) into the±offsets accepted bycomfort_zone_orange/comfort_zone_redofpyedautils.plots.comfort.plot_mollier_hx()(andplot_mollier_hx_fast), so the h,x diagram and the comfort compass can share one definition: the orange band encloses the mild stage, the red band the moderate stage, and everything beyond red is severe.The relative-humidity limits are given in %-points (as for the compass) and returned as a fraction (0–1, as used by the diagram); the red band is widened where needed so it always encloses the orange one.
Example:
lims = dict(temp_stage_limits=(1.0, 2.5), hum_stage_limits=(5, 10)) dist = comfort_compass_distribution(df, (20, 26), (30, 65), **lims) bands = comfort_warning_bands(**lims) plot_mollier_hx(df_hx, comfort_zone_orange=bands["orange"], comfort_zone_red=bands["red"])
- Parameters:
temp_stage_limits –
(mild, moderate)K over the comfort band. Default(1.0, 2.5).hum_stage_limits –
(mild, moderate)%-points of relative humidity over the band. Default(5.0, 10.0).hum_abs_stage_limits –
(mild, moderate)kg/kg of absolute humidity over the band. Default(0.001, 0.0025)(1 and 2.5 g/kg).
- Returns:
{"orange": {...}, "red": {...}}, each a dict with the offsets"temperature"[K],"rel_humidity"[fraction] and"abs_humidity"[kg/kg].
- pyedautils.comfort.VENTILATION_THRESHOLD = 0.0003#
|x_outdoor - x_room|up to this value [kg/kg] (0.3 g/kg) counts as “no noticeable change” inventilation_effect().
- pyedautils.comfort.ventilation_effect(room, outdoor, pressure: float = 101325.0, threshold: float = 0.0003) str[source]#
What airing does to the room air: dry it, add moisture, or neither.
Airing mixes room and outside air, so the room state moves along the straight line towards the outside state in the h,x diagram. Whether the room gets drier depends only on the absolute humidity: outside air with 90 % relative humidity at 0 °C still dries a room at 21 °C, because it holds less water per kg of air. The comparison is the same as the ventilation note of
pyedautils.plots.comfort.plot_mollier_hx()(outdoor=) and of the d3-mollierhx overlay helpers.Example:
>>> ventilation_effect(room=(21, 50), outdoor=(0, 80)) 'dries' >>> ventilation_effect(room=(16, 70), outdoor=(26, 60)) 'humidifies'
- Parameters:
room – Room air as
(temperature [°C], relative humidity [%])or aMoistAirStatefrompyedautils.psychrometrics.state().outdoor – Outside air, same forms as
room.pressure – Air pressure [Pa] used to convert
(t, rh)pairs to absolute humidity. Default 101325 (sea level).threshold – Difference in absolute humidity [kg/kg] up to which airing counts as
"neutral". Default0.0003(0.3 g/kg).
- Returns:
"dries"when the outside air holds more thanthresholdless water than the room air,"humidifies"when it holds more thanthresholdmore, otherwise"neutral".
See also
Ventilating in winter and airing a cellar in summer, explained with the h,x diagram: https://retomarek.github.io/pyedautils/guides/mollier_hx.html#hx-winter-ventilation