Module 7 — Variables issues d'agrégations et de fenêtres temporelles
Les modules précédents transformaient une ligne à la fois. Ici, on construit de l'information en regardant plusieurs lignes ensemble : l'historique d'un client, la moyenne d'une catégorie, la tendance des sept derniers jours. C'est le domaine où l'ingénierie des variables produit ses plus gros gains — et où la fuite de données guette à chaque étape.
Agréger par groupe : résumer un historique
Le cas typique : une table de transactions, plusieurs lignes par client, et un modèle qui prédit au niveau du client. Il faut résumer l'historique en variables.
agg = df.groupby("client_id").agg(
nb_achats = ("montant", "count"),
montant_moyen = ("montant", "mean"),
montant_median = ("montant", "median"),
montant_max = ("montant", "max"),
montant_ecart = ("montant", "std"),
nb_categories = ("categorie", "nunique"),
)
Chaque fonction d'agrégation capte un aspect distinct du comportement. Le compte mesure l'intensité de la relation, la moyenne le niveau habituel, la médiane ce même niveau en résistant aux extrêmes, le maximum la capacité de pointe, l'écart-type la régularité — un client au panier stable ne se comporte pas comme un client erratique de même moyenne. Le nombre de valeurs distinctes mesure la diversité, souvent très prédictive de la fidélité.
L'écart au comportement habituel : la variable qui change tout
Voici le type de variable évoqué au module 1, et il mérite d'être construit systématiquement. Une transaction de 500 euros n'a aucun sens dans l'absolu : elle est banale pour un client qui dépense 600 euros en moyenne, et hautement suspecte pour un client habituellement à 30 euros.
L'information n'est donc pas le montant, mais l'écart entre le montant et l'habitude :
df["moyenne_client"] = df.groupby("client_id")["montant"].transform("mean")
df["ecart_type_client"] = df.groupby("client_id")["montant"].transform("std")
df["ecart_relatif"] = (df["montant"] - df["moyenne_client"]) / df["ecart_type_client"]
Cette dernière variable est un score z calculé par client plutôt que sur la population. C'est un modèle de raisonnement à réutiliser largement : rapporter une valeur à sa référence pertinente — l'historique du client, la moyenne de sa catégorie de produit, la normale de la saison. Beaucoup de gains de performance viennent de là, et non d'un algorithme plus sophistiqué.
Notez que transform est ici préférable à agg : il renvoie une valeur par ligne d'origine et se joint donc naturellement, sans fusion manuelle.
Fenêtres glissantes et décalages : la règle d'or
Sur des données ordonnées dans le temps, on veut résumer le passé récent : moyenne des sept derniers jours, somme du mois écoulé, tendance. Ces variables sont puissantes et représentent la principale source de fuite de ce cours.
La règle est absolue : une fenêtre ne doit contenir que du passé strict. Une moyenne glissante centrée, ou une fenêtre incluant la ligne courante, utilise la valeur qu'on cherche à prédire.
df = df.sort_values(["client_id", "date"])
# Valeur precedente : le passe, sans ambiguite
df["montant_precedent"] = df.groupby("client_id")["montant"].shift(1)
# Moyenne des 7 lignes precedentes : shift(1) AVANT rolling
df["moyenne_7"] = (
df.groupby("client_id")["montant"]
.transform(lambda s: s.shift(1).rolling(7, min_periods=1).mean())
)
Le shift(1) placé avant le rolling est le détail qui décide de la validité de tout le modèle. Sans lui, la fenêtre inclut la ligne courante : la variable contient une part de la cible, le score d'entraînement devient spectaculaire, et la production s'effondre. C'est l'erreur la plus fréquente sur données temporelles.
Une fois ces bases posées, les variables dérivées s'enchaînent : le rapport entre la moyenne des 7 derniers jours et celle des 30 derniers mesure une accélération ; la différence avec la valeur précédente mesure une variation ; le nombre de jours depuis le dernier événement mesure une récence au niveau de la ligne.
Agréger par catégorie plutôt que par individu
L'agrégation ne concerne pas que les individus. Résumer par catégorie de produit, par région, par jour de la semaine fournit une référence à laquelle rapporter chaque observation : ce produit est-il cher pour sa catégorie ? Ce magasin performe-t-il au-dessus de sa région ?
Attention toutefois : agréger la cible par catégorie, c'est faire l'encodage par la cible du module 4, avec la même exigence de lissage et de calcul hors échantillon. Agréger d'autres variables que la cible ne pose pas ce problème.
D'abord, l'ordre du tri : sans sort_values sur la date, shift et rolling produisent silencieusement n'importe quoi. Ensuite, le groupe : oublier le groupby mélange l'historique de plusieurs clients. Enfin, la disponibilité : cette agrégation sera-t-elle calculable en production, au moment de prédire, avec les seules données déjà connues ? Une variable qui exige une table complète recalculée chaque nuit n'a pas le même coût qu'un simple shift.
En résumé
- L'agrégation par groupe résume un historique ; compte, moyenne, médiane, maximum, écart-type et nombre de valeurs distinctes captent chacun un aspect du comportement.
- L'écart au comportement habituel (score z par individu) est souvent plus prédictif que la valeur brute : rapporter une valeur à sa référence pertinente est un réflexe à généraliser.
- Une fenêtre glissante ne doit contenir que du passé strict :
shift(1)avantrolling, sous peine de fuite massive. - Agréger par catégorie fournit des références utiles, mais agréger la cible relève de l'encodage par la cible et de ses précautions.
Module suivant : la fuite de données, ses formes les plus fréquentes et les signaux qui doivent alerter.