1

Amélioration des vues basiques

This commit is contained in:
2025-11-20 21:12:02 +01:00
parent 8979f48c23
commit df7fbf07ed
12 changed files with 332 additions and 51 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -4,27 +4,29 @@ On peut désormais tracer nos premiers graphiques simples et bruts.
S'ils ne sont pas très instructifs par rapport à ce que nous fournissent Home Assistant et InfluxDB, ils nous permettent au moins de nous assurer que tout fonctionne, et que les données semblent cohérentes.
Les fichiers CSV correspondant à chaque figure sont conservés dans `data/` dans ce dossier.
On se limite dans un premier temps aux 7 derniers jours.
Les graphiques couvrent maintenant toute la période disponible dans `data/weather_minutely.csv`.
Une agrégation automatique réduit le nombre de points pour rester lisible (plus de courbes "peignes"), et l'axe des dates utilise un format compact qui évite tout chevauchement de labels.
On peut au besoin restreindre la période avec `--days` ou imposer une fréquence d'agrégation avec `--resample`.
```shell
python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py"
```
![](figures/temperature_last_7_days.png)
![](figures/temperature_overview.png)
![](figures/pressure_last_7_days.png)
![](figures/pressure_overview.png)
![](figures/humidity_last_7_days.png)
![](figures/humidity_overview.png)
![](figures/rain_rate_last_7_days.png)
![](figures/rain_rate_overview.png)
![](figures/illuminance_last_7_days.png)
![](figures/wind_speed_overview.png)
![](figures/wind_speed_last_7_days.png)
![](figures/wind_direction_overview.png)
![](figures/wind_direction_last_7_days.png)
![](figures/illuminance_overview.png)
![](figures/sun_elevation_last_7_days.png)
![](figures/sun_elevation_overview.png)
## Vues calendrier

View File

@@ -1,5 +1,5 @@
# scripts/plot_basic_variables.py
"""Génère des séries temporelles simples (7 jours) pour chaque variable météo."""
"""Génère des séries temporelles simples pour chaque variable météo."""
from __future__ import annotations
@@ -7,7 +7,6 @@ import argparse
from pathlib import Path
import sys
import matplotlib.pyplot as plt
import pandas as pd
@@ -16,7 +15,7 @@ if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from meteo.dataset import load_raw_csv
from meteo.plots import export_plot_dataset
from meteo.plots import PlotChoice, PlotStyle, plot_basic_series, recommended_style, resample_series_for_plot
from meteo.variables import Variable, VARIABLES
@@ -25,47 +24,32 @@ DOC_DIR = Path(__file__).resolve().parent.parent
DEFAULT_OUTPUT_DIR = DOC_DIR / "figures"
def _prepare_slice(df: pd.DataFrame, *, last_days: int) -> pd.DataFrame:
"""Extrait la fenêtre temporelle souhaitée et applique une moyenne horaire pour lisser la courbe."""
def _select_window(df: pd.DataFrame, *, last_days: int | None) -> pd.DataFrame:
"""Extrait la fenêtre temporelle souhaitée (ou la totalité si None)."""
if last_days is None:
return df
end = df.index.max()
start = end - pd.Timedelta(days=last_days)
df_slice = df.loc[start:end]
numeric_slice = df_slice.select_dtypes(include="number")
if numeric_slice.empty:
raise RuntimeError("Aucune colonne numérique disponible pour les moyennes horaires.")
return numeric_slice.resample("1h").mean()
return df.loc[start:end]
def _plot_variable(df_hourly: pd.DataFrame, var: Variable, output_dir: Path) -> Path | None:
"""Trace la série pour une variable et retourne le chemin de l'image générée."""
if var.column not in df_hourly.columns:
print(f"⚠ Colonne absente pour {var.key} ({var.column}).")
return None
series = df_hourly[var.column].dropna()
if series.empty:
print(f"⚠ Aucun point valide pour {var.key} dans l'intervalle choisi.")
return None
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / f"{var.key}_last_7_days.png"
export_plot_dataset(series.to_frame(name=var.column), output_path)
plt.figure()
plt.plot(series.index, series)
plt.xlabel("Temps (UTC)")
def _format_ylabel(var: Variable) -> str:
unit_text = f" ({var.unit})" if var.unit else ""
plt.ylabel(f"{var.label}{unit_text}")
plt.title(f"{var.label} - Moyenne horaire sur les 7 derniers jours")
plt.grid(True)
plt.tight_layout()
plt.savefig(output_path, dpi=150)
plt.close()
print(f"✔ Graphique généré : {output_path}")
return output_path
return f"{var.label}{unit_text}"
def _aggregation_label(choice: PlotChoice, freq: str) -> str:
"""Texte court pour indiquer l'agrégation appliquée."""
base = "moyenne"
if callable(choice.agg) and getattr(choice.agg, "__name__", "") == "_circular_mean_deg":
base = "moyenne circulaire"
elif choice.agg == "sum":
base = "somme"
elif choice.agg == "median":
base = "médiane"
return f"{base} {freq}"
def main(argv: list[str] | None = None) -> None:
@@ -78,8 +62,23 @@ def main(argv: list[str] | None = None) -> None:
parser.add_argument(
"--days",
type=int,
default=7,
help="Nombre de jours à afficher (par défaut : 7).",
default=None,
help="Nombre de jours à afficher (par défaut : toute la période disponible).",
)
parser.add_argument(
"--style",
choices=[style.value for style in PlotStyle],
help="Style de représentation à utiliser pour toutes les variables (par défaut : recommandations par variable).",
)
parser.add_argument(
"--resample",
help="Fréquence pandas à utiliser pour l'agrégation temporelle (par défaut : calcul automatique).",
)
parser.add_argument(
"--max-points",
type=int,
default=420,
help="Nombre de points cible après agrégation automatique (par défaut : 420).",
)
parser.add_argument(
"--output-dir",
@@ -93,7 +92,7 @@ def main(argv: list[str] | None = None) -> None:
raise FileNotFoundError(f"Dataset introuvable : {CSV_PATH}")
df = load_raw_csv(CSV_PATH)
df_hourly = _prepare_slice(df, last_days=args.days)
df_window = _select_window(df, last_days=args.days)
selected: list[Variable]
if args.only:
@@ -105,8 +104,44 @@ def main(argv: list[str] | None = None) -> None:
else:
selected = list(VARIABLES)
output_dir: Path = args.output_dir
output_dir.mkdir(parents=True, exist_ok=True)
for variable in selected:
_plot_variable(df_hourly, variable, args.output_dir)
if variable.column not in df_window.columns:
print(f"⚠ Colonne absente pour {variable.key} ({variable.column}).")
continue
series = df_window[variable.column].dropna()
if series.empty:
print(f"⚠ Aucun point valide pour {variable.key} sur la période choisie.")
continue
style_choice = recommended_style(variable, args.style)
aggregated, freq_used = resample_series_for_plot(
series,
variable=variable,
freq=args.resample,
target_points=args.max_points,
)
if aggregated.empty:
print(f"⚠ Pas de points après agrégation pour {variable.key}.")
continue
output_path = output_dir / f"{variable.key}_overview.png"
annotate_freq = _aggregation_label(style_choice, freq_used)
plot_basic_series(
aggregated,
variable=variable,
output_path=output_path,
style=style_choice.style,
title=f"{variable.label} — évolution temporelle",
ylabel=_format_ylabel(variable),
annotate_freq=annotate_freq,
)
print(f"✔ Graphique généré : {output_path}")
if __name__ == "__main__":