1

Refactoring

This commit is contained in:
Richard Dern 2025-11-19 22:46:04 +01:00
parent 6a78dbb50d
commit 3a1f7e2a7e
10 changed files with 240 additions and 120 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -12,10 +12,10 @@ python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py"
![](figures/temperature_last_7_days.png)
![](figures/humidity_last_7_days.png)
![](figures/pressure_last_7_days.png)
![](figures/humidity_last_7_days.png)
![](figures/rain_rate_last_7_days.png)
![](figures/illuminance_last_7_days.png)
@ -35,12 +35,14 @@ Les images générées sont stockées dans `figures/calendar/` et les CSV corres
python "docs/03 - Premiers graphiques/scripts/plot_calendar_overview.py"
```
![](figures/calendar/calendar_rain_2025.png)
![](figures/calendar/calendar_temperature_2025.png)
![](figures/calendar/calendar_pressure_2025.png)
![](figures/calendar/calendar_humidity_2025.png)
![](figures/calendar/calendar_rain_2025.png)
![](figures/calendar/calendar_illuminance_2025.png)
![](figures/calendar/calendar_wind_2025.png)

View File

@ -4,8 +4,6 @@ from __future__ import annotations
from pathlib import Path
import sys
import calendar
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[3]
@ -13,44 +11,69 @@ if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from meteo.dataset import load_raw_csv
from meteo.analysis import compute_daily_rainfall_totals
from meteo.plots import export_plot_dataset, plot_calendar_heatmap, plot_weekday_profiles
from meteo.variables import VARIABLES_BY_KEY
from meteo.plots import (
CalendarHeatmapSpec,
daily_mean_series,
generate_calendar_heatmaps,
rainfall_daily_total_series,
)
DOC_DIR = Path(__file__).resolve().parent.parent
CSV_PATH = PROJECT_ROOT / "data" / "weather_minutely.csv"
OUTPUT_DIR = DOC_DIR / "figures" / "calendar"
WEEKDAY_VARIABLE_KEYS = ["temperature", "humidity", "wind_speed", "illuminance"]
def _format_calendar_matrix(series: pd.Series, year: int, agg_label: str) -> pd.DataFrame:
"""
Transforme une série quotidienne en matrice mois x jours (1-31).
"""
start = pd.Timestamp(year=year, month=1, day=1, tz=series.index.tz)
end = pd.Timestamp(year=year, month=12, day=31, tz=series.index.tz)
filtered = series.loc[(series.index >= start) & (series.index <= end)]
matrix = pd.DataFrame(
np.nan,
index=[calendar.month_name[m][:3] for m in range(1, 13)],
columns=list(range(1, 32)),
HEATMAP_SPECS: tuple[CalendarHeatmapSpec, ...] = (
CalendarHeatmapSpec(
key="rain",
agg_label="Pluie (mm)",
title_template="Pluie quotidienne - {year}",
cmap="Blues",
colorbar_label="mm",
aggregator=rainfall_daily_total_series,
),
CalendarHeatmapSpec(
key="temperature",
agg_label="Température (°C)",
title_template="Température moyenne quotidienne - {year}",
cmap="coolwarm",
colorbar_label="°C",
aggregator=daily_mean_series("temperature"),
),
CalendarHeatmapSpec(
key="humidity",
agg_label="Humidité relative (%)",
title_template="Humidité relative quotidienne - {year}",
cmap="PuBu",
colorbar_label="%",
aggregator=daily_mean_series("humidity"),
),
CalendarHeatmapSpec(
key="pressure",
agg_label="Pression (hPa)",
title_template="Pression moyenne quotidienne - {year}",
cmap="Greens",
colorbar_label="hPa",
aggregator=daily_mean_series("pressure"),
),
CalendarHeatmapSpec(
key="illuminance",
agg_label="Illuminance (lux)",
title_template="Illuminance moyenne quotidienne - {year}",
cmap="YlOrBr",
colorbar_label="lux",
aggregator=daily_mean_series("illuminance"),
),
CalendarHeatmapSpec(
key="wind",
agg_label="Vent (km/h)",
title_template="Vitesse moyenne du vent - {year}",
cmap="Purples",
colorbar_label="km/h",
aggregator=daily_mean_series("wind_speed"),
),
)
for timestamp, value in filtered.items():
month = timestamp.month
day = timestamp.day
matrix.at[calendar.month_name[month][:3], day] = value
matrix.index.name = f"{agg_label} ({year})"
return matrix
def compute_daily_mean(df: pd.DataFrame, column: str) -> pd.Series:
return df[column].resample("1D").mean()
def main() -> None:
if not CSV_PATH.exists():
@ -76,91 +99,20 @@ def main() -> None:
latest_year = df.index.year.max()
print(f"Année retenue pour le calendrier : {latest_year}")
daily_totals = compute_daily_rainfall_totals(df=df)
daily_rain = daily_totals["daily_total"]
rain_matrix = _format_calendar_matrix(daily_rain, latest_year, "Pluie (mm)")
rain_matrix.attrs["cmap"] = "Blues"
rain_matrix.attrs["colorbar_label"] = "mm"
rain_path = OUTPUT_DIR / f"calendar_rain_{latest_year}.png"
plot_calendar_heatmap(
matrix=rain_matrix,
output_path=rain_path,
title=f"Pluie quotidienne - {latest_year}",
cmap="Blues",
colorbar_label="mm",
results = generate_calendar_heatmaps(
df=df,
specs=HEATMAP_SPECS,
year=latest_year,
output_dir=OUTPUT_DIR,
)
print(f"✔ Heatmap pluie {latest_year} : {rain_path}")
daily_temp = compute_daily_mean(df, "temperature")
temp_matrix = _format_calendar_matrix(daily_temp, latest_year, "Température (°C)")
temp_matrix.attrs["cmap"] = "coolwarm"
temp_matrix.attrs["colorbar_label"] = "°C"
temp_path = OUTPUT_DIR / f"calendar_temperature_{latest_year}.png"
plot_calendar_heatmap(
matrix=temp_matrix,
output_path=temp_path,
title=f"Température moyenne quotidienne - {latest_year}",
cmap="coolwarm",
colorbar_label="°C",
)
print(f"✔ Heatmap température {latest_year} : {temp_path}")
if "pressure" in df.columns:
daily_pressure = compute_daily_mean(df, "pressure")
pressure_matrix = _format_calendar_matrix(daily_pressure, latest_year, "Pression (hPa)")
pressure_matrix.attrs["cmap"] = "Greens"
pressure_matrix.attrs["colorbar_label"] = "hPa"
pressure_path = OUTPUT_DIR / f"calendar_pressure_{latest_year}.png"
plot_calendar_heatmap(
matrix=pressure_matrix,
output_path=pressure_path,
title=f"Pression moyenne quotidienne - {latest_year}",
cmap="Greens",
colorbar_label="hPa",
)
print(f"✔ Heatmap pression {latest_year} : {pressure_path}")
if "illuminance" in df.columns:
daily_lux = compute_daily_mean(df, "illuminance")
lux_matrix = _format_calendar_matrix(daily_lux, latest_year, "Illuminance (lux)")
lux_matrix.attrs["cmap"] = "YlOrBr"
lux_matrix.attrs["colorbar_label"] = "lux"
lux_path = OUTPUT_DIR / f"calendar_illuminance_{latest_year}.png"
plot_calendar_heatmap(
matrix=lux_matrix,
output_path=lux_path,
title=f"Illuminance moyenne quotidienne - {latest_year}",
cmap="YlOrBr",
colorbar_label="lux",
)
print(f"✔ Heatmap illuminance {latest_year} : {lux_path}")
if "wind_speed" in df.columns:
daily_wind = compute_daily_mean(df, "wind_speed")
wind_matrix = _format_calendar_matrix(daily_wind, latest_year, "Vent (km/h)")
wind_matrix.attrs["cmap"] = "Purples"
wind_matrix.attrs["colorbar_label"] = "km/h"
wind_path = OUTPUT_DIR / f"calendar_wind_{latest_year}.png"
plot_calendar_heatmap(
matrix=wind_matrix,
output_path=wind_path,
title=f"Vitesse moyenne du vent - {latest_year}",
cmap="Purples",
colorbar_label="km/h",
)
print(f"✔ Heatmap vent {latest_year} : {wind_path}")
hourly = df[WEEKDAY_VARIABLE_KEYS].resample("1h").mean()
weekday_stats = hourly.groupby(hourly.index.dayofweek).mean()
weekday_path = OUTPUT_DIR / "weekday_profiles.png"
variables = [VARIABLES_BY_KEY[key] for key in WEEKDAY_VARIABLE_KEYS]
plot_weekday_profiles(
weekday_df=weekday_stats,
variables=variables,
output_path=weekday_path,
title="Profils moyens par jour de semaine",
)
print(f"✔ Profils hebdomadaires : {weekday_path}")
for result in results:
title = result.spec.title_template.format(year=latest_year)
if result.output_path:
print(f"{title} : {result.output_path}")
else:
reason = f" ({result.reason})" if result.reason else ""
print(f"⚠ Heatmap ignorée pour {result.spec.key}{reason}.")
print("✔ Graphiques calendrier générés.")

View File

@ -2,6 +2,14 @@ from __future__ import annotations
from .base import export_plot_dataset
from .calendar import plot_calendar_heatmap, plot_weekday_profiles
from .calendar_overview import (
CalendarHeatmapResult,
CalendarHeatmapSpec,
daily_mean_series,
format_calendar_matrix,
generate_calendar_heatmaps,
rainfall_daily_total_series,
)
from .correlations import (
plot_correlation_heatmap,
plot_lagged_correlation,
@ -31,6 +39,12 @@ __all__ = [
"export_plot_dataset",
"plot_calendar_heatmap",
"plot_weekday_profiles",
"CalendarHeatmapResult",
"CalendarHeatmapSpec",
"daily_mean_series",
"format_calendar_matrix",
"generate_calendar_heatmaps",
"rainfall_daily_total_series",
"plot_correlation_heatmap",
"plot_lagged_correlation",
"plot_rolling_correlation_heatmap",

View File

@ -0,0 +1,152 @@
"""Utilitaires pour générer des heatmaps calendrier configurables."""
from __future__ import annotations
import calendar
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Sequence
import numpy as np
import pandas as pd
from meteo.analysis import compute_daily_rainfall_totals
from .calendar import plot_calendar_heatmap
CalendarAggregator = Callable[[pd.DataFrame], pd.Series]
__all__ = [
"CalendarHeatmapSpec",
"CalendarHeatmapResult",
"daily_mean_series",
"rainfall_daily_total_series",
"format_calendar_matrix",
"generate_calendar_heatmaps",
]
@dataclass(frozen=True)
class CalendarHeatmapSpec:
"""Description d'une heatmap calendrier à générer."""
key: str
agg_label: str
title_template: str
cmap: str
colorbar_label: str
aggregator: CalendarAggregator
@dataclass(frozen=True)
class CalendarHeatmapResult:
"""Résultat d'une tentative de génération de heatmap calendrier."""
spec: CalendarHeatmapSpec
output_path: Path | None
reason: str | None = None
def daily_mean_series(column: str) -> CalendarAggregator:
"""Retourne un agrégateur qui calcule la moyenne quotidienne d'une colonne."""
def _aggregator(df: pd.DataFrame) -> pd.Series:
if column not in df.columns:
raise KeyError(column)
if not isinstance(df.index, pd.DatetimeIndex):
raise TypeError("Le DataFrame doit être indexé par des timestamps pour les moyennes quotidiennes.")
return df[column].resample("1D").mean()
return _aggregator
def rainfall_daily_total_series(df: pd.DataFrame) -> pd.Series:
"""Calcule les cumuls quotidiens de précipitations."""
totals = compute_daily_rainfall_totals(df=df)
return totals["daily_total"]
def format_calendar_matrix(series: pd.Series, year: int, agg_label: str) -> pd.DataFrame:
"""Transforme une série quotidienne en matrice calendrier mois x jours."""
if not isinstance(series.index, pd.DatetimeIndex):
raise TypeError("La série doit avoir un index temporel pour être convertie en calendrier.")
tz = series.index.tz
start = pd.Timestamp(year=year, month=1, day=1, tz=tz)
end = pd.Timestamp(year=year, month=12, day=31, tz=tz)
filtered = series.loc[(series.index >= start) & (series.index <= end)]
matrix = pd.DataFrame(
np.nan,
index=[calendar.month_name[m][:3] for m in range(1, 13)],
columns=list(range(1, 32)),
)
for timestamp, value in filtered.items():
matrix.at[calendar.month_name[timestamp.month][:3], timestamp.day] = value
matrix.index.name = f"{agg_label} ({year})"
return matrix
def generate_calendar_heatmaps(
df: pd.DataFrame,
specs: Sequence[CalendarHeatmapSpec],
*,
year: int,
output_dir: str | Path,
) -> list[CalendarHeatmapResult]:
"""Génère l'ensemble des heatmaps calendrier décrites dans `specs`."""
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
results: list[CalendarHeatmapResult] = []
for spec in specs:
try:
daily_series = spec.aggregator(df)
except Exception as exc:
results.append(
CalendarHeatmapResult(
spec=spec,
output_path=None,
reason=str(exc),
)
)
continue
if daily_series is None or daily_series.empty:
results.append(
CalendarHeatmapResult(
spec=spec,
output_path=None,
reason="Série vide ou invalide.",
)
)
continue
try:
matrix = format_calendar_matrix(daily_series, year, spec.agg_label)
except Exception as exc: # pragma: no cover - remonté au résultat
results.append(
CalendarHeatmapResult(
spec=spec,
output_path=None,
reason=str(exc),
)
)
continue
output_path = output_dir / f"calendar_{spec.key}_{year}.png"
plot_calendar_heatmap(
matrix=matrix,
output_path=output_path,
title=spec.title_template.format(year=year, label=spec.agg_label),
cmap=spec.cmap,
colorbar_label=spec.colorbar_label,
)
results.append(CalendarHeatmapResult(spec=spec, output_path=output_path.resolve()))
return results