← Parcours
📈

Séries temporelles

Intermédiaire
🧠Comprendre en lisant n'est pas savoir écrire.🧠Un module est fini quand tu peux le reproduire fenêtre fermée.🧠Écris le problème en français avant d'écrire du code.🧠Une question impossible cache trois questions faciles.🧠Retape le code à la main. Puis referme et réécris-le.🧠Essaie d'abord, cherche ensuite.🧠Cherche « comment on écrit ça », jamais « qu'est-ce que je dois faire ».🧠Pour comprendre du code, casse-le.⌨️Une fonction qui plante sur un cas limite n'est pas finie.⌨️Si un bloc a un nom clair, il mérite d'être une fonction.⌨️Savoir lire une erreur vaut plus que connaître dix méthodes.⌨️Diagnostiquer, c'est éliminer dans l'ordre — pas essayer au hasard.⌨️Une optimisation sans mesure avant/après est une croyance.🔍Regarde le fichier avant de le charger.🔍Une performance anormalement bonne est une hypothèse à réfuter.🔍Cette valeur existe-t-elle à l'instant où je dois prédire ?🔍fit sur le train, transform partout.🔍Un chiffre qu'on ne sait pas raconter est un chiffre qu'on n'a pas compris.🔍Un graphique sans question n'a rien à dire.🤖Un score d'entraînement parfait n'est jamais une bonne nouvelle.🤖Le jeu de test ne se regarde qu'une fois.🤖Plus tu essaies de configurations, plus ton meilleur score est optimiste.🤖Un seuil de décision est un arbitrage économique, pas un réglage technique.🤖Commence toujours par une baseline. Note son score.🤖Le meilleur modèle n'est pas le plus précis, c'est celui qu'on peut maintenir.🚀Un modèle en production n'est pas un livrable, c'est un système vivant.🚀Un secret publié est un secret compromis.🚀Pour chaque permission : que se passe-t-il si cette machine est compromise ?🚀Un post-mortem qui cherche un coupable ne produit aucune amélioration.🚀« Nous n'utilisons pas cette variable » n'est pas une garantie d'équité.🚀Rends l'arbitrage explicite et chiffré. Ne le tranche pas en silence.🚀Un résultat qu'on ne sait pas reproduire est une anecdote.

🎯 Objectifs d'apprentissage

À l'issue de ce module, tu seras capable de :

  • 01Tester et obtenir la stationnarité d'une série
  • 02Ajuster un modèle SARIMA et valider ses résidus
  • 03Modéliser saisonnalités multiples et jours fériés avec Prophet
  • 04Valider un modèle temporel par validation croisée glissante

Prérequis

Modules Mathématiques et ML classique

Concepts fondamentaux des séries temporelles

50 min

Une série temporelle est une séquence de valeurs indexées dans le temps. Ce qui la distingue d'un dataset classique : l'ordre temporel est une information en soi, et les observations ne sont pas indépendantes entre elles.

Composantes d'une série temporelle
| Composante | Description | Exemple |

|--|--|--|

| Tendance (Trend) | Direction à long terme | Chiffre d'affaires croissant |

| Saisonnalité (Seasonality) | Cycle répétitif à période fixe | Pics de ventes en décembre |

| Résidus (Residuals) | Bruit aléatoire | Fluctuations imprévisibles |

| Cyclicité | Oscillations à période variable | Cycles économiques |

Décomposition avec statsmodels

import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose

# Charger et indexer
df = pd.read_csv("ventes.csv", parse_dates=["date"], index_col="date")
df = df.asfreq("MS")  # fréquence mensuelle, début de mois

# Décomposition additive (ou multiplicative si la saisonnalité amplifie la tendance)
decomp = seasonal_decompose(df["ventes"], model="additive")
decomp.plot()
plt.tight_layout()
plt.show()

# Accéder aux composantes
trend    = decomp.trend
seasonal = decomp.seasonal
residual = decomp.resid

Stationnarité — pourquoi c'est crucial

Un processus est stationnaire si sa moyenne, sa variance et son autocovariance ne changent pas dans le temps. La plupart des modèles statistiques (ARIMA) supposent la stationnarité.

Test de Dickey-Fuller augmenté (ADF) :

from statsmodels.tsa.stattools import adfuller

result = adfuller(df["ventes"])
print(f"ADF Statistic: {result[0]:.4f}")
print(f"p-value: {result[1]:.4f}")
# p < 0.05 → la série est stationnaire (on rejette H0 de racine unitaire)

# Si non-stationnaire → différencier
df["ventes_diff"] = df["ventes"].diff().dropna()
adfuller(df["ventes_diff"])  # re-tester après différenciation

Autocorrélation — la mémoire de la série

from statsmodels.graphics.tsaplots import plot_acf, plot_pacf

fig, axes = plt.subplots(1, 2, figsize=(14, 4))
plot_acf(df["ventes"].dropna(), lags=36, ax=axes[0])
plot_pacf(df["ventes"].dropna(), lags=36, ax=axes[1])
plt.tight_layout()
plt.show()
# ACF : corrélation avec les lags 1, 2, ..., k
# PACF : corrélation "pure" avec lag k, en contrôlant les lags intermédiaires

Validation temporelle — la règle absolue

En séries temporelles, on ne shuffle jamais les données avant de splitter :

# ✅ Correct : split chronologique
n = len(df)
train = df.iloc[:int(n * 0.8)]
test  = df.iloc[int(n * 0.8):]

# ❌ Interdit : shuffle aléatoire (data leakage temporel)
# from sklearn.model_selection import train_test_split
# train, test = train_test_split(df, test_size=0.2)  → FAUX

Walk-forward validation (Time Series Cross-Validation)

from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)
for fold, (train_idx, val_idx) in enumerate(tscv.split(df)):
    X_train, X_val = df.iloc[train_idx], df.iloc[val_idx]
    # Entraîner sur train → évaluer sur val
    # Chaque fold agrandit la fenêtre d'entraînement
    print(f"Fold {fold+1}: train={len(X_train)}, val={len(X_val)}")

Métriques adaptées aux séries temporelles

from sklearn.metrics import mean_absolute_error, mean_squared_error
import numpy as np

y_true = test["ventes"].values
y_pred = model_predictions

mae  = mean_absolute_error(y_true, y_pred)
rmse = np.sqrt(mean_squared_error(y_true, y_pred))
mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100  # % d'erreur

print(f"MAE : {mae:.2f}")
print(f"RMSE : {rmse:.2f}")
print(f"MAPE : {mape:.2f}%")

✍️ Exercices de la leçon

2 exercices

Fais-les dans l'ordre, dans un vrai fichier .py — pas dans ta tête. Ne déplie la correction qu'après avoir écrit quelque chose, même faux.

🔧

Exercice ADécomposer et tester la stationnarité

Application directe · Tu appliques ce que tu viens de lire.

import numpy as np, pandas as pd
dates = pd.date_range("2019-01-01", periods=72, freq="MS")
tendance = np.linspace(100, 180, 72)
saison   = 25 * np.sin(2 * np.pi * dates.month / 12)
bruit    = np.random.default_rng(0).normal(0, 6, 72)
serie    = pd.Series(tendance + saison + bruit, index=dates, name="ventes")
  1. 1.décompose la série en tendance, saisonnalité et résidus, et affiche les 4 panneaux
  2. 2.teste la stationnarité avec le test de Dickey-Fuller augmenté (ADF)
  3. 3.applique une différenciation d'ordre 1, reteste
  4. 4.interprète les deux p-values en une phrase chacune
  5. 5.dis si tu choisirais un modèle additif ou multiplicatif, et pourquoi
🎯

Exercice BLe découpage qui triche avec le futur

Page blanche · Aucun squelette : à toi de choisir la méthode.

Page blanche. Démonstration à construire.

Un collègue évalue son modèle de prévision de ventes avec :

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

Il obtient un excellent score. Tu sais que c'est faux — mais l'affirmer ne suffit pas.

Construis l'expérience qui le prouve :
1. entraîne le même modèle avec un découpage aléatoire, puis avec un découpage chronologique

2. compare les deux scores et chiffre l'écart

3. explique par quel mécanisme précis l'aléatoire triche

4. montre comment TimeSeriesSplit fonctionne, schéma ou affichage des indices à l'appui

5. écris la règle en une phrase

Attention : le problème n'est pas seulement « le test contient du passé ». Il est plus subtil. Cherche ce qui relie deux points voisins dans le temps.

🤖

Un point pas clair ? Clique sur 💬 Demander au tuteur en bas à droite.