Une série temporelle est une suite de mesures prises dans le temps : ventes quotidiennes, température horaire, nombre de connexions par minute. Ce qui la distingue d’un tableau classique, c’est que l’ordre des lignes porte de l’information. On ne mélange jamais les lignes, et l’on se demande toujours : quelle est la fréquence ? Y a-t-il des trous ?
Construire un DatetimeIndex
La première étape consiste à convertir la colonne de dates et à la placer en index :
import numpy as np
import pandas as pd
rng = np.random.default_rng(0)
df = pd.DataFrame({
"date": pd.date_range("2024-01-01", periods=90, freq="D").astype(str),
"ventes": rng.integers(50, 150, size=90),
})
df["date"] = pd.to_datetime(df["date"]) # texte -> datetime64
df = df.set_index("date").sort_index() # index temporel trié
print(df.loc["2024-02"].head()) # tout le mois de février
print(df.loc["2024-01-10":"2024-01-15"]) # une plage de dates (bornes incluses)
Avec un DatetimeIndex, vous accédez aussi à des attributs utiles : df.index.day_of_week (0 = lundi ; l’ancien nom dayofweek est déprécié depuis pandas 3.1), df.index.month, df.index.year.
Changer de fréquence avec resample
resample regroupe les observations par période, un peu comme un groupby sur le temps. Il faut toujours préciser l’agrégation :
mensuel = df["ventes"].resample("ME").sum() # total par fin de mois
hebdo_moy = df["ventes"].resample("W").mean() # moyenne par semaine
Choisissez l’agrégation selon la nature de la mesure : on somme des volumes (ventes, visites) et on moyenne des niveaux (température, stock moyen). Depuis pandas 2.2, l’alias de fin de mois est "ME" ("MS" pour le début de mois) ; l’ancien "M" n’est plus accepté dans pandas 3.
Dans l’autre sens, passer à une fréquence plus fine (suréchantillonnage) crée des trous qu’il faut combler, par exemple avec .ffill() ou .interpolate().
Fenêtres glissantes et décalages
Une moyenne mobile lisse le bruit pour révéler la tendance :
df["moy_7j"] = df["ventes"].rolling(window=7).mean() # NaN sur les 6 premières lignes
df["lag_1"] = df["ventes"].shift(1) # valeur de la veille
df["var_j"] = df["ventes"].diff() # ventes(t) - ventes(t-1)
df["var_pct"] = df["ventes"].pct_change() # variation relative
rolling(7)regarde les 7 dernières valeurs, la valeur courante incluse. Les premières lignes valentNaNtant que la fenêtre n’est pas complète (paramètremin_periodspour assouplir).shift(1)décale vers le bas : la ligne du 2 janvier reçoit la valeur du 1er janvier. C’est la brique de base des lags que nous utiliserons pour la prévision.diff()est équivalent àserie - serie.shift(1).
Repérer les trous
Une série « propre » a une fréquence régulière. Pour vérifier :
complet = pd.date_range(df.index.min(), df.index.max(), freq="D")
manquants = complet.difference(df.index)
df = df.reindex(complet) # les jours absents apparaissent en NaN
Rendre les trous explicites est préférable à les ignorer : un jour sans vente (0) n’est pas la même chose qu’un jour sans donnée (NaN).
À retenir
Index temporel trié, fréquence connue, agrégation adaptée, et prudence avec les fenêtres : ces quatre réflexes évitent la majorité des erreurs dans la suite du cours.
Exercices
Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.
Ventes mensuelles
Écrivez la fonction
ventes_mensuelles(df)qui reçoit un DataFrame indexé par date (fréquence journalière) avec une colonneventes, et renvoie une Series des ventes totales par mois (alias de fin de mois"ME").import numpy as np import pandas as pd rng = np.random.default_rng(42) idx = pd.date_range("2024-01-01", periods=90, freq="D") df = pd.DataFrame({"ventes": rng.integers(50, 150, size=90)}, index=idx) def ventes_mensuelles(df): # TODO : rééchantillonner par mois et sommer passMoyenne mobile, lag et variation
Écrivez
ajouter_indicateurs(df)qui renvoie une copie du DataFrame avec trois nouvelles colonnes :moy_7j: moyenne mobile des ventes sur 7 jours (fenêtre complète obligatoire) ;lag_1: les ventes de la veille ;var_j: la différence ventes(t) - ventes(t-1).
import numpy as np import pandas as pd rng = np.random.default_rng(7) idx = pd.date_range("2024-03-01", periods=30, freq="D") df = pd.DataFrame({"ventes": rng.normal(100, 10, 30).round(1)}, index=idx) def ajouter_indicateurs(df): out = df.copy() # TODO return outResample ou rolling ?
Vous voulez un tableau avec une ligne par semaine contenant le total des ventes. Quelle instruction utilisez-vous ?
- df['ventes'].rolling(7).sum()
- df['ventes'].resample('W').sum()
- df['ventes'].shift(7)
- df['ventes'].diff(7)
Quiz de fin de leçon
Que contient la ligne du 10 mars après df[‘ventes’].shift(1) ?
- Les ventes du 11 mars
- Les ventes du 9 mars
- La moyenne des ventes jusqu'au 10 mars
- NaN
Pourquoi les 6 premières valeurs de rolling(7).mean() sont-elles NaN ?
- Parce que pandas ignore le début des séries
- Parce que la fenêtre de 7 observations n'est pas encore complète
- Parce que l'index n'est pas trié
- Parce qu'il faut utiliser resample avant
Pour passer des températures horaires à une valeur par jour, quelle agrégation est la plus pertinente ?
- sum
- mean
- count
- first
La suite du cours
- Tendance, saisonnalité et stationnarité18 min
- Baselines et évaluation d'une prévision15 min
- Modèles classiques : lissage exponentiel et ARIMA20 min
- Prévoir avec le machine learning et faire un backtest20 min