Réorganisation
@@ -1,17 +0,0 @@
|
||||
# Installation de l'environnement de base
|
||||
|
||||
Après avoir cloné le dépôt :
|
||||
|
||||
```shell
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
python -c "import pandas, influxdb_client, sklearn; print('OK')"
|
||||
```
|
||||
|
||||
- On installe l'environnement virtuel de python
|
||||
- On entre dans cet environnement
|
||||
- On met à jour le gestionnaire de paquets pip
|
||||
- On installe les dépendances définies dans `requirements.txt`
|
||||
- On vérifie que les dépendances sont correctement installées
|
||||
@@ -1,7 +1,43 @@
|
||||
# Test de l'environnement de travail
|
||||
# Installation, configuration et tests
|
||||
|
||||
## Installation de l'environnement de base
|
||||
|
||||
Après avoir cloné le dépôt :
|
||||
|
||||
```shell
|
||||
python -m scripts.test_influx_connection
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
python -c "import pandas, influxdb_client, sklearn; print('OK')"
|
||||
```
|
||||
|
||||
- On installe l'environnement virtuel de python
|
||||
- On entre dans cet environnement
|
||||
- On met à jour le gestionnaire de paquets pip
|
||||
- On installe les dépendances définies dans `requirements.txt`
|
||||
- On vérifie que les dépendances sont correctement installées
|
||||
|
||||
## Configuration
|
||||
|
||||
```shell
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
On copie le fichier de configuration d'exemple, puis on l'ouvre pour l'adapter à notre cas.
|
||||
|
||||
- `INFLUXDB_URL` : URL de l'api du serveur InfluxDB2 (cela inclue probablement le port 8086)
|
||||
- `INFLUXDB_TOKEN` : le jeton d'authentification à créer dans votre compte InfluxDB2
|
||||
- `INFLUXDB_ORG` : l'organisation à laquelle le token est rattaché
|
||||
- `INFLUXDB_BUCKET` : le nom du bucket dans lequel les données sont stockées
|
||||
- `STATION_LATITUDE` : latitude GPS de la station météo
|
||||
- `STATION_LONGITUDE` : longitude GPS de la station météo
|
||||
- `STATION_ELEVATION` : altitude de la station météo
|
||||
|
||||
## Tests de l'environnement de travail
|
||||
|
||||
```shell
|
||||
python "docs/01 - Installation, configuration et tests/scripts/test_influx_connection.py"
|
||||
```
|
||||
|
||||
```output
|
||||
@@ -24,7 +60,7 @@ Exemple de point :
|
||||
Ensuite, on peut demander à InfluxDB de nous détailler ce qu'il stocke :
|
||||
|
||||
```shell
|
||||
python -m scripts.test_influx_schema
|
||||
python "docs/01 - Installation, configuration et tests/scripts/test_influx_schema.py"
|
||||
```
|
||||
|
||||
```output
|
||||
@@ -82,7 +118,7 @@ Champs pour measurement « °C » :
|
||||
Mais pour obtenir les données dont on a besoin, il faut aussi connaitre les entités manipulées par Influx :
|
||||
|
||||
```shell
|
||||
python -m scripts.test_influx_entities
|
||||
python "docs/01 - Installation, configuration et tests/scripts/test_influx_entities.py"
|
||||
```
|
||||
|
||||
```output
|
||||
@@ -0,0 +1,65 @@
|
||||
# tests/test_influx_connection.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from contextlib import closing
|
||||
|
||||
from influxdb_client.client.exceptions import InfluxDBError
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from meteo.config import InfluxSettings
|
||||
from meteo.influx_client import create_influx_client, test_basic_query
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Teste la communication avec le serveur InfluxDB :
|
||||
|
||||
1. Chargement de la configuration depuis l'environnement.
|
||||
2. Ping du serveur.
|
||||
3. Exécution d'une requête simple sur le bucket configuré.
|
||||
"""
|
||||
settings = InfluxSettings.from_env()
|
||||
|
||||
print("Configuration InfluxDB chargée :")
|
||||
print(f" URL : {settings.url}")
|
||||
print(f" Org : {settings.org}")
|
||||
print(f" Bucket : {settings.bucket}")
|
||||
print()
|
||||
|
||||
# Utilisation de `closing` pour garantir la fermeture du client.
|
||||
with closing(create_influx_client(settings)) as client:
|
||||
print("→ Ping du serveur InfluxDB…")
|
||||
if not client.ping():
|
||||
raise SystemExit("Échec du ping InfluxDB. Vérifiez l'URL et l'état du serveur.")
|
||||
|
||||
print("✔ Ping OK")
|
||||
print("→ Requête de test sur le bucket…")
|
||||
|
||||
tables = test_basic_query(client, settings.bucket)
|
||||
|
||||
# On fait un retour synthétique
|
||||
nb_tables = len(tables)
|
||||
nb_records = sum(len(table.records) for table in tables)
|
||||
print(f"✔ Requête de test réussie : {nb_tables} table(s), {nb_records} enregistrement(s) trouvés.")
|
||||
|
||||
if nb_records == 0:
|
||||
print("⚠ Le bucket est accessible, mais aucune donnée sur la dernière heure.")
|
||||
else:
|
||||
# Affiche un aperçu de la première table / premier record
|
||||
first_table = tables[0]
|
||||
first_record = first_table.records[0]
|
||||
print("Exemple de point :")
|
||||
print(f" time : {first_record.get_time()}")
|
||||
print(f" measurement : {first_record.get_measurement()}")
|
||||
print(f" field : {first_record.get_field()}")
|
||||
print(f" value : {first_record.get_value()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,68 @@
|
||||
# tests/test_influx_entities.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from contextlib import closing
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from meteo.config import InfluxSettings
|
||||
from meteo.influx_client import create_influx_client
|
||||
from meteo.schema import (
|
||||
list_measurements,
|
||||
list_measurement_tag_keys,
|
||||
list_measurement_tag_values,
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Explore les tags des measurements du bucket :
|
||||
|
||||
- affiche les keys de tags pour chaque measurement
|
||||
- si un tag `entity_id` est présent, affiche la liste de ses valeurs
|
||||
"""
|
||||
settings = InfluxSettings.from_env()
|
||||
|
||||
print(f"Bucket InfluxDB : {settings.bucket}")
|
||||
print()
|
||||
|
||||
with closing(create_influx_client(settings)) as client:
|
||||
measurements = list_measurements(client, settings.bucket)
|
||||
|
||||
if not measurements:
|
||||
print("⚠ Aucun measurement trouvé dans ce bucket.")
|
||||
return
|
||||
|
||||
for meas in measurements:
|
||||
print(f"Measurement « {meas} »")
|
||||
tag_keys = list_measurement_tag_keys(client, settings.bucket, meas)
|
||||
if not tag_keys:
|
||||
print(" (aucun tag trouvé)")
|
||||
print()
|
||||
continue
|
||||
|
||||
print(" Tag keys :")
|
||||
for key in tag_keys:
|
||||
print(f" - {key}")
|
||||
|
||||
if "entity_id" in tag_keys:
|
||||
entity_ids = list_measurement_tag_values(
|
||||
client,
|
||||
settings.bucket,
|
||||
meas,
|
||||
tag="entity_id",
|
||||
)
|
||||
print(" entity_id possibles :")
|
||||
for eid in entity_ids:
|
||||
print(f" - {eid}")
|
||||
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,54 @@
|
||||
# tests/test_influx_schema.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from contextlib import closing
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from meteo.config import InfluxSettings
|
||||
from meteo.influx_client import create_influx_client
|
||||
from meteo.schema import list_measurements, list_measurement_fields
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Explore le schéma du bucket InfluxDB configuré :
|
||||
|
||||
- liste des measurements disponibles
|
||||
- pour chacun, liste des champs (_field) et de leur type
|
||||
"""
|
||||
settings = InfluxSettings.from_env()
|
||||
|
||||
print(f"Bucket InfluxDB : {settings.bucket}")
|
||||
print()
|
||||
|
||||
with closing(create_influx_client(settings)) as client:
|
||||
measurements = list_measurements(client, settings.bucket)
|
||||
|
||||
if not measurements:
|
||||
print("⚠ Aucun measurement trouvé dans ce bucket.")
|
||||
return
|
||||
|
||||
print("Measurements disponibles :")
|
||||
for name in measurements:
|
||||
print(f" - {name}")
|
||||
print()
|
||||
|
||||
for name in measurements:
|
||||
print(f"Champs pour measurement « {name} » :")
|
||||
fields = list_measurement_fields(client, settings.bucket, name)
|
||||
if not fields:
|
||||
print(" (aucun champ trouvé)")
|
||||
else:
|
||||
for field in fields:
|
||||
print(f" - {field.name} (type: {field.type})")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,12 +0,0 @@
|
||||
# Configuration
|
||||
|
||||
```shell
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
On copie le fichier de configuration d'exemple, puis on l'ouvre pour l'adapter à notre cas.
|
||||
|
||||
- `INFLUXDB_URL` : URL de l'api du serveur InfluxDB2 (cela inclue probablement le port 8086)
|
||||
- `INFLUXDB_TOKEN` : le jeton d'authentification à créer dans votre compte InfluxDB2
|
||||
- `INFLUXDB_ORG` : l'organisation à laquelle le token est rattaché
|
||||
- `INFLUXDB_BUCKET` : le nom du bucket dans lequel les données sont stockées
|
||||
@@ -1,4 +1,34 @@
|
||||
# Ajustements
|
||||
# Préparation des données
|
||||
|
||||
Cette étape regroupe l'export initial depuis InfluxDB ainsi que les scripts d'ajustement nécessaires pour obtenir un dataset minuté propre.
|
||||
|
||||
## Export des données
|
||||
|
||||
```shell
|
||||
python "docs/02 - Préparation des données/scripts/export_station_data.py"
|
||||
```
|
||||
|
||||
La sortie est assez longue, et inclut un certain nombre d'avertissements qui peuvent être ignorés.
|
||||
L'important est que le script se termine sur :
|
||||
|
||||
```output
|
||||
✔ Export terminé : /Users/richard/Documents/donnees_meteo/data/weather_raw_7d.csv
|
||||
```
|
||||
|
||||
(Le chemin changera sur votre propre machine)
|
||||
|
||||
Vérifiez que le fichier est bien créé et qu'il contient des données.
|
||||
|
||||
À la place de `export_station_data.py`, on peut aussi lancer :
|
||||
|
||||
```shell
|
||||
python "docs/02 - Préparation des données/scripts/export_station_data_full.py"
|
||||
```
|
||||
|
||||
Au lieu de télécharger les données des 7 derniers jours, l'ensemble des données stockées sur le serveur pour ce bucket seront téléchargées, ce qui, selon la granularité et l'ancienneté des données peut prendre un certain temps et occuper un espace disque conséquent.
|
||||
Mon fichier complet contient plus d'un million d'enregistrements et pèse 70Mo.
|
||||
|
||||
## Ajustements
|
||||
|
||||
Le fichier peut être rapidement inspecté avec la commande `head` :
|
||||
|
||||
@@ -24,7 +54,7 @@ On peut voir que HomeAssistant écrit une nouvelle entrée pour chaque capteur,
|
||||
Le script suivant s'occupe de regrouper les données de capteurs dont l'enregistrement est proche :
|
||||
|
||||
```shell
|
||||
python -m scripts.format_raw_csv
|
||||
python "docs/02 - Préparation des données/scripts/format_raw_csv.py"
|
||||
```
|
||||
|
||||
```output
|
||||
@@ -59,35 +89,42 @@ Il reste des cellules vides : en effet, HA n'enregistre pas la valeur d'un capte
|
||||
On fait donc :
|
||||
|
||||
```shell
|
||||
python -m scripts.fill_formatted_1s
|
||||
python "docs/02 - Préparation des données/scripts/fill_formatted_1s.py"
|
||||
```
|
||||
|
||||
```output
|
||||
Fichier 1s formaté chargé : data/weather_formatted_1s.csv
|
||||
Lignes : 630171, colonnes : ['temperature', 'humidity', 'pressure', 'illuminance', 'wind_speed', 'wi
|
||||
nd_direction', 'rain_rate'] Après propagation des dernières valeurs connues : 630171 lignes
|
||||
Lignes : 630171, colonnes : ['temperature', 'humidity', 'pressure', 'illuminance', 'wind_speed', 'wind_direction', 'rain_rate']
|
||||
Après propagation des dernières valeurs connues : 630171 lignes
|
||||
✔ Fichier 1s 'complet' écrit dans : /Users/richard/Documents/donnees_meteo/data/weather_filled_1s.csv
|
||||
```
|
||||
|
||||
On peut maintenant s'assurer d'avoir une seule ligne par minute, avec toutes les valeurs de capteurs :
|
||||
## Enrichissements (saisons et position du soleil)
|
||||
|
||||
Une fois les données nettoyées, on peut les enrichir avec des métadonnées météorologiques simples :
|
||||
|
||||
- regrouper les points par minute,
|
||||
- ajouter la saison correspondant à chaque observation (en fonction de l'hémisphère),
|
||||
- calculer la hauteur du soleil si la latitude/longitude de la station sont configurées.
|
||||
|
||||
Ces opérations sont réalisées par :
|
||||
|
||||
```shell
|
||||
python -m scripts.make_minutely_dataset
|
||||
python "docs/02 - Préparation des données/scripts/make_minutely_dataset.py"
|
||||
```
|
||||
|
||||
Ce qui va produire le fichier `data/weather_minutely.csv`.
|
||||
Le script produit `data/weather_minutely.csv`. Pensez à définir `STATION_LATITUDE`, `STATION_LONGITUDE` et `STATION_ELEVATION` dans votre `.env` pour permettre le calcul de la position du soleil ; sinon, seule la colonne `season` sera ajoutée.
|
||||
|
||||
On peut s'assurer que plus aucune information n'est manquante :
|
||||
|
||||
```shell
|
||||
python -m scripts.check_missing_values
|
||||
python "docs/02 - Préparation des données/scripts/check_missing_values.py"
|
||||
```
|
||||
|
||||
```output
|
||||
Dataset chargé : data/weather_minutely.csv
|
||||
Lignes : 321881
|
||||
Colonnes : ['temperature', 'humidity', 'pressure', 'illuminance', 'wind_speed', 'wind_direction', 'r
|
||||
ain_rate']
|
||||
Colonnes : ['temperature', 'humidity', 'pressure', 'illuminance', 'wind_speed', 'wind_direction', 'rain_rate']
|
||||
=== Synthèse des valeurs manquantes ===
|
||||
Total de cellules : 2253167
|
||||
Cellules manquantes : 0
|
||||
@@ -111,14 +148,13 @@ Valeurs manquantes par colonne :
|
||||
Le script suivant nous permet de vérifier rapidement si des problèmes majeurs peuvent être découverts :
|
||||
|
||||
```shell
|
||||
python -m scripts.describe_minutely_dataset
|
||||
python "docs/02 - Préparation des données/scripts/describe_minutely_dataset.py"
|
||||
```
|
||||
|
||||
```output
|
||||
Dataset minuté chargé : data/weather_minutely.csv
|
||||
Lignes : 321881
|
||||
Colonnes : ['temperature', 'humidity', 'pressure', 'illuminance', 'wind_speed', 'wind_direction', 'r
|
||||
ain_rate'] Période : 2025-03-10 09:35:00+00:00 → 2025-11-17 00:41:00+00:00
|
||||
Colonnes : ['temperature', 'humidity', 'pressure', 'illuminance', 'wind_speed', 'wind_direction', 'rain_rate'] Période : 2025-03-10 09:35:00+00:00 → 2025-11-17 00:41:00+00:00
|
||||
|
||||
=== describe() ===
|
||||
temperature humidity pressure ... wind_speed wind_direction rain_rate
|
||||
@@ -169,11 +205,10 @@ Name: count, dtype: int64
|
||||
Nombre d'intervalles ≠ 60s : 17589
|
||||
```
|
||||
|
||||
Il y a donc des trous entre certains jeux de données.
|
||||
Ces écarts peuvent être identifiés avec le script suivant :
|
||||
|
||||
```shell
|
||||
python -m scripts.list_time_gaps
|
||||
python "docs/02 - Préparation des données/scripts/list_time_gaps.py"
|
||||
```
|
||||
|
||||
```
|
||||
@@ -185,17 +220,16 @@ Nombre de gaps : 17589
|
||||
Total minutes manquantes (théoriques) : 40466
|
||||
|
||||
Top 10 des gaps les plus longs :
|
||||
- De 2025-06-21 19:09:00+00:00 à 2025-06-21 20:10:00+00:00 (durée: 0 days 01:01:00, manquants: 60, de
|
||||
2025-06-21 19:10:00+00:00 à 2025-06-21 20:09:00+00:00) - De 2025-08-10 22:17:00+00:00 à 2025-08-10 23:15:00+00:00 (durée: 0 days 00:58:00, manquants: 57, de
|
||||
2025-08-10 22:18:00+00:00 à 2025-08-10 23:14:00+00:00) - De 2025-09-24 20:34:00+00:00 à 2025-09-24 21:32:00+00:00 (durée: 0 days 00:58:00, manquants: 57, de
|
||||
2025-09-24 20:35:00+00:00 à 2025-09-24 21:31:00+00:00) - De 2025-06-21 10:58:00+00:00 à 2025-06-21 11:55:00+00:00 (durée: 0 days 00:57:00, manquants: 56, de
|
||||
2025-06-21 10:59:00+00:00 à 2025-06-21 11:54:00+00:00) - De 2025-07-10 07:17:00+00:00 à 2025-07-10 08:14:00+00:00 (durée: 0 days 00:57:00, manquants: 56, de
|
||||
2025-07-10 07:18:00+00:00 à 2025-07-10 08:13:00+00:00) - De 2025-07-24 03:52:00+00:00 à 2025-07-24 04:46:00+00:00 (durée: 0 days 00:54:00, manquants: 53, de
|
||||
2025-07-24 03:53:00+00:00 à 2025-07-24 04:45:00+00:00) - De 2025-10-28 08:31:00+00:00 à 2025-10-28 09:23:00+00:00 (durée: 0 days 00:52:00, manquants: 51, de
|
||||
2025-10-28 08:32:00+00:00 à 2025-10-28 09:22:00+00:00) - De 2025-03-16 15:31:00+00:00 à 2025-03-16 16:20:00+00:00 (durée: 0 days 00:49:00, manquants: 48, de
|
||||
2025-03-16 15:32:00+00:00 à 2025-03-16 16:19:00+00:00) - De 2025-06-21 12:22:00+00:00 à 2025-06-21 13:08:00+00:00 (durée: 0 days 00:46:00, manquants: 45, de
|
||||
2025-06-21 12:23:00+00:00 à 2025-06-21 13:07:00+00:00) - De 2025-06-21 17:25:00+00:00 à 2025-06-21 18:10:00+00:00 (durée: 0 days 00:45:00, manquants: 44, de
|
||||
2025-06-21 17:26:00+00:00 à 2025-06-21 18:09:00+00:00)
|
||||
- De 2025-06-21 19:09:00+00:00 à 2025-06-21 20:10:00+00:00 (durée: 0 days 01:01:00, manquants: 60, de 2025-06-21 19:10:00+00:00 à 2025-06-21 20:09:00+00:00)
|
||||
- De 2025-08-10 22:17:00+00:00 à 2025-08-10 23:15:00+00:00 (durée: 0 days 00:58:00, manquants: 57, de 2025-08-10 22:18:00+00:00 à 2025-08-10 23:14:00+00:00)
|
||||
- De 2025-09-24 20:34:00+00:00 à 2025-09-24 21:32:00+00:00 (durée: 0 days 00:58:00, manquants: 57, de 2025-09-24 20:35:00+00:00 à 2025-09-24 21:31:00+00:00)
|
||||
- De 2025-06-21 10:58:00+00:00 à 2025-06-21 11:55:00+00:00 (durée: 0 days 00:57:00, manquants: 56, de 2025-06-21 10:59:00+00:00 à 2025-06-21 11:54:00+00:00)
|
||||
- De 2025-07-10 07:17:00+00:00 à 2025-07-10 08:14:00+00:00 (durée: 0 days 00:57:00, manquants: 56, de 2025-07-10 07:18:00+00:00 à 2025-07-10 08:13:00+00:00)
|
||||
- De 2025-07-24 03:52:00+00:00 à 2025-07-24 04:46:00+00:00 (durée: 0 days 00:54:00, manquants: 53, de 2025-07-24 03:53:00+00:00 à 2025-07-24 04:45:00+00:00)
|
||||
- De 2025-10-28 08:31:00+00:00 à 2025-10-28 09:23:00+00:00 (durée: 0 days 00:52:00, manquants: 51, de 2025-10-28 08:32:00+00:00 à 2025-10-28 09:22:00+00:00)
|
||||
- De 2025-03-16 15:31:00+00:00 à 2025-03-16 16:20:00+00:00 (durée: 0 days 00:49:00, manquants: 48, de 2025-03-16 15:32:00+00:00 à 2025-03-16 16:19:00+00:00)
|
||||
- De 2025-06-21 12:22:00+00:00 à 2025-06-21 13:08:00+00:00 (durée: 0 days 00:46:00, manquants: 45, de 2025-06-21 12:23:00+00:00 à 2025-06-21 13:07:00+00:00)
|
||||
- De 2025-06-21 17:25:00+00:00 à 2025-06-21 18:10:00+00:00 (durée: 0 days 00:45:00, manquants: 44, de 2025-06-21 17:26:00+00:00 à 2025-06-21 18:09:00+00:00)
|
||||
```
|
||||
|
||||
Ces trous dans les données peuvent correspondre à des pannes de connexion entre la station et mon réseau, un redémarrage de mon serveur (physique ou logiciel), au redémarrage de la box ou du point d'accès sans-fil, etc.
|
||||
@@ -0,0 +1,58 @@
|
||||
# scripts/check_missing_values.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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.quality import summarize_missing_values
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
|
||||
|
||||
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 chargé : {CSV_PATH}")
|
||||
print(f" Lignes : {len(df)}")
|
||||
print(f" Colonnes : {list(df.columns)}")
|
||||
|
||||
summary = summarize_missing_values(df)
|
||||
|
||||
print()
|
||||
print("=== Synthèse des valeurs manquantes ===")
|
||||
print(f"Total de cellules : {summary.total_cells}")
|
||||
print(f"Cellules manquantes : {summary.missing_cells}")
|
||||
print(f"Fraction manquante : {summary.fraction_missing:.6f}")
|
||||
print(f"Lignes complètes : {summary.rows_fully_complete}")
|
||||
print(f"Lignes avec des trous : {summary.rows_with_missing}")
|
||||
print(f"Fraction lignes complètes : {summary.fraction_rows_complete:.6f}")
|
||||
|
||||
print()
|
||||
print("Valeurs manquantes par colonne :")
|
||||
for col, n_missing in summary.missing_by_column.items():
|
||||
print(f" - {col:13s} : {n_missing}")
|
||||
|
||||
if summary.missing_cells == 0:
|
||||
print()
|
||||
print("✔ Aucune valeur manquante dans le dataset minuté.")
|
||||
else:
|
||||
print()
|
||||
print("⚠ Il reste des valeurs manquantes.")
|
||||
print(" Exemple de lignes concernées :")
|
||||
rows_with_missing = df[df.isna().any(axis=1)]
|
||||
print(rows_with_missing.head(10))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,67 @@
|
||||
# scripts/describe_minutely_dataset.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
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
|
||||
|
||||
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(f" Période : {df.index[0]} → {df.index[-1]}")
|
||||
print()
|
||||
|
||||
# 1. Résumé statistique classique
|
||||
print("=== describe() ===")
|
||||
print(df.describe())
|
||||
print()
|
||||
|
||||
# 2. Min / max par variable avec leurs dates
|
||||
print("=== Min / max avec dates ===")
|
||||
for col in df.columns:
|
||||
series = df[col]
|
||||
|
||||
min_val = series.min()
|
||||
max_val = series.max()
|
||||
min_ts = series.idxmin()
|
||||
max_ts = series.idxmax()
|
||||
|
||||
print(f"- {col}:")
|
||||
print(f" min = {min_val} à {min_ts}")
|
||||
print(f" max = {max_val} à {max_ts}")
|
||||
print()
|
||||
|
||||
# 3. Vérification rapide de la continuité temporelle
|
||||
print("=== Vérification de la continuité temporelle ===")
|
||||
diffs = df.index.to_series().diff().dropna()
|
||||
counts = diffs.value_counts().sort_index()
|
||||
|
||||
print("Différences d'intervalle (top 5):")
|
||||
print(counts.head())
|
||||
print()
|
||||
|
||||
nb_not_60s = (diffs != pd.Timedelta(minutes=1)).sum()
|
||||
print(f"Nombre d'intervalles ≠ 60s : {nb_not_60s}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,51 @@
|
||||
# tests/export_station_data.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from contextlib import closing
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from meteo.config import InfluxSettings
|
||||
from meteo.influx_client import create_influx_client
|
||||
from meteo.station_config import default_station_config
|
||||
from meteo.export import export_station_data
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Exporte les données de la station météo vers un fichier CSV brut.
|
||||
|
||||
Par défaut, on exporte les 7 derniers jours dans `data/weather_raw_7d.csv`.
|
||||
"""
|
||||
settings = InfluxSettings.from_env()
|
||||
station_config = default_station_config()
|
||||
|
||||
print("Configuration InfluxDB :")
|
||||
print(f" URL : {settings.url}")
|
||||
print(f" Org : {settings.org}")
|
||||
print(f" Bucket : {settings.bucket}")
|
||||
print()
|
||||
|
||||
with closing(create_influx_client(settings)) as client:
|
||||
print("→ Export des 7 derniers jours…")
|
||||
output_path = export_station_data(
|
||||
client=client,
|
||||
bucket=settings.bucket,
|
||||
config=station_config,
|
||||
start="-7d", # à ajuster plus tard si besoin
|
||||
stop=None, # now()
|
||||
output_path="data/weather_raw_7d.csv",
|
||||
file_format="csv",
|
||||
)
|
||||
|
||||
print()
|
||||
print(f"✔ Export terminé : {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,56 @@
|
||||
# tests/export_station_data_full.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from contextlib import closing
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from meteo.config import InfluxSettings
|
||||
from meteo.influx_client import create_influx_client
|
||||
from meteo.station_config import default_station_config
|
||||
from meteo.export import export_station_data
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Exporte l'historique complet de la station météo vers un fichier CSV.
|
||||
|
||||
On utilise `start=0`, ce qui signifie "depuis le début des données"
|
||||
(en pratique depuis l'epoch, donc tout ce que le bucket contient).
|
||||
"""
|
||||
settings = InfluxSettings.from_env()
|
||||
station_config = default_station_config()
|
||||
|
||||
print("Configuration InfluxDB :")
|
||||
print(f" URL : {settings.url}")
|
||||
print(f" Org : {settings.org}")
|
||||
print(f" Bucket : {settings.bucket}")
|
||||
print()
|
||||
|
||||
print("⚠ Attention : un export complet peut produire un fichier volumineux "
|
||||
"et prendre un certain temps si l'historique est long.")
|
||||
print()
|
||||
|
||||
with closing(create_influx_client(settings)) as client:
|
||||
print("→ Export de l'historique complet…")
|
||||
output_path = export_station_data(
|
||||
client=client,
|
||||
bucket=settings.bucket,
|
||||
config=station_config,
|
||||
start="0", # depuis le début des données
|
||||
stop=None, # jusqu'à maintenant
|
||||
output_path="data/weather_raw_full.csv",
|
||||
file_format="csv", # vous pouvez mettre "parquet" si vous préférez
|
||||
)
|
||||
|
||||
print()
|
||||
print(f"✔ Export terminé : {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,38 @@
|
||||
# scripts/fill_formatted_1s.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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, fill_missing_with_previous
|
||||
|
||||
|
||||
INPUT_CSV_PATH = Path("data/weather_formatted_1s.csv")
|
||||
OUTPUT_CSV_PATH = Path("data/weather_filled_1s.csv")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not INPUT_CSV_PATH.exists():
|
||||
print(f"⚠ Fichier introuvable : {INPUT_CSV_PATH}")
|
||||
print(' Lancez d\'abord : python "docs/02 - Préparation des données/scripts/format_raw_csv.py"')
|
||||
return
|
||||
|
||||
df_1s = load_raw_csv(INPUT_CSV_PATH)
|
||||
print(f"Fichier 1s formaté chargé : {INPUT_CSV_PATH}")
|
||||
print(f" Lignes : {len(df_1s)}, colonnes : {list(df_1s.columns)}")
|
||||
|
||||
df_filled = fill_missing_with_previous(df_1s)
|
||||
print(f"Après propagation des dernières valeurs connues : {len(df_filled)} lignes")
|
||||
|
||||
OUTPUT_CSV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
df_filled.to_csv(OUTPUT_CSV_PATH, index_label="time")
|
||||
print(f"✔ Fichier 1s 'complet' écrit dans : {OUTPUT_CSV_PATH.resolve()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
37
docs/02 - Préparation des données/scripts/format_raw_csv.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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, combine_close_observations
|
||||
|
||||
|
||||
RAW_CSV_PATH = Path("data/weather_raw_full.csv")
|
||||
OUTPUT_CSV_PATH = Path("data/weather_formatted_1s.csv")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not RAW_CSV_PATH.exists():
|
||||
print(f"⚠ Fichier brut introuvable : {RAW_CSV_PATH}")
|
||||
return
|
||||
|
||||
df_raw = load_raw_csv(RAW_CSV_PATH)
|
||||
print(f"Fichier brut chargé : {RAW_CSV_PATH}")
|
||||
print(f" Lignes : {len(df_raw)}, colonnes : {list(df_raw.columns)}")
|
||||
print(f" Type d'index : {type(df_raw.index)}")
|
||||
|
||||
df_fmt = combine_close_observations(df_raw, freq="1s", agg="mean")
|
||||
print(f"Après combinaison (1s) : {len(df_fmt)} lignes")
|
||||
|
||||
OUTPUT_CSV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
df_fmt.to_csv(OUTPUT_CSV_PATH, index_label="time")
|
||||
print(f"✔ Fichier formaté écrit dans : {OUTPUT_CSV_PATH.resolve()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
52
docs/02 - Préparation des données/scripts/list_time_gaps.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# scripts/list_time_gaps.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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.gaps import find_time_gaps
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
|
||||
|
||||
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)}")
|
||||
|
||||
gaps = find_time_gaps(df)
|
||||
total_missing = sum(g.missing_intervals for g in gaps)
|
||||
|
||||
print()
|
||||
print("=== Gaps temporels détectés ===")
|
||||
print(f"Nombre de gaps : {len(gaps)}")
|
||||
print(f"Total minutes manquantes (théoriques) : {total_missing}")
|
||||
print()
|
||||
|
||||
if not gaps:
|
||||
print("✔ Aucun gap détecté, la série est parfaitement régulière.")
|
||||
return
|
||||
|
||||
print("Top 10 des gaps les plus longs :")
|
||||
gaps_sorted = sorted(gaps, key=lambda g: g.missing_intervals, reverse=True)[:10]
|
||||
for g in gaps_sorted:
|
||||
print(
|
||||
f"- De {g.before} à {g.after} "
|
||||
f"(durée: {g.duration}, manquants: {g.missing_intervals}, "
|
||||
f"de {g.missing_start} à {g.missing_end})"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,65 @@
|
||||
# scripts/make_minutely_dataset.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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, resample_to_minutes
|
||||
from meteo.config import StationLocation
|
||||
from meteo.solar import add_solar_elevation_column
|
||||
from meteo.season import add_season_column
|
||||
|
||||
|
||||
FORMATTED_CSV_PATH = Path("data/weather_filled_1s.csv")
|
||||
OUTPUT_CSV_PATH = Path("data/weather_minutely.csv")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not FORMATTED_CSV_PATH.exists():
|
||||
print(f"⚠ Fichier formaté introuvable : {FORMATTED_CSV_PATH}")
|
||||
print(' Lancez d\'abord : python "docs/02 - Préparation des données/scripts/fill_formatted_1s.py"')
|
||||
return
|
||||
|
||||
df_1s = load_raw_csv(FORMATTED_CSV_PATH)
|
||||
print(f"Fichier 1s chargé : {FORMATTED_CSV_PATH}")
|
||||
print(f" Lignes : {len(df_1s)}, colonnes : {list(df_1s.columns)}")
|
||||
|
||||
df_min = resample_to_minutes(df_1s)
|
||||
print(f"Après resampling 60s : {len(df_min)} lignes")
|
||||
|
||||
hemisphere = "north"
|
||||
location = StationLocation.from_env(optional=True)
|
||||
|
||||
if location is not None:
|
||||
hemisphere = "south" if location.latitude < 0 else "north"
|
||||
print(
|
||||
f"Ajout de l'élévation solaire (lat={location.latitude}, lon={location.longitude}, "
|
||||
f"alt={location.elevation_m} m)..."
|
||||
)
|
||||
add_solar_elevation_column(
|
||||
df_min,
|
||||
latitude=location.latitude,
|
||||
longitude=location.longitude,
|
||||
elevation_m=location.elevation_m,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"ℹ Coordonnées GPS non définies (STATION_LATITUDE / STATION_LONGITUDE). "
|
||||
"La colonne sun_elevation ne sera pas ajoutée."
|
||||
)
|
||||
print("ℹ Saison : hypothèse par défaut = hémisphère nord. Définissez STATION_LATITUDE pour adapter.")
|
||||
|
||||
add_season_column(df_min, hemisphere=hemisphere)
|
||||
|
||||
OUTPUT_CSV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
df_min.to_csv(OUTPUT_CSV_PATH, index_label="time")
|
||||
print(f"✔ Dataset minuté écrit dans : {OUTPUT_CSV_PATH.resolve()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
docs/03 - Premiers graphiques/figures/humidity_last_7_days.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 62 KiB |
BIN
docs/03 - Premiers graphiques/figures/pressure_last_7_days.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
docs/03 - Premiers graphiques/figures/rain_rate_last_7_days.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 79 KiB |
BIN
docs/03 - Premiers graphiques/figures/wind_speed_last_7_days.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
69
docs/03 - Premiers graphiques/index.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Premiers graphiques
|
||||
|
||||
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.
|
||||
|
||||
## Température
|
||||
|
||||
```shell
|
||||
python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py" --only temperature
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Humidité relative
|
||||
|
||||
```shell
|
||||
python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py" --only humidity
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Pression atmosphérique
|
||||
|
||||
```shell
|
||||
python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py" --only pressure
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Précipitations instantanées
|
||||
|
||||
```shell
|
||||
python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py" --only rain_rate
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Luminance
|
||||
|
||||
```shell
|
||||
python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py" --only illuminance
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Vitesse du vent
|
||||
|
||||
```shell
|
||||
python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py" --only wind_speed
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Direction du vent
|
||||
|
||||
```shell
|
||||
python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py" --only wind_direction
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Élévation solaire (si disponible après enrichissement)
|
||||
|
||||
```shell
|
||||
python "docs/03 - Premiers graphiques/scripts/plot_basic_variables.py" --only sun_elevation
|
||||
```
|
||||
|
||||

|
||||
113
docs/03 - Premiers graphiques/scripts/plot_basic_variables.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# scripts/plot_basic_variables.py
|
||||
"""Génère des séries temporelles simples (7 jours) pour chaque variable météo."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
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 export_plot_dataset
|
||||
from meteo.variables import Variable, VARIABLES
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
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."""
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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)")
|
||||
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
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> None:
|
||||
parser = argparse.ArgumentParser(description="Trace les séries simples pour chaque variable météo.")
|
||||
parser.add_argument(
|
||||
"--only",
|
||||
nargs="*",
|
||||
help="Clés de variables à tracer (par défaut : toutes).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--days",
|
||||
type=int,
|
||||
default=7,
|
||||
help="Nombre de jours à afficher (par défaut : 7).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=Path,
|
||||
default=DEFAULT_OUTPUT_DIR,
|
||||
help="Dossier où stocker les figures.",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if not CSV_PATH.exists():
|
||||
raise FileNotFoundError(f"Dataset introuvable : {CSV_PATH}")
|
||||
|
||||
df = load_raw_csv(CSV_PATH)
|
||||
df_hourly = _prepare_slice(df, last_days=args.days)
|
||||
|
||||
selected: list[Variable]
|
||||
if args.only:
|
||||
keys = set(args.only)
|
||||
selected = [var for var in VARIABLES if var.key in keys]
|
||||
missing = keys - {var.key for var in selected}
|
||||
if missing:
|
||||
raise KeyError(f"Variables inconnues : {sorted(missing)}")
|
||||
else:
|
||||
selected = list(VARIABLES)
|
||||
|
||||
for variable in selected:
|
||||
_plot_variable(df_hourly, variable, args.output_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
docs/04 - Corrélations binaires/figures/correlation_heatmap.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 497 KiB |
|
After Width: | Height: | Size: 432 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 610 KiB |
|
After Width: | Height: | Size: 587 KiB |
|
After Width: | Height: | Size: 379 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 364 KiB |
|
After Width: | Height: | Size: 351 KiB |
|
After Width: | Height: | Size: 390 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 505 KiB |
|
After Width: | Height: | Size: 479 KiB |
|
After Width: | Height: | Size: 325 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 445 KiB |
|
After Width: | Height: | Size: 430 KiB |
|
After Width: | Height: | Size: 363 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 512 KiB |
|
After Width: | Height: | Size: 471 KiB |
|
After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 574 KiB |
|
After Width: | Height: | Size: 403 KiB |
|
After Width: | Height: | Size: 201 KiB |
72
docs/04 - Corrélations binaires/index.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Corrélations binaires
|
||||
|
||||
Cette étape regroupe l'ensemble des scripts dédiés aux corrélations et comparaisons directes entre variables.
|
||||
Chaque figure déposée dans `figures/` possède son CSV compagnon exporté dans le dossier `data/` au même emplacement.
|
||||
|
||||
```shell
|
||||
python "docs/04 - Corrélations binaires/scripts/plot_all_pairwise_scatter.py"
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Heatmap globale
|
||||
|
||||
```shell
|
||||
python "docs/04 - Corrélations binaires/scripts/plot_correlation_heatmap.py"
|
||||
```
|
||||
|
||||

|
||||
@@ -0,0 +1,52 @@
|
||||
# scripts/plot_all_pairwise_scatter.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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.variables import iter_variable_pairs
|
||||
from meteo.plots import plot_scatter_pair
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
DOC_DIR = Path(__file__).resolve().parent.parent
|
||||
OUTPUT_DIR = DOC_DIR / "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()
|
||||
@@ -0,0 +1,52 @@
|
||||
# scripts/plot_correlation_heatmap.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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.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")
|
||||
DOC_DIR = Path(__file__).resolve().parent.parent
|
||||
OUTPUT_PATH = DOC_DIR / "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()
|
||||
96
docs/04 - Corrélations binaires/scripts/plot_correlations.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# scripts/plot_correlations.py
|
||||
"""Produit les nuages de points ciblés entre variables sélectionnées."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Sequence
|
||||
|
||||
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.variables import Variable, VARIABLES, VARIABLES_BY_KEY, iter_variable_pairs
|
||||
from meteo.plots import plot_scatter_pair
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
DOC_DIR = Path(__file__).resolve().parent.parent
|
||||
SCATTER_DIR = DOC_DIR / "figures" / "pairwise_scatter"
|
||||
|
||||
|
||||
def _select_variables(keys: Sequence[str] | None) -> list[Variable]:
|
||||
if not keys:
|
||||
return list(VARIABLES)
|
||||
try:
|
||||
selected = [VARIABLES_BY_KEY[key] for key in keys]
|
||||
except KeyError as exc:
|
||||
raise SystemExit(f"Variable inconnue : {exc.args[0]!r}.") from exc
|
||||
return selected
|
||||
|
||||
|
||||
def _generate_pairwise_scatter(
|
||||
df: pd.DataFrame,
|
||||
variables: Sequence[Variable],
|
||||
*,
|
||||
sample_step: int,
|
||||
) -> None:
|
||||
pairs = iter_variable_pairs()
|
||||
selected = [(vx, vy) for vx, vy in pairs if vx in variables and vy in variables]
|
||||
if not selected:
|
||||
print("⚠ Aucun couple sélectionné pour les nuages de points.")
|
||||
return
|
||||
|
||||
SCATTER_DIR.mkdir(parents=True, exist_ok=True)
|
||||
for var_x, var_y in selected:
|
||||
output_path = SCATTER_DIR / f"scatter_{var_x.key}_vs_{var_y.key}.png"
|
||||
print(f"→ Scatter {var_y.key} vs {var_x.key}")
|
||||
plot_scatter_pair(df, var_x=var_x, var_y=var_y, output_path=output_path, sample_step=sample_step)
|
||||
print(f"✔ {len(selected)} nuage(s) de points généré(s) dans {SCATTER_DIR}.")
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Génère des nuages de points pairwise entre variables.")
|
||||
parser.add_argument(
|
||||
"--dataset",
|
||||
type=Path,
|
||||
default=CSV_PATH,
|
||||
help="Dataset à utiliser (par défaut : data/weather_minutely.csv).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--variables",
|
||||
nargs="*",
|
||||
help="Restreint l'analyse à certaines clés de variables.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--scatter-step",
|
||||
type=int,
|
||||
default=20,
|
||||
help="Pas d'échantillonnage pour les nuages de points individuels.",
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
dataset_path = args.dataset
|
||||
if not dataset_path.exists():
|
||||
raise SystemExit(f"Dataset introuvable : {dataset_path}")
|
||||
|
||||
df = load_raw_csv(dataset_path)
|
||||
print(f"Dataset chargé : {dataset_path} ({len(df)} lignes)")
|
||||
print()
|
||||
|
||||
variables = _select_variables(args.variables)
|
||||
|
||||
_generate_pairwise_scatter(df, variables, sample_step=args.scatter_step)
|
||||
print("✔ Terminé.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
raise SystemExit(main())
|
||||
@@ -1,20 +0,0 @@
|
||||
# Export des données
|
||||
|
||||
```shell
|
||||
python -m scripts.export_station_data
|
||||
```
|
||||
|
||||
La sortie est assez longue, et inclut un certain nombre d'avertissements qui peuvent être ignorés.
|
||||
L'important est que le script se termine sur :
|
||||
|
||||
```output
|
||||
✔ Export terminé : /Users/richard/Documents/donnees_meteo/data/weather_raw_7d.csv
|
||||
```
|
||||
|
||||
(Le chemin changera sur votre propre machine)
|
||||
|
||||
Vérifiez que le fichier est bien créé et qu'il contient des données.
|
||||
|
||||
À la place de `scripts.export_station_data`, on pourrait aussi lancer `scripts.export_station_data_full`.
|
||||
Au lieu de télécharger les données des 7 derniers jours, l'ensemble des données stockées sur le serveur pour ce bucket seront téléchargées, ce qui, selon la granularité et l'ancienneté des données peut prendre un certain temps et occuper un espace disque conséquent.
|
||||
Mon fichier complet contient plus d'un million d'enregistrements et pèse 70Mo.
|
||||
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 256 KiB |
29
docs/05 - Corrélations binaires avancées/index.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Corrélations binaires avancées
|
||||
|
||||
## Corrélations décalées
|
||||
|
||||
```shell
|
||||
python "docs/05 - Corrélations binaires avancées/scripts/plot_lagged_correlations.py"
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Corrélations glissantes
|
||||
|
||||
```shell
|
||||
python "docs/05 - Corrélations binaires avancées/scripts/plot_rolling_correlation_heatmap.py"
|
||||
```
|
||||
|
||||

|
||||
@@ -0,0 +1,64 @@
|
||||
# scripts/plot_lagged_correlations.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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.variables import VARIABLES_BY_KEY
|
||||
from meteo.analysis import compute_lagged_correlation
|
||||
from meteo.plots import plot_lagged_correlation
|
||||
from meteo.correlation_presets import DEFAULT_LAGGED_PAIRS
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
DOC_DIR = Path(__file__).resolve().parent.parent
|
||||
OUTPUT_DIR = DOC_DIR / "figures" / "lagged_correlations"
|
||||
|
||||
|
||||
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 DEFAULT_LAGGED_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()
|
||||
@@ -0,0 +1,65 @@
|
||||
# scripts/plot_rolling_correlation_heatmap.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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.variables import VARIABLES_BY_KEY
|
||||
from meteo.analysis import compute_rolling_correlations_for_pairs
|
||||
from meteo.plots import plot_rolling_correlation_heatmap
|
||||
from meteo.correlation_presets import DEFAULT_ROLLING_PAIRS
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
DOC_DIR = Path(__file__).resolve().parent.parent
|
||||
OUTPUT_PATH = DOC_DIR / "figures" / "rolling_correlations" / "rolling_correlation_heatmap.png"
|
||||
|
||||
WINDOW_MINUTES = 180 # 3 heures pour observer les tendances synoptiques
|
||||
STEP_MINUTES = 30 # on n'échantillonne qu'un point sur 30 minutes
|
||||
|
||||
|
||||
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()
|
||||
|
||||
pairs = [(VARIABLES_BY_KEY[a], VARIABLES_BY_KEY[b]) for a, b in DEFAULT_ROLLING_PAIRS]
|
||||
|
||||
rolling_df = compute_rolling_correlations_for_pairs(
|
||||
df=df,
|
||||
pairs=pairs,
|
||||
window_minutes=WINDOW_MINUTES,
|
||||
min_valid_fraction=0.7,
|
||||
step_minutes=STEP_MINUTES,
|
||||
method="pearson",
|
||||
)
|
||||
|
||||
if rolling_df.empty:
|
||||
print("⚠ Impossible de calculer les corrélations glissantes (données insuffisantes).")
|
||||
return
|
||||
|
||||
output_path = plot_rolling_correlation_heatmap(
|
||||
rolling_corr=rolling_df,
|
||||
output_path=OUTPUT_PATH,
|
||||
cmap="coolwarm",
|
||||
vmin=-1.0,
|
||||
vmax=1.0,
|
||||
)
|
||||
|
||||
print(f"✔ Heatmap de corrélations glissantes enregistrée : {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 93 KiB |
13
docs/06 - Corrélations multiples/index.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Corrélations multiples
|
||||
|
||||
## Hexbin colorés
|
||||
|
||||
```shell
|
||||
python "docs/06 - Corrélations multiples/scripts/plot_hexbin_explorations.py"
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -0,0 +1,70 @@
|
||||
# scripts/plot_hexbin_explorations.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
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.variables import VARIABLES_BY_KEY
|
||||
from meteo.plots import plot_hexbin_with_third_variable
|
||||
from meteo.correlation_presets import DEFAULT_HEXBIN_SCENARIOS
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
DOC_DIR = Path(__file__).resolve().parent.parent
|
||||
OUTPUT_DIR = DOC_DIR / "figures" / "hexbin_explorations"
|
||||
|
||||
|
||||
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 scenario in DEFAULT_HEXBIN_SCENARIOS:
|
||||
var_x = VARIABLES_BY_KEY[scenario.key_x]
|
||||
var_y = VARIABLES_BY_KEY[scenario.key_y]
|
||||
var_color = VARIABLES_BY_KEY[scenario.key_color]
|
||||
|
||||
filename = scenario.filename
|
||||
output_path = OUTPUT_DIR / filename
|
||||
|
||||
reduce_func = scenario.get_reduce_func()
|
||||
reduce_label = scenario.get_reduce_label()
|
||||
|
||||
gridsize = scenario.gridsize
|
||||
mincnt = scenario.mincnt
|
||||
|
||||
description = scenario.description
|
||||
print(f"→ Hexbin {var_y.key} vs {var_x.key} (couleur = {var_color.key})")
|
||||
print(f" {description}")
|
||||
|
||||
plot_hexbin_with_third_variable(
|
||||
df=df,
|
||||
var_x=var_x,
|
||||
var_y=var_y,
|
||||
var_color=var_color,
|
||||
output_path=output_path,
|
||||
gridsize=gridsize,
|
||||
mincnt=mincnt,
|
||||
reduce_func=reduce_func,
|
||||
reduce_func_label=reduce_label,
|
||||
cmap="magma",
|
||||
)
|
||||
print(f" ✔ Graphique enregistré : {output_path}")
|
||||
print()
|
||||
|
||||
print("✔ Tous les graphiques hexbin ont été générés.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,68 +0,0 @@
|
||||
# Premiers graphiques
|
||||
|
||||
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.
|
||||
|
||||
## Température
|
||||
|
||||
```shell
|
||||
python -m scripts.plot_basic_variables --only temperature
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Humidité relative
|
||||
|
||||
```shell
|
||||
python -m scripts.plot_basic_variables --only humidity
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Pression atmosphérique
|
||||
|
||||
```shell
|
||||
python -m scripts.plot_basic_variables --only pressure
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Précipitations instantanées
|
||||
|
||||
```shell
|
||||
python -m scripts.plot_basic_variables --only rain_rate
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Luminance
|
||||
|
||||
```shell
|
||||
python -m scripts.plot_basic_variables --only illuminance
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Vitesse du vent
|
||||
|
||||
```shell
|
||||
python -m scripts.plot_basic_variables --only wind_speed
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Direction du vent
|
||||
|
||||
```shell
|
||||
python -m scripts.plot_basic_variables --only wind_direction
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Élévation solaire (si disponible après enrichissement)
|
||||
|
||||
```shell
|
||||
python -m scripts.plot_basic_variables --only sun_elevation
|
||||
```
|
||||
|
||||

|
||||