Skip to content

Instantly share code, notes, and snippets.

@fertek
Last active December 3, 2025 22:20
Show Gist options
  • Select an option

  • Save fertek/d2f513bed74c507d1ec9dbf95d7c4704 to your computer and use it in GitHub Desktop.

Select an option

Save fertek/d2f513bed74c507d1ec9dbf95d7c4704 to your computer and use it in GitHub Desktop.
# /// script
# dependencies = [
# "plotnine",
# ]
# requires-python = "==3.13"
# ///
# Pozn.: Graf lze kdykoliv přegenerovat příkazem `uv run spotreba_elektriny.py`,
# což stáhne závislosti do uv cache a uloží nový `spotreba_elektriny.png`.
# Alternativně lze spustit přímo z Gistu:
# `uv run https://gist.githubusercontent.com/fertek/d2f513bed74c507d1ec9dbf95d7c4704/raw`
"""
Z https://www.usetreno.cz/clanky/spotreba-elektrokotle/:
> Elektrokotel má výkon obvykle kolem 15 kW. Pokud pracuje 5 hodin denně pro domácnost o velikosti mezi 150 a 200 m2, bude jeho spotřeba okolo 75 kWh.
Z https://www.cez.cz/cs/clanky/vytapeni/elektrokotel-a-jeho-spotreba-kdy-se-financne-vyplati-174095:
> Průměrná roční spotřeba elektrokotle pro 3člennou domácnost činí zhruba 18 100 kWh na vytápění a dalších 2 100 kWh na ohřev vody. Celkem tedy 20 200 kWh.
Z https://www.akoupelnyatopeni.cz/elektrokotel-a-jeho-rocni-spotreba-prehledne-px389774:
Průměrná denní spotřeba 9 kW kotle při dvanáctihodinovém vytápění činí 113 kW.
Z https://www.tzb-info.cz/tabulky-a-vypocty/47-vypocet-potreby-tepla-pro-vytapeni-vetrani-a-pripravu-teple-vody:
Celková roční potřeba energie na vytápění: 53 MWh/rok
"""
#!/usr/bin/env python3
from datetime import datetime
from pathlib import Path
from typing import List, Tuple
import pandas as pd
from plotnine import (
aes,
element_text,
element_rect,
geom_line,
geom_label,
geom_point,
geom_rect,
geom_segment,
geom_text,
ggplot,
guides,
labs,
scale_color_manual,
scale_fill_manual,
scale_x_datetime,
scale_y_continuous,
theme,
)
from plotnine.guides import guide_legend
Reading = Tuple[datetime, int]
# Kratší zápis: každý záznam je (datetime, kWh).
dt = datetime.fromisoformat
READINGS: List[Reading] = [
(dt("2025-11-21 12:42"), 5100),
(dt("2025-11-21 18:33"), 5164),
(dt("2025-11-22 12:53"), 5298),
(dt("2025-11-22 23:07"), 5381),
(dt("2025-11-23 09:25"), 5463),
(dt("2025-11-23 16:40"), 5507),
(dt("2025-11-24 09:16"), 5580),
(dt("2025-11-25 16:16"), 5744),
(dt("2025-11-26 08:41"), 5802),
(dt("2025-11-26 14:56"), 5844),
(dt("2025-11-26 22:19"), 5869),
(dt("2025-11-27 08:33"), 5898),
(dt("2025-11-27 13:08"), 5929),
(dt("2025-11-27 22:09"), 5962),
(dt("2025-11-28 08:15"), 5994),
(dt("2025-11-28 14:59"), 6025),
(dt("2025-12-02 18:59"), 6215),
(dt("2025-12-02 22:00"), 6243),
(dt("2025-12-03 01:26"), 6261),
(dt("2025-12-03 08:47"), 6302),
(dt("2025-12-03 16:39"), 6366),
(dt("2025-12-03 21:38"), 6397),
]
def main() -> None:
df = prepare_data(READINGS)
df_daily = compute_daily(df)
month_names = {
1: "leden",
2: "únor",
3: "březen",
4: "duben",
5: "květen",
6: "červen",
7: "červenec",
8: "srpen",
9: "září",
10: "říjen",
11: "listopad",
12: "prosinec",
}
x_breaks = pd.date_range(df["at"].min().normalize(), df["at"].max().normalize(), freq="1D")
x_labels = [
(f"{d:%d}\n" + month_names[d.month]) if (d.day == 1 or i == 0) else f"{d:%d}\n"
for i, d in enumerate(x_breaks)
]
plot = (
ggplot(df, aes("at", "kwh_rel"))
+ geom_line(aes(color="series_line"), size=0.6)
+ geom_point(aes(color="series_line"), size=1.8, show_legend=False)
+ geom_segment(
df[df["is_first_of_day"]],
aes(
x="at",
xend="at",
y="kwh_rel",
yend="kwh_rel + label_offset",
),
color="#666666",
size=0.3,
inherit_aes=False,
)
+ geom_label(
df[df["is_first_of_day"]],
aes(y="kwh_rel + label_offset", label="kwh_rel"),
va="bottom",
size=6,
fill="white",
alpha=0.85,
label_size=0,
color="#333333",
)
+ geom_rect(
df_daily,
aes(
xmin="date",
xmax="date_end",
ymin=0,
ymax="delta",
fill="series_fill",
),
alpha=0.35,
inherit_aes=False,
) # denní přírůstky (součet za den, vyplní celý den)
+ geom_text(
df_daily,
aes(x="date_mid", y="delta_pos", label="delta_label"),
color="#5AA27F",
size=6,
va="bottom",
)
+ scale_x_datetime(
breaks=x_breaks,
labels=x_labels,
minor_breaks=None,
)
+ scale_y_continuous(
breaks=range(0, int(df["kwh_rel"].max()) + 500, 500),
minor_breaks=range(0, int(df["kwh_rel"].max()) + 200, 100),
)
+ scale_color_manual(
name="",
breaks=["Kumulovaná spotřeba"],
labels=["Kumulovaná spotřeba"],
values={"Kumulovaná spotřeba": "#000000"},
)
+ scale_fill_manual(
name="",
breaks=["Denní přírůstek (kWh)"],
labels=["Denní přírůstek (kWh)"],
values={"Denní přírůstek (kWh)": "#5AA27F"},
)
+ labs(
x="Čas",
y="kWh",
title="Spotřeba elektřiny",
color="",
fill="",
)
+ theme(
axis_text_x=element_text(rotation=0, hjust=0.5, size=7),
legend_position=(0.02, 0.98),
legend_justification=("left", "top"),
legend_background=element_rect(fill="white", alpha=0.85, color=None),
legend_direction="vertical",
legend_box="vertical",
legend_box_spacing=0.1,
)
+ guides(
color=guide_legend(ncol=1),
fill=guide_legend(ncol=1),
)
)
output = Path("spotreba_elektriny.png")
plot.save(output, width=6, height=4, dpi=150)
print(f"Vykresleno do {output}")
def prepare_data(readings: List[Reading]) -> pd.DataFrame:
df = pd.DataFrame(readings, columns=["at", "kwh"]).sort_values("at").reset_index(drop=True)
base = df["kwh"].iloc[0]
df["kwh_rel"] = df["kwh"] - base # první bod jde na nulu
df["delta"] = df["kwh"].diff().fillna(0) # přírůstky mezi měřeními
df["date"] = df["at"].dt.normalize() # den bez času pro denní součty
df["series_line"] = "Kumulovaná spotřeba"
df["label_offset"] = 40 + df["kwh_rel"] * 0.08
df["is_first_of_day"] = ~df["date"].duplicated()
return df
def compute_daily(df: pd.DataFrame) -> pd.DataFrame:
# Rozdělení přírůstků na kalendářní dny lineární interpolací přes půlnoc.
daily: dict[pd.Timestamp, float] = {}
for i in range(len(df) - 1):
t0, k0 = df.loc[i, ["at", "kwh"]]
t1, k1 = df.loc[i + 1, ["at", "kwh"]]
delta = k1 - k0
total_seconds = (t1 - t0).total_seconds()
if total_seconds <= 0:
continue
start = t0
while start < t1:
next_midnight = (start + pd.Timedelta(days=1)).normalize()
segment_end = min(next_midnight, t1)
segment_seconds = (segment_end - start).total_seconds()
if segment_seconds > 0:
share = delta * (segment_seconds / total_seconds)
daily[start.normalize()] = daily.get(start.normalize(), 0) + share
start = segment_end
df_daily = (
pd.DataFrame(
{"date": list(daily.keys()), "delta": list(daily.values())},
)
.sort_values("date")
.reset_index(drop=True)
)
df_daily["date_end"] = df_daily["date"] + pd.Timedelta(days=1)
df_daily["date_mid"] = df_daily["date"] + pd.Timedelta(hours=12)
df_daily["delta_label"] = df_daily["delta"].round().astype(int)
df_daily["delta_pos"] = df_daily["delta"] + 20
df_daily["series_fill"] = "Denní přírůstek (kWh)"
return df_daily
if __name__ == "__main__":
main()
@fertek
Copy link
Author

fertek commented Dec 3, 2025

Výsledek - spotreba_elektriny.png:

spotreba_elektriny

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment