Vous cherchez à anticiper les valeurs de puissance pour la semaine à venir en utilisant la Régression Linéaire Multiple (MLR) ? Ce guide vous fournira une méthodologie complète et le code Python nécessaire, en tenant compte de la nature de vos données (normalisées, une seule colonne de 437 796 mesures se terminant le 31 octobre 2023) et de votre objectif de prédiction jusqu'au 7 novembre 2023.
La Régression Linéaire Multiple (MLR) est une technique statistique qui vise à modéliser la relation entre une variable dépendante (la cible, ici votre puissance) et plusieurs variables indépendantes (les prédicteurs ou caractéristiques). La formule générale est :
\[ Y = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + \dots + \beta_n X_n + \epsilon \]
Où :
Votre jeu de données contient 437 796 lignes mais une seule colonne de valeurs de puissance normalisées. La MLR exige plusieurs prédicteurs. Comment faire ? La solution réside dans l'ingénierie des caractéristiques (feature engineering) : nous allons créer de nouvelles colonnes (caractéristiques) à partir des informations disponibles :
Avec 437 796 points de données se terminant le '2023-10-31 23:59:31', cela couvre environ 304 jours (437796 / (60*24) ≈ 304). Cela suggère fortement que vos données ont une fréquence d'une minute. Nous baserons notre code sur cette hypothèse. Si votre fréquence est différente (par exemple, horaire), vous devrez ajuster les calculs de lags en conséquence.
Assurez-vous d'avoir les bibliothèques nécessaires installées :
pip install pandas numpy scikit-learn matplotlib
Chargeons ensuite vos données. Nous supposons qu'elles sont dans un fichier CSV nommé puissance_data.csv avec une colonne 'timestamp' et une colonne 'power'. Adaptez le code si votre structure est différente.
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
from datetime import timedelta
# --- Configuration ---
DATA_FILE = 'puissance_data.csv' # Mettez ici le chemin vers votre fichier
TIMESTAMP_COL = 'timestamp' # Nom de la colonne timestamp
POWER_COL = 'power' # Nom de la colonne puissance (normalisée)
FREQ = 'min' # Fréquence supposée des données ('T' ou 'min')
LAST_TIMESTAMP_TRAIN = pd.to_datetime('2023-10-31 23:59:31')
PREDICTION_START = pd.to_datetime('2023-11-01 00:00:00')
PREDICTION_END = pd.to_datetime('2023-11-07 23:59:00')
# --- Chargement et Vérification Initiale ---
print("Chargement des données...")
try:
# Essayez de charger avec la colonne timestamp comme index
df = pd.read_csv(DATA_FILE, parse_dates=[TIMESTAMP_COL], index_col=TIMESTAMP_COL)
print(f"Données chargées avec succès depuis {DATA_FILE}.")
except FileNotFoundError:
print(f"ERREUR : Le fichier '{DATA_FILE}' n'a pas été trouvé.")
# Quitter ou gérer l'erreur comme nécessaire
exit()
except KeyError:
# Si la colonne timestamp n'existe pas, ou si elle n'est pas l'index
try:
df = pd.read_csv(DATA_FILE, parse_dates=[TIMESTAMP_COL])
df = df.set_index(TIMESTAMP_COL)
print(f"Données chargées et colonne '{TIMESTAMP_COL}' définie comme index.")
except Exception as e:
print(f"ERREUR : Impossible de charger ou de traiter le fichier CSV : {e}")
exit()
except Exception as e:
print(f"ERREUR inattendue lors du chargement des données : {e}")
exit()
# Assurer le bon nom de colonne pour la puissance
if POWER_COL not in df.columns:
# Si une seule colonne, la renommer
if len(df.columns) == 1:
print(f"Avertissement : Une seule colonne trouvée, renommée en '{POWER_COL}'.")
df.columns = [POWER_COL]
else:
print(f"ERREUR : Colonne '{POWER_COL}' non trouvée dans les données.")
exit()
# Vérifier la forme
print(f"Forme initiale des données : {df.shape}")
if df.shape[0] != 437796:
print(f"Avertissement : Le nombre de lignes ({df.shape[0]}) ne correspond pas aux 437796 attendus.")
# Trier par index (temps) et supprimer les doublons éventuels
df = df.sort_index()
df = df[~df.index.duplicated(keep='first')]
print(f"Données triées et doublons d'index supprimés. Nouvelle forme : {df.shape}")
# Vérifier la dernière date
if not df.empty:
actual_last_date = df.index.max()
print(f"Dernier timestamp dans les données chargées : {actual_last_date}")
# Comparaison prudente à cause des fuseaux horaires potentiels
if abs((actual_last_date - LAST_TIMESTAMP_TRAIN).total_seconds()) > 60: # Tolérance de 1 minute
print(f"Avertissement : Le dernier timestamp ({actual_last_date}) ne correspond pas exactement à {LAST_TIMESTAMP_TRAIN}.")
else:
print("ERREUR : Le DataFrame est vide après chargement.")
exit()
# Vérifier les valeurs manquantes
if df[POWER_COL].isnull().any():
print("Valeurs manquantes détectées. Suppression des lignes concernées...")
initial_rows = len(df)
df.dropna(subset=[POWER_COL], inplace=True)
print(f"{initial_rows - len(df)} lignes avec NaN supprimées.")
print(f"Vérification de la normalisation (min/max de '{POWER_COL}') : {df[POWER_COL].min()} / {df[POWER_COL].max()}")
print("\nExploration des premières et dernières lignes :")
print(df.head())
print(df.tail())
Créons les caractéristiques temporelles et retardées (lags).
print("\nCréation des caractéristiques (Features)...")
# --- Caractéristiques Temporelles ---
df['hour'] = df.index.hour
df['minute'] = df.index.minute # Peut être utile à la fréquence minute
df['dayofweek'] = df.index.dayofweek # Lundi=0, Dimanche=6
df['dayofyear'] = df.index.dayofyear
df['month'] = df.index.month
df['weekofyear'] = df.index.isocalendar().week.astype(int)
# Utiliser sin/cos pour les caractéristiques cycliques (améliore la performance de MLR)
df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
df['dayofweek_sin'] = np.sin(2 * np.pi * df['dayofweek'] / 7)
df['dayofweek_cos'] = np.cos(2 * np.pi * df['dayofweek'] / 7)
df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12)
df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12)
# --- Caractéristiques Retardées (Lags) ---
# Important : Les périodes de lag doivent correspondre à la fréquence (en minutes ici)
lag_periods_minutes = {
'lag_1h': 60,
'lag_2h': 120,
'lag_24h': 24 * 60,
'lag_48h': 48 * 60,
'lag_1w': 7 * 24 * 60
}
print("Ajout des caractéristiques retardées (cela peut prendre du temps)...")
for name, lag_min in lag_periods_minutes.items():
df[name] = df[POWER_COL].shift(lag_min)
print(f" - {name} (décalage de {lag_min} minutes) ajouté.")
# --- Nettoyage après création des caractéristiques ---
print("Suppression des lignes avec NaN créés par les lags...")
initial_rows = len(df)
df.dropna(inplace=True)
print(f"{initial_rows - len(df)} lignes supprimées. Forme finale pour l'entraînement : {df.shape}")
if df.empty:
print("ERREUR : Plus de données après suppression des NaN des lags. Vérifiez les périodes de lag ou la quantité de données.")
exit()
# --- Définition des Features et de la Cible ---
# Exclure les caractéristiques temporelles brutes si on utilise sin/cos
features_cols = [
'minute', 'dayofyear', 'weekofyear', # Garder des indicateurs non cycliques ou moins bien capturés par sin/cos
'hour_sin', 'hour_cos',
'dayofweek_sin', 'dayofweek_cos',
'month_sin', 'month_cos'
] + list(lag_periods_minutes.keys()) # Ajouter tous les lags
target_col = POWER_COL
X_train = df[features_cols]
y_train = df[target_col]
print(f"\nCaractéristiques sélectionnées pour l'entraînement ({len(features_cols)}):")
print(features_cols)
print(f"Forme de X_train : {X_train.shape}, Forme de y_train : {y_train.shape}")
Entraînons le modèle LinearRegression de scikit-learn.
print("\nEntraînement du modèle de Régression Linéaire Multiple...")
model = LinearRegression()
model.fit(X_train, y_train)
print("Modèle entraîné avec succès.")
# Afficher les coefficients (optionnel)
# print("\nCoefficients du modèle:")
# coeff_df = pd.DataFrame(model.coef_, index=features_cols, columns=['Coefficient'])
# print(coeff_df)
# print(f"Ordonnée à l'origine (Intercept) : {model.intercept_}")
C'est l'étape la plus délicate. Pour prédire la semaine du 1er au 7 novembre, nous devons générer les caractéristiques pour chaque minute future. Les caractéristiques temporelles sont faciles à calculer, mais les caractéristiques retardées (lags) dépendent des valeurs de puissance futures... que nous essayons justement de prédire !
La solution est une prédiction itérative :
Attention : Cette approche peut entraîner une accumulation des erreurs (error propagation), surtout sur un horizon long comme une semaine. Les prédictions peuvent devenir moins fiables au fil du temps.
print("\nPréparation de la prédiction future (itérative)...")
# Créer l'index temporel pour la période de prédiction
future_index = pd.date_range(start=PREDICTION_START, end=PREDICTION_END, freq=FREQ)
print(f"Index temporel futur créé : {len(future_index)} points de {future_index.min()} à {future_index.max()}")
# Créer un DataFrame pour stocker les caractéristiques futures et les prédictions
df_future = pd.DataFrame(index=future_index)
# Combiner les données historiques récentes et le DataFrame futur pour faciliter le calcul des lags
# Nous avons besoin des données historiques remontant au moins au lag le plus long (1 semaine)
max_lag_minutes = max(lag_periods_minutes.values())
required_history_start = PREDICTION_START - timedelta(minutes=max_lag_minutes)
# Sélectionner l'historique pertinent (DataFrame 'df' original avant dropna)
# Recharger si nécessaire ou utiliser une copie si 'df' a été modifié par dropna
try:
df_hist_for_lags = pd.read_csv(DATA_FILE, parse_dates=[TIMESTAMP_COL], index_col=TIMESTAMP_COL)
df_hist_for_lags = df_hist_for_lags.sort_index()
df_hist_for_lags = df_hist_for_lags[~df_hist_for_lags.index.duplicated(keep='first')]
df_hist_for_lags = df_hist_for_lags[[POWER_COL]] # Garder seulement la puissance
except Exception as e:
print(f"ERREUR lors du rechargement des données pour les lags : {e}. Utilisation de df avant dropna.")
# Attention, si df a été modifié, il faut le recharger
# Solution simple: utiliser df_train et y_train pour reconstruire une partie
df_hist_for_lags = pd.DataFrame({POWER_COL: y_train}, index=X_train.index)
df_combined = pd.concat([df_hist_for_lags[df_hist_for_lags.index >= required_history_start],
pd.DataFrame(index=future_index)]) # Concaténer avec l'index futur vide
print(f"DataFrame combiné (historique récent + futur) créé. Forme : {df_combined.shape}")
# Initialiser la colonne de puissance prédite (sera remplie itérativement)
df_combined['predicted_power'] = np.nan
# Remplir la partie historique avec les vraies valeurs
df_combined['predicted_power'].fillna(df_combined[POWER_COL], inplace=True)
print("Génération des caractéristiques et prédiction itérative (cela peut prendre du temps)...")
# Boucle sur chaque timestamp futur à prédire
for t in future_index:
if t not in df_combined.index:
print(f"Timestamp {t} non trouvé dans df_combined, arrêt.")
break
current_features = {}
# 1. Calculer les caractéristiques temporelles pour le temps 't'
current_features['hour'] = t.hour
current_features['minute'] = t.minute
current_features['dayofweek'] = t.dayofweek
current_features['dayofyear'] = t.dayofyear
current_features['month'] = t.month
current_features['weekofyear'] = t.isocalendar().week
current_features['hour_sin'] = np.sin(2 * np.pi * current_features['hour'] / 24)
current_features['hour_cos'] = np.cos(2 * np.pi * current_features['hour'] / 24)
current_features['dayofweek_sin'] = np.sin(2 * np.pi * current_features['dayofweek'] / 7)
current_features['dayofweek_cos'] = np.cos(2 * np.pi * current_features['dayofweek'] / 7)
current_features['month_sin'] = np.sin(2 * np.pi * current_features['month'] / 12)
current_features['month_cos'] = np.cos(2 * np.pi * current_features['month'] / 12)
# 2. Calculer les caractéristiques retardées (lags) pour le temps 't'
valid_lags = True
for name, lag_min in lag_periods_minutes.items():
lag_timestamp = t - timedelta(minutes=lag_min)
if lag_timestamp in df_combined.index:
# Utiliser la valeur prédite si elle existe (pour les lags qui tombent dans la période de prédiction),
# sinon la valeur historique réelle.
lag_value = df_combined.loc[lag_timestamp, 'predicted_power']
if pd.isna(lag_value):
# Essayer de récupérer la valeur historique si 'predicted_power' est NaN (devrait pas arriver avec fillna)
lag_value = df_combined.loc[lag_timestamp, POWER_COL]
if pd.isna(lag_value):
print(f"AVERTISSEMENT : Valeur de lag manquante pour {name} au temps {t} (timestamp lag: {lag_timestamp}). Utilisation de 0.")
current_features[name] = 0 # Ou une autre stratégie (ex: moyenne, ffill)
valid_lags = False # Marquer comme potentiellement problématique
else:
current_features[name] = lag_value
else:
print(f"AVERTISSEMENT : Timestamp de lag {lag_timestamp} hors des limites pour {name} au temps {t}. Utilisation de 0.")
current_features[name] = 0 # Pas assez d'historique
valid_lags = False
# 3. Préparer les caractéristiques pour le modèle (DataFrame d'une ligne)
# Assurer le bon ordre des colonnes, correspondant à X_train.columns
current_X = pd.DataFrame([current_features], index=[t])[features_cols]
# 4. Faire la prédiction
prediction = model.predict(current_X)[0]
# 5. Stocker la prédiction dans df_combined pour qu'elle soit utilisée pour les futurs lags
df_combined.loc[t, 'predicted_power'] = prediction
# Afficher la progression (optionnel)
if t.minute == 0 and t.second == 0: # Toutes les heures
print(f" Prédiction pour {t} : {prediction:.4f}")
# Extraire les prédictions finales
predictions_final = df_combined.loc[future_index, ['predicted_power']]
print("\nPrédictions générées pour la semaine future.")
print("Premières prédictions :")
print(predictions_final.head())
print("\nDernières prédictions :")
print(predictions_final.tail())
# Sauvegarder les prédictions (optionnel)
# predictions_final.to_csv('predictions_puissance_semaine_future.csv')
# print("Prédictions sauvegardées dans 'predictions_puissance_semaine_future.csv'")
Visualiser les données historiques récentes et les prédictions aide à évaluer qualitativement le modèle.
print("\nVisualisation des résultats...")
plt.figure(figsize=(15, 7))
# Afficher les dernières semaines de données réelles
plt.plot(df.index[-7*24*60*2:], df[POWER_COL].tail(7*24*60*2), label='Données Historiques Réelles (2 dernières semaines)', color='blue', alpha=0.7)
# Afficher les prédictions
plt.plot(predictions_final.index, predictions_final['predicted_power'], label='Prédictions MLR (Semaine Future)', color='red', linestyle='--')
plt.title('Prédiction de Puissance (Normalisée) avec MLR')
plt.xlabel('Temps')
plt.ylabel('Puissance Normalisée')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# Zoom sur la transition
plt.figure(figsize=(15, 7))
zoom_start = LAST_TIMESTAMP_TRAIN - timedelta(days=2)
zoom_end = PREDICTION_START + timedelta(days=2)
# Historique zoomé
plt.plot(df.loc[zoom_start:LAST_TIMESTAMP_TRAIN].index, df.loc[zoom_start:LAST_TIMESTAMP_TRAIN, POWER_COL], label='Données Historiques (Zoom)', color='blue')
# Prédictions zoomées
plt.plot(predictions_final.loc[PREDICTION_START:zoom_end].index, predictions_final.loc[PREDICTION_START:zoom_end, 'predicted_power'], label='Prédictions MLR (Zoom)', color='red', linestyle='--')
plt.title('Zoom sur la Transition Historique / Prédiction')
plt.xlabel('Temps')
plt.ylabel('Puissance Normalisée')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
Pour comprendre quels facteurs influencent le plus les prédictions de notre modèle MLR, nous pouvons visualiser l'importance relative des différentes catégories de caractéristiques que nous avons créées. Le graphique radar ci-dessous donne une estimation qualitative de l'impact potentiel de chaque groupe de caractéristiques sur la prédiction de la puissance. Notez que l'importance réelle peut varier et une analyse plus approfondie (comme l'analyse des coefficients ou les tests de permutation) serait nécessaire pour une quantification précise.
Ce graphique suggère que les caractéristiques retardées (capturant l'autocorrélation et les cycles journaliers/hebdomadaires) ainsi que les caractéristiques temporelles cycliques (heure, jour de la semaine) ont potentiellement le plus grand impact sur la capacité du modèle MLR à prédire la puissance. Les lags plus longs (hebdomadaires) et les caractéristiques annuelles (mois) peuvent être légèrement moins influents mais restent importants pour capturer les tendances saisonnières.
Le diagramme ci-dessous résume les étapes clés que nous avons suivies pour passer de vos données brutes aux prédictions de puissance pour la semaine à venir en utilisant la régression linéaire multiple.
LinearRegression"]
id3c["model.fit(X_train, y_train)"]
id4["4. Prédiction Future"]
id4a["Création Index Futurmodel.predict()"]
id4b5["Stockage Prédiction"]
id5["5. Analyse & Visualisation"]
id5a["Affichage Prédictions"]
id5b["Graphiques (Historique vs Préd.)"]
id5c["Évaluation (Visuelle)"]
id5d["(Optionnel) Sauvegarde CSV"]
Ce processus structuré permet de s'assurer que toutes les étapes nécessaires sont couvertes, de la préparation initiale des données à la génération et à l'évaluation des prédictions finales, en passant par l'étape cruciale de création des caractéristiques adaptées à un modèle MLR pour une série temporelle.
Pour mieux comprendre les différentes variables que nous avons ajoutées pour alimenter le modèle MLR, voici un tableau résumant leur type, leur objectif et un exemple.
| Nom de la Caractéristique | Type | Objectif | Exemple de Calcul / Valeur |
|---|---|---|---|
hour, minute, dayofweek, dayofyear, month, weekofyear |
Temporel (Brut) | Capturer les moments spécifiques dans le temps. | df.index.hour -> 14 |
hour_sin, hour_cos |
Temporel (Cyclique) | Représenter la nature cyclique de l'heure dans une journée (0h est proche de 23h). | np.sin(2*np.pi*df['hour']/24) |
dayofweek_sin, dayofweek_cos |
Temporel (Cyclique) | Représenter la nature cyclique des jours de la semaine (Dimanche est proche de Lundi). | np.cos(2*np.pi*df['dayofweek']/7) |
month_sin, month_cos |
Temporel (Cyclique) | Représenter la nature cyclique des mois de l'année (Décembre est proche de Janvier). | np.sin(2*np.pi*df['month']/12) |
lag_1h |
Retardé (Lag) | Utiliser la valeur de puissance d'il y a 1 heure pour prédire la valeur actuelle. Capture l'autocorrélation à court terme. | df['power'].shift(60) |
lag_24h |
Retardé (Lag) | Utiliser la valeur de puissance d'il y a 24 heures. Capture les schémas journaliers. | df['power'].shift(1440) |
lag_1w |
Retardé (Lag) | Utiliser la valeur de puissance d'il y a 1 semaine. Capture les schémas hebdomadaires. | df['power'].shift(10080) |
Cette combinaison de caractéristiques temporelles (brutes et cycliques) et retardées (lags à différentes échelles) fournit au modèle MLR une vue plus riche du contexte de chaque point de données, améliorant ainsi sa capacité à effectuer des prédictions informées.
Pour compléter ce guide écrit, la vidéo ci-dessous offre une excellente introduction visuelle et pratique à l'implémentation de la Régression Linéaire Multiple en utilisant la bibliothèque scikit-learn en Python. Bien qu'elle ne traite pas spécifiquement de la prédiction de séries temporelles avec l'approche itérative que nous avons détaillée, elle couvre les concepts fondamentaux de la MLR, la préparation des données (sélection des features X et de la cible y), l'entraînement du modèle (fit) et la réalisation de prédictions (predict), qui sont toutes des étapes clés de notre processus.
Regarder cette vidéo peut vous aider à solidifier votre compréhension des mécanismes de base de la MLR et de son implémentation avec sklearn, rendant le code fourni dans ce guide plus facile à appréhender.
freq) dans pd.date_range.Pas nécessairement. La MLR est un modèle relativement simple qui peut bien fonctionner si les relations sont principalement linéaires et si les caractéristiques temporelles et retardées capturent bien la dynamique. Cependant, pour les séries temporelles complexes avec des non-linéarités, des saisonnalités multiples ou des dépendances à long terme, des modèles plus sophistiqués comme SARIMA, Prophet, ou des réseaux de neurones (LSTM, GRU, Transformers) peuvent offrir une meilleure précision. La MLR est un bon point de départ en raison de sa simplicité et de son interprétabilité.
Plusieurs pistes peuvent être explorées :
Vous devez ajuster deux parties principales du code :
La visualisation est utile, mais une évaluation quantitative est nécessaire. Vous devriez idéalement mettre de côté une partie de vos données historiques comme "ensemble de test" (par exemple, la dernière semaine ou les dernières semaines avant le '2023-10-31'). Entraînez le modèle sur les données précédant cet ensemble de test, puis faites des prédictions sur la période de test et comparez-les aux valeurs réelles en utilisant des métriques comme :
Où \(y_i\) est la valeur réelle et \(\hat{y}_i\) est la valeur prédite. Calculez ces métriques sur votre ensemble de test pour avoir une idée de la performance attendue sur les données futures.