Séries temporelles et Machine Learning : prévoir ventes, stocks et anomalies avec Python
Vous avez des historiques de ventes, des relevés de capteurs IoT ou des logs serveur horodatés. Vous voulez anticiper la demande, optimiser vos stocks ou repérer des comportements anormaux avant qu’ils ne deviennent des incidents. Les séries temporelles sont le terrain de jeu naturel du machine learning pour ces cas d’usage — et Python offre un écosystème mature pour les traiter. Cet article s’adresse aux data scientists et développeurs Python qui veulent aller au-delà d’ARIMA et exploiter des modèles ML modernes sur des données temporelles. Nous allons construire, pas à pas, trois pipelines fonctionnels : prévision de ventes avec Prophet et LightGBM, gestion de stocks par seuils dynamiques, et détection d’anomalies avec Isolation Forest. Chaque section livre du code testable, des résultats attendus et les pièges à éviter. Si vous avez déjà construit un modèle ML avec scikit-learn, vous avez les bases pour suivre.
Préparer et explorer une série temporelle avec Pandas
Avant de modéliser, il faut structurer les données. Une série temporelle bien préparée repose sur un index DatetimeIndex, une fréquence régulière et des valeurs manquantes traitées. Voici un pipeline de préparation complet.
Installez les dépendances du projet :
```bash pip install pandas numpy scikit-learn lightgbm prophet matplotlib ```
Chargez et préparez un jeu de données de ventes quotidiennes :
```python import pandas as pd import numpy as np # Charger les données (CSV avec colonnes ‘date’ et ‘sales’) df = pd.read_csv(“sales_data.csv”, parse_dates=[“date”]) df = df.set_index(“date”).sort_index() # Vérifier et imposer une fréquence quotidienne df = df.asfreq(“D”) # Traiter les valeurs manquantes par interpolation linéaire df[“sales”] = df[“sales”].interpolate(method=“linear”) # Décomposition pour visualiser tendance, saisonnalité et résidu from statsmodels.tsa.seasonal import seasonal_decompose decomposition = seasonal_decompose(df[“sales”], model=“additive”, period=7) decomposition.plot() ```
Sortie attendue : quatre graphiques superposés montrant la série originale, la tendance, la composante saisonnière hebdomadaire et le résidu. La saisonnalité révèle les patterns récurrents que nos modèles vont exploiter.
Les points clés de cette préparation :
asfreq("D")comble les trous dans l’index et rend la série régulière- L’interpolation linéaire est un bon défaut pour les ventes ; pour des capteurs, préférez
method="time" - Le paramètre
period=7correspond à la saisonnalité hebdomadaire — adaptez-le à vos données (30 pour mensuel, 365 pour annuel)
Prévoir les ventes : Prophet vs LightGBM
Deux approches dominent la prévision de séries temporelles en ML : les modèles dédiés comme Prophet et les modèles tabulaires alimentés par du feature engineering temporel. Comparons-les sur le même jeu de données.
Approche 1 : Prophet pour une baseline rapide
Prophet, développé par Meta, excelle sur les séries avec saisonnalités multiples et jours fériés. Il demande un DataFrame avec deux colonnes : ds (date) et y (valeur).
```python from prophet import Prophet # Préparer le format attendu par Prophet df_prophet = df.reset_index().rename(columns={“date”: “ds”, “sales”: “y”}) # Séparer train / test (80/20) split = int(len(df_prophet) * 0.8) train = df_prophet.iloc[:split] test = df_prophet.iloc[split:] # Entraîner le modèle model = Prophet( yearly_seasonality=True, weekly_seasonality=True, daily_seasonality=False, changepoint_prior_scale=0.05 # régularisation des changements de tendance ) model.fit(train) # Prédire sur la période de test future = model.make_future_dataframe(periods=len(test)) forecast = model.predict(future) # Évaluer from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error y_pred = forecast.iloc[split:][“yhat”].values y_true = test[“y”].values print(f”MAE : {mean_absolute_error(y_true, y_pred):.2f}”) print(f”MAPE : {mean_absolute_percentage_error(y_true, y_pred):.2%}”) ```
Sortie attendue (ordre de grandeur sur des ventes quotidiennes) :
```bash MAE : 142.37 MAPE : 8.23% ```
Approche 2 : LightGBM avec feature engineering temporel
Les modèles de gradient boosting comme LightGBM ne comprennent pas nativement le temps. Il faut transformer la date en features exploitables : lags, moyennes mobiles, indicateurs calendaires. C’est plus de travail, mais le gain en précision est souvent significatif.
```python import lightgbm as lgb from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error def create_time_features(df, target_col=“sales”, lags=[1, 7, 14, 28]): """Génère des features temporelles à partir d’une série.""" out = df.copy() # Features calendaires out[“day_of_week”] = out.index.dayofweek out[“month”] = out.index.month out[“day_of_year”] = out.index.dayofyear out[“is_weekend”] = (out.index.dayofweek >= 5).astype(int) # Lags for lag in lags: out[f”lag_{lag}”] = out[target_col].shift(lag) # Moyennes mobiles out[“rolling_7”] = out[target_col].shift(1).rolling(7).mean() out[“rolling_30”] = out[target_col].shift(1).rolling(30).mean() # Écart-type mobile (proxy de volatilité) out[“rolling_std_7”] = out[target_col].shift(1).rolling(7).std() return out.dropna() # Construire le dataset df_feat = create_time_features(df) # Split temporel (pas de shuffle — jamais sur des séries temporelles) split = int(len(df_feat) * 0.8) feature_cols = [c for c in df_feat.columns if c != “sales”] X_train = df_feat.iloc[:split][feature_cols] y_train = df_feat.iloc[:split][“sales”] X_test = df_feat.iloc[split:][feature_cols] y_test = df_feat.iloc[split:][“sales”] # Entraîner LightGBM model_lgb = lgb.LGBMRegressor( n_estimators=500, learning_rate=0.05, max_depth=6, num_leaves=31, subsample=0.8, colsample_bytree=0.8, random_state=42 ) model_lgb.fit( X_train, y_train, eval_set=[(X_test, y_test)], callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)] ) y_pred_lgb = model_lgb.predict(X_test) print(f”MAE : {mean_absolute_error(y_test, y_pred_lgb):.2f}”) print(f”MAPE : {mean_absolute_percentage_error(y_test, y_pred_lgb):.2%}”) ```
LightGBM surpasse généralement Prophet de 10 à 30 % en MAE sur des données avec des patterns complexes, au prix d’un feature engineering plus conséquent. L’importance des features (via model_lgb.feature_importances_) révèle souvent que lag_1, rolling_7 et day_of_week dominent.
Pour aller plus loin sur les fondamentaux du ML appliqué, consultez notre guide sur la prédiction du churn client qui utilise une méthodologie similaire de feature engineering.
Optimiser les stocks avec des seuils dynamiques
La prévision des ventes n’est que la moitié du problème. Pour la gestion des stocks, il faut traduire les prédictions en seuils de réapprovisionnement qui s’adaptent à la saisonnalité et à l’incertitude. L’idée : utiliser les intervalles de confiance du modèle pour calculer un stock de sécurité dynamique.
```python def compute_dynamic_safety_stock( predictions, actuals, lead_time_days=5, service_level=0.95 ): """ Calcule un stock de sécurité dynamique basé sur l’erreur de prévision. Args: predictions: prévisions du modèle (array) actuals: valeurs réelles (array) lead_time_days: délai de réapprovisionnement en jours service_level: niveau de service cible (0.95 = 95%) """ from scipy.stats import norm # Erreur de prévision (écart-type sur fenêtre glissante) errors = actuals - predictions forecast_std = pd.Series(errors).rolling(30, min_periods=7).std().values # Facteur z pour le niveau de service z = norm.ppf(service_level) # Stock de sécurité = z * sigma * sqrt(lead_time) safety_stock = z * forecast_std * np.sqrt(lead_time_days) # Point de commande = prévision sur le lead_time + stock de sécurité reorder_point = predictions * lead_time_days + safety_stock return pd.DataFrame({ “prediction”: predictions, “safety_stock”: safety_stock, “reorder_point”: reorder_point }) stock_df = compute_dynamic_safety_stock(y_pred_lgb, y_test.values) print(stock_df.describe().round(1)) ```
Ce calcul s’inspire de la formule classique de stock de sécurité, mais avec un forecast_std calculé dynamiquement sur les erreurs récentes du modèle ML. Quand le modèle est moins précis (lancement produit, promotions), le stock de sécurité augmente automatiquement. Cette approche est directement applicable dans des contextes industriels — comme l’anticipation des retards de chantier ou l’adaptation de la planification en temps réel.
Détecter les anomalies avec Isolation Forest
Les anomalies dans une série temporelle peuvent signaler des fraudes, des pannes matérielles ou des erreurs de saisie. Isolation Forest est particulièrement adapté : il isole les points aberrants sans faire d’hypothèse sur la distribution des données.
La clé est de ne pas appliquer Isolation Forest directement sur les valeurs brutes, mais sur des features dérivées qui capturent le comportement “normal” de la série.
```python from sklearn.ensemble import IsolationForest def detect_anomalies(df, target_col=“sales”, contamination=0.05): """ Détecte les anomalies dans une série temporelle via Isolation Forest. Args: df: DataFrame avec DatetimeIndex target_col: colonne cible contamination: proportion estimée d’anomalies (5% par défaut) """ feat = pd.DataFrame(index=df.index) # Écart par rapport à la moyenne mobile rolling_mean = df[target_col].rolling(7, center=True).mean() feat[“deviation”] = df[target_col] - rolling_mean # Écart par rapport à la médiane du même jour de la semaine weekly_median = df.groupby(df.index.dayofweek)[target_col].transform(“median”) feat[“weekly_deviation”] = df[target_col] - weekly_median # Variation jour à jour feat[“day_change”] = df[target_col].pct_change() # Z-score glissant rolling_std = df[target_col].rolling(14).std() feat[“z_score”] = (df[target_col] - rolling_mean) / rolling_std feat = feat.dropna() # Isolation Forest iso = IsolationForest( n_estimators=200, contamination=contamination, random_state=42 ) feat[“anomaly”] = iso.fit_predict(feat) feat[“anomaly_score”] = iso.decision_function(feat) # -1 = anomalie, 1 = normal n_anomalies = (feat[“anomaly”] == -1).sum() print(f”Anomalies détectées : {n_anomalies} / {len(feat)} ({n_anomalies/len(feat):.1%})”) return feat anomalies = detect_anomalies(df) # Afficher les 10 anomalies les plus extrêmes top_anomalies = anomalies[anomalies[“anomaly”] == -1].nsmallest(10, “anomaly_score”) print(top_anomalies[[“deviation”, “z_score”, “anomaly_score”]]) ```
Sortie attendue :
```bash Anomalies détectées : 18 / 335 (5.4%) deviation z_score anomaly_score date 2024-11-29 -892.3 -3.21 -0.187 2024-12-25 -1203.7 -4.02 -0.211 2024-03-15 734.1 2.87 -0.163 … ```
Les anomalies détectées correspondent typiquement aux jours fériés, promotions exceptionnelles ou erreurs de données. Pour un cas d’usage complet en contexte métier, notre article sur la détection automatique des anomalies d’inventaire détaille l’intégration en production.
Industrialiser le pipeline : de l’expérimentation à la production
Un notebook qui tourne en local n’est pas un système de prévision. Pour passer en production, trois éléments sont indispensables.
Validation temporelle rigoureuse
Ne jamais utiliser train_test_split avec shuffle=True sur des séries temporelles. Cela crée une fuite temporelle (data leakage) : le modèle voit le futur pendant l’entraînement. Utilisez plutôt TimeSeriesSplit de scikit-learn :
```python from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) scores = [] for train_idx, test_idx in tscv.split(X_train): X_tr, X_te = X_train.iloc[train_idx], X_train.iloc[test_idx] y_tr, y_te = y_train.iloc[train_idx], y_train.iloc[test_idx] model_lgb.fit(X_tr, y_tr) pred = model_lgb.predict(X_te) scores.append(mean_absolute_error(y_te, pred)) print(f”MAE moyen (CV temporelle) : {np.mean(scores):.2f} +/- {np.std(scores):.2f}”) ```
Monitoring et réentraînement
Les séries temporelles dérivent (concept drift). Un modèle entraîné sur les données de 2024 perd en précision sur 2025. Mettez en place :
- Un suivi du MAPE hebdomadaire en production — si la métrique dépasse un seuil, déclencher un réentraînement
- Un pipeline CI/CD pour automatiser les tests et le déploiement des nouvelles versions du modèle. Notre guide sur le CI/CD pour le machine learning couvre exactement cette problématique
- Un versionning des données et des modèles avec
MLflowouDVC
Structurer la donnée en amont
La qualité des prévisions dépend directement de la qualité des données sources. Si vos historiques sont éparpillés dans des fichiers Excel, la priorité est de moderniser votre patrimoine data vers un data warehouse structuré avant de déployer du ML.
Les solutions de data science de Flowt couvrent l’ensemble de cette chaîne, de la consolidation des données au déploiement de modèles prédictifs en production.
Conclusion
Les séries temporelles en machine learning avec Python offrent un levier concret pour anticiper la demande, piloter les stocks et détecter les anomalies. Nous avons vu comment préparer les données avec Pandas, comparer Prophet et LightGBM pour la prévision, calculer des seuils de stock dynamiques et isoler les points aberrants avec Isolation Forest. La clé du passage en production reste la validation temporelle rigoureuse et le monitoring du drift. Si vous souhaitez déployer des modèles prédictifs sur vos données métier, les équipes data science de Flowt peuvent vous accompagner du prototypage à la mise en production. Demandez un audit IA gratuit pour évaluer le potentiel prédictif de vos séries temporelles.