1

Compare commits

..

No commits in common. "3a1f7e2a7e45374352615c2a8f01f65577779c1c" and "79603b7c3e664cdd8aaa5a8e6eafd55e801a6e81" have entirely different histories.

12 changed files with 219 additions and 318 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@ -4,18 +4,16 @@ 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. 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. 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.
```shell ```shell
python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py" python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py"
``` ```
![](figures/temperature_last_7_days.png) ![](figures/temperature_last_7_days.png)
![](figures/pressure_last_7_days.png)
![](figures/humidity_last_7_days.png) ![](figures/humidity_last_7_days.png)
![](figures/pressure_last_7_days.png)
![](figures/rain_rate_last_7_days.png) ![](figures/rain_rate_last_7_days.png)
![](figures/illuminance_last_7_days.png) ![](figures/illuminance_last_7_days.png)
@ -25,26 +23,3 @@ python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py"
![](figures/wind_direction_last_7_days.png) ![](figures/wind_direction_last_7_days.png)
![](figures/sun_elevation_last_7_days.png) ![](figures/sun_elevation_last_7_days.png)
## Vues calendrier
Les vues calendrier permettent de visualiser, jour par jour, les cumuls ou moyennes quotidiennes sur la dernière année complète disponible.
Les images générées sont stockées dans `figures/calendar/` et les CSV correspondants dans `data/calendar/`.
```shell
python "docs/03 - Premiers graphiques/scripts/plot_calendar_overview.py"
```
![](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)
Ces vues, bien que simples en principe, mettent déjà mieux en évidence les fluctuations au cours du temps.

View File

@ -1,121 +0,0 @@
# docs/03 - Premiers graphiques/scripts/plot_calendar_overview.py
from __future__ import annotations
from pathlib import Path
import sys
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[3]
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 (
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"
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"),
),
)
def main() -> None:
if not CSV_PATH.exists():
print(f"⚠ Fichier introuvable : {CSV_PATH}")
return
df = load_raw_csv(CSV_PATH)
if df.empty:
print("⚠ Dataset vide.")
return
if not isinstance(df.index, pd.DatetimeIndex):
print("⚠ Le dataset doit avoir un index temporel.")
return
print(f"Dataset minuté chargé : {CSV_PATH}")
print(f" Lignes : {len(df)}")
print(f" Colonnes : {list(df.columns)}")
print()
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
latest_year = df.index.year.max()
print(f"Année retenue pour le calendrier : {latest_year}")
results = generate_calendar_heatmaps(
df=df,
specs=HEATMAP_SPECS,
year=latest_year,
output_dir=OUTPUT_DIR,
)
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.")
if __name__ == "__main__":
main()

View File

@ -2,14 +2,6 @@ from __future__ import annotations
from .base import export_plot_dataset from .base import export_plot_dataset
from .calendar import plot_calendar_heatmap, plot_weekday_profiles 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 ( from .correlations import (
plot_correlation_heatmap, plot_correlation_heatmap,
plot_lagged_correlation, plot_lagged_correlation,
@ -39,12 +31,6 @@ __all__ = [
"export_plot_dataset", "export_plot_dataset",
"plot_calendar_heatmap", "plot_calendar_heatmap",
"plot_weekday_profiles", "plot_weekday_profiles",
"CalendarHeatmapResult",
"CalendarHeatmapSpec",
"daily_mean_series",
"format_calendar_matrix",
"generate_calendar_heatmaps",
"rainfall_daily_total_series",
"plot_correlation_heatmap", "plot_correlation_heatmap",
"plot_lagged_correlation", "plot_lagged_correlation",
"plot_rolling_correlation_heatmap", "plot_rolling_correlation_heatmap",

View File

@ -1,152 +0,0 @@
"""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

View File

@ -0,0 +1,216 @@
# scripts/plot_calendar_overview.py
from __future__ import annotations
from pathlib import Path
import calendar
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
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
CSV_PATH = Path("data/weather_minutely.csv")
OUTPUT_DIR = Path("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)),
)
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 plot_combined_calendar(
matrices: dict[str, pd.DataFrame],
output_path: Path,
*,
title: str,
) -> None:
if not matrices:
return
combined_dataset = pd.concat(matrices, axis=1)
export_plot_dataset(combined_dataset, output_path)
n = len(matrices)
fig, axes = plt.subplots(n, 1, figsize=(14, 4 * n), sharex=True)
if n == 1:
axes = [axes]
for ax, (label, matrix) in zip(axes, matrices.items()):
data = matrix.to_numpy(dtype=float)
im = ax.imshow(data, aspect="auto", interpolation="nearest", cmap=matrix.attrs.get("cmap", "viridis"))
ax.set_xticks(np.arange(matrix.shape[1]))
ax.set_xticklabels(matrix.columns, rotation=90)
ax.set_yticks(np.arange(matrix.shape[0]))
ax.set_yticklabels(matrix.index)
ax.set_ylabel(label)
cbar = fig.colorbar(im, ax=ax)
if matrix.attrs.get("colorbar_label"):
cbar.set_label(matrix.attrs["colorbar_label"])
axes[-1].set_xlabel("Jour du mois")
fig.suptitle(title)
fig.tight_layout(rect=[0, 0, 1, 0.97])
fig.savefig(output_path, dpi=150)
plt.close(fig)
def main() -> None:
if not CSV_PATH.exists():
print(f"⚠ Fichier introuvable : {CSV_PATH}")
return
df = load_raw_csv(CSV_PATH)
if df.empty:
print("⚠ Dataset vide.")
return
if not isinstance(df.index, pd.DatetimeIndex):
print("⚠ Le dataset doit avoir un index temporel.")
return
print(f"Dataset minuté chargé : {CSV_PATH}")
print(f" Lignes : {len(df)}")
print(f" Colonnes : {list(df.columns)}")
print()
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
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",
)
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}")
matrices_for_combined = {
"Pluie (mm)": rain_matrix,
"Température (°C)": temp_matrix,
}
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}")
matrices_for_combined["Pression (hPa)"] = pressure_matrix
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}")
matrices_for_combined["Illuminance (lux)"] = lux_matrix
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}")
matrices_for_combined["Vent (km/h)"] = wind_matrix
combined_path = OUTPUT_DIR / f"calendar_combined_{latest_year}.png"
plot_combined_calendar(
matrices=matrices_for_combined,
output_path=combined_path,
title=f"Calendrier combiné {latest_year}",
)
print(f"✔ Calendrier combiné : {combined_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}")
print("✔ Graphiques calendrier générés.")
if __name__ == "__main__":
main()

View File

@ -29,10 +29,7 @@ PLOT_SCRIPTS: tuple[PlotScript, ...] = (
"plot_basic_variables", "plot_basic_variables",
PROJECT_ROOT / "docs" / "03 - Premiers graphiques" / "scripts" / "plot_basic_variables.py", PROJECT_ROOT / "docs" / "03 - Premiers graphiques" / "scripts" / "plot_basic_variables.py",
), ),
PlotScript( PlotScript("plot_calendar_overview", PROJECT_ROOT / "scripts" / "plot_calendar_overview.py"),
"plot_calendar_overview",
PROJECT_ROOT / "docs" / "03 - Premiers graphiques" / "scripts" / "plot_calendar_overview.py",
),
PlotScript( PlotScript(
"plot_all_pairwise_scatter", "plot_all_pairwise_scatter",
PROJECT_ROOT / "docs" / "04 - Corrélations binaires" / "scripts" / "plot_all_pairwise_scatter.py", PROJECT_ROOT / "docs" / "04 - Corrélations binaires" / "scripts" / "plot_all_pairwise_scatter.py",