1
2025-11-26 17:22:57 +01:00

17 KiB
Raw Permalink Blame History

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, cestàdire une série temporelle où chaque minute possède une observation complète pour toutes les variables utiles. Lobjectif est de passer dun format brut, pensé pour la domotique temps réel, à un format tabulaire lisible par les bibliothèques danalyse comme pandas.

Export des données

python "docs/02 - Préparation des données/scripts/export_station_data.py"

Ce script se connecte au serveur InfluxDB configuré au chapitre précédent, interroge les mesures de la station sur une fenêtre récente (par défaut les sept derniers jours) et les exporte dans un fichier CSV data/weather_raw_7d.csv. On quitte ainsi la base de données de séries temporelles pour un format beaucoup plus simple à manipuler avec pandas, tout en conservant lhorodatage précis.

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 :

✔ 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 :

python "docs/02 - Préparation des données/scripts/export_station_data_full.py"

Au lieu de télécharger les données des sept 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 70 Mo.

En pratique, il est souvent plus confortable de développer et tester les scripts suivants sur quelques jours de données (fichier weather_raw_7d.csv), puis de relancer le pipeline complet sur lhistorique complet une fois les étapes stabilisées. Les deux scripts sappuient sur lAPI HTTP dInfluxDB 2.x et son langage de requête Flux (voir la documentation dédiée à Flux).

Ajustements

Le CSV exporté reste très proche du format de stockage utilisé par la domotique : chaque capteur envoie une mesure à des instants légèrement différents, et InfluxDB stocke donc plusieurs lignes par seconde, chacune ne contenant quune seule variable renseignée. Pour lanalyse statistique, on cherche au contraire à obtenir une table où chaque horodatage regroupe toutes les variables de la station sur une grille temporelle régulière.

Le fichier peut être rapidement inspecté avec la commande head :

head data/weather_raw_full.csv
time,temperature,humidity,pressure,illuminance,wind_speed,wind_direction,rain_rate
2025-03-10 09:35:23.156646+00:00,,,996.95,,,,
2025-03-10 09:35:23.158538+00:00,10.6,,,,,,
2025-03-10 09:35:23.162398+00:00,,83.0,,,,,
2025-03-10 09:35:23.164634+00:00,,,,,7.4,,
2025-03-10 09:35:23.170122+00:00,,,,,,256.0,
2025-03-10 09:35:23.183555+00:00,,,,,,,0.0
2025-03-10 09:35:41.211148+00:00,,,,20551.2,,,
2025-03-10 09:36:22.638255+00:00,,,,,12.2,,
2025-03-10 09:36:22.640356+00:00,,,,,,306.0,

On peut voir que Home Assistant écrit une nouvelle entrée pour chaque capteur, alors qu'on aurait pu s'attendre à une ligne unique pour l'ensemble des capteurs. Cest un format très pratique pour la collecte temps réel, mais moins confortable pour lanalyse : il faut dabord tout remettre en forme.

Le script suivant s'occupe de regrouper les données de capteurs dont l'enregistrement est proche :

python "docs/02 - Préparation des données/scripts/format_raw_csv.py"
Fichier brut chargé : data/weather_raw_full.csv
  Lignes : 1570931, colonnes : ['temperature', 'humidity', 'pressure', 'illuminance', 'wind_speed', 'wind_direction', 'rain_rate']
  Type d'index : <class 'pandas.core.indexes.datetimes.DatetimeIndex'>
Après combinaison (1s) : 630171 lignes
✔ Fichier formaté écrit dans : /Users/richard/Documents/donnees_meteo/data/weather_formatted_1s.csv

Un nouveau document CSV intermédiaire est donc créé. On voit que lon passe denviron 1,57 million de lignes à 630 000 lignes « combinées » : le script regroupe les mesures de tous les capteurs tombant dans la même seconde, en utilisant lhorodatage comme clé. Lindex de type DatetimeIndex indiqué dans la sortie est laxe temporel standard de pandas (voir la documentation de pandas.DatetimeIndex). Cette étape correspond à une première forme de rééchantillonnage temporel, classique lorsquon prépare une série pour lanalyse.

head data/weather_formatted_1s.csv
time,temperature,humidity,pressure,illuminance,wind_speed,wind_direction,rain_rate
2025-03-10 09:35:23+00:00,10.6,83.0,996.95,,7.4,256.0,0.0
2025-03-10 09:35:41+00:00,,,,20551.2,,,
2025-03-10 09:36:22+00:00,,,,20247.6,12.2,306.0,
2025-03-10 09:36:52+00:00,,,,20199.6,9.3,246.0,
2025-03-10 09:37:22+00:00,,,,20034.0,7.9,,
2025-03-10 09:37:52+00:00,,,,20124.0,7.4,284.0,
2025-03-10 09:38:22+00:00,,,,19860.0,9.7,215.0,
2025-03-10 09:39:22+00:00,,,,19722.0,11.4,203.0,
2025-03-10 09:40:22+00:00,,,,19720.8,10.0,209.0,

Il reste des cellules vides : en effet, Home Assistant n'enregistre pas la valeur d'un capteur si elle n'a pas changé depuis la dernière fois. On se retrouve donc avec une série temporelle à pas régulier (1 s), mais encore parsemée de trous.

On fait donc :

python "docs/02 - Préparation des données/scripts/fill_formatted_1s.py"
Fichier 1s formaté chargé : data/weather_formatted_1s.csv
  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

Cette seconde passe applique un remplissage par propagation de la dernière valeur connue (forward fill ou ffill dans pandas) : tant quun capteur ne publie pas de nouvelle mesure, on considère que la précédente reste valable. Cest une hypothèse raisonnable pour des variables relativement lisses comme la température ou la pression, moins pour des phénomènes très brusques ; lobjectif ici est surtout dobtenir un dataset sans valeurs manquantes, ce qui simplifie grandement les analyses et les modèles dans les chapitres suivants.

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, qui aideront ensuite à interpréter les graphiques et à construire des modèles plus pertinents :

  • regrouper les points par minute, pour lisser légèrement le bruit tout en restant réactif ; ce pas de 60 secondes est un bon compromis entre fidélité au signal brut et taille raisonnable du dataset ;
  • ajouter la saison correspondant à chaque observation (en fonction de l'hémisphère), ce qui permet de comparer facilement les comportements printemps/été/automne/hiver et de relier nos courbes aux notions classiques de saisons en météorologie ;
  • calculer la hauteur du soleil si la latitude/longitude de la station sont configurées, afin de disposer dune estimation de lélévation solaire audessus de lhorizon (jour/nuit, midi solaire, etc.) en sappuyant sur la bibliothèque dastronomie astral.

Ces opérations sont réalisées par :

python "docs/02 - Préparation des données/scripts/make_minutely_dataset.py"

Le script produit data/weather_minutely.csv. Chaque ligne de ce fichier correspond à une minute, avec toutes les variables météo alignées (température, humidité, pression, vent, pluie, etc.) et, si les coordonnées de la station sont connues, des colonnes supplémentaires comme la saison et lélévation du soleil.

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.

Ce fichier minuté est le jeu de données de référence utilisé dans la majorité des chapitres suivants.

Pipeline simplifié

Un script tout simple permet de faire automatiquement tout ce qu'on vient de voir, dans le bon ordre, sans avoir à lancer chaque étape à la main. Il supprime tous les fichiers CSV existants : il faudra donc relancer la génération des images dans les étapes suivantes pour qu'elles intègrent les nouvelles données.

python -m scripts.refresh_data_pipeline

Ce module Python orchestre lexport depuis InfluxDB, la mise en forme à 1 seconde, le remplissage des valeurs manquantes puis la construction du dataset minuté. Le fait de repartir de zéro à chaque exécution garantit que weather_minutely.csv reflète bien létat actuel de la base, au prix dun temps de calcul un peu plus long.

Vérification des données

Avant dexplorer les graphiques ou de lancer des modèles, on vérifie que le dataset minuté est cohérent : pas de valeurs manquantes, des ordres de grandeur plausibles, et une grille temporelle effectivement régulière. On peut dabord s'assurer que plus aucune information n'est manquante :

python "docs/02 - Préparation des données/scripts/check_missing_values.py"
Dataset chargé : data/weather_minutely.csv
  Lignes   : 321881
  Colonnes : ['temperature', 'humidity', 'pressure', 'illuminance', 'wind_speed', 'wind_direction', 'rain_rate']
=== Synthèse des valeurs manquantes ===
Total de cellules      : 2253167
Cellules manquantes    : 0
Fraction manquante     : 0.000000
Lignes complètes       : 321881
Lignes avec des trous  : 0
Fraction lignes complètes : 1.000000

Valeurs manquantes par colonne :
  - temperature   : 0
  - humidity      : 0
  - pressure      : 0
  - illuminance   : 0
  - wind_speed    : 0
  - wind_direction : 0
  - rain_rate     : 0

✔ Aucune valeur manquante dans le dataset minuté.

Ce premier contrôle confirme que toutes les lignes de weather_minutely.csv sont complètes : aucune cellule nest manquante, ce qui évitera bien des subtilités dans les analyses ultérieures.

Le script suivant nous permet de vérifier rapidement si des problèmes majeurs peuvent être découverts :

python "docs/02 - Préparation des données/scripts/describe_minutely_dataset.py"
Dataset minuté chargé : data/weather_minutely.csv
  Lignes   : 321881
  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
count  321881.000000  321881.000000  321881.000000  ...  321881.000000   321881.000000  321881.000000
mean       15.004488      74.131993    1010.683189  ...       2.877190      181.977411       0.108216
std         6.349077      18.885843       8.210283  ...       3.151080       88.089334       0.820691
min        -2.200000      20.000000     976.973123  ...       0.000000        0.000000       0.000000
25%        10.277778      59.000000    1005.420000  ...       0.000000       96.000000       0.000000
50%        14.600000      77.666667    1011.514287  ...       2.333549      210.000000       0.000000
75%        19.000000      91.000000    1015.900000  ...       4.650000      247.666196       0.000000
max        34.888889      99.000000    1033.187174  ...      26.554176      360.000000      42.672000

[8 rows x 7 columns]

=== Min / max avec dates ===
- temperature:
    min = -2.2 à 2025-03-17 05:16:00+00:00
    max = 34.8888888888889 à 2025-07-02 15:59:00+00:00
- humidity:
    min = 20.0 à 2025-04-30 15:22:00+00:00
    max = 99.0 à 2025-03-11 06:29:00+00:00
- pressure:
    min = 976.973122738378 à 2025-10-23 05:06:00+00:00
    max = 1033.18717416804 à 2025-10-10 17:12:00+00:00
- illuminance:
    min = 0.0 à 2025-03-10 17:44:00+00:00
    max = 133520.394 à 2025-07-29 11:48:00+00:00
- wind_speed:
    min = 0.0 à 2025-03-10 14:31:00+00:00
    max = 26.554176 à 2025-06-26 00:10:00+00:00
- wind_direction:
    min = 0.0 à 2025-03-12 04:57:00+00:00
    max = 360.0 à 2025-03-12 07:33:00+00:00
- rain_rate:
    min = 0.0 à 2025-03-10 09:35:00+00:00
    max = 42.672 à 2025-06-15 03:10:00+00:00

=== Vérification de la continuité temporelle ===
Différences d'intervalle (top 5):
time
0 days 00:01:00    304291
0 days 00:02:00      9426
0 days 00:03:00      3562
0 days 00:04:00      1740
0 days 00:05:00      1142
Name: count, dtype: int64

Nombre d'intervalles ≠ 60s : 17589

Ce deuxième script fournit un aperçu statistique classique (fonction describe() de pandas) et quelques min/max datés pour chaque variable : on y vérifie que les valeurs restent plausibles (par exemple pas de température à +80 °C ni de vent négatif) et que les extrêmes correspondent à des dates réalistes. La section sur les différences dintervalle permet déjà de repérer que certains pas ne sont pas strictement de 60 secondes, ce qui est courant sur des données issues de capteurs, mais quil faut garder en tête pour la suite. Ce type de contrôle sinscrit dans une démarche danalyse exploratoire de données.

Ces écarts peuvent être identifiés plus finement avec le script suivant :

python "docs/02 - Préparation des données/scripts/list_time_gaps.py"
Dataset minuté chargé : data/weather_minutely.csv
  Lignes   : 321881

=== Gaps temporels détectés ===
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)

Ce troisième script ne corrige rien : il se contente de lister précisément les intervalles dans lesquels des minutes manquent dans la série, ainsi que leur durée. 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 sansfil, etc. Mais ils peuvent également correspondre aux modifications opérées dans les scripts précédents !

Ces scripts sont intéressants parce qu'ils mettent en évidence des facteurs indirects, contribuant à la qualité des données soumise. On peut prendre toutes les précautions, on peut avoir l'intuition d'avoir tout géré, et se rassurer parce qu'on utilise des outils fiables, mais il existera toujours des manques dans les données.

Il faut être capable de les identifier, et il faut les prendre en compte dans tout calcul ultérieur.

Une fois que tout est passé en revue, on passe d'un jeu contenant plus d'un million d'enregistrements à un jeu n'en contenant plus que 300 000.