degree_days#
Degree days and climate correction (Witterungsbereinigung) of heating
energy — Swiss heating degree days HGT 20/12 (SIA 381/3), accumulated
temperature differences ATD (SIA 380:2015 / ISO 15927-6), plain degree
days GT and cooling degree days CDD, a reference climatology, the
correction factor DD_ref / DD_actual, a fixed-base degree-day
regression with ASHRAE Guideline 14 metrics, and the BFE/Prognos GT&S
method (degree days and solar radiation).
See Degree Days & Climate Correction for usage examples. The plotting counterparts live in plots; Swiss reference series can be downloaded with open_data_ch and meteo_swiss.
Background#
Method |
Definition |
Source |
|---|---|---|
HGT 20/12 |
Σ (20 °C − T_mean) over days with T_mean ≤ 12 °C; heating days HT |
SIA 381/3, MeteoSwiss, BFE |
ATD |
Σ max(0, T_base − T_mean) with one base (heating limit), ideally building-specific |
SIA 380:2015, SN EN ISO 15927-6 |
GT |
Σ (20 °C − T_mean) over all days below 20 °C, no heating limit |
BFE/Prognos GT&S |
CDD |
Σ (T_mean − 18.3 °C) over days with T_mean ≥ 18.3 °C |
BFE |
Correction |
|
SIA 380, cantonal statistics |
GT&S |
Σ_m share_m · (1 + e_GT,m · ΔGT_m + e_S,m · ΔS_m) with functional elasticities |
Prognos 2003/2015 for the BFE |
API Reference#
Degree days and climate correction (Witterungsbereinigung) of heating energy.
Provides the Swiss degree-day conventions and the classic degree-day based climate correction of measured heating energy:
HGT 20/12 heating degree days per SIA 381/3 and MeteoSwiss: sum of
20 °C − T_meanover all days withT_mean <= 12 °C(heating days HT). Used by the BFE (population-weighted Swiss series since 1980) and most cantonal energy statistics.ATD accumulated temperature difference per SIA 380:2015 and SN EN ISO 15927-6: sum of
max(0, T_base − T_mean)with a single base (heating-limit) temperature, ideally building-specific.GT plain degree days
20 °C − T_meanwithout heating limit (basis of the BFE/Prognos GT&S method) and CDD cooling degree days (BFE: base 18.3 °C).A reference climatology (monthly means over a climate-normal period such as 1991–2020), the correction factor
DD_ref / DD_actualand the normalization of a consumption whose weather-dependent share is known.A fixed-base degree-day regression (
E = a + b · HDD) with ASHRAE Guideline 14 fit metrics, usable with the balance temperature frompyedautils.energy_signature.compute_pes().The generic GT&S correction factor (Prognos 2003/2015 for the BFE: degree days and solar radiation with monthly elasticities and a normal-year load curve).
Plotting counterparts live in pyedautils.plots.degree_days; Swiss
open-data sources for reference values in
pyedautils.weather.open_data_ch and
pyedautils.weather.meteo_swiss.
- pyedautils.degree_days.HGT_ROOM_TEMP_CH = 20.0#
Room temperature of the Swiss HGT 20/12 convention (SIA 381/3) [°C].
- pyedautils.degree_days.HGT_HEATING_LIMIT_CH = 12.0#
Heating limit of the Swiss HGT 20/12 convention (SIA 381/3) [°C].
- pyedautils.degree_days.CDD_BASE_BFE = 18.3#
Base temperature of the BFE cooling degree days [°C].
- pyedautils.degree_days.GT_BASE = 20.0#
Base of the plain degree days (“Gradtage”) used by the GT&S method [°C].
- pyedautils.degree_days.degree_days(temperature, base: float = 20.0, threshold: float | None = 12.0, mode: str = 'heating', method: str = 'daily_mean', min_coverage: float = 0.75) DataFrame[source]#
Daily degree days of an outdoor temperature series.
Heating: a day counts as degree-day day when
t_mean <= threshold(threshold=None: whent_mean < base); its degree days arebase − t_mean. Cooling: a day counts whent_mean >= threshold(None:base); its degree days aret_mean − base.- Parameters:
temperature – Outdoor temperature [°C] as Series with DatetimeIndex, DataFrame with DatetimeIndex and one column, or positional
[timestamp, value]DataFrame. Daily or finer resolution.base – Reference (room) temperature [°C]. Default 20.
threshold – Heating limit [°C] deciding whether a day counts. Default 12 (Swiss HGT 20/12).
Noneusesbase.mode –
"heating"or"cooling".method –
"daily_mean"(degree days from the daily mean) or"hourly"(ISO 15927-6 style: mean of the positive hourly differences, i.e. degree-hours / hours; requires sub-daily data). With athresholdthe day still has to qualify via its daily mean; without, every hour belowbasecounts.min_coverage – Minimum fraction of expected samples per day for sub-daily data; days below are NaN. Default 0.75.
- Returns:
Indexed by day with columns
t_mean[°C],dd[Kd] (0 on non-degree-day days, NaN on missing days),is_dd_day(bool) andcoverage(0–1).- Return type:
pd.DataFrame
- Raises:
ValueError – On invalid mode/method,
base <= thresholdfor heating (base < thresholdfor cooling), coarser-than-daily input ormethod="hourly"with daily input.
- pyedautils.degree_days.aggregate_degree_days(daily: DataFrame, freq: str | None = 'MS', scale_incomplete: bool = False, season_start_month: int = 7) DataFrame[source]#
Aggregate daily degree days to months, years or heating seasons.
- Parameters:
daily – Output of
degree_days().freq –
"MS"(calendar months),"YS"(calendar years),"season"(heating seasons starting atseason_start_month) orNone(returndailyunchanged).scale_incomplete – Scale
ddanddd_daysof periods with missing days byn_days / n_valid(IWU rule for incomplete months). Default False.season_start_month – First month of a heating season (default 7, i.e. July–June, labelled e.g.
"2022/2023").
- Returns:
Indexed by period start with columns
dd,dd_days(number of degree-day days),n_days(calendar days of the period),n_valid(days with data),completeness(n_valid / n_days) andt_mean."season"adds aseasonlabel column. Periods without any data have NaNdd.- Return type:
pd.DataFrame
- Raises:
ValueError – On an unsupported
freq(note that"M"/"Y"are rejected; use"MS"/"YS").
- pyedautils.degree_days.heating_degree_days(temperature, room_temp: float = 20.0, heating_limit: float | None = 12.0, freq: str | None = 'MS', method: str = 'daily_mean', min_coverage: float = 0.75, scale_incomplete: bool = False) DataFrame[source]#
Heating degree days HGT and heating days HT (Swiss convention HGT 20/12).
HGT = Σ (room_temp − t_mean)over all days witht_mean <= heating_limit(SIA 381/3, MeteoSwiss, BFE).heating_limit=Nonegives the plain degree days GT of the BFE/Prognos GT&S method (all days witht_mean < room_temp).- Parameters:
temperature – Outdoor temperature, see
degree_days().room_temp – Room temperature [°C]. Default 20.
heating_limit – Heating limit [°C]. Default 12.
Nonefor GT.freq – Aggregation, see
aggregate_degree_days()(Nonefor daily values).method –
"daily_mean"or"hourly", seedegree_days().min_coverage – Minimum daily sample coverage for sub-daily input.
scale_incomplete – Scale incomplete periods, see
aggregate_degree_days().
- Returns:
As
aggregate_degree_days()withddrenamed tohgt[Kd] anddd_daystoht[d].- Return type:
pd.DataFrame
- pyedautils.degree_days.accumulated_temperature_difference(temperature, base: float = 12.0, freq: str | None = 'MS', method: str = 'daily_mean', min_coverage: float = 0.75, scale_incomplete: bool = False) DataFrame[source]#
Accumulated temperature difference ATD per SIA 380:2015 / ISO 15927-6.
ATD = Σ max(0, base − t_mean)with a single base temperature (the heating limit). SIA 380 recommends a building-specific base; the balance temperaturetbofpyedautils.energy_signature.compute_pes()can be used directly. The Stadt Zürich open data usesbase = 12 °C.- Parameters:
temperature – Outdoor temperature, see
degree_days().base – Base / heating-limit temperature [°C]. Default 12.
freq – Aggregation, see
aggregate_degree_days().method –
"daily_mean"or"hourly".min_coverage – Minimum daily sample coverage for sub-daily input.
scale_incomplete – Scale incomplete periods.
- Returns:
As
aggregate_degree_days()withddrenamed toatd[Kd] anddd_daystoht[d].- Return type:
pd.DataFrame
- pyedautils.degree_days.cooling_degree_days(temperature, base: float = 18.3, threshold: float | None = None, freq: str | None = 'MS', method: str = 'daily_mean', min_coverage: float = 0.75, scale_incomplete: bool = False) DataFrame[source]#
Cooling degree days CDD and cooling days CD.
CDD = Σ (t_mean − base)over all days witht_mean >= threshold(BFE: base and threshold 18.3 °C).- Parameters:
temperature – Outdoor temperature, see
degree_days().base – Base temperature [°C]. Default 18.3.
threshold – Day-counting threshold [°C];
Noneusesbase.freq – Aggregation, see
aggregate_degree_days().method –
"daily_mean"or"hourly".min_coverage – Minimum daily sample coverage for sub-daily input.
scale_incomplete – Scale incomplete periods.
- Returns:
As
aggregate_degree_days()withddrenamed tocdd[Kd] anddd_daystocd[d].- Return type:
pd.DataFrame
- class pyedautils.degree_days.ReferenceDegreeDays(monthly: pd.Series, monthly_days: pd.Series, annual: float, annual_days: float, n_years: int, period: Tuple[str, str])[source]#
Bases:
NamedTupleMonthly degree-day climatology of a reference period.
- monthly#
Mean degree days per calendar month [Kd], index 1–12.
- Type:
pandas.Series
- monthly_days#
Mean degree-day days per calendar month [d] (NaN if the source had no day counts).
- Type:
pandas.Series
- annual#
Sum of
monthly[Kd].- Type:
float
- annual_days#
Sum of
monthly_days[d].- Type:
float
- n_years#
Number of years contributing data.
- Type:
int
- period#
(first_year, last_year)as strings.- Type:
Tuple[str, str]
- monthly: Series#
Alias for field number 0
- monthly_days: Series#
Alias for field number 1
- annual: float#
Alias for field number 2
- annual_days: float#
Alias for field number 3
- n_years: int#
Alias for field number 4
- period: Tuple[str, str]#
Alias for field number 5
- pyedautils.degree_days.climatology_from_monthly(monthly_dd, period: Tuple[str, str] | None = ('1991', '2020'), min_completeness: float = 0.9) ReferenceDegreeDays[source]#
Monthly degree-day climatology from a monthly time series.
- Parameters:
monthly_dd – Monthly degree days with a DatetimeIndex: the output of
aggregate_degree_days()/heating_degree_days()(uses itscompletenessand day-count columns) or a plain Series, e.g.download_degree_days_bfe()["hgt"].period –
(first_year, last_year)inclusive, e.g. the climate normal("1991", "2020");Noneuses all data.min_completeness – Months with a lower
completenessare ignored.
- Returns:
Climatology with 12 monthly means.
- Return type:
- Raises:
ValueError – If no month of the period has data, or a calendar month has no valid value.
- pyedautils.degree_days.reference_degree_days(temperature, period: Tuple[str, str] | None = ('1991', '2020'), base: float = 20.0, threshold: float | None = 12.0, mode: str = 'heating', method: str = 'daily_mean', min_coverage: float = 0.75, min_completeness: float = 0.9) ReferenceDegreeDays[source]#
Degree-day climatology computed from a long outdoor temperature series.
Convenience wrapper:
degree_days()→ monthlyaggregate_degree_days()→climatology_from_monthly().- Parameters:
temperature – Long outdoor temperature series (e.g. MeteoSwiss daily values via
pyedautils.weather.meteo_swiss.download_data()withgranularity="d").period – Reference years, inclusive. Default climate normal 1991–2020.
base – Base temperature [°C], see
degree_days().threshold – Day-counting threshold [°C], see
degree_days().mode –
"heating"or"cooling".method –
"daily_mean"or"hourly".min_coverage – Minimum daily sample coverage for sub-daily input.
min_completeness – Minimum monthly completeness.
- Returns:
Climatology with 12 monthly means.
- Return type:
- pyedautils.degree_days.climate_correction_factor(dd_actual, dd_reference)[source]#
Degree-day based climate correction factor
f = DD_ref / DD_actual.Multiply the weather-dependent consumption of a period by
fto get the consumption under reference (normal-year) weather.f < 1means the period was colder than the reference.- Parameters:
dd_actual – Degree days of the analysed period(s): scalar or Series with a DatetimeIndex (e.g. monthly or yearly
hgt).dd_reference – Reference degree days: scalar, Series indexed by calendar month 1–12 (matched via
index.month), Series with the same DatetimeIndex, or aReferenceDegreeDays(monthlyfor Series input,annualfor scalar input).
- Returns:
Correction factor(s); NaN where
dd_actualis 0 (a warning is logged).- Return type:
float or pd.Series
- Raises:
ValueError – On negative degree days or incompatible inputs.
- pyedautils.degree_days.normalize_consumption(energy, dd_actual, dd_reference, weather_dependent_share: float = 1.0)[source]#
Climate-correct a measured consumption to reference weather.
E_norm = E · (1 − s) + E · s · fwithsthe weather-dependent (space-heating) share andffromclimate_correction_factor(). Domestic hot water and process shares are left unchanged, as in SIA 380 and the cantonal energy statistics.The share can be derived from the energy signature, e.g. with
pes = compute_pes(data)andE_totalthe measured annual energy:s = 1 − (pes.p_dhw + pes.p_dhwc) · hours / E_total.- Parameters:
energy – Measured consumption: scalar or Series aligned with
dd_actual(same DatetimeIndex).dd_actual – Degree days of the same period(s).
dd_reference – Reference degree days, see
climate_correction_factor().weather_dependent_share – Share
sin 0–1. Default 1 (everything is corrected).
- Returns:
Climate-corrected consumption.
- Return type:
float or pd.Series
- Raises:
ValueError – If
weather_dependent_shareis outside 0–1.
- class pyedautils.degree_days.DegreeDayLine(base: float, intercept: float, slope: float, r2: float, cvrmse: float, nmbe: float, n: int, freq: str, data: pd.DataFrame)[source]#
Bases:
NamedTupleResult of
fit_degree_day_line().- base#
Base temperature [°C] used for the heating degree days.
- Type:
float
- intercept#
Weather-independent consumption per day [kWh/d].
- Type:
float
- slope#
Consumption per degree day [kWh/Kd].
- Type:
float
- r2#
Coefficient of determination.
- Type:
float
- cvrmse#
Coefficient of variation of the RMSE [%] (ASHRAE G14).
- Type:
float
- nmbe#
Normalized mean bias error [%] (ASHRAE G14).
- Type:
float
- n#
Number of periods used.
- Type:
int
- freq#
"D"or"MS".- Type:
str
- data#
Per-period DataFrame with
t_mean,hdd(per day),energy(per day),n_daysandfitted.- Type:
pandas.DataFrame
- base: float#
Alias for field number 0
- intercept: float#
Alias for field number 1
- slope: float#
Alias for field number 2
- r2: float#
Alias for field number 3
- cvrmse: float#
Alias for field number 4
- nmbe: float#
Alias for field number 5
- n: int#
Alias for field number 6
- freq: str#
Alias for field number 7
- data: DataFrame#
Alias for field number 8
- pyedautils.degree_days.fit_degree_day_line(energy, temperature, base: float = 12.0, freq: str = 'D', min_coverage: float = 0.75) DegreeDayLine[source]#
Fit
E = a + b · HDD(base)with a fixed base temperature.Daily: ordinary least squares of daily energy on daily heating degree days
max(0, base − t_mean). Monthly (freq="MS"): weighted least squares of energy per day on degree days per day, weighted by the number of valid days (CalTRACK billing convention). The base temperature is not searched; pass the balance temperature frompyedautils.energy_signature.compute_pes()(pes.tb) or a known heating limit.- Parameters:
energy – Consumption [kWh] (sub-daily, daily or monthly) as Series or
[timestamp, value]DataFrame; sub-daily values are summed per day.temperature – Outdoor temperature [°C], daily or finer.
base – Base temperature [°C]. Default 12.
freq –
"D"or"MS".min_coverage – Minimum daily sample coverage for sub-daily temperature.
- Returns:
Fit parameters, ASHRAE Guideline 14 metrics and the per-period data.
- Return type:
- Raises:
ValueError – On unsupported
freq, timezone mismatch or fewer than three usable periods.
- class pyedautils.degree_days.GTSResult(ratio: float, factor: float, monthly: pd.DataFrame)[source]#
Bases:
NamedTupleResult of
gts_correction_factor().- ratio#
Weather-weighted annual load relative to the normal year (the Prognos “Korrekturfaktor”): divide the measured consumption by it.
> 1means colder and/or less sunny than the reference.- Type:
float
- factor#
1 / ratio; multiply the measured consumption by it (same convention asclimate_correction_factor()).- Type:
float
- monthly#
DataFrame indexed by month 1–12 with
share(normal-year load share),d_gtandd_rad(relative deviations of degree days and radiation from the reference),e_gtande_rad(resulting elasticities),effect(relative change of the monthly demand) andcontribution(weather-weighted monthly share;ratiois their sum).- Type:
pandas.DataFrame
- ratio: float#
Alias for field number 0
- factor: float#
Alias for field number 1
- monthly: DataFrame#
Alias for field number 2
- pyedautils.degree_days.gts_correction_factor(gt_actual, gt_norm, rad_actual, rad_norm, load_share_norm, elasticity_gt: Mapping[int, Tuple[float, float]] | Tuple[Sequence[float], Sequence[float]], elasticity_rad: Mapping[int, Tuple[float, float]] | Tuple[Sequence[float], Sequence[float]]) GTSResult[source]#
Annual climate correction factor of the BFE/Prognos GT&S method.
Degree-day and radiation method (“Gradtag- und Strahlungsverfahren”, Prognos 2003/2015 for the BFE): for every month the relative deviations of degree days
ΔGTand solar radiationΔSfrom the reference period are weighted with functional elasticitiese = c + a · Δand applied to the normal-year load share:ratio = Σ_m share_m · (1 + e_GT,m · ΔGT_m + e_S,m · ΔS_m)The elasticities and load shares depend on the building category and year (derived from SIA 380/1 model buildings) and are not published in full;
GTS_MFH_2010_EXAMPLEholds the documented example for multi-family houses in 2010.- Parameters:
gt_actual – Monthly degree days GT (base 20 °C, no heating limit) of the analysed year: 12 values, Series indexed by month or by month-start timestamps, or mapping
{month: value}.gt_norm – Monthly reference degree days (same shape).
rad_actual – Monthly global radiation of the analysed year [MJ/m² or any consistent unit].
rad_norm – Monthly reference global radiation.
load_share_norm – Normal-year monthly shares of the annual space heating demand (sum 1).
elasticity_gt – Degree-day elasticities per month as
{month: (c, a)}or(c_values, a_values).elasticity_rad – Radiation elasticities per month, same format.
- Returns:
ratio,factorand the monthly breakdown.- Return type:
- Raises:
ValueError – If any input does not cover 12 months or the load shares do not sum to 1 (±1 %).
- pyedautils.degree_days.GTS_MFH_2010_EXAMPLE: Dict[str, object] = {'elasticity_gt': ([1.22, 1.3, 1.43, 1.58, 3.0, 4.89, 4.89, 4.89, 2.86, 1.56, 1.25, 1.2], [0.007, 0.054, 0.136, -0.09, 1.373, 7.711, 7.711, 7.711, 1.034, -0.043, 0.047, 0.025]), 'elasticity_rad': ([-0.11, -0.19, -0.31, -0.42, -1.44, -2.8, -2.8, -2.8, -1.26, -0.34, -0.12, -0.08], [0.002, 0.008, 0.034, -0.025, 0.66, 4.044, 4.044, 4.044, 0.471, 0.01, 0.003, 0.001]), 'gt_norm': [605, 515, 452, 348, 205, 126, 65, 69, 177, 305, 469, 562], 'load_share_norm': [0.196, 0.153, 0.118, 0.08, 0.023, 0.002, 0.001, 0.001, 0.022, 0.074, 0.146, 0.185], 'rad_norm': [112, 176, 323, 423, 545, 579, 609, 526, 360, 224, 117, 86]}#
Documented GT&S parameters for the category “Wohnen Mehrfamilienhäuser”, year 2010 (Prognos 2015, “Witterungsbereinigung auf Basis von Gradtagen und Solarstrahlung”, Tabelle 2-9). Reference period 1984–2002, Swiss mean of 53 MeteoSwiss stations; radiation in MJ/m². Elasticities are
(c, a)pairs ofe = c + a · ΔwithΔas a fraction.
- pyedautils.degree_days.GTS_CH_2010_WEATHER: Dict[str, Sequence[float]] = {'gt': [668, 535, 476, 298, 249, 101, 39, 90, 197, 340, 442, 642], 'rad': [110, 185, 350, 534, 464, 587, 688, 495, 422, 238, 131, 95]}#
Swiss mean weather of the year 2010 from the same source (Tabelle 2-9): monthly degree days GT and global radiation [MJ/m²]. Together with
GTS_MFH_2010_EXAMPLEthis reproduces the documented ratio 1.063.