Files
Server-scripts/home/generateAlinaChart.py
T
2026-10-24 15:20:23 +02:00

306 lines
8.0 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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")