306 lines
8.0 KiB
Python
Executable File
306 lines
8.0 KiB
Python
Executable File
#!/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")
|