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_LO the limit is constant at ADAPTIVE_MAX_T_ROOM_LO, above ADAPTIVE_MAX_T_OA_HI it is constant at ADAPTIVE_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_LO the limit is constant at ADAPTIVE_MIN_T_ROOM_LO, above ADAPTIVE_MIN_T_OA_HI it is constant at ADAPTIVE_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, True for 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] (see align_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] (see align_hourly()).

  • method – "adaptive" uses the SIA 180 upper boundary curve (sia180_max_temp()); "fixed" uses FIXED_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_room column.

  • 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_DIRECTIONS and stage in _COMPASS_STAGES (25 keys). These are exactly the keys produced by comfort_compass_distribution() and consumed by pyedautils.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 of pyedautils.plots.comfort.plot_comfort_compass().

Humidity is judged by relative humidity against hum_band. Pass hum_abs_band to 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 and pressure.

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=False to count the rows as given (e.g. when the input is already daily).

Parameters:
  • data – DataFrame holding temperature and humidity columns. With aggregate_daily=True it must have a DatetimeIndex (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_band is 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 with hum_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 by comfort_zone_orange / comfort_zone_red of pyedautils.plots.comfort.plot_mollier_hx() (and plot_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” in ventilation_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 a MoistAirState from pyedautils.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". Default 0.0003 (0.3 g/kg).

Returns:

"dries" when the outside air holds more than threshold less water than the room air, "humidifies" when it holds more than threshold more, 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

See also#