← Blog

XGBoost pour la prévision de demande industrielle

Pourquoi XGBoost bat ARIMA et Prophet sur les séries temporelles industrielles irrégulières.

Le chef de prod qui avait raison, et les chiffres qui avaient tort

C’est une conversation que j’ai eue dans une ETI de décolletage de 180 personnes, en Haute-Savoie. Le directeur supply chain avait investi dans un ERP à 400K€ avec un module de prévision. Le modèle tournait depuis deux ans. L’équipe l’utilisait comme référence pour les ordres de fabrication.

Le chef de production, lui, continuait à planifier à l’instinct. Il avait ses carnets. Ses annotations. Sa connaissance des clients qui commandent gros en septembre avant l’arrêt d’août, du commercial qui “sécurise” en ajoutant 20 % à chaque commande prévisionnelle, de l’acheteur Tier 1 qui décale toujours sa commande trimestrielle de trois semaines.

On a backtesté les deux approches sur 18 mois. Le modèle ERP avait un MAPE de 34 %. Le chef de prod, recomposé à partir de ses décisions réelles, était à 19 %.

Le problème n’était pas que le modèle ERP était mauvais. C’est qu’il ignorait toutes les structures causales que le chef de prod avait intégrées inconsciemment depuis quinze ans sur ce marché.

Cet article explique pourquoi les approches statistiques classiques (ARIMA, Prophet) ont des limites structurelles sur les séries industrielles irrégulières, pourquoi XGBoost change la donne, et comment le déployer correctement — sans raccourcis.

Pourquoi les séries industrielles cassent ARIMA et Prophet

ARIMA et Prophet sont de bons modèles. Il faut être honnête là-dessus. Sur des séries régulières, à saisonnalité marquée, avec peu de ruptures structurelles, ils font très bien le travail. Le problème, c’est que les séries industrielles ne ressemblent pas à ça.

La demande industrielle est structurellement irrégulière

Une PME de sous-traitance automobile qui livre 12 clients a une demande agrégée cabossée : quelques gros clients qui commandent par lots, des délais client qui varient, des révisions de plannings en cours de mois, des arrêts techniques qui décalent les besoins. La série hebdomadaire ressemble moins à une sinusoïde qu’à une suite de créneaux avec du bruit.

ARIMA fait une hypothèse forte : la série est stationnaire (ou le devient après différenciation). Elle suppose que les structures de dépendance temporelle sont stables. Une rupture de contrat, un nouveau client, un changement de process — tout ça viole l’hypothèse de stationnarité. ARIMA ne sait pas le gérer proprement.

Prophet fait mieux sur la saisonnalité multiple (journalière, hebdomadaire, annuelle) et intègre nativement les jours fériés. Mais il suppose une structure additive ou multiplicative qu’on impose au modèle. Si la réalité est non-linéaire — par exemple, la demande explose de façon disproportionnée dès que le carnet de commandes client dépasse un seuil — Prophet ne le capture pas.

Les variables exogènes font toute la différence

Le vrai problème des approches statistiques classiques : elles modélisent la demande comme une série univariée. Elles ignorent les variables causales.

Sur le terrain, la demande industrielle dépend de choses qui n’ont rien à voir avec le passé de la série elle-même : cours des matières premières (si acier monte, le client anticipe), carnets de commandes client visibles (un client B2B vous informe souvent de son planning trimestriel), saisonnalité spécifique secteur (automobile : shutdown juillet-août, forte reprise septembre), conditions météo (BTP, agroalimentaire).

Intégrer ces variables exogènes dans ARIMA (ARIMAX) est techniquement possible, mais la complexité de modélisation explose rapidement. XGBoost les digère nativement, sans effort.

Les petits volumes violent les lois des grands nombres

Un article en fin de gamme, un composant lié à un seul client, une référence en phase de croissance : la série de demande mensuelle tient en 24 points. ARIMA a besoin d’au moins 50 points pour être stable. Sur 24 points, l’intervalle de confiance est si large qu’il est inutilisable opérationnellement.

XGBoost, appliqué sur l’ensemble du catalogue en panel (toutes les références simultanément), partage les patterns entre références et tire des inférences robustes même sur peu de données par SKU.

Le pipeline de prévision XGBoost

Voici l’architecture que nous utilisons sur les projets client.

flowchart TD
    A[Données ERP brutes\nHistorique commandes, sorties stock] --> B[Nettoyage & détection anomalies\nRuptures, retours, doublons]
    B --> C[Feature Engineering\nLags · Rolling stats · Calendrier · Exogènes]
    C --> D[Walk-forward split\nTrain / Val / Test sans data leakage]
    D --> E[Entraînement XGBoost\nOptuna hyperparameter search]
    E --> F[Évaluation MAPE / RMSE / Biais]
    F --> G{Métriques OK ?}
    G -- Non --> H[Itération features\nou segment différent]
    H --> C
    G -- Oui --> I[Prévision en production\nBatch hebdomadaire]
    I --> J[Monitoring dérive\nMAE rolling 4 semaines]
    J --> K{Dérive détectée ?}
    K -- Oui --> L[Ré-entraînement\nou alerte manuelle]
    K -- Non --> I

Feature engineering — c’est là que tout se joue

Le feature engineering est la partie la plus importante, et la plus sous-estimée. Un XGBoost avec de bonnes features bat un LSTM avec des features pauvres dans 80 % des cas industriels.

Lag features

La base. On prend la demande à différents horizons passés et on les donne comme variables d’entrée :

  • demand_lag_1 — demande la semaine précédente
  • demand_lag_2, demand_lag_4 — deux semaines, quatre semaines
  • demand_lag_52 — même semaine l’an dernier (capture la saisonnalité annuelle)
  • demand_lag_13, demand_lag_26 — quarts d’année (capture les rhythmes trimestriels)

Le choix des lags dépend du contexte. Pour un marché automobile avec des révisions trimestrielles, les lags à 12 et 13 semaines sont souvent les plus prédictifs. Pour un marché spot, les lags courts (1-4 semaines) dominent.

Rolling statistics

Sur une fenêtre glissante de 4, 8, 12 semaines, on calcule :

  • Moyenne — tendance locale
  • Écart-type — volatilité récente
  • Min / Max — bornes observées
  • Coefficient de variation — volatilité relative

Ces statistiques capturent le comportement récent de la série. Un écart-type élevé sur les 4 dernières semaines est un signal fort d’instabilité — le modèle doit prévoir avec une incertitude plus large.

Feature engineering calendaire — le détail qui change tout en France

Les jours fériés français ont un impact massif sur la demande industrielle. Le lundi de Pâques, l’Ascension (qui tombe un jeudi et génère un pont), la Toussaint, Noël — ces dates créent des baisses de demande anticipées et des rebonds post-fériés qui ne sont pas capturés par une simple variable “mois”.

Variables calendaires utiles :

# Jours fériés France (bibliothèque workalendar ou jours_feries)
df['is_holiday_fr'] = df['date'].apply(lambda d: d in fr_holidays)

# Ponts potentiels (vendredi entre jeudi férié et week-end)
df['is_bridge_day'] = df.apply(detect_bridge_day, axis=1)

# Semaine calendaire (ISO)
df['week_of_year'] = df['date'].dt.isocalendar().week

# Semaines d'arrêt août — variable critique industrie
df['is_august_shutdown'] = df['date'].apply(
    lambda d: d.month == 8 and d.isocalendar().week in [31, 32, 33]
)

# Numéro du trimestre
df['quarter'] = df['date'].dt.quarter

# Jours ouvrés dans le mois (varie selon les fériés)
df['working_days_in_month'] = df['date'].apply(count_working_days_in_month)

L’arrêt d’août mérite une attention particulière. Dans beaucoup de secteurs industriels français (automobile, agroalimentaire, mécanique), les semaines 31-33 sont quasi mortes. Mais les semaines 29-30 sont sur-demandées — les clients constituent un stock avant l’arrêt. Et les semaines 34-36 sont également sur-demandées — le redémarrage génère un effet rattrapage. Une feature binaire is_august_shutdown + des lags autour de cette période capturent ce pattern précisément.

Variables exogènes

Selon le secteur, plusieurs variables exogènes peuvent améliorer significativement la prévision :

VariableSourceSecteurs concernés
Cours acier (LME)Bloomberg / Yahoo FinanceMécanique, chaudronnerie, automobile
Indice PMI manufacturierINSEE / MarkitTous secteurs B2B
Carnet de commandes clientEDI / portail clientSous-traitants avec visibilité J+90
Consommation électricité nationaleRTE open dataIndustries énergo-intensives
Météo localeOpen-Meteo APIBTP, agroalimentaire, textile
Taux USD/EURECBImport/export matières premières

Ces variables ne sont pas toutes disponibles partout. Commencer par les deux ou trois les plus accessibles et les plus corrélées avec la demande dans votre secteur.

Gestion des ruptures structurelles

Une rupture structurelle — un nouveau client majeur qui entre, un produit qui est discontinué, un changement de process — rend inutilisables les données antérieures à la rupture pour prévoir l’avenir.

La gestion des ruptures est souvent ignorée dans les tutoriels. Sur le terrain, c’est critique.

Détection automatique. Le test de Chow ou le test CUSUM détectent statistiquement les points de rupture dans une série. En pratique, on complète toujours par une validation manuelle : les ruptures économiques sont rarement des signaux statistiques nets.

Fenêtrage adaptatif. Plutôt que d’entraîner sur tout l’historique disponible, on définit une fenêtre d’entraînement maximale (typiquement 2 ans). Si une rupture est détectée, on coupe la fenêtre à la date de rupture. Le modèle est entraîné uniquement sur les données post-rupture.

def detect_and_trim_series(df, series_col, date_col, max_window_weeks=104):
    """
    Détecte les ruptures structurelles et retourne une fenêtre propre.
    Utilise le test CUSUM sur la dérivée de la moyenne mobile.
    """
    from ruptures import Binseg
    import numpy as np

    values = df[series_col].values
    # Détection via ruptures library (Binseg)
    algo = Binseg(model="rbf").fit(values.reshape(-1, 1))
    breakpoints = algo.predict(n_bkps=2)  # max 2 ruptures

    # Si rupture récente (< max_window_weeks), couper avant
    last_breakpoint = max(breakpoints[:-1]) if len(breakpoints) > 1 else 0
    cutoff_idx = max(last_breakpoint, len(values) - max_window_weeks)

    return df.iloc[cutoff_idx:].copy()

Walk-forward backtesting — l’unique façon de valider

La validation croisée standard (k-fold) est interdite sur les séries temporelles. Elle introduit du data leakage : des données du futur informent le modèle sur le passé. Les métriques sont flatteuses et fausses.

La seule méthode correcte : le walk-forward.

Entraînement sur S1→S52        | Test sur S53-S56  (4 semaines)
Entraînement sur S1→S56        | Test sur S57-S60
Entraînement sur S1→S60        | Test sur S61-S64
...

À chaque fold, on ré-entraîne sur tout ce qui précède, on teste sur la fenêtre suivante. Les métriques agrégées sur tous les folds donnent une estimation non-biaisée de la performance réelle.

import xgboost as xgb
import numpy as np
import pandas as pd
from sklearn.metrics import mean_absolute_percentage_error

def walk_forward_validation(df, feature_cols, target_col, n_folds=8, horizon=4):
    """
    Walk-forward validation sur séries hebdomadaires.
    
    Args:
        df: DataFrame trié par date (index = semaines)
        feature_cols: liste des features
        target_col: nom de la variable cible
        n_folds: nombre de folds de test
        horizon: horizon de prévision en semaines
    
    Returns:
        dict avec MAPE, RMSE, biais par fold
    """
    results = []
    n = len(df)
    min_train_size = n - (n_folds * horizon)

    for fold in range(n_folds):
        train_end = min_train_size + fold * horizon
        test_start = train_end
        test_end = test_start + horizon

        if test_end > n:
            break

        X_train = df[feature_cols].iloc[:train_end]
        y_train = df[target_col].iloc[:train_end]
        X_test = df[feature_cols].iloc[test_start:test_end]
        y_test = df[target_col].iloc[test_start:test_end]

        model = xgb.XGBRegressor(
            n_estimators=300,
            learning_rate=0.05,
            max_depth=5,
            subsample=0.8,
            colsample_bytree=0.8,
            random_state=42,
            n_jobs=-1,
            verbosity=0
        )
        model.fit(X_train, y_train,
                  eval_set=[(X_test, y_test)],
                  early_stopping_rounds=20,
                  verbose=False)

        y_pred = model.predict(X_test)
        mape = mean_absolute_percentage_error(y_test, y_pred)
        rmse = np.sqrt(np.mean((y_test - y_pred) ** 2))
        bias = np.mean(y_pred - y_test)  # positif = sur-prévision

        results.append({
            'fold': fold + 1,
            'mape': mape,
            'rmse': rmse,
            'bias': bias,
            'n_train': train_end
        })

    results_df = pd.DataFrame(results)
    print(f"MAPE moyen : {results_df['mape'].mean():.1%} ± {results_df['mape'].std():.1%}")
    print(f"Biais moyen : {results_df['bias'].mean():.2f} (+ = sur-prévision)")
    return results_df

Le biais est une métrique souvent ignorée. Un modèle avec un MAPE de 18 % mais un biais systématique de +12 % (sur-prévision) génère du surstock chronique. Le biais et la variance doivent être suivis séparément.

Tableau comparatif honnête

MéthodeMAPE typique (B2B industriel)Gestion exogènesSaisonnalité multiplePetits volumesComplexité déploiement
ARIMA / SARIMA25-45 %Partielle (ARIMAX)DifficileMauvaiseFaible
Prophet20-35 %Oui (regressors)BonneMoyenneFaible
XGBoost14-25 %ExcellentVia featuresBonne (panel)Moyenne
LightGBM13-23 %ExcellentVia featuresBonne (panel)Moyenne
LSTM / Transformer15-28 %BonExcellentMauvaise (< 100 pts)Élevée
Lissage exponentiel (Holt-Winters)28-50 %NonBasiqueBonneTrès faible

Lecture honnête de ce tableau. Les fourchettes sont larges parce qu’elles dépendent du secteur, de la qualité des features, et de l’horizon de prévision. Un ARIMA bien paramétré sur une série régulière avec beaucoup d’historique peut dépasser XGBoost. LightGBM est souvent légèrement plus rapide que XGBoost à performances équivalentes — les deux sont interchangeables dans la plupart des contextes.

XGBoost et LightGBM dominent sur les données industrielles tabulaires avec features engineerées. Les LSTM ne valent leur complexité que si la dépendance temporelle est longue (> 50 pas) et que vous avez un historique suffisant (> 3 ans hebdomadaire).

Quand XGBoost ne marche PAS

Cette section est la plus importante de l’article. L’honnêteté intellectuelle fait partie de la méthode.

Historique insuffisant

XGBoost a besoin de features — en particulier des lags et des rolling stats. Si vous n’avez que 18 mois d’historique hebdomadaire, votre fenêtre de lags utiles est limitée. Les features de saisonnalité annuelle (lag 52 semaines) ne peuvent pas être calculées. Dans ce cas, Prophet ou un simple lissage exponentiel sont souvent plus robustes parce qu’ils encodent des a priori sur la structure de la série.

Règle pratique : moins de 2 ans d’historique → Prophet ou lissage exponentiel. Plus de 2 ans avec features exogènes → XGBoost.

Séries longues avec forte dépendance séquentielle

Sur certains signaux industriels (conso énergétique minute-par-minute, vibrations machine), la dépendance temporelle s’étale sur des centaines de pas. Les lag features deviennent trop nombreuses pour être encodées manuellement. Un LSTM ou un Transformer capturera mieux ces dynamiques longues. XGBoost n’a pas de mémoire séquentielle native — elle est entièrement portée par les features.

Données de panel avec hétérogénéité forte

Un catalogue de 5 000 SKUs avec des comportements très hétérogènes (certains articles hebdomadaires, d’autres mensuels, d’autres totalement sporadiques) ne se prête pas bien à un seul modèle XGBoost. Il faut segmenter le catalogue (volume régulier / intermittent / sporadique) et appliquer des approches différentes par segment. La demande intermittente (articles qui tournent 0 sur plusieurs semaines) se gère mieux avec des modèles spécialisés (Croston, SBA, ZIP).

Absence de données exogènes pertinentes

XGBoost n’a pas de magie. Si votre seule variable d’entrée est l’historique de la demande elle-même, les performances seront proches d’un modèle statistique classique, avec plus de complexité. La valeur de XGBoost vient précisément de sa capacité à intégrer des variables exogènes. Sans elles, la justification de la complexité est plus faible.

Horizon long (> 12 semaines)

XGBoost en mode direct (prédit N semaines à l’avance en une seule inférence) fonctionne bien sur les horizons courts à moyens (1-8 semaines). Sur les horizons longs (12+ semaines), l’accumulation d’incertitude sur les features lag devient problématique. Les modèles probabilistes (prévisionnistes bayésiens, Prophet avec incertitude) sont mieux adaptés pour donner des intervalles de confiance exploitables à long terme.

Déploiement opérationnel — le sujet qu’on évite

Un modèle XGBoost de prévision qui reste dans un notebook Jupyter n’a aucune valeur opérationnelle. La valeur vient du déploiement en production.

Architecture minimale d’un pipeline de prévision hebdomadaire :

  1. Extraction automatique des données depuis l’ERP (requêtes SQL planifiées ou export CSV FTP)
  2. Recalcul des features à J+1 après la clôture de la semaine
  3. Inférence batch — le modèle produit les prévisions pour les 4-8 semaines suivantes
  4. Export vers l’ERP ou un fichier partagé lisible par le planificateur
  5. Monitoring des métriques — MAE rolling sur fenêtre glissante de 4 semaines
  6. Alerte dérive si MAE dépasse un seuil (typiquement 1.5× la performance baseline)
  7. Ré-entraînement périodique — mensuel ou trimestriel selon la vitesse de dérive

Le point 7 est souvent oublié. Un modèle XGBoost entraîné en janvier sur 2 ans d’historique sera dégradé en décembre si le marché a évolué. Le concept de drift est documenté dans la littérature MLOps — il faut le monitorer explicitement.

Résumé opérationnel

Pour un directeur supply chain qui veut décider vite :

SituationRecommandation
< 2 ans d’historique, < 500 SKUsProphet ou Holt-Winters. Déployable en 2 semaines.
≥ 2 ans, exogènes disponibles, enjeux fortsXGBoost / LightGBM. Déployable en 6-10 semaines.
Demande intermittente (> 30 % zéros)Modèles Croston / SBA. XGBoost en complément pour les tops volumes.
Signal temps réel (< 1h de granularité)LSTM ou modèles séquentiels.
Budget de projet limité, besoin rapideETS (lissage exponentiel) + calendrier manuel. Résultats sous-optimaux mais immédiats.

La meilleure méthode de prévision n’est pas celle qui a le meilleur benchmark académique. C’est celle qui est déployée, maintenue, et utilisée par les planificateurs — avec confiance.

Un modèle à 22 % de MAPE utilisé tous les lundis par les équipes vaut mieux qu’un modèle à 15 % de MAPE mort dans un notebook depuis le dernier changement de version Python.


À lire aussi :

Cet article vous a été utile ?

BCUB3 est une petite structure. Si vous pensez à un collègue ou un partenaire qui pourrait en tirer quelque chose, la meilleure manière de nous aider est de partager le lien. Et si vous avez un cas concret à discuter, parlons-en directement.

Prendre un RDV de cadrage