Une méthode qui sépare une série en trois morceaux : la tendance de fond, le cycle qui se répète et le reste. On voit alors ce que le cycle masquait : un changement de niveau, une dérive, des pics isolés. STL (Seasonal-Trend decomposition using Loess) est robuste aux valeurs aberrantes et s'applique à n'importe quelle période : jour, semaine, année.
Une photo de groupe où l'on retire successivement le décor et la foule pour ne garder que la personne qui fait une grimace. La tendance est le décor, le cycle la foule, le reste la grimace.
Pour chaque position du cycle (chaque heure de la journée, par exemple), on lisse les valeurs observées d'un cycle à l'autre. On obtient le profil saisonnier.
Sur la série privée de son cycle, un lissage local (loess) dégage le niveau de fond. On répète ces deux étapes jusqu'à stabilisation.
Série = tendance + saison + reste. Le reste doit ressembler à du bruit ; ce qui en dépasse nettement est une anomalie. En version robuste, les points aberrants sont repérés et n'influencent plus la tendance ni la saison.
Une mesure par heure, avec un cycle quotidien marqué qui masque le reste : quelques pics anormaux, et un changement de niveau fin février qu'on devine mal sur la courbe brute.
La tendance fait apparaître le saut de niveau du 28 février, net une fois le cycle retiré. Les pics isolés ressortent dans le reste et sont listés avec leur horodatage. La force de la saisonnalité chiffre le poids du cycle quotidien.
Pas de métrique de précision : on vérifie que le reste ressemble à du bruit, sans cycle ni tendance résiduels. Les anomalies se jugent ensuite avec les équipes de maintenance : capteur défaillant ou vrai incident.
Noms donnés pour R (stl, base) et Python (statsmodels).
La longueur du cycle, en nombre de points : 24 pour un cycle quotidien en données horaires, 7 pour une semaine en journalier, 12 pour une année en mensuel. En R, elle se déclare dans ts().
La souplesse du profil saisonnier. En R, "periodic" impose un cycle identique sur toute la série. En Python, seasonal (7 par défaut, impair) fixe sur combien de cycles le profil est lissé : plus grand, plus stable.
La souplesse de la tendance. Plus la fenêtre est courte, plus la tendance suit les changements rapides, au risque d'absorber une partie du bruit. La valeur par défaut convient le plus souvent.
À activer dès que la série contient des pics ou des erreurs : les points aberrants reçoivent un poids faible et se retrouvent dans le reste, au lieu de déformer la tendance et la saison.
# Surveillance machine : décomposition STL en R
capteurs <- read.csv("capteurs_machine.csv")
temp <- ts(capteurs$temperature, frequency = 24) # cycle de 24 heures
# robust = TRUE : les pics isolés ne déforment ni la tendance ni la saison
dec <- stl(temp, s.window = "periodic", robust = TRUE)
comp <- dec$time.series
# Force de la saisonnalité : part de la variation expliquée par le cycle (0 à 1)
force <- max(0, 1 - var(comp[, "remainder"]) / var(comp[, "seasonal"] + comp[, "remainder"]))
cat("Force de la saisonnalité :", round(force, 2), "\n")
# Tendance moyenne par jour autour du 28 février : le saut de niveau apparaît sans le cycle
jours <- substr(capteurs$horodatage, 1, 10)
tendance <- tapply(as.numeric(comp[, "trend"]), jours, mean)
print(round(tendance[names(tendance) >= "2025-02-24" & names(tendance) <= "2025-03-04"], 1))
# Anomalies : restes au-delà de 6 écarts-types robustes (mad)
reste <- as.numeric(comp[, "remainder"])
anomalies <- which(abs(reste) > 6 * mad(reste))
cat(length(anomalies), "heures anormales\n")
print(data.frame(horodatage = capteurs$horodatage[anomalies], reste = round(reste[anomalies], 1)))
# Surveillance machine : décomposition STL en Python
import numpy as np
import pandas as pd
from statsmodels.tsa.seasonal import STL
capteurs = pd.read_csv("capteurs_machine.csv", parse_dates=["horodatage"], index_col="horodatage")
temp = capteurs["temperature"]
# Cycle de 24 heures ; robust=True : les pics isolés ne déforment ni la tendance ni la saison
dec = STL(temp, period=24, robust=True).fit()
# Force de la saisonnalité : part de la variation expliquée par le cycle (0 à 1)
force = max(0, 1 - dec.resid.var() / (dec.seasonal + dec.resid).var())
print("Force de la saisonnalité :", round(force, 2))
# Tendance moyenne par jour autour du 28 février : le saut de niveau apparaît sans le cycle
print(dec.trend.resample("D").mean().loc["2025-02-24":"2025-03-04"].round(1))
# Anomalies : restes au-delà de 6 écarts-types robustes (MAD)
reste = dec.resid
mad = 1.4826 * np.median(np.abs(reste - np.median(reste)))
anomalies = reste[np.abs(reste) > 6 * mad]
print(len(anomalies), "heures anormales")
print(anomalies.round(1))
Seasonal and Trend decomposition using Loess : décomposition en saison et tendance par régression locale (loess). La méthode a été publiée en 1990 par Cleveland et ses coauteurs, et elle est disponible en standard dans R.
La décomposition classique utilise des moyennes mobiles et impose un profil saisonnier identique chaque année. STL laisse la saison évoluer lentement, accepte n'importe quelle période et possède une version robuste aux valeurs aberrantes.
On retire la composante saisonnière de la série : série désaisonnalisée = série - saison. Il reste la tendance et le reste, ce qui permet de comparer des mois ou des heures différents sans l'effet du cycle.
Trouve les instants où le niveau ou la variance change. Complète STL, dont la tendance lisse les marches.
Voir la fiche → la décomposition qui prévoitDécompose aussi en tendance et saisons, puis prolonge chaque composante pour prévoir.
Voir la fiche → la décomposition du pauvreUne moyenne mobile sur un cycle complet donne une tendance. Plus simple, mais sensible aux pics et sans profil saisonnier.
Voir la fiche →Dataistudio forme les équipes au machine learning et à l'IA, sur des cas concrets.
Nous utilisons des cookies de mesure d'audience et de suivi publicitaire pour comprendre la fréquentation du site et l'efficacité de nos annonces. Rien n'est déposé sans votre accord. En savoir plus