Last active
December 3, 2025 22:20
-
-
Save fertek/d2f513bed74c507d1ec9dbf95d7c4704 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # /// 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() |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Výsledek -
spotreba_elektriny.png: