2026-04-14 23:55:15 +02:00
|
|
|
|
import pandas as pd
|
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
|
|
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"})
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-04-23 13:05:45 +02:00
|
|
|
|
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):
|
2026-04-14 23:55:15 +02:00
|
|
|
|
"""
|
|
|
|
|
|
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 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],
|
2026-04-23 13:05:45 +02:00
|
|
|
|
label=labels[i],
|
|
|
|
|
|
linestyle=linestyle[i]
|
2026-04-14 23:55:15 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
|
|
# Build output path from ods_path
|
|
|
|
|
|
ods_path = "/data/Nextcloud/Documents/Dokumente/Shared/Alina/Wachstum.ods"
|
|
|
|
|
|
output_dir = os.path.dirname(ods_path)
|
|
|
|
|
|
output_path = os.path.join(output_dir, "Wachstum.png")
|
|
|
|
|
|
|
|
|
|
|
|
# Load specific sheets into data structures (DataFrames)
|
|
|
|
|
|
alina_df = pd.read_excel(
|
|
|
|
|
|
ods_path,
|
|
|
|
|
|
sheet_name="Alina",
|
|
|
|
|
|
engine="odf"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
|
)
|
2026-04-23 13:05:45 +02:00
|
|
|
|
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")
|
2026-04-14 23:55:15 +02:00
|
|
|
|
|
|
|
|
|
|
# Ensure Datum is datetime (safe to run even if already converted)
|
2026-04-23 13:05:45 +02:00
|
|
|
|
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] )
|
2026-04-14 23:55:15 +02:00
|
|
|
|
|
|
|
|
|
|
who_weight_daily = interpolate_daily(who_weight_df)
|
|
|
|
|
|
who_height_daily = interpolate_daily(who_height_df)
|
|
|
|
|
|
who_head_daily = interpolate_daily(who_head_df)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 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]
|
2026-04-23 13:05:45 +02:00
|
|
|
|
linestyle_list = ["--" if p == "P3" or p == "P97" else "-" for p in percentile_list]
|
2026-04-14 23:55:15 +02:00
|
|
|
|
# ---------- 1. WEIGHT ----------
|
|
|
|
|
|
ax = axes[0]
|
|
|
|
|
|
plot_percentiles(
|
|
|
|
|
|
ax,
|
|
|
|
|
|
x,
|
|
|
|
|
|
who_weight_daily,
|
|
|
|
|
|
percentile_list,
|
|
|
|
|
|
colors=colors_list,
|
|
|
|
|
|
linewidths=linewidths_list,
|
2026-04-23 13:05:45 +02:00
|
|
|
|
labels=label_list,
|
|
|
|
|
|
linestyle=linestyle_list
|
2026-04-14 23:55:15 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ax.plot(alina_df["Datum"], alina_df["Gewicht"],
|
|
|
|
|
|
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,
|
2026-04-23 13:05:45 +02:00
|
|
|
|
labels=label_list,
|
|
|
|
|
|
linestyle=linestyle_list
|
2026-04-14 23:55:15 +02:00
|
|
|
|
)
|
|
|
|
|
|
ax.plot(alina_df["Datum"], alina_df["Größe"],
|
|
|
|
|
|
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,
|
2026-04-23 13:05:45 +02:00
|
|
|
|
labels=label_list,
|
|
|
|
|
|
linestyle=linestyle_list
|
2026-04-14 23:55:15 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ax.plot(alina_df["Datum"], alina_df["Kopfumfang"],
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
print("Fin")
|