From 07e24ad97e6dcb78a9c30134e6b325f8a3c40ca5 Mon Sep 17 00:00:00 2001 From: Lucas Media Date: Wed, 21 Oct 2026 22:00:39 +0200 Subject: [PATCH] move generateAlina chart --- home/generateAlinaChart.py | 305 +++++++++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100755 home/generateAlinaChart.py diff --git a/home/generateAlinaChart.py b/home/generateAlinaChart.py new file mode 100755 index 0000000..0af524e --- /dev/null +++ b/home/generateAlinaChart.py @@ -0,0 +1,305 @@ +#!/home/lucas/scripts/.venv/bin/python +import subprocess +import sys +from pathlib import Path + +import matplotlib.pyplot as plt +import pandas as pd + + +def interpolate_daily(df): + df = df.copy() + df["Datum"] = pd.to_datetime(df["Datum"], errors="coerce") + df = df.sort_values("Datum").set_index("Datum") + + daily_index = pd.date_range(df.index.min(), df.index.max(), freq="D") + + return ( + df.reindex(daily_index) + .interpolate(method="time") + .reset_index() + .rename(columns={"index": "Datum"}) + ) + + +def plot_percentiles( + ax, + x, + data_dict, + p_keys, + colors=None, + linewidths=None, + labels=None, + linestyle=None, + fill=True, + fill_color="lightcoral", + fill_alpha=0.25, +): + """ + Plot multiple percentile curves from a dictionary like who_head_daily. + + Parameters + ---------- + ax : matplotlib axis + x : array-like (x values) + data_dict : dict-like, e.g. who_head_daily + p_keys : list of keys, e.g. ["P3", "P50", "P97"] + colors : list of colors (optional) + linewidths : list of linewidths (optional) + fill : whether to fill between min and max percentile + :param labels: + """ + + n = len(x) + + if labels is None: + labels = [None] * len(p_keys) + if linestyle is None: + linestyle = [None] * len(p_keys) + if colors is None: + colors = [None] * len(p_keys) + if linewidths is None: + linewidths = [1.5] * len(p_keys) + + # Plot lines + for i, key in enumerate(p_keys): + ax.plot( + x, + data_dict[key][:n], + color=colors[i], + linewidth=linewidths[i], + label=labels[i], + linestyle=linestyle[i], + ) + + # Fill between min and max percentile + if fill and len(p_keys) >= 2: + lower = data_dict[p_keys[0]][:n] + upper = data_dict[p_keys[-1]][:n] + + ax.fill_between(x, lower, upper, color=fill_color, alpha=fill_alpha) + + return ax + + +result = subprocess.run(["syncNC", "-al"], check=True) +alina_path = Path("/data/nextcloud/syncuser/Wachstum") +ods_path = alina_path / "WachstumsDatenWHO.ods" +csv_path = alina_path / "WachstumAlina.csv" +output_path = alina_path / "WachstumAlina.png" + +output_dir = ods_path.name + +# Load specific sheets into data structures (DataFrames) +alina_df = pd.read_csv(csv_path) + +who_weight_df = pd.read_excel(ods_path, sheet_name="WHO Gewicht", engine="odf") +who_height_df = pd.read_excel(ods_path, sheet_name="WHO Größe", engine="odf") +who_head_df = pd.read_excel(ods_path, sheet_name="WHO Kopfumfang", engine="odf") + + +def calculateDateFromBrithday(df, birthday): + + new_df = pd.DataFrame(columns=["Datum"]) + for idx, (val1, val2) in enumerate(zip(df["Month"], df["Week"])): + if not pd.isna(val1): + new_df.loc[idx, "Datum"] = birthday + pd.DateOffset(months=val1) + if not pd.isna(val2): + new_df.loc[idx, "Datum"] = birthday + pd.DateOffset(weeks=val2) + + return pd.to_datetime(new_df["Datum"], format="%d.%m.%Y", errors="coerce") + + +alina_df["Datum"] = pd.to_datetime( + alina_df["Datum"], format="%d.%m.%Y", errors="coerce" +) + +# Ensure Datum is datetime (safe to run even if already converted) +who_weight_df["Datum"] = calculateDateFromBrithday(who_weight_df, alina_df["Datum"][0]) +who_height_df["Datum"] = calculateDateFromBrithday(who_height_df, alina_df["Datum"][0]) +who_head_df["Datum"] = calculateDateFromBrithday(who_head_df, alina_df["Datum"][0]) + +who_weight_daily = interpolate_daily(who_weight_df) +who_height_daily = interpolate_daily(who_height_df) +who_head_daily = interpolate_daily(who_head_df) + +print(alina_df["Datum"]) +# Plot +last_alina_date = alina_df["Datum"].max() + pd.DateOffset(months=2) +x = who_weight_daily["Datum"][who_weight_daily["Datum"] <= last_alina_date] +fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(12, 10), sharex=True) + +percentile_list = ["P1", "P3", "P10", "P25", "P50", "P75", "P90", "P97", "P99"] +colors_list = ["lightcoral" if p != "P50" else "darkred" for p in percentile_list] +linewidths_list = [1.5 if p != "P50" else 2.5 for p in percentile_list] +label_list = [ + p if p == "P50" or p == "P3" or p == "P97" else "" for p in percentile_list +] +linestyle_list = ["--" if p == "P3" or p == "P97" else "-" for p in percentile_list] +# ---------- 1. WEIGHT ---------- +ax = axes[0] +plot_percentiles( + ax, + x, + who_weight_daily, + percentile_list, + colors=colors_list, + linewidths=linewidths_list, + labels=label_list, + linestyle=linestyle_list, +) + +ax.plot( + alina_df["Datum"], + alina_df["Gewicht"].interpolate(limit_area="inside"), + color="green", + linewidth=1.5, + marker="o", + label="Alina", +) + +last_idx = alina_df["Gewicht"].last_valid_index() +who_row = who_weight_daily.loc[ + who_weight_daily["Datum"] == alina_df["Datum"][last_idx] +].iloc[0] +perzentil = (alina_df["Gewicht"][last_idx] - who_row["P50"]) / ( + (who_row["P97"] - who_row["P50"]) / 47 +) + 50 + +ax.text( + alina_df["Datum"].iloc[-1] + pd.DateOffset(days=1), + alina_df["Gewicht"].iloc[-1] + 0.5, + f"{perzentil:.2f} %", + fontsize=10, + ha="left", + va="bottom", + color="green", +) + +ax.set_ylabel("Gewicht [kg]") +ax.set_title("Wachstum") +# Enable minor ticks +ax.minorticks_on() + +# Configure major grid (e.g., solid, darker) +ax.grid(which="major", color="black", linestyle="-", linewidth=0.8) + +# Configure minor grid (e.g., dashed, lighter, thinner) +ax.grid(which="minor", color="gray", linestyle="--", linewidth=0.4, alpha=0.5) +ax.legend() + + +# ---------- 2. HEIGHT ---------- +ax = axes[1] +plot_percentiles( + ax, + x, + who_height_daily, + percentile_list, + colors=colors_list, + linewidths=linewidths_list, + labels=label_list, + linestyle=linestyle_list, +) +ax.plot( + alina_df["Datum"], + alina_df["Größe"].interpolate(limit_area="inside"), + color="green", + linewidth=1.5, + marker="o", +) + +last_idx = alina_df["Größe"].last_valid_index() +who_row = who_height_daily.loc[ + who_height_daily["Datum"] == alina_df["Datum"][last_idx] +].iloc[0] +perzentil = (alina_df["Größe"][last_idx] - who_row["P50"]) / ( + (who_row["P97"] - who_row["P50"]) / 47 +) + 50 + +ax.text( + alina_df["Datum"].iloc[-1] + pd.DateOffset(days=1), + alina_df["Größe"].iloc[-1] + 0.5, + f"{perzentil:.2f} %", + fontsize=10, + ha="left", + va="bottom", + color="green", +) + + +ax.set_ylabel("Größe [cm]") +# Enable minor ticks +ax.minorticks_on() + +# Configure major grid (e.g., solid, darker) +ax.grid(which="major", color="black", linestyle="-", linewidth=0.8) +# Configure minor grid (e.g., dashed, lighter, thinner) +ax.grid(which="minor", color="gray", linestyle="--", linewidth=0.4, alpha=0.5) + + +# ---------- 3. HEAD CIRCUMFERENCE ---------- +ax = axes[2] + +plot_percentiles( + ax, + x, + who_head_daily, + percentile_list, + colors=colors_list, + linewidths=linewidths_list, + labels=label_list, + linestyle=linestyle_list, +) + +ax.plot( + alina_df["Datum"], + alina_df["Kopfumfang"].interpolate(limit_area="inside"), + color="green", + linewidth=1.5, + marker="o", +) + +last_idx = alina_df["Kopfumfang"].last_valid_index() +who_row = who_head_daily.loc[ + who_head_daily["Datum"] == alina_df["Datum"][last_idx] +].iloc[0] +perzentil = (alina_df["Kopfumfang"][last_idx] - who_row["P50"]) / ( + (who_row["P97"] - who_row["P50"]) / 47 +) + 50 + +ax.text( + alina_df["Datum"].iloc[-1] + pd.DateOffset(days=1), + alina_df["Kopfumfang"].iloc[-1] + 0.5, + f"{perzentil:.2f} %", + fontsize=10, + ha="left", + va="bottom", + color="green", +) + +ax.set_ylabel("Kopfumfang [cm]") +# Enable minor ticks +ax.minorticks_on() + +# Configure major grid (e.g., solid, darker) +ax.grid(which="major", color="black", linestyle="-", linewidth=0.8) + +# Configure minor grid (e.g., dashed, lighter, thinner) +ax.grid(which="minor", color="gray", linestyle="--", linewidth=0.4, alpha=0.5) + +# ---------- X‑AXIS ---------- +axes[-1].set_xlabel("Datum") + +N = 7 # label every Nth tick + +axes[-1].set_xticks(x[::N]) +axes[-1].set_xticklabels(x.iloc[::N].dt.strftime("%d.%m.%Y"), rotation=45, ha="right") + +plt.tight_layout() +plt.savefig(output_path, dpi=150) +plt.close() + +subprocess.run(["syncNC", "-al"], check=True) + +print("Fin")