Energy Signature#
This example demonstrates two building energy signature plots:
Simple scatter — daily mean outside temperature vs daily energy consumption, colored by season
Proposed Energy Signature (PES) — iterative algorithm (Eriksson et al., 2020) to find balance temperature, heat loss coefficient, standby and hot-water power, with regression lines
All plots are interactive — hover to see exact values.
# Configure Plotly to render as HTML (required for Jupyter Book)
import plotly.io as pio
pio.renderers.default = "notebook_connected"
Simple Energy Signature#
A scatter plot of daily energy consumption against daily mean outside temperature, colored by season. This is the most basic way to visualize how a building’s heating demand relates to outdoor conditions.
import numpy as np
import pandas as pd
from importlib import resources
# Load the bundled sample data
data_path = resources.files("pyedautils") / "data" / "bldg_engy_sig_simple.csv"
df_simple = pd.read_csv(data_path, sep=";")
df_simple.head()
| timestamp | temperature | value | |
|---|---|---|---|
| 0 | 2018-03-21 | 2.82 | 176.54 |
| 1 | 2018-03-22 | 1.08 | 196.75 |
| 2 | 2018-03-23 | 5.33 | 148.92 |
| 3 | 2018-03-24 | 9.90 | 101.15 |
| 4 | 2018-03-25 | 9.11 | 100.82 |
Outlier removal — temperature-binned quantiles#
A global or seasonal filter cannot catch points that are unusually high for their temperature. Instead we bin the outside temperature (2 °C bins), compute the 1st/99th percentile of the target column within each bin, and drop everything outside.
def remove_outliers_by_temp_bin(df, temp_col, value_col, bin_width=2,
lower_q=0.01, upper_q=0.99):
"""Remove rows where value_col falls outside the per-temperature-bin quantiles."""
df = df.copy()
df["_bin"] = pd.cut(df[temp_col], bins=np.arange(
df[temp_col].min() - bin_width,
df[temp_col].max() + bin_width * 2,
bin_width,
))
bounds = df.groupby("_bin", observed=True)[value_col].quantile(
[lower_q, upper_q]
).unstack()
bounds.columns = ["lo", "hi"]
df = df.join(bounds, on="_bin")
mask = df[value_col].between(df["lo"], df["hi"])
print(f"Removed {(~mask).sum()} outliers out of {len(df)} rows "
f"({bin_width} °C bins, {lower_q:.0%}/{upper_q:.0%} quantiles)")
return df.loc[mask].drop(columns=["_bin", "lo", "hi"])
df_simple = remove_outliers_by_temp_bin(df_simple, "temperature", "value")
Removed 29 outliers out of 576 rows (2 °C bins, 1%/99% quantiles)
from pyedautils.plots import plot_energy_signature
fig = plot_energy_signature(
data=df_simple,
title="Building Energy Signature",
xlab="Outside Temperature [°C]",
ylab="Heating Energy [kWh/d]",
)
fig.show()
Proposed Energy Signature (PES)#
The PES method (Eriksson et al., 2020) iteratively determines:
T_b — balance temperature: outdoor temperature below which heating is needed
Q_tot — total heat loss coefficient [kW/K]
P_dhwc — domestic hot water circulation demand (standby) [kW]
P_dhw — domestic hot water demand [kW]
The algorithm uses hourly data with outdoor temperature, power consumption, and room temperature.
data_path = resources.files("pyedautils") / "data" / "bldg_engy_sig_pes.csv"
df_pes = pd.read_csv(data_path)
df_pes.head()
| timestamp | outside_temp | power | room_temp | |
|---|---|---|---|---|
| 0 | 2018-12-01 00:00:00 | 6.878333 | 7.143 | 23.300000 |
| 1 | 2018-12-01 01:00:00 | 7.046667 | 0.159 | 23.300000 |
| 2 | 2018-12-01 02:00:00 | 7.048333 | 0.159 | 23.300000 |
| 3 | 2018-12-01 03:00:00 | 6.950000 | 0.160 | 23.300000 |
| 4 | 2018-12-01 04:00:00 | 6.738333 | 0.159 | 23.266667 |
Outlier removal — temperature-binned quantiles#
Remove power values that are extreme for their temperature range.
df_pes = remove_outliers_by_temp_bin(df_pes, "outside_temp", "power")
Removed 188 outliers out of 11927 rows (2 °C bins, 1%/99% quantiles)
Compute PES parameters#
You can compute the PES parameters directly using compute_pes:
from pyedautils.energy_signature import compute_pes
result = compute_pes(df_pes)
print(f"Balance temperature: T_b = {result.tb:.1f} °C")
print(f"Heat loss coefficient: Q_tot = {result.q_tot:.4f} kW/K")
print(f"DHWC demand (standby): P_dhwc = {result.p_dhwc:.4f} kW")
print(f"DHW demand: P_dhw = {result.p_dhw:.4f} kW")
print(f"Internal heat gains: P_ihg = {result.p_ihg:.4f} kW")
Balance temperature: T_b = 17.6 °C
Heat loss coefficient: Q_tot = 0.0865 kW/K
DHWC demand (standby): P_dhwc = 0.0544 kW
DHW demand: P_dhw = 0.7377 kW
Internal heat gains: P_ihg = 0.0000 kW
PES Plot#
The plot shows daily mean power vs outdoor temperature, with three characteristic lines: the heating line (slope = Q_tot), the base load line (P_dhw + P_dhwc), and the DHWC line (P_dhwc).
from pyedautils.plots import plot_energy_signature_pes
fig = plot_energy_signature_pes(
data=df_pes,
title="Proposed Energy Signature",
xlab="Outside Temperature [°C]",
ylab="Power [kW]",
)
fig.show()
With internal heat gains#
If internal heat gains (people, appliances, solar) are known, pass
them via the p_ihg parameter to improve accuracy:
fig = plot_energy_signature_pes(
data=df_pes,
p_ihg=0.54,
title="PES with Internal Heat Gains",
)
fig.show()