Time Series · Regression · Machine Learning

📈 Retail Demand Forecasting — Two MAE Plateaus

📈 Pronóstico de Demanda Retail — Dos Mesetas de MAE

A CRISP-ML(Q) study on retail demand forecasting. Headline finding: on the Kaggle retail dataset used here, two distinct MAE plateaus emerge depending on whether the existing Demand Forecast column enters the pipeline as a residual prior or is dropped as a leakage trap — MAE ~69 vs MAE ~7.4 on the same data, the same models, the same train/test split.

Un estudio CRISP-ML(Q) sobre pronóstico de demanda retail. Hallazgo principal: en el dataset de Kaggle usado aquí, emergen dos mesetas distintas de MAE según cómo entra la columna Demand Forecast al pipeline — como prior residual o descartada por leakage — MAE ~69 vs MAE ~7.4 sobre los mismos datos, los mismos modelos, el mismo split temporal.

📊 Dataset: Retail Store Inventory (Kaggle, CC0)
📝 Records: Registros: 73,100 (5 stores × 20 products × 731 days)
🏭 Industry: Industria: Retail · Supply Chain · S&OP Retail · Supply Chain · S&OP
Python LightGBM HistGradientBoosting RandomForest Prophet pmdarima PyTorch (LSTM) SHAP Streamlit CRISP-ML(Q)
🎯
Business
📊
Data
🔧
Preparation
🧠
Modeling
Evaluation
🚀
Deployment
01

🎯 Business Understanding

Retail stores carry the wrong amount of inventory most days. Too much, and capital is locked up in shelf space; too little, and sales walk out the door — along with the customer. Optimizing the daily reorder decision per (Store × Product) is one of the highest-ROI applications of ML in supply chain.

The cost structure is asymmetric. Industry sources quantify each side independently: some estimates place stockout-related losses in the 4–8% of annual sales range [ToolsGroup] (figures vary by sector and methodology), with 9% of customers permanently switching to a competitor after a single stockout — rising to 55% after repeated stockouts [Opensend, 2024]. Overstock carrying costs run 20–30% of inventory value annually.

But the most interesting finding in this lab isn't about cost. It's about framing: in any production S&OP / MRP system, the existing planning software already publishes a Demand Forecast 1+ weeks before the prediction window. How that forecast enters your pipeline — as a feature to drop because of leakage concerns, or as a prior the model corrects — changes the holdout MAE on this dataset from ~69 to ~7.4.

Project goals:

  • Forecast daily demand per (Store × Product) with horizon t+1.
  • Test whether the existing Demand Forecast column should be dropped (leakage) or used as a residual prior (production framing).
  • Run a champion-challenger backtest to confirm the result isn't a single-split artifact.
  • Deploy as a Streamlit app a planner can use without help from data.

Las tiendas retail cargan la cantidad equivocada de inventario casi todos los días. De más, y el capital queda atrapado en estantería; de menos, y las ventas se van por la puerta — junto con el cliente. Optimizar la decisión diaria de reorden por (Tienda × Producto) es una de las aplicaciones de ML de mayor ROI en supply chain.

La estructura de costos es asimétrica. Las fuentes de industria cuantifican cada lado por separado: algunas estimaciones sitúan las pérdidas por stockout en el rango del 4–8% de las ventas anuales [ToolsGroup], con 9% de clientes que se cambian permanentemente a un competidor tras un solo stockout — escalando a 55% tras stockouts repetidos [Opensend, 2024]. El carrying cost de overstock corre en 20–30% del valor del inventario por año.

Pero el hallazgo más interesante de este lab no es sobre costo. Es sobre framing: en cualquier sistema S&OP / MRP de producción, el software de planning ya publica un Demand Forecast 1+ semanas antes de la ventana de predicción. Cómo entra ese forecast a tu pipeline — como feature que descartar por leakage, o como prior que el modelo corrige — cambia el MAE en holdout en este dataset de ~69 a ~7.4.

Objetivos del proyecto:

  • Pronosticar demanda diaria por (Tienda × Producto) con horizonte t+1.
  • Probar si la columna Demand Forecast debe descartarse (leakage) o usarse como prior residual (framing de producción).
  • Correr un backtest champion-challenger para confirmar que el resultado no es artefacto de un solo split.
  • Desplegar como app Streamlit que un planner pueda usar sin ayuda del equipo de datos.
Problem type
Tipo de problema
Time-series regression (panel data)
Regresión de series de tiempo (panel)
Target
Units Sold (integer, censored by Inventory Level)
Units Sold (entero, censurado por Inventory Level)
Main metrics
Métricas principales
MAE, RMSE, sMAPE
Decision metric
Métrica de decisión
P80 quantile coverage
Cobertura cuantil P80
Stakeholder
Demand Planner / Supply Chain Manager
Demand Planner / Supply Chain Manager
02

📊 Data Understanding

Single dataset, multiple diagnostics. The most important finding before any modeling: this Kaggle dataset has within-group autocorrelation ≈ 0 — the synthetic generator produced memoryless series, so lag features add no signal. This is why classical univariate methods (ARIMA, ETS) plateau at the mean baseline and the multivariate ML methods plateau at ~69. The plateau is a property of this dataset, not of any model.

Un solo dataset, múltiples diagnósticos. El hallazgo más importante antes de modelar: este dataset de Kaggle tiene autocorrelación intra-grupo ≈ 0 — el generador sintético produjo series sin memoria, así que los lag features no aportan señal. Por eso los métodos clásicos univariados (ARIMA, ETS) se quedan en la baseline media y los métodos ML multivariados se quedan en ~69. La meseta es una propiedad del dataset, no de algún modelo en particular.

Diagnostic Diagnóstico Result Resultado
Within-group lag_1 autocorr. ≈ 0 (memoryless)(sin memoria)
lag_28 autocorr. ≈ 0
Demand ForecastUnits Sold ρ = 0.997 ⚠️ (synthetic near-oracle)(near-oracle sintético)
Stockout rateTasa de stockout n/a (target is Units Sold, already censored)n/a (el target es Units Sold, ya censurado)
Stationarity (ADF p-value per series)Estacionariedad (ADF p-value por serie) < 0.05 on most series → d=0< 0.05 en casi todas → d=0
Pricing elasticityElasticidad de precio ≈ 0 (synthetic limitation)(limitación sintética)

⚠️ Honest caveat: this is a synthetic Kaggle dataset. The ρ=0.997 between Demand Forecast and Units Sold is unrealistically high — a real existing forecast typically has correlation 0.7–0.85 with actuals. The methodology shown here generalizes (residual prior > dropped feature), but the absolute MAE numbers and the size of the gap will narrow on real data.

⚠️ Caveat honesto: este es un dataset sintético de Kaggle. La ρ=0.997 entre Demand Forecast y Units Sold es irrealísticamente alta — un forecast existente real típicamente tiene correlación 0.7–0.85 con la realidad. La metodología mostrada aquí generaliza (prior residual > feature descartado), pero los números absolutos de MAE y el tamaño del gap se acortarán en datos reales.

03

🔧 Data Preparation

Feature engineering — leakage-safe lag and rolling features. Lags are built per (Store × Product) with shift() so they never see the future:

Feature engineering — lag y rolling features a prueba de leakage. Los lags se construyen por (Tienda × Producto) con shift() para que nunca vean el futuro:

# Sort by group + date first, so shift() references the prior calendar day.
df_model = df_model.sort_values(['Store ID', 'Product ID', 'Date'])

# Lags built per (Store x Product) using shift() — never sees the future.
g = df_model.groupby(['Store ID', 'Product ID'])
df_model['Demand_lag_1'] = g['Units Sold'].shift(1)
df_model['Demand_lag_7'] = g['Units Sold'].shift(7)

# Rolling features use transform() so the window stays inside each group.
df_model['Demand_roll7_mean'] = g['Units Sold'].transform(
    lambda s: s.shift(1).rolling(7).mean())

# Quality gate (the "Q" in CRISP-ML(Q)) — asserted in-notebook.
assert (df_model.groupby(['Store ID', 'Product ID'])['Date']
        .apply(lambda d: (d.diff().dropna() == pd.Timedelta('1D')).all()).all()), \
       'Lag grouping broken'
  • Date features + Fourier encoding for cyclical signals (month, day-of-week encoded as sin/cos pairs).
  • Mixed-scaler ColumnTransformerStandardScaler, MinMaxScaler, PowerTransformer and OneHotEncoder per feature type.
  • Time-based split — train ends strictly before the holdout starts. No random shuffling.
  • Date features + codificación Fourier para señales cíclicas (mes y día de semana codificados como pares sin/cos).
  • ColumnTransformer multi-scalerStandardScaler, MinMaxScaler, PowerTransformer y OneHotEncoder según el tipo de feature.
  • Split temporal — el train termina estrictamente antes de que empiece el holdout. Sin shuffle aleatorio.
04

🧠 Modeling

Phase 4 progresses through several regimes, all evaluated on the same holdout (MAE, lower is better):

La Fase 4 recorre varios regímenes, todos evaluados en el mismo holdout (MAE, menor es mejor):

Regime Régimen What was tested Qué se probó Result on this dataset Resultado en este dataset
Naive baselinesBaselines naive mean, lag-1, lag-7, rolling-7 MAE 89 (mean) – 120 (lag-1)
Univariate classicalClásicos univariados Auto-ARIMA, ETS Holt-Winters, Prophet MAE 89 – 112 (Prophet)
Univariate deepDeep univariado LSTM (4-channel, 60-step) MAE 88.9 (didn't learn)MAE 88.9 (no aprendió)
Multivariate ML (no DF)ML multivariado (sin DF) LightGBM, RandomForest, Stacking, CatBoost, HGB, ExtraTrees MAE ~69 plateauMeseta MAE ~69
Residual learning (DF as prior)Residual learning (DF como prior) DF + HGB / RF / LightGBM on Units Sold − DF MAE ~7.4 plateau

The two-plateau finding (the headline):

El hallazgo de las dos mesetas (lo central):

            Same dataset · Same split · Same model families

  NO DF (model only)        multivariate ML  -> MAE ~69 ── plateau
                             univariate       -> MAE ~89 (mean)

  DF AS RESIDUAL PRIOR      DF + HGB(features)  -> MAE 7.43
                             DF + RF(features)   -> MAE 7.45
                             DF + LGBM(features) -> MAE 7.46

  target = Units Sold - Demand Forecast          # residual
  pred   = Demand Forecast + model(features)      # add back at inference

Champion-challenger backtest (18 rolling 30-day windows):

Backtest champion-challenger (18 ventanas rolling de 30 días):

Model Wins / 18 Mean MAE Std
HGB_residual17 🏆7.390.08
RF_residual17.410.08
LightGBM_residual07.430.09
DF_puro (forecast as-is)(forecast tal cual)08.310.11
HGB / RF / LightGBM_direct069.1–69.40.8–0.9

On this dataset, the framing decision dominates the algorithm decision — direct-vs-residual moves MAE by ~62 units; the choice of gradient booster within the residual regime moves it by ~0.04.

Important caveat: the size of the framing-vs-algorithm gap is unusually extreme here because Demand Forecast is a synthetic near-oracle (ρ=0.997 with target). On real data with a noisier existing forecast (ρ ≈ 0.7–0.85), the gap narrows substantially — but the design principle (residual prior > dropped feature) generalizes.

En este dataset, la decisión de framing pesa más que la elección del algoritmo — directo-vs-residual mueve el MAE en ~62 unidades; elegir un gradient booster distinto dentro del régimen residual lo mueve en ~0.04.

Caveat importante: el tamaño del gap framing-vs-algoritmo es inusualmente extremo aquí porque Demand Forecast es un near-oracle sintético (ρ=0.997 con el target). En datos reales con un forecast existente más ruidoso (ρ ≈ 0.7–0.85), el gap se acorta bastante — pero el principio de diseño (prior residual > feature descartado) generaliza.

05

✅ Evaluation

Why this isn't a single-split artifact. Three sanity checks built into Phase 5:

  • 18-window rolling backtest. The framing gap holds across every window. HGB residual wins 17/18; no direct contender ever beats any residual contender.
  • Bias decomposition. The residual improvement is almost entirely bias correction. DF puro overshoots by +5.05 units systematically; residual learning reduces that to +0.10. This is operationally meaningful: less systematic bias = less wasted safety stock.
  • Per-segment error analysis. MAE is uniform across stores (67–70 in the no-DF regime; 7.2–7.6 in the residual regime). No catastrophic segment hiding the average.

Residual diagnostics. Bias ≈ 0, symmetric residual distribution. A heteroscedasticity check compares the residual standard deviation on the upper half of predictions vs. the lower half — a ratio close to 1.0 indicates the model is well specified, while ratios > 1.5 would suggest a log1p-transformed target. The notebook runs this comparison explicitly.

SHAP feature importance. In the no-DF regime, Inventory Level and the lag/rolling features dominate the contribution — exactly the cross-sectional signal the multivariate ML methods exploit and the univariate ones can't see. In the residual regime, the split is cleaner: Demand Forecast (injected via the prior, not as a feature) carries most of the signal; the contextual features contribute the bias-correction layer.

Why MAE over MAPE. MAPE explodes when actuals are near zero, and daily SKU-level demand frequently sits at 4–10 units. sMAPE is reported alongside MAE everywhere.

Por qué esto no es artefacto de un solo split. Tres chequeos de robustez en la Fase 5:

  • Backtest rolling de 18 ventanas. El gap de framing se mantiene en cada ventana. HGB residual gana 17/18; ningún contendiente directo le gana jamás a uno residual.
  • Descomposición de sesgo. La mejora del residual es casi enteramente corrección de sesgo. DF puro sobreestima +5.05 unidades de forma sistemática; el residual learning lo reduce a +0.10. Es relevante operativamente: menos sesgo sistemático = menos safety stock desperdiciado.
  • Análisis de error por segmento. MAE uniforme entre tiendas (67–70 en el régimen sin DF; 7.2–7.6 en el residual). Sin segmento catastrófico escondiendo el promedio.

Diagnóstico de residuos. Bias ≈ 0, distribución simétrica. Un chequeo de heteroscedasticidad compara la desviación estándar del residuo en la mitad superior de las predicciones vs. la inferior — un ratio cercano a 1.0 indica que el modelo está bien especificado, mientras que ratios > 1.5 sugerirían un target transformado con log1p. El notebook corre esta comparación explícitamente.

SHAP. En el régimen sin DF, Inventory Level y los lag/rolling features dominan la contribución — justo la señal cross-sectional que el ML multivariado explota y los univariados no ven. En el régimen residual, la separación es más limpia: Demand Forecast (inyectado vía el prior, no como feature) carga la mayor parte de la señal; los features contextuales aportan la capa de corrección de sesgo.

Por qué MAE sobre MAPE. MAPE explota cuando el valor real es cercano a cero, y la demanda diaria a nivel SKU suele estar en 4–10 unidades. sMAPE se reporta junto al MAE en todos lados.

06

🚀 Deployment

The Streamlit app loads four serialized models for cold-start routing, letting a planner enter a Store × Product context and get a real-time demand forecast plus the P80 reorder recommendation — no data team required.

  • model.pkl — Stage 2 LightGBM (full features, requires lag history)
  • model_stage1.pkl — cold-start fallback (contextual only)
  • model_contextual.pkl — App-aligned 15-feature model (form-only)
  • model_q80.pkl — P80 quantile model for the reorder rule

model_metadata.pkl carries feature_columns, package versions, training date and holdout metrics — the app reindexes form input to match the training schema exactly (mismatches silently produce wrong predictions, so this is asserted).

La app Streamlit carga cuatro modelos serializados para el routing de cold-start, permitiendo a un planner ingresar un contexto Tienda × Producto y obtener un pronóstico de demanda en tiempo real más la recomendación de reorden P80 — sin necesidad del equipo de datos.

  • model.pkl — Stage 2 LightGBM (features completas, requiere historia de lags)
  • model_stage1.pkl — fallback de cold-start (solo contextual)
  • model_contextual.pkl — modelo App-aligned de 15 features (solo formulario)
  • model_q80.pkl — modelo cuantil P80 para la regla de reorden

model_metadata.pkl guarda feature_columns, versiones de paquetes, fecha de entrenamiento y métricas de holdout — la app reindexa el formulario para que calce exactamente con el schema de entrenamiento (un mismatch produce predicciones incorrectas en silencio, así que está aserto).

🖥️ Inventory Forecasting DemoDemo de Forecasting de Inventario

● Live

From forecast to reorder — the operational bridge:

Del pronóstico al reorden — el puente operativo:

P80_demand_next_day  = 130 units   # output of model_q80.pkl
lead_time            = 3 days
avg_daily_demand     = 100 units

reorder_point ≈ avg_daily_demand × lead_time + (P80 − mean) × √lead_time
              ≈ 300              + (130 − 100) × √3
              ≈ 300              + 30          × 1.732
              ≈ 352 units

This is a simplified operational approximation: scaling a one-day forecast quantile by √lead_time assumes independent daily errors and stable variance. In production, the preferred approach estimates the demand quantile over the full lead-time window directly.

Operational hooks:

  • Drift monitoring — PSI on key features. Industry thresholds: < 0.1 no change, 0.1–0.2 minor, ≥ 0.2 significant → retrain [Fiddler AI].
  • Bias tracking — cumulative residual ÷ MAE. Statistical threshold is 3.75 MAD (3-sigma at 99% service level), commonly rounded to |tracking signal| > 4.
  • Retrain cadence — quarterly by default, weekly only if drift triggers fire.

Es una aproximación operativa simplificada: escalar un cuantil de pronóstico de un día por √lead_time asume errores diarios independientes y varianza estable. En producción, lo ideal es estimar el cuantil de demanda sobre toda la ventana del lead time directamente.

Hooks operacionales:

  • Monitoreo de drift — PSI sobre features clave. Umbrales de industria: < 0.1 sin cambio, 0.1–0.2 menor, ≥ 0.2 significativo → reentrenar [Fiddler AI].
  • Tracking de bias — residuo acumulado ÷ MAE. Umbral estadístico de 3.75 MAD (3-sigma a 99% de servicio), comúnmente redondeado a |tracking signal| > 4.
  • Cadencia de reentrenamiento — trimestral por defecto, semanal solo si dispara drift.
💡

Business Impact

Impacto de Negocio

Illustrative business case

Escenario de negocio ilustrativo

For a retail chain with 50 stores and 5,000 SKUs (~250,000 Store × Product combinations), implementing this pipeline could generate estimated savings of $1M – $3M USD annually through two mechanisms: lower safety stock at the same service level, and fewer stockouts at the same inventory level. These figures are illustrative — calibrate with company-specific data before quoting.

Para una cadena retail con 50 tiendas y 5.000 SKUs (~250.000 combinaciones tienda × producto), implementar este pipeline podría generar ahorros estimados de $1M – $3M USD anuales mediante dos mecanismos: menor safety stock al mismo nivel de servicio, y menos stockouts al mismo nivel de inventario. Estas cifras son ilustrativas — calíbralas con datos propios de la empresa antes de cotizar.

  • Reduced safety stock at the same service level. Gartner reports each 1% forecast accuracy improvement yields a 2.7% reduction in finished goods inventory [Gartner, via Manokhin]; McKinsey estimates a 10–20% accuracy improvement trims inventory costs ~5%.
  • Fewer stockouts at the same inventory level. The P80 quantile model targets the asymmetric cost structure directly. Industry case studies of quantile-based replenishment report stockout reductions of 15–30% [ToolsGroup].

Worked calculation for the $1M–$3M range:

  • Menor safety stock al mismo nivel de servicio. Gartner reporta que cada 1% de mejora en forecast accuracy genera 2.7% de reducción en finished goods inventory [Gartner, vía Manokhin]; McKinsey estima que una mejora del 10–20% reduce costos de inventario ~5%.
  • Menos stockouts al mismo nivel de inventario. El modelo cuantil P80 ataca la estructura de costos asimétrica directamente. Case studies de industria con reorden basado en cuantil reportan reducciones de stockout del 15–30% [ToolsGroup].

Cálculo detallado del rango $1M–$3M:

Worked calculation:
  Stores: 50, SKUs: 5,000  ->  250,000 (Store x Product) pairs
  Avg inventory per pair: $200  ->  Total stocked inventory: $50M
  Carrying cost rate: 25% (midpoint of industry 20-30%)
  ->  Current annual carrying cost: $12.5M

  Forecast accuracy lift  ->  10% inventory reduction (Gartner ratio)
  Inventory freed: $5M    ->  carrying cost saved: $1.25M/yr
  Stockout reduction 20%:     ~$2.4M/yr saved
  Net annual benefit:         ~$3M (mid case)

  Lower bound (cautious adoption, partial routing): ~$1M/yr

Key benefits:

  • Reduced working capital tied up in inventory
  • Higher in-stock service level (fewer lost sales)
  • Adaptive safety stock by SKU (auto-wider on erratic items)
  • Cold-start path for new SKU launches
  • Auditable, explainable forecasts (SHAP per prediction)

Beneficios clave:

  • Reducción de capital de trabajo atrapado en inventario
  • Mayor nivel de servicio in-stock (menos ventas perdidas)
  • Safety stock adaptativo por SKU (auto-más-ancho en items erráticos)
  • Ruta de cold-start para lanzamientos de SKU nuevos
  • Pronósticos auditables y explicables (SHAP por predicción)
🔬

What this lab teaches that most don't

Lo que este lab enseña y la mayoría no

  • Framing decisions can dominate algorithm decisions. On this dataset, the choice of how Demand Forecast enters the pipeline matters far more than the choice of gradient booster.
  • The "leakage trap" intuition has two valid framings. As an unlagged feature, Demand Forecast is leakage. As a residual prior, it's the canonical S&OP signal — and the rigorous deployment choice.
  • Univariate methods are not bad — they're miscalibrated to this dataset. ARIMA, ETS, Prophet topped out near the mean baseline because lag features carry zero signal here. On real retail with autocorrelation, classical methods would compete with multivariate ML.
  • Quantile beats point forecast for asymmetric costs. When the stockout/overstock cost ratio is 3:1 or worse, train the quantile model that targets the service level directly.
  • Synthetic data has a ceiling — name it. Don't quote absolute MAE numbers from a synthetic dataset as if they were industry benchmarks. The methodology generalizes; the absolute numbers don't.
  • Las decisiones de framing pueden pesar más que las de algoritmo. En este dataset, cómo entra Demand Forecast al pipeline importa mucho más que qué gradient booster eliges.
  • La intuición del "leakage trap" tiene dos framings válidos. Como feature sin lag, Demand Forecast es leakage. Como prior residual, es la señal canónica de S&OP — y la decisión de deployment rigurosa.
  • Los métodos univariados no son malos — están mal calibrados a este dataset. ARIMA, ETS y Prophet se quedaron cerca de la baseline media porque aquí los lag features no cargan señal. En retail real con autocorrelación, los métodos clásicos competirían con el ML multivariado.
  • El cuantil supera al pronóstico puntual cuando los costos son asimétricos. Cuando el ratio de costo stockout/overstock es 3:1 o peor, entrena el cuantil que apunta al nivel de servicio directamente.
  • El dato sintético tiene techo — hay que nombrarlo. No cites números absolutos de MAE de un dataset sintético como si fueran benchmarks de industria. La metodología generaliza; los números absolutos no.
🧑‍💼

For Supply Chain Professionals

Para Profesionales de Supply Chain

If you plan inventory for a living and skipped the code above — this section translates what the models produced into the language of replenishment, safety stock, and service levels. No Python required.

Translating the data-science words

Si planificas inventario y saltaste el código de arriba — esta sección traduce lo que producen los modelos al lenguaje de reabastecimiento, safety stock y nivel de servicio. No requiere Python.

Traducción del lenguaje técnico

ML / notebook term Término ML / notebook Supply-chain equivalent Equivalente supply-chain Plain meaning Significado simple
Target / Units SoldDemandDemandaThe thing we forecastLo que pronosticamos
MAE = 7.4Forecast Accuracy (FA)Forecast Accuracy (FA)Typical miss is ~7.4 units (residual regime)Error típico ~7.4 unidades (régimen residual)
Residual learningUse existing forecast as priorUsar el forecast existente como priorThe model corrects the ERP's forecast, doesn't replace itEl modelo corrige el forecast del ERP, no lo reemplaza
P80 quantile80% service-level targetObjetivo de servicio 80%Stock level that covers demand 4 days out of 5Nivel que cubre demanda 4 de cada 5 días
LeakageCheatingHacer trampaUsing info you wouldn't have on forecast dayUsar info que no tendrías el día del pronóstico
DriftForecast going stalePronóstico desactualizadoAccuracy decaying as the market changesPrecisión que baja con el cambio de mercado

From forecast to reorder quantity — worked example

A forecast alone doesn't tell you how much to order. Here's the bridge, in four steps, for one product:

Del pronóstico al pedido — ejemplo

Un pronóstico no te dice cuánto pedir. El puente, en cuatro pasos:

Step 1 - Expected demand over lead time
  Forecast = 100 units/day * Lead time = 3 days  ->  300 units to cover

Step 2 - Pick a service level (business decision, not technical)
  "Avoid a stockout 95% of cycles"  ->  Z = 1.65
  "Avoid a stockout 80% of cycles"  ->  P80 quantile from the model

Step 3 - Safety stock (the buffer)
  Classic:    safety_stock = Z * forecast_error * sqrt(lead_time)
              = 1.65 * 18 * sqrt(3) ~ 51 units
  Data-driven (preferred): use the quantile model directly
              safety_stock = P80_forecast - mean_forecast
              automatically wider for erratic SKUs, tighter for steady ones

Step 4 - Reorder Point (ROP)
  ROP = expected_lead_time_demand + safety_stock = 300 + 51 = 351 units
  When on-hand inventory drops below 351, place the order.
Paso 1 - Demanda esperada en el lead time
  Pronóstico = 100 unidades/día * Lead time = 3 días  ->  cubrir 300 unidades

Paso 2 - Elegir nivel de servicio (decisión de negocio)
  "Evitar stockout 95% de los ciclos"  ->  Z = 1.65
  "Evitar stockout 80% de los ciclos"  ->  cuantil P80 del modelo

Paso 3 - Safety stock (el colchón)
  Clásico:     safety_stock = Z * error_pronóstico * sqrt(lead_time)
               = 1.65 * 18 * sqrt(3) ~ 51 unidades
  Data-driven: usar directamente el modelo cuantil
               safety_stock = P80_pronóstico - pronóstico_medio
               más ancho para SKUs erráticos, más ajustado para estables

Paso 4 - Punto de Reorden (ROP)
  ROP = demanda_lead_time + safety_stock = 300 + 51 = 351 unidades
  Cuando el inventario disponible baja a 351, se pide.

A smaller forecast error shrinks the safety stock you need for the same service level. That's where the dollar payoff of a better model shows up.

Service level ↔ which quantile to predict

Un error de pronóstico menor reduce el safety stock necesario para el mismo nivel de servicio. Ahí vive el ROI del modelo.

Nivel de servicio ↔ qué cuantil pedirle al modelo

Target service level Nivel de servicio Quantile Cuantil When to use Cuándo usarlo
50%P50 / point forecastpronóstico puntualStockouts ~50% of cycles — too thin for real shelvesStockouts ~50% — demasiado fino para retail real
80% (typical retail)80% (retail típico)P80Moderate buffer, balanced cash use — built in this labColchón moderado, uso balanceado de capital — construido en este lab
95% (essentials, FMCG)95% (esenciales, FMCG)P95Fat buffer, higher availabilityColchón amplio, alta disponibilidad
99% (safety-critical)99% (crítico)P99Reserve for items where a stockout is unacceptableSolo para items donde un stockout es inaceptable

Newsvendor rule of thumb: the right service level isn't gut feel — it's stockout_cost ÷ (stockout_cost + overstock_cost). If a stockout costs $20 and an overstock $5, the optimal is 20/25 = 0.80 — exactly why this lab built the P80 model.

Regla newsvendor: el nivel de servicio correcto no es intuición — es costo_stockout ÷ (costo_stockout + costo_overstock). Si stockout cuesta $20 y overstock $5, el óptimo es 20/25 = 0.80 — por eso este lab construyó el modelo P80.

Don't plan every SKU the same way — ABC/XYZ

No planificar todo SKU igual — ABC/XYZ

X (steady demand) X (demanda estable) Y (variable) Y (variable) Z (erratic / lumpy) Z (errática / lumpy)
A (top 20% revenue)(top 20% revenue) Model shines — tight buffersEl modelo brilla — colchones ajustados Highest ROI — review weeklyMayor ROI — revisar semanalmente Use intermittent-demand methods (Croston / TSB)Usar métodos de demanda intermitente (Croston/TSB)
B (next 30%)(siguiente 30%) Automate fullyAutomatizar completo Medium-high valueValor medio-alto Same caveat as A-ZMismo caveat que A-Z
C (long tail)(cola larga) Set-and-forgetSet-and-forget Don't over-engineerNo sobrediseñar Don't botherNo vale la pena

Use the model's per-SKU error rate as the X/Y/Z axis: low error = X, high error = Z. The value of the forecasting system is not distributed uniformly across the catalog — identifying where it lives is pre-modeling work, not post.

Watch bias, not just error

MAE tells you how big the misses are. It does not tell you which direction. A model can post a great MAE while quietly under-forecasting every week — and you feel that as chronic stockouts.

  • Forecast bias = mean of (forecast − actual). Near 0 = balanced. Persistently positive = over-forecasting (excess stock). Persistently negative = under-forecasting (stockouts).
  • Tracking Signal = cumulative forecast error ÷ MAD (here MAE is used as an operational approximation of MAD). Statistical threshold is 3.75 MAD (3-sigma at 99% service level), rounded to |TS| > 4 for operational use.
  • Review bias by segment (region, category, store) — opposite biases cancel out in the total and hide the problem.

Anti-patterns this lab prevents

  • Bullwhip. Using Units Ordered to forecast demand would feed our own past over-ordering back into the forecast — so it's dropped unless lagged.
  • Censored signal. Forecasting Units Sold builds in existing stockouts; the lab is explicit that the target is censored by Inventory Level.
  • Same-day leakage. Every feature is lagged or known at decision-time.
  • False complexity. Adding more features didn't help; the simpler model wins on holdout.
  • Treating the existing forecast as either gospel or noise. It's neither — residual learning treats it as a prior the model corrects.

Fitting the model into S&OP

The model is a tool inside your planning rhythm, not a replacement for it.

El valor del sistema no se distribuye uniformemente en el catálogo — identificar dónde se concentra es trabajo previo al modelo, no posterior. Usa el error por SKU del modelo como eje X/Y/Z: bajo error = X, alto error = Z.

Mirar el sesgo, no solo el error

El MAE te dice qué tan grandes son los errores. No te dice la dirección. Un modelo puede tener excelente MAE y sub-pronosticar cada semana — el equipo lo siente como stockouts crónicos.

  • Sesgo (bias) = promedio de (pronóstico − real). Cerca de 0 = balanceado. Persistente positivo = sobre-pronóstico (exceso stock). Persistente negativo = sub-pronóstico (stockouts).
  • Tracking Signal = error de pronóstico acumulado ÷ MAD (aquí se usa MAE como aproximación operativa del MAD). Umbral estadístico de 3.75 MAD (3-sigma a 99% servicio), redondeado a |TS| > 4 en uso operacional.
  • Revisar sesgo por segmento (región, categoría, tienda) — los sesgos opuestos se cancelan en el total y esconden el problema.

Anti-patrones que este lab previene

  • Bullwhip. Usar Units Ordered para pronosticar demanda alimentaría el pronóstico con nuestras propias órdenes pasadas — por eso se elimina salvo que esté rezagado (con lag).
  • Señal censurada. Pronosticar Units Sold incorpora los stockouts existentes; el lab es explícito en que el target está censurado por Inventory Level.
  • Leakage same-day. Todo feature está rezagado (con lag) o conocido al momento de decisión.
  • Falsa complejidad. Agregar más features no ayudó; el modelo más simple gana en holdout.
  • Tratar el forecast existente como verdad absoluta o como ruido. No es ni uno ni otro — el residual learning lo trata como un prior que el modelo corrige.

Encajar el modelo en S&OP

El modelo es una herramienta dentro de tu ritmo de planning, no un reemplazo de él.

Cadence Cadencia What to use the model for Para qué usar el modelo
DailyDiariaReplenishment per SKU (P80 + ROP). Lab's sweet spot.Reabastecimiento por SKU (P80 + ROP). Sweet spot del lab.
WeeklySemanalAggregate to category × store; compare actual vs forecastAgregar a categoría × tienda; comparar real vs pronóstico
MonthlyMensualBias / tracking signal review; decide whether to retrainRevisión de sesgo / tracking signal; decidir si reentrenar
QuarterlyTrimestralABC/XYZ refresh; consider expanding feature setRefresh ABC/XYZ; considerar expandir feature set

Forecast Value Added (FVA): every cycle, ask "did the model beat simply repeating last period?" If not, drop it — complexity without FVA is just cost.

Forecast Value Added (FVA): cada ciclo, pregúntate "¿el modelo superó simplemente repetir el último período?" Si no, descártalo — complejidad sin FVA es solo costo.

Glossary

Glosario

Term Término Meaning Significado
Lead timeDays from placing an order to receiving itDías desde el pedido a la recepción
Safety stockBuffer for demand above forecastColchón para demanda por encima del pronóstico
ROPReorder Point — inventory level that triggers a new orderPunto de Reorden — nivel que dispara un nuevo pedido
Service levelNivel de servicioTarget % of cycles you avoid a stockout% de ciclos en que evitas stockout
Days of coverDays of coverOn-hand inventory ÷ daily demandStock ÷ demanda diaria
StockoutDemand you couldn't fillDemanda que no pudiste cubrir
NewsvendorMath for optimal stock under uncertain demandModelo para stock óptimo con demanda incierta
BullwhipDemand swings amplifying upstreamVariabilidad amplificada río arriba
FVAForecast Value Added — gain over a naïve forecastMejora sobre un pronóstico naïve
S&OPSales & Operations PlanningPlanificación de Ventas y Operaciones
🔍

Sources

Fuentes

Quantitative claims and methodological decisions in this lab are supported by a combination of public industry references, technical documentation, and selected academic literature.

Las afirmaciones cuantitativas y decisiones metodológicas de este lab se apoyan en una combinación de referencias públicas de industria, documentación técnica y literatura académica seleccionada.

CRISP-ML(Q) methodology

Metodología CRISP-ML(Q)

Studer S. et al. (2020). Towards CRISP-ML(Q). arXiv:2003.05155 · MDPI MAKE 3(2)

Stockout and overstock economics

Economía de stockout y overstock

ToolsGroup — Cost of Stockouts vs Overstock · Opensend — Stock-out Rate Statistics · WAIR — Financial Impact of Overstock & Stockouts

Forecast accuracy → inventory reduction

Forecast accuracy → reducción de inventario

Manokhin V. — Benefits of Improving Forecast Accuracy · Bamboo Rose — 10% Forecast Accuracy on $500M Brand · Lokad — Financial Impact of Forecasting Accuracy

Drift detection (PSI threshold)

Detección de drift (umbral PSI)

Fiddler AI — Measuring Data Drift with PSI · GeeksforGeeks — Population Stability Index

Tracking signal (forecast bias)

Tracking signal (bias de pronóstico)

Value Chain Planning — Tracking Signal · Umbrex — Demand Forecast Bias

Newsvendor & quantile forecasting

Newsvendor y forecasting cuantil

MetricGate — Newsvendor Model · Dagher P. — Quantile Forecasting for Inventory Planning

Data leakage & gradient boosting in time series

Data leakage y gradient boosting en series de tiempo

IBM Think — What is Data Leakage? · M5 Uncertainty — Gradient Boosted Trees · scikit-learn — Lagged Features for Time Series