Energy Signature#

This example demonstrates two building energy signature plots:

  1. Simple scatter — daily mean outside temperature vs daily energy consumption, colored by season

  2. 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()