Premières analyses de corrélation
124
docs/08 - Corrélations.md
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
# Corrélations
|
||||||
|
|
||||||
|
## Matrice de corrélation
|
||||||
|
|
||||||
|
| | Température | Humidité relative | Pression atm. | Précipitations | Luminance | Vitesse du vent | Direction du vent |
|
||||||
|
| --------------------- | ----------- | ----------------- | ------------- | -------------- | --------- | --------------- | ----------------- |
|
||||||
|
| **Température** | — | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
| **Humidité relative** | ✅ | — | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
| **Pression atm.** | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| **Précipitations** | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ |
|
||||||
|
| **Luminance** | ✅ | ✅ | ✅ | ✅ | — | ❌ | ❌ |
|
||||||
|
| **Vitesse du vent** | ❌ | ❌ | ✅ | ✅ | ❌ | — | ❌ |
|
||||||
|
| **Direction du vent** | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | — |
|
||||||
|
|
||||||
|
## Température ↔ Humidité relative ✅
|
||||||
|
|
||||||
|
L’humidité relative (HR) est, par définition, le rapport entre la quantité de vapeur d’eau réellement présente dans l’air et la quantité maximale qu’il pourrait contenir à cette température. Cette “quantité maximale” est donnée par la pression de vapeur saturante, qui augmente de façon quasi exponentielle avec la température, selon la relation de Clausius–Clapeyron.([asclimateservices.org][1])
|
||||||
|
|
||||||
|
Conséquences directes :
|
||||||
|
|
||||||
|
- si la quantité absolue de vapeur d’eau reste constante et que la température **augmente**, la pression de vapeur saturante augmente encore plus vite → l’air devient proportionnellement **moins humide** (HR diminue) ;
|
||||||
|
- si la température **baisse** sans changement d’humidité absolue, l’air se rapproche de la saturation → HR **augmente**.
|
||||||
|
|
||||||
|
On s’attend donc à une relation forte et systématique entre température et humidité relative : même sans changement de masse d’air, le simple réchauffement/refroidissement diurne suffit à faire varier l’HR.
|
||||||
|
|
||||||
|
## Pression atmosphérique ↔ Temps sensible, humidité, précipitations ✅
|
||||||
|
|
||||||
|
En surface, la pression atmosphérique organise la circulation de l’air et la formation des systèmes météo (anticyclones et dépressions). Les centres de **basse pression** sont associés à de l’air qui converge en surface et s’élève, se refroidit, et condense sa vapeur d’eau en nuages et précipitations. À l’inverse, les **hautes pressions** sont associées à de l’air qui descend, se réchauffe et se dessèche, donnant du temps généralement sec et dégagé.([Centre pour l'Éducation Scientifique][2])
|
||||||
|
|
||||||
|
D’où plusieurs liens attendus :
|
||||||
|
|
||||||
|
- **Pression ↔ Précipitations** ✅
|
||||||
|
Dépressions (basse pression) → air ascendant, condensation, nuages, pluies ; anticyclones (haute pression) → air subsident, ciel souvent dégagé et précipitations rares. Des études relient explicitement les quantités de précipitations aux variations de pression au passage de systèmes météo.([ResearchGate][3])
|
||||||
|
|
||||||
|
- **Pression ↔ Humidité relative** ✅
|
||||||
|
Les basses pressions s’accompagnent souvent d’air plus humide (nuages épais, air proche de la saturation), les hautes pressions d’air plus sec, surtout en basses couches. Ce n’est pas une loi exacte point par point, mais climatologiquement très robuste.([Cordulus][4])
|
||||||
|
|
||||||
|
- **Pression ↔ Luminance** ✅
|
||||||
|
Anticyclones → ciel dégagé, ensoleillement fort, grande amplitude thermique jour/nuit ; dépressions → couverture nuageuse, lumière atténuée et plus diffuse, températures lissées.([Wikipédia][5])
|
||||||
|
|
||||||
|
- **Pression ↔ Température** ✅
|
||||||
|
Pression, température et densité de l’air sont reliées par l’équation d’état (air quasi parfait). Dans la pratique, les systèmes de haute pression sont souvent associés à des nuits plus froides (refroidissement radiatif sous ciel dégagé) et des journées plus chaudes, tandis que les basses pressions limitent les contrastes via les nuages et les précipitations.([Wikipédia][5])
|
||||||
|
|
||||||
|
## Pression ↔ Vent (vitesse & direction) ✅
|
||||||
|
|
||||||
|
Le vent est essentiellement une réponse de l’air aux **gradients de pression** : l’air tend à se déplacer des zones de haute pression vers les zones de basse pression. La force de ce mouvement dépend de la pente du “relief de pression” (gradient de pression), et sa direction est fortement influencée par la force de Coriolis.([ESS][6])
|
||||||
|
|
||||||
|
- **Vitesse du vent ↔ Pression** ✅
|
||||||
|
Plus les isobares (lignes d’égale pression) sont serrées, plus le gradient de pression est fort, et plus le vent est rapide. Le vent “géostrophique”, solution idéale de l’équilibre entre gradient de pression et Coriolis, donne une bonne approximation de cette relation : vitesse du vent proportionnelle au gradient de pression.([Wikipédia][7])
|
||||||
|
|
||||||
|
- **Direction du vent ↔ Pression** ✅
|
||||||
|
En régime moyen, le vent souffle approximativement **parallèle aux isobares**, avec les basses pressions à sa gauche dans l’hémisphère Nord (droite dans l’hémisphère Sud), conformément à l’équilibre géostrophique. La direction du vent est donc un reflet direct de la disposition des centres de haute et basse pression autour du point d’observation.([Wikipédia][7])
|
||||||
|
|
||||||
|
## Précipitations ↔ Humidité & Température ✅
|
||||||
|
|
||||||
|
Les précipitations sont la manifestation macroscopique de la condensation de vapeur d’eau en gouttes suffisamment grosses pour tomber. Il y a donc un lien très direct avec le contenu en eau de l’atmosphère et la température :
|
||||||
|
|
||||||
|
- **Humidité ↔ Précipitations** ✅
|
||||||
|
Pour qu’il pleuve, il faut que l’air atteigne (localement) la saturation : humidité relative proche de 100 %. Lorsque des systèmes dépressionnaires ou des fronts forcent l’ascendance de l’air, celui-ci se refroidit, sa vapeur d’eau condense et la saturation est atteinte → formation de nuages et de pluie.([NCICS][8])
|
||||||
|
|
||||||
|
- **Température ↔ Précipitations** ✅
|
||||||
|
La capacité de l’air à contenir de la vapeur d’eau augmente d’environ 6–7 % par degré de réchauffement, selon la relation de Clausius–Clapeyron.([asclimateservices.org][1])
|
||||||
|
À teneur en humidité relative comparable, un air plus chaud peut donc alimenter des précipitations plus intenses. La phase de la précipitation (neige, pluie, pluie verglaçante) dépend aussi directement de la température dans la colonne d’air.
|
||||||
|
|
||||||
|
## Nuages, Luminance & Pluie ✅
|
||||||
|
|
||||||
|
Le capteur de luminance mesure essentiellement l’éclairement lumineux en surface, lui-même directement lié au rayonnement solaire incident. Ce dernier est contrôlé par la hauteur du Soleil, mais aussi très fortement par la **nébulosité** (nuages) et, dans une moindre mesure, les aérosols.
|
||||||
|
|
||||||
|
- **Luminance ↔ Précipitations** ✅
|
||||||
|
Les situations pluvieuses sont presque toujours associées à des nuages épais et/ou profonds (stratiformes, cumulonimbus). Ces nuages réduisent nettement le rayonnement direct et donc la luminance mesurée en surface, même s’il existe des cas particuliers de “silver lining” où le rayonnement diffus peut être localement renforcé.([American Meteorological Society Journals][9])
|
||||||
|
|
||||||
|
- **Luminance ↔ Humidité / Pression / Température** ✅
|
||||||
|
Plus généralement, les jours anticycloniques secs et stables (haute pression) sont caractérisés par une forte luminance en journée ; à l’inverse, les situations dépressionnaires humides (basse pression, air proche de la saturation) se traduisent par une baisse significative de l’éclairement au sol.([Wikipédia][5])
|
||||||
|
|
||||||
|
## Vent ↔ Précipitations ✅
|
||||||
|
|
||||||
|
Les épisodes pluvieux marqués (passage de fronts, lignes de grains, orages) sont fréquemment associés à :
|
||||||
|
|
||||||
|
- une augmentation de la **vitesse du vent**, liée au gradient de pression plus fort autour des systèmes dépressionnaires et aux courants descendantes / rafales convectives ;
|
||||||
|
- des **directions de vent caractéristiques** pour un site donné (par exemple flux d’ouest ou de sud-ouest apportant de l’air maritime humide sur la France).
|
||||||
|
|
||||||
|
Les études climatologiques et les diagnostics synoptiques mettent systématiquement en avant cette co-occurrence entre précipitations, gradients de pression marqués et vents plus forts.([NCICS][8])
|
||||||
|
|
||||||
|
Pour autant, la relation n’est pas parfaitement déterministe : on peut avoir du vent sans pluie (front sec, mistral, tramontane…), et des pluies faibles sous vents modestes. On parle donc plutôt de **tendance statistique robuste** que de loi exacte.
|
||||||
|
|
||||||
|
## Pourquoi des ❌ dans la matrice ?
|
||||||
|
|
||||||
|
Les ❌ ne signifient pas “aucune relation possible”, mais :
|
||||||
|
|
||||||
|
> “Il n’existe pas de relation simple, universelle, monotone entre ces deux grandeurs, valable partout et tout le temps.”
|
||||||
|
|
||||||
|
Quelques exemples :
|
||||||
|
|
||||||
|
- **Température ↔ Vitesse / Direction du vent** ❌
|
||||||
|
Le vent dépend de gradients horizontaux de pression et de température à grande échelle, pas de la valeur de la température locale à votre station. Vous pouvez avoir une journée très chaude sans vent, ou un refroidissement brutal par intrusion d’air froid très venteux : la relation locale instantanée n’est pas simple.
|
||||||
|
|
||||||
|
- **Luminance ↔ Vent** ❌
|
||||||
|
On peut avoir un grand soleil avec un vent soutenu (temps anticyclonique venteux), ou un ciel uniformément gris et quasi sans vent. Lien, encore une fois, via la situation météo d’ensemble, pas directement entre “lumière” et “vitesse de l’air”.
|
||||||
|
|
||||||
|
- **Vent ↔ Humidité / Température ↔ Direction du vent** ❌ (au sens général)
|
||||||
|
Sur **un site donné**, il est fréquent d’observer que certains secteurs de vent (advections maritimes, continentales, montagne/plaine, etc.) sont associés à des signatures de température ou d’humidité typiques : c’est le “climat local” des masses d’air.
|
||||||
|
Mais ce pattern dépend énormément de la géographie. Il n’existe pas, par exemple, de règle universelle “vent du nord = air sec” valable partout sur Terre. D’où le ❌ au sens “relation globale, généralisable”.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Toutes ces corrélations sont explorées par les scripts `scripts/plot_all_pairwise_scatter.py` et `scripts/plot_lagged_correlations.py`, qui vont produire des graphiques dans `figures/pairwise_scatter` et `figures/lagged_correlations`. Mais, bien qu'intéressantes et instructives, ces images sont encore primitives.
|
||||||
|
|
||||||
|
Néanmoins, un graphique de type "heatmap" permet déjà de quantifier ces corrélations en se basant sur les données concrètes :
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python -m scripts.plot_correlation_heatmap
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[1]: https://asclimateservices.org/code-and-equations/clausius-clapeyron/?utm_source=chatgpt.com "Clausius Clapeyron"
|
||||||
|
[2]: https://scied.ucar.edu/learning-zone/how-weather-works/highs-and-lows-air-pressure?utm_source=chatgpt.com "The Highs and Lows of Air Pressure"
|
||||||
|
[3]: https://www.researchgate.net/publication/323079756_A_generalized_relationship_between_atmospheric_pressure_and_precipitation_associated_with_a_passing_weather_system?utm_source=chatgpt.com "A generalized relationship between atmospheric pressure ..."
|
||||||
|
[4]: https://www.cordulus.com/glossary/atmospheric-pressure?utm_source=chatgpt.com "What is Atmospheric pressure?"
|
||||||
|
[5]: https://en.wikipedia.org/wiki/Pressure_system?utm_source=chatgpt.com "Pressure system"
|
||||||
|
[6]: https://www.ess.uci.edu/~yu/class/ess55/lecture.4.motion.all.pdf?utm_source=chatgpt.com "Lecture 4: Pressure and Wind"
|
||||||
|
[7]: https://en.wikipedia.org/wiki/Geostrophic_wind?utm_source=chatgpt.com "Geostrophic wind"
|
||||||
|
[8]: https://ncics.org/cics-news/quantifying-the-relationship-between-extreme-precipitation-and-atmospheric-water-vapor/?utm_source=chatgpt.com "Quantifying the Relationship Between Extreme ..."
|
||||||
|
[9]: https://journals.ametsoc.org/view/journals/apme/42/10/1520-0450_2003_042_1421_ccboai_2.0.co_2.xml?utm_source=chatgpt.com "Cloud Coverage Based on All-Sky Imaging and Its Impact on ..."
|
||||||
BIN
figures/correlation_heatmap.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
figures/lagged_correlations/lagcorr_humidity_to_rain_rate.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 50 KiB |
BIN
figures/lagged_correlations/lagcorr_pressure_to_illuminance.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
figures/lagged_correlations/lagcorr_pressure_to_rain_rate.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
figures/lagged_correlations/lagcorr_pressure_to_wind_speed.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
figures/lagged_correlations/lagcorr_temperature_to_humidity.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
figures/lagged_correlations/lagcorr_temperature_to_rain_rate.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
figures/pairwise_scatter/scatter_humidity_vs_illuminance.png
Normal file
|
After Width: | Height: | Size: 317 KiB |
BIN
figures/pairwise_scatter/scatter_humidity_vs_pressure.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
figures/pairwise_scatter/scatter_humidity_vs_rain_rate.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
figures/pairwise_scatter/scatter_humidity_vs_wind_direction.png
Normal file
|
After Width: | Height: | Size: 380 KiB |
BIN
figures/pairwise_scatter/scatter_humidity_vs_wind_speed.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
|
After Width: | Height: | Size: 344 KiB |
BIN
figures/pairwise_scatter/scatter_illuminance_vs_wind_speed.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
figures/pairwise_scatter/scatter_pressure_vs_illuminance.png
Normal file
|
After Width: | Height: | Size: 238 KiB |
BIN
figures/pairwise_scatter/scatter_pressure_vs_rain_rate.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
figures/pairwise_scatter/scatter_pressure_vs_wind_direction.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
figures/pairwise_scatter/scatter_pressure_vs_wind_speed.png
Normal file
|
After Width: | Height: | Size: 180 KiB |
BIN
figures/pairwise_scatter/scatter_rain_rate_vs_illuminance.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
figures/pairwise_scatter/scatter_rain_rate_vs_wind_direction.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
figures/pairwise_scatter/scatter_rain_rate_vs_wind_speed.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
figures/pairwise_scatter/scatter_temperature_vs_humidity.png
Normal file
|
After Width: | Height: | Size: 279 KiB |
BIN
figures/pairwise_scatter/scatter_temperature_vs_illuminance.png
Normal file
|
After Width: | Height: | Size: 286 KiB |
BIN
figures/pairwise_scatter/scatter_temperature_vs_pressure.png
Normal file
|
After Width: | Height: | Size: 222 KiB |
BIN
figures/pairwise_scatter/scatter_temperature_vs_rain_rate.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 377 KiB |
BIN
figures/pairwise_scatter/scatter_temperature_vs_wind_speed.png
Normal file
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 200 KiB |
117
meteo/analysis.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# meteo/analysis.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from .variables import Variable
|
||||||
|
|
||||||
|
|
||||||
|
def compute_correlation_matrix(
|
||||||
|
df: pd.DataFrame,
|
||||||
|
*,
|
||||||
|
method: Literal["pearson", "spearman"] = "pearson",
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Calcule la matrice de corrélation entre toutes les colonnes numériques
|
||||||
|
du DataFrame.
|
||||||
|
|
||||||
|
Attention :
|
||||||
|
- La direction du vent est traitée ici comme une variable scalaire 0–360°,
|
||||||
|
ce qui n'est pas idéal pour une analyse circulaire. On affinera plus tard
|
||||||
|
si besoin (représentation en sin/cos).
|
||||||
|
"""
|
||||||
|
numeric_df = df.select_dtypes(include=["number"])
|
||||||
|
corr = numeric_df.corr(method=method)
|
||||||
|
return corr
|
||||||
|
|
||||||
|
|
||||||
|
def compute_correlation_matrix_for_variables(
|
||||||
|
df: pd.DataFrame,
|
||||||
|
variables: Sequence[Variable],
|
||||||
|
*,
|
||||||
|
method: Literal["pearson", "spearman"] = "pearson",
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Calcule la matrice de corrélation pour un sous-ensemble de variables,
|
||||||
|
dans un ordre bien défini.
|
||||||
|
|
||||||
|
Paramètres
|
||||||
|
----------
|
||||||
|
df :
|
||||||
|
DataFrame contenant les colonnes à analyser.
|
||||||
|
variables :
|
||||||
|
Séquence de Variable décrivant les colonnes à prendre en compte.
|
||||||
|
method :
|
||||||
|
Méthode de corrélation pandas (pearson, spearman, ...).
|
||||||
|
|
||||||
|
Retour
|
||||||
|
------
|
||||||
|
DataFrame :
|
||||||
|
Matrice de corrélation, index et colonnes dans le même ordre que
|
||||||
|
`variables`, avec les colonnes pandas correspondant aux noms de colonnes
|
||||||
|
du DataFrame (ex: "temperature", "humidity", ...).
|
||||||
|
"""
|
||||||
|
columns = [v.column for v in variables]
|
||||||
|
missing = [c for c in columns if c not in df.columns]
|
||||||
|
if missing:
|
||||||
|
raise KeyError(f"Colonnes manquantes dans le DataFrame : {missing!r}")
|
||||||
|
|
||||||
|
numeric_df = df[columns].astype(float)
|
||||||
|
corr = numeric_df.corr(method=method)
|
||||||
|
|
||||||
|
# On s'assure de l'ordre
|
||||||
|
corr = corr.loc[columns, columns]
|
||||||
|
return corr
|
||||||
|
|
||||||
|
|
||||||
|
def compute_lagged_correlation(
|
||||||
|
df: pd.DataFrame,
|
||||||
|
var_x: Variable,
|
||||||
|
var_y: Variable,
|
||||||
|
*,
|
||||||
|
max_lag_minutes: int = 360,
|
||||||
|
step_minutes: int = 10,
|
||||||
|
method: Literal["pearson", "spearman"] = "pearson",
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Calcule la corrélation entre deux variables pour une série de décalages
|
||||||
|
temporels (lags).
|
||||||
|
|
||||||
|
Convention :
|
||||||
|
- lag > 0 : X "précède" Y de `lag` minutes.
|
||||||
|
On corrèle X(t) avec Y(t + lag).
|
||||||
|
- lag < 0 : Y "précède" X de |lag| minutes.
|
||||||
|
On corrèle X(t) avec Y(t + lag), lag étant négatif.
|
||||||
|
|
||||||
|
Implémentation :
|
||||||
|
- On utilise un DataFrame avec les deux colonnes,
|
||||||
|
puis on applique un `shift` sur Y.
|
||||||
|
"""
|
||||||
|
if var_x.column not in df.columns or var_y.column not in df.columns:
|
||||||
|
raise KeyError("Les colonnes demandées ne sont pas présentes dans le DataFrame.")
|
||||||
|
|
||||||
|
series_x = df[var_x.column]
|
||||||
|
series_y = df[var_y.column]
|
||||||
|
|
||||||
|
lags = range(-max_lag_minutes, max_lag_minutes + 1, step_minutes)
|
||||||
|
results: list[tuple[int, float]] = []
|
||||||
|
|
||||||
|
for lag in lags:
|
||||||
|
# Y décalé de -lag : pour lag positif, on corrèle X(t) à Y(t + lag)
|
||||||
|
shifted_y = series_y.shift(-lag)
|
||||||
|
pair = pd.concat([series_x, shifted_y], axis=1).dropna()
|
||||||
|
|
||||||
|
if pair.empty:
|
||||||
|
corr = np.nan
|
||||||
|
else:
|
||||||
|
corr = pair.iloc[:, 0].corr(pair.iloc[:, 1], method=method)
|
||||||
|
|
||||||
|
results.append((lag, corr))
|
||||||
|
|
||||||
|
lag_df = pd.DataFrame(results, columns=["lag_minutes", "correlation"])
|
||||||
|
lag_df = lag_df.set_index("lag_minutes")
|
||||||
|
|
||||||
|
return lag_df
|
||||||
149
meteo/plots.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
# meteo/plots.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from .variables import Variable
|
||||||
|
|
||||||
|
|
||||||
|
def plot_scatter_pair(
|
||||||
|
df: pd.DataFrame,
|
||||||
|
var_x: Variable,
|
||||||
|
var_y: Variable,
|
||||||
|
output_path: str | Path,
|
||||||
|
*,
|
||||||
|
sample_step: int = 10,
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Trace un nuage de points (scatter) pour une paire de variables.
|
||||||
|
|
||||||
|
- On sous-échantillonne les données avec `sample_step` (par exemple,
|
||||||
|
1 point sur 10) pour éviter un graphique illisible.
|
||||||
|
"""
|
||||||
|
output_path = Path(output_path)
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# On ne garde que les colonnes pertinentes et les lignes complètes
|
||||||
|
df_pair = df[[var_x.column, var_y.column]].dropna()
|
||||||
|
|
||||||
|
if sample_step > 1:
|
||||||
|
df_pair = df_pair.iloc[::sample_step, :]
|
||||||
|
|
||||||
|
plt.figure()
|
||||||
|
plt.scatter(df_pair[var_x.column], df_pair[var_y.column], s=5, alpha=0.5)
|
||||||
|
plt.xlabel(f"{var_x.label} ({var_x.unit})")
|
||||||
|
plt.ylabel(f"{var_y.label} ({var_y.unit})")
|
||||||
|
plt.title(f"{var_y.label} en fonction de {var_x.label}")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(output_path, dpi=150)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
return output_path.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def plot_lagged_correlation(
|
||||||
|
lag_df: pd.DataFrame,
|
||||||
|
var_x: Variable,
|
||||||
|
var_y: Variable,
|
||||||
|
output_path: str | Path,
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Trace la corrélation en fonction du lag (en minutes) entre deux variables.
|
||||||
|
"""
|
||||||
|
output_path = Path(output_path)
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
plt.figure()
|
||||||
|
plt.plot(lag_df.index, lag_df["correlation"])
|
||||||
|
plt.axvline(0, linestyle="--") # lag = 0
|
||||||
|
plt.xlabel("Décalage (minutes)\n(lag > 0 : X précède Y)")
|
||||||
|
plt.ylabel("Corrélation")
|
||||||
|
plt.title(f"Corrélation décalée : {var_x.label} → {var_y.label}")
|
||||||
|
plt.grid(True)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(output_path, dpi=150)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
return output_path.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def plot_correlation_heatmap(
|
||||||
|
corr: pd.DataFrame,
|
||||||
|
variables: Sequence[Variable],
|
||||||
|
output_path: str | Path,
|
||||||
|
*,
|
||||||
|
annotate: bool = True,
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Trace une heatmap de la matrice de corrélation.
|
||||||
|
|
||||||
|
Paramètres
|
||||||
|
----------
|
||||||
|
corr :
|
||||||
|
Matrice de corrélation (index et colonnes doivent correspondre
|
||||||
|
aux noms de colonnes des variables).
|
||||||
|
variables :
|
||||||
|
Liste de Variable, dans l'ordre où elles doivent apparaître.
|
||||||
|
output_path :
|
||||||
|
Chemin du fichier image à écrire.
|
||||||
|
annotate :
|
||||||
|
Si True, affiche la valeur numérique dans chaque case.
|
||||||
|
"""
|
||||||
|
output_path = Path(output_path)
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
columns = [v.column for v in variables]
|
||||||
|
labels = [v.label for v in variables]
|
||||||
|
|
||||||
|
# On aligne la matrice sur l'ordre désiré
|
||||||
|
corr = corr.loc[columns, columns]
|
||||||
|
|
||||||
|
data = corr.to_numpy()
|
||||||
|
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
im = ax.imshow(data, vmin=-1.0, vmax=1.0)
|
||||||
|
|
||||||
|
# Ticks et labels
|
||||||
|
ax.set_xticks(np.arange(len(labels)))
|
||||||
|
ax.set_yticks(np.arange(len(labels)))
|
||||||
|
ax.set_xticklabels(labels, rotation=45, ha="right")
|
||||||
|
ax.set_yticklabels(labels)
|
||||||
|
|
||||||
|
# Axe en haut/bas selon préférence (ici on laisse en bas)
|
||||||
|
ax.set_title("Matrice de corrélation (coef. de Pearson)")
|
||||||
|
|
||||||
|
# Barre de couleur
|
||||||
|
cbar = plt.colorbar(im, ax=ax)
|
||||||
|
cbar.set_label("Corrélation")
|
||||||
|
|
||||||
|
# Annotation des cases
|
||||||
|
if annotate:
|
||||||
|
n = data.shape[0]
|
||||||
|
for i in range(n):
|
||||||
|
for j in range(n):
|
||||||
|
if i == j:
|
||||||
|
text = "—"
|
||||||
|
else:
|
||||||
|
val = data[i, j]
|
||||||
|
if np.isnan(val):
|
||||||
|
text = ""
|
||||||
|
else:
|
||||||
|
text = f"{val:.2f}"
|
||||||
|
ax.text(
|
||||||
|
j,
|
||||||
|
i,
|
||||||
|
text,
|
||||||
|
ha="center",
|
||||||
|
va="center",
|
||||||
|
)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(output_path, dpi=150)
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
return output_path.resolve()
|
||||||
82
meteo/variables.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# meteo/variables.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Variable:
|
||||||
|
"""
|
||||||
|
Décrit une variable météorologique exploitable dans les analyses.
|
||||||
|
|
||||||
|
- key : identifiant interne (stable, sans espace)
|
||||||
|
- column : nom de la colonne dans le DataFrame
|
||||||
|
- label : libellé humain pour les graphiques
|
||||||
|
- unit : unité d'affichage (texte libre)
|
||||||
|
"""
|
||||||
|
|
||||||
|
key: str
|
||||||
|
column: str
|
||||||
|
label: str
|
||||||
|
unit: str
|
||||||
|
|
||||||
|
|
||||||
|
VARIABLES: List[Variable] = [
|
||||||
|
Variable(
|
||||||
|
key="temperature",
|
||||||
|
column="temperature",
|
||||||
|
label="Température",
|
||||||
|
unit="°C",
|
||||||
|
),
|
||||||
|
Variable(
|
||||||
|
key="humidity",
|
||||||
|
column="humidity",
|
||||||
|
label="Humidité relative",
|
||||||
|
unit="%",
|
||||||
|
),
|
||||||
|
Variable(
|
||||||
|
key="pressure",
|
||||||
|
column="pressure",
|
||||||
|
label="Pression atmosphérique",
|
||||||
|
unit="hPa",
|
||||||
|
),
|
||||||
|
Variable(
|
||||||
|
key="rain_rate",
|
||||||
|
column="rain_rate",
|
||||||
|
label="Précipitations",
|
||||||
|
unit="mm/h",
|
||||||
|
),
|
||||||
|
Variable(
|
||||||
|
key="illuminance",
|
||||||
|
column="illuminance",
|
||||||
|
label="Luminance",
|
||||||
|
unit="lx",
|
||||||
|
),
|
||||||
|
Variable(
|
||||||
|
key="wind_speed",
|
||||||
|
column="wind_speed",
|
||||||
|
label="Vitesse du vent",
|
||||||
|
unit="km/h",
|
||||||
|
),
|
||||||
|
Variable(
|
||||||
|
key="wind_direction",
|
||||||
|
column="wind_direction",
|
||||||
|
label="Direction du vent",
|
||||||
|
unit="°",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
VARIABLES_BY_KEY: Dict[str, Variable] = {v.key: v for v in VARIABLES}
|
||||||
|
|
||||||
|
|
||||||
|
def iter_variable_pairs() -> list[tuple[Variable, Variable]]:
|
||||||
|
"""
|
||||||
|
Retourne la liste de toutes les paires (i, j) avec i < j, pour
|
||||||
|
éviter les doublons et les diagonales.
|
||||||
|
"""
|
||||||
|
pairs: list[tuple[Variable, Variable]] = []
|
||||||
|
for i, vi in enumerate(VARIABLES):
|
||||||
|
for vj in VARIABLES[i + 1 :]:
|
||||||
|
pairs.append((vi, vj))
|
||||||
|
return pairs
|
||||||
45
scripts/plot_all_pairwise_scatter.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# scripts/plot_all_pairwise_scatter.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from meteo.dataset import load_raw_csv
|
||||||
|
from meteo.variables import iter_variable_pairs
|
||||||
|
from meteo.plots import plot_scatter_pair
|
||||||
|
|
||||||
|
|
||||||
|
CSV_PATH = Path("data/weather_minutely.csv")
|
||||||
|
OUTPUT_DIR = Path("figures/pairwise_scatter")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
if not CSV_PATH.exists():
|
||||||
|
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||||
|
return
|
||||||
|
|
||||||
|
df = load_raw_csv(CSV_PATH)
|
||||||
|
print(f"Dataset minuté chargé : {CSV_PATH}")
|
||||||
|
print(f" Lignes : {len(df)}")
|
||||||
|
print(f" Colonnes : {list(df.columns)}")
|
||||||
|
|
||||||
|
pairs = iter_variable_pairs()
|
||||||
|
print(f"Nombre de paires de variables : {len(pairs)}")
|
||||||
|
|
||||||
|
for var_x, var_y in pairs:
|
||||||
|
filename = f"scatter_{var_x.key}_vs_{var_y.key}.png"
|
||||||
|
output_path = OUTPUT_DIR / filename
|
||||||
|
|
||||||
|
print(f"→ Trace {var_y.key} en fonction de {var_x.key} → {output_path}")
|
||||||
|
plot_scatter_pair(
|
||||||
|
df=df,
|
||||||
|
var_x=var_x,
|
||||||
|
var_y=var_y,
|
||||||
|
output_path=output_path,
|
||||||
|
sample_step=10, # un point sur 10 : ≈ 32k points au lieu de 320k
|
||||||
|
)
|
||||||
|
|
||||||
|
print("✔ Tous les graphiques de nuages de points ont été générés.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
45
scripts/plot_correlation_heatmap.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# scripts/plot_correlation_heatmap.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from meteo.dataset import load_raw_csv
|
||||||
|
from meteo.variables import VARIABLES
|
||||||
|
from meteo.analysis import compute_correlation_matrix_for_variables
|
||||||
|
from meteo.plots import plot_correlation_heatmap
|
||||||
|
|
||||||
|
|
||||||
|
CSV_PATH = Path("data/weather_minutely.csv")
|
||||||
|
OUTPUT_PATH = Path("figures/correlation_heatmap.png")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
if not CSV_PATH.exists():
|
||||||
|
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||||
|
print(" Assurez-vous d'avoir généré le dataset minuté.")
|
||||||
|
return
|
||||||
|
|
||||||
|
df = load_raw_csv(CSV_PATH)
|
||||||
|
print(f"Dataset minuté chargé : {CSV_PATH}")
|
||||||
|
print(f" Lignes : {len(df)}")
|
||||||
|
print(f" Colonnes : {list(df.columns)}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
corr = compute_correlation_matrix_for_variables(df, VARIABLES, method="pearson")
|
||||||
|
|
||||||
|
print("Matrice de corrélation (aperçu) :")
|
||||||
|
print(corr)
|
||||||
|
print()
|
||||||
|
|
||||||
|
output_path = plot_correlation_heatmap(
|
||||||
|
corr=corr,
|
||||||
|
variables=VARIABLES,
|
||||||
|
output_path=OUTPUT_PATH,
|
||||||
|
annotate=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"✔ Heatmap de corrélation sauvegardée dans : {output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
69
scripts/plot_lagged_correlations.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# scripts/plot_lagged_correlations.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from meteo.dataset import load_raw_csv
|
||||||
|
from meteo.variables import VARIABLES_BY_KEY
|
||||||
|
from meteo.analysis import compute_lagged_correlation
|
||||||
|
from meteo.plots import plot_lagged_correlation
|
||||||
|
|
||||||
|
|
||||||
|
CSV_PATH = Path("data/weather_minutely.csv")
|
||||||
|
OUTPUT_DIR = Path("figures/lagged_correlations")
|
||||||
|
|
||||||
|
|
||||||
|
# Paires à analyser (clé de variable X, clé de variable Y)
|
||||||
|
# Convention : X précède potentiellement Y
|
||||||
|
INTERESTING_PAIRS: list[tuple[str, str]] = [
|
||||||
|
("temperature", "humidity"),
|
||||||
|
("temperature", "rain_rate"),
|
||||||
|
("pressure", "rain_rate"),
|
||||||
|
("pressure", "wind_speed"),
|
||||||
|
("pressure", "illuminance"),
|
||||||
|
("illuminance", "temperature"),
|
||||||
|
("humidity", "rain_rate"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
if not CSV_PATH.exists():
|
||||||
|
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||||
|
return
|
||||||
|
|
||||||
|
df = load_raw_csv(CSV_PATH)
|
||||||
|
print(f"Dataset minuté chargé : {CSV_PATH}")
|
||||||
|
print(f" Lignes : {len(df)}")
|
||||||
|
print(f" Colonnes : {list(df.columns)}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for key_x, key_y in INTERESTING_PAIRS:
|
||||||
|
var_x = VARIABLES_BY_KEY[key_x]
|
||||||
|
var_y = VARIABLES_BY_KEY[key_y]
|
||||||
|
|
||||||
|
print(f"→ Corrélation décalée : {var_x.key} → {var_y.key}")
|
||||||
|
|
||||||
|
lag_df = compute_lagged_correlation(
|
||||||
|
df=df,
|
||||||
|
var_x=var_x,
|
||||||
|
var_y=var_y,
|
||||||
|
max_lag_minutes=360, # ± 6 heures
|
||||||
|
step_minutes=10, # pas de 10 minutes
|
||||||
|
method="pearson",
|
||||||
|
)
|
||||||
|
|
||||||
|
filename = f"lagcorr_{var_x.key}_to_{var_y.key}.png"
|
||||||
|
output_path = OUTPUT_DIR / filename
|
||||||
|
|
||||||
|
plot_lagged_correlation(
|
||||||
|
lag_df=lag_df,
|
||||||
|
var_x=var_x,
|
||||||
|
var_y=var_y,
|
||||||
|
output_path=output_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
print("✔ Graphiques de corrélation décalée générés.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||