Demand · Censored Data · ML · Supply Chain

📦 Censored Demand — recovering the sales your ERP never sees

📦 Demanda Censurada — recuperar la venta que el sistema no ve

A CRISP-ML(Q) study on censored demand estimation using FreshRetailNet-50K (50,000 store×product series, 4.5M rows, with labeled stockout hours). Headline finding: forecasting observed sale_amount systematically underestimates demand (bias −0.132) because ~44% of days are censored by stockouts. Training the same model on recovered latent demand nearly closes that bias (−0.014) and cuts MAE from 0.402 → 0.378 (−5.9%) on a clean holdout. But recovery is not a free lunch — only feature-rich models maintain low bias without flipping it.

Un estudio CRISP-ML(Q) sobre estimación de demanda censurada con FreshRetailNet-50K (50.000 series tienda×producto, 4.5M filas, con horas de quiebre etiquetadas). Hallazgo principal: pronosticar la sale_amount observada subestima la demanda de forma sistemática (bias −0.132) porque ~44% de los días están censurados por quiebres. Entrenar el mismo modelo sobre la demanda latente recuperada casi cierra ese sesgo (−0.014) y baja el MAE de 0.402 → 0.378 (−5.9%) en un holdout limpio. Pero recuperar la demanda no es un free lunch — solo los modelos ricos en features mantienen el sesgo bajo sin invertirlo.

📊 Dataset: FreshRetailNet-50K (Dingdong-Inc, HuggingFace)
📝 Records: Registros: 4.5M rows · 50,000 series · 90 days · hourly resolution
🏭 Industry: Industria: Retail · Perishables · Demand Planning Retail · Perecederos · Demand Planning
🎯 Metric: Métrica: MAE · Bias · clean holdout (206k rows, stock-only days)
Python LightGBM XGBoost CatBoost Prophet statsforecast Censored Data CRISP-ML(Q)
🎯
Business
📊
Data
🔧
Preparation
🧠
Modeling
Evaluation
🚀
Deployment
01

🎯 Business Understanding

In retail, Units Sold is not demand — it's demand censored by stock: when a shelf goes empty, real demand existed but became invisible in the records. That's lost sales / unmet demand / demand unconstraining. The question this lab answers:

Can I estimate real (uncensored) demand from observed sales + stockout labels, and does forecasting on the recovered signal produce a better model than forecasting on the observed (censored) signal?

This is a real problem, rarely studied with real labeled stockout data, and it connects directly to the operational frontier: the only way to "see" lost sales is to detect the empty shelf — today, with computer vision / IoT on-shelf availability (OSA). Forecasting + CV + operations: one tightly connected problem.

Project goals:

  • Show the censorship trap: demonstrate that forecasting observed sales trains a model to underestimate demand exactly where it matters most (the stockout occurs because demand exceeded stock).
  • Recover latent demand: estimate real demand in censored hours and reconstruct the unconstrained demand signal.
  • Compare on a clean holdout: evaluate bias and MAE on stock-only days, the only fair comparison.
  • Quantify hidden lost sales: measure the demand that the ERP never recorded.
  • Test recovery across 10 model families — not all models can use the recovered target effectively.

En retail, Units Sold no es demanda — es demanda censurada por el stock: cuando la percha se vacía, la demanda real existió pero quedó invisible en los registros. Eso es venta perdida / demanda no satisfecha / demand unconstraining. La pregunta que responde este lab:

¿Puedo estimar la demanda real (no censurada) a partir de la venta observada + etiquetas de quiebre, y da un modelo mejor pronosticar sobre la señal recuperada que sobre la observada (censurada)?

Es un problema real, poco estudiado con datos reales de quiebre etiquetados, y conecta directamente con la frontera operativa: la única forma de "ver" la venta perdida es detectar la percha vacía — hoy, con visión computacional / IoT de disponibilidad en percha (OSA). Forecasting + CV + operaciones: un problema estrechamente conectado.

Objetivos del proyecto:

  • Mostrar la trampa del censuramiento: demostrar que pronosticar venta observada entrena al modelo para subestimar demanda justo donde más importa (el quiebre ocurre porque la demanda superó al stock).
  • Recuperar la demanda latente: estimar la demanda real en horas censuradas y reconstruir la señal de demanda sin restricción.
  • Comparar en holdout limpio: evaluar bias y MAE en días con stock — la única comparación justa.
  • Cuantificar la venta perdida oculta: medir la demanda que el ERP nunca registró.
  • Probar la recuperación en 10 familias de modelos — no todos los modelos pueden aprovechar el target recuperado de forma efectiva.
Problem type
Tipo de problema
Censored demand estimation + time-series forecasting
Estimación de demanda censurada + pronóstico de series de tiempo
Target
sale_amount observed vs. demand_recovered (estimated)
sale_amount observada vs. demand_recovered (estimada)
Main metrics
Métricas principales
MAE · Bias (mean residual)
Holdout strategy
Estrategia de holdout
Clean holdout — 206k rows with stock only (no censored days)
Holdout limpio — 206k filas solo con stock (sin días censurados)
Stakeholder
Demand Planner · OSA / CV team · S&OP analyst
Demand Planner · equipo OSA / CV · analista S&OP
02

📊 Data Understanding

FreshRetailNet-50K (Dingdong-Inc, HuggingFace) is the first large benchmark for censored demand with labeled stockout annotations — the majority of retail datasets hide this. 50,000 store×product series, 90 days, hourly resolution; 898 stores, 18 cities, 863 perishable SKUs.

Verdict: this dataset has real censorship and real signal — unlike synthetic datasets where drivers are noise.

FreshRetailNet-50K (Dingdong-Inc, HuggingFace) es el primer benchmark grande de demanda censurada con anotaciones de quiebre de stock etiquetadas — la mayoría de los datasets de retail esconden esto. 50.000 series tienda×producto, 90 días, resolución horaria; 898 tiendas, 18 ciudades, 863 SKUs perecederos.

Veredicto: este dataset tiene censura real y señal real — a diferencia de los sintéticos donde los drivers son ruido.

Diagnostic Diagnóstico Result Resultado
% store-hours in stockout% horas-tienda en quiebre 24.9%
% days with ≥1 OOS hour% días con ≥1 hora de quiebre 44.3%
Avg OOS hours per day with stockoutHoras OOS promedio por día con quiebre 7.2 of 16 commercial hours (~45% of commercial day)
Hourly sale: OOS vs. in-stockVenta horaria: OOS vs. con stock 0.004/h vs. 0.054/h → 13× less during stockout
Hidden lost sales (this run)Venta perdida oculta (esta corrida) ≈19% of observed volume · 866,515 units
corr(sale, discount) −0.333 (real price elasticity)(elasticidad de precio real)
Holiday liftLift en feriados +26.9% (holiday vs. non-holiday mean daily sale)(venta media diaria, feriado vs. no feriado)

⚠️ Honest caveat: real demand is unobservable — the recovered demand is an estimate, never evaluated against ground truth. Numbers below are from the full 50,000-series run (N_SERIES = None). The bias improvement and lost-sales estimate remain estimate- and imputation-dependent.

⚠️ Caveat honesto: la demanda real es inobservable — la demanda recuperada es una estimación, nunca se evalúa contra la verdad. Los números de abajo son de la corrida completa de 50.000 series (N_SERIES = None). La mejora de bias y la estimación de venta perdida siguen dependiendo del supuesto de imputación.

03

🔧 Data Preparation

The core data preparation challenge: constructing the recovered demand target. In censored hours (hours_stock_status == 1), the observed sale is suppressed. The estimation strategy: impute from the same product's in-stock hourly rate within the same day, then aggregate to daily demand.

El desafío central de preparación de datos: construir el target de demanda recuperada. En las horas censuradas (hours_stock_status == 1), la venta observada está suprimida. La estrategia de estimación: imputar desde la tasa horaria con stock del mismo producto en el mismo día, luego agregar a demanda diaria.

# Censored demand recovery — the core operation.
# hours_stock_status: array[24], where 1 = STOCKOUT (empty shelf), 0 = in-stock.
# hours_sale: array[24], hourly sales.

# Step 1: compute mean in-stock hourly rate for this series × day
in_stock_mask = (hours_stock_status == 0)
in_stock_rate = hours_sale[in_stock_mask].mean()  # sale per in-stock hour

# Step 2: impute censored hours with in-stock rate
recovered_hours = hours_sale.copy()
recovered_hours[~in_stock_mask] = in_stock_rate   # fill OOS hours

# Step 3: aggregate to daily recovered demand
demand_recovered = recovered_hours.sum()           # unconstrained daily demand

# Holdout: evaluate ONLY on days where no stockout occurred.
# On censored days, sale_amount and demand_recovered are both estimates — not fair targets.
holdout_clean = holdout_df[holdout_df['oos_hours_frac'] == 0]

Feature engineering:

  • Sales lags and rolling windows shifted ≥ horizon (no leakage by construction).
  • Exogenous signals: discount (corr −0.333), holiday_flag (+26.9% lift), weather (avg_temperature, precpt).
  • Censorship features: OOS fraction per day, prior-day OOS indicator — available at forecast time.

Feature engineering:

  • Lags y rolling de ventas desplazados ≥ horizonte (sin leakage por construcción).
  • Señales exógenas: discount (corr −0.333), holiday_flag (lift +26.9%), clima (avg_temperature, precpt).
  • Features de censura: fracción de horas OOS por día, indicador OOS del día anterior — disponibles en el momento del pronóstico.
04

🧠 Modeling

10 model families tested across two training targets: observed sales (censored) vs. recovered demand (unconstrained). Evaluated on the clean holdout (206k rows, stock-only days). Lower MAE and bias closer to 0 is better.

10 familias de modelos probadas sobre dos targets de entrenamiento: venta observada (censurada) vs. demanda recuperada (sin restricción). Evaluadas en el holdout limpio (206k filas, solo días con stock). MAE menor y bias más cercano a 0 es mejor.

Model Modelo Bias (observed) Bias (observado) Bias (recovered) Bias (recuperado) MAE change Cambio MAE
Naive / Moving AverageNaive / Media Móvil −0.132 bias flips positivebias invierte a positivo MAE risesMAE sube
ETS / Holt-Winters · Theta · SARIMA −0.132 bias flips positivebias invierte a positivo MAE risesMAE sube
Prophet −0.132 ≈ 0 +7% MAE, bias ≈ 0+7% MAE, bias ≈ 0
LSTM −0.132 mixed — depends on depthmixto — depende de la profundidad MAE rises slightlyMAE sube levemente
LightGBM / XGBoost / CatBoost −0.132 ≈ 0 (−0.014) +7% MAE, bias ≈ 0

The non-free-lunch finding: recovering the target raises MAE for almost all models — the recovered signal is noisier. But only gradient boosters and Prophet (both feature-rich) maintain bias close to zero. Pure univariate extrapolators improve MAE but flip to positive bias — they raise the level without being able to localize the correction. Recovering the target is necessary, but so is a model rich enough to use it.

El hallazgo del non-free-lunch: recuperar el target sube el MAE de casi todos los modelos — la señal recuperada es más ruidosa. Pero solo los gradient boosters y Prophet (ambos ricos en features) mantienen el bias cercano a cero. Los extrapoladores univariados puros mejoran el MAE pero invierten a bias positivo — suben el nivel sin poder localizar la corrección. Recuperar el target es necesario, pero también hace falta un modelo lo bastante rico para usarlo.

05

✅ Evaluation

Full benchmark results (50,000 series, 4.5M rows):

Resultados del benchmark completo (50.000 series, 4.5M filas):

Scenario Escenario Bias MAE Notes Notas
Baseline — forecast observed sales Baseline — pronosticar venta observada −0.132 0.402 What everyone does — trains to underestimateLo que hace todo el mundo — entrena para subestimar
LightGBM on recovered demand LightGBM sobre demanda recuperada −0.014 0.378 Bias nearly closed, MAE −5.9%Bias casi cerrado, MAE −5.9%
Naive / univariate on recovered demand Naive / univariado sobre demanda recuperada +positive (flipped)+positivo (invertido) risessube Over-corrects — can't localize the fixSobre-corrige — no puede localizar la corrección

Segment analysis: censorship-prone vs. stable series

The improvement concentrates where censorship lives. For stockout-prone series (high OOS fraction):

  • Bias: −0.154 → −0.022 (nearly closed)
  • For stable series (low OOS fraction): recovery adds minimal gain — they were already well estimated.

This is the correct result: demand recovery should only move the signal where the censorship was. A uniform improvement would be suspicious.

Análisis por segmento: series propensas a quiebre vs. estables

La mejora se concentra donde vive la censura. Para series propensas a quiebre (fracción OOS alta):

  • Bias: −0.154 → −0.022 (casi cerrado)
  • Para series estables (fracción OOS baja): la recuperación agrega ganancia mínima — ya estaban bien estimadas.

Este es el resultado correcto: la recuperación de demanda debe mover la señal solo donde hubo censura. Una mejora uniforme sería sospechosa.

⚠️ The ground truth caveat: recovered demand is estimated, never validated against true demand (which is unobservable). The bias improvement measures how much the model's systematic underestimation closes — not how close it is to the "real" number. This is the fundamental limit of all censored demand methods.

⚠️ El caveat de ground truth: la demanda recuperada es una estimación, nunca validada contra la demanda real (que es inobservable). La mejora de bias mide cuánto se cierra la subestimación sistemática del modelo — no qué tan cerca está del número "real". Este es el límite fundamental de todos los métodos de demanda censurada.

06

🚀 Deployment

This lab has no Streamlit demo — the deliverable is the methodology and the quantification. The notebooks (notebooks/Censored_Demand_CRISPML.ipynb EN and notebooks/Censored_Demand_CRISPML_ES.ipynb ES, bilingual twins) are the primary artifact.

The business angle: lost sales your ERP never recorded

The hidden lost sales estimate (≈19% of observed volume, 866,515 units in this run) is the number that resonates in a boardroom. When a shelf is empty for 45% of a commercial day and the ERP records ~0 sales, the planning system trains on the censored signal and plans for a future with the same systematic underestimation. The cycle repeats.

Operational bridge: connecting to Computer Vision / OSA

Demand unconstraining requires stockout labels. Today those labels come from manual shelf audits — expensive, infrequent, and delayed. The next step is detecting empty shelves automatically via computer vision (OSA), feeding real-time stockout labels into the demand recovery pipeline. That closes the loop: CV detects the censorship event, forecasting corrects for it, planning acts on the real demand signal.

Este lab no tiene demo Streamlit — el entregable es la metodología y la cuantificación. Los notebooks (notebooks/Censored_Demand_CRISPML.ipynb EN y notebooks/Censored_Demand_CRISPML_ES.ipynb ES, gemelos bilingües) son el artefacto principal.

El ángulo de negocio: venta perdida que el ERP nunca registró

La estimación de venta perdida oculta (≈19% del volumen observado, 866.515 unidades en esta corrida) es el número que resuena en una sala de directivos. Cuando una percha está vacía el 45% del día comercial y el ERP registra ~0 ventas, el sistema de planning entrena sobre la señal censurada y planea para un futuro con la misma subestimación sistemática. El ciclo se repite.

El puente operativo: conexión con Visión Computacional / OSA

El demand unconstraining requiere etiquetas de quiebre. Hoy esas etiquetas vienen de auditorías manuales de percha — caras, infrecuentes y tardías. El siguiente paso es detectar perchas vacías automáticamente via visión computacional (OSA), alimentando etiquetas de quiebre en tiempo real al pipeline de recuperación de demanda. Eso cierra el ciclo: CV detecta el evento de censura, forecasting lo corrige, planning actúa sobre la señal de demanda real.

What this lab teaches that most don't:

  • Your demand signal is right-censored. If you're forecasting Units Sold without checking for stockout censorship, you're training a model to underestimate demand in exactly the situations where underestimation hurts most.
  • Recovery is not a free lunch. Recovering the target almost always raises MAE. The win is in bias, not in point-error. Test across multiple model families before claiming recovery works.
  • Only feature-rich models can use the recovered signal. Univariate extrapolators can't localize the correction — they overshoot and flip to positive bias. Gradient boosting + rich features is the effective combination.
  • The improvement concentrates where it should. Recovery gains are largest in stockout-prone series. Stable series don't need it. This is the sanity check that the method is doing the right thing.
  • 19% of observed volume is invisible to your ERP. That's not a data quality problem — it's a structural consequence of how stockouts interact with sales records. The only fix is to measure demand, not sales.

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

  • Tu señal de demanda está censurada por la derecha. Si pronosticas Units Sold sin chequear censura por quiebre, entrenas al modelo para subestimar demanda justo en las situaciones donde más duele subestimar.
  • La recuperación no es un free lunch. Recuperar el target casi siempre sube el MAE. La ganancia está en el bias, no en el error puntual. Prueba en múltiples familias de modelos antes de declarar que la recuperación funciona.
  • Solo los modelos ricos en features pueden usar la señal recuperada. Los extrapoladores univariados no pueden localizar la corrección — se pasan y invierten el bias a positivo. Gradient boosting + features ricas es la combinación efectiva.
  • La mejora se concentra donde debe. Las ganancias de recuperación son mayores en series propensas a quiebre. Las series estables no la necesitan. Ese es el chequeo de cordura de que el método está haciendo lo correcto.
  • El 19% del volumen observado es invisible para tu ERP. No es un problema de calidad de datos — es una consecuencia estructural de cómo los quiebres interactúan con los registros de venta. La única solución es medir demanda, no venta.
💡

Business Impact

Impacto de Negocio

The headline number: ≈19% of observed sales volume — 866,515 units in this run — is demand that occurred during stockout hours and was never recorded in any system. A planning process that uses only observed sales structurally under-orders, under-staffs, and under-promotes for the fraction of demand that its own data cannot see.

El número que importa: ≈19% del volumen de ventas observado — 866.515 unidades en esta corrida — es demanda que ocurrió durante horas de quiebre y nunca se registró en ningún sistema. Un proceso de planning que solo usa venta observada sub-ordena, sub-asigna personal y sub-promociona estructuralmente para la fracción de demanda que sus propios datos no pueden ver.

  • Bias correction (−0.132 → −0.014): the model stops systematically under-forecasting. Each unit of systematic under-forecast translates directly into excess stockouts and excess lost sales — uncorrected bias compounds over every forecast cycle.
  • MAE reduction (−5.9%): on a clean holdout, smaller errors mean tighter safety stock buffers at the same service level — or higher service level at the same stock.
  • Segment targeting: the improvement concentrates in stockout-prone series. That's where a retailer's OSA investment (shelf cameras, IoT sensors) pays off most directly.
  • Service-availability gap (~84% OSA): read the ≈19% of invisible volume as a gross service gap — suppressed demand ÷ observed sales — and the business runs, in potential volume, near ~84% of real availability while the ERP reports 100%. This is a gross-censoring estimate on the full run, distinct from a model-bias metric.
  • The trap compounds (bullwhip): training on censored sales doesn't only lower the forecast — it depresses the ERP's safety-stock math (lower mean, clipped variance), so buffers are set too low and the system stays chronically vulnerable to the next stockout. The dataset's own authors describe this as a self-perpetuating cycle of underestimation and waste.
  • Corrección de bias (−0.132 → −0.014): el modelo deja de sub-pronosticar sistemáticamente. Cada unidad de sub-pronóstico sistemático se traduce directamente en más quiebres y más venta perdida — el bias no corregido se acumula en cada ciclo de pronóstico.
  • Reducción de MAE (−5.9%): en un holdout limpio, errores menores permiten buffers de safety stock más ajustados al mismo nivel de servicio — o mayor nivel de servicio al mismo stock.
  • Targeting por segmento: la mejora se concentra en series propensas a quiebre. Ahí es donde la inversión de OSA de un retailer (cámaras de percha, sensores IoT) rinde más directamente.
  • Brecha de disponibilidad (~84% OSA): léase ese ≈19% de volumen invisible como brecha bruta de servicio —demanda suprimida ÷ venta observada— y el negocio opera, en volumen potencial, cerca del ~84% de disponibilidad real mientras el ERP reporta 100%. Es una estimación de censura bruta sobre la corrida completa, distinta de una métrica de sesgo de modelo.
  • La trampa se acumula (efecto látigo): entrenar sobre venta censurada no solo baja el pronóstico — deprime el cálculo de stock de seguridad del ERP (media más baja, varianza recortada), así los buffers quedan demasiado bajos y el sistema queda crónicamente vulnerable al siguiente quiebre. Los propios autores del dataset lo describen como un ciclo auto-perpetuante de subestimación y desperdicio.
🔍

Sources

Fuentes

Quantitative claims and methodological decisions are supported by the dataset's verified profiling, quality-gated analyses, and the following references.

Las afirmaciones cuantitativas y decisiones metodológicas se apoyan en el profiling verificado del dataset, análisis con quality gates, y las siguientes referencias.

Dataset

Dataset

FreshRetailNet-50K (Dingdong-Inc, HuggingFace). First large benchmark for censored demand with labeled stockout annotations. 50,000 series, 4.5M rows, hourly resolution. Paper: Wang Y. et al. (2026). FreshRetailNet-50K: A Stockout-Annotated Censored Demand Dataset for Latent Demand Recovery and Forecasting in Fresh Retail. arXiv:2505.16319.

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)

Censored demand / demand unconstraining

Demanda censurada / demand unconstraining

Vulcano G., van Ryzin G. & Ratliff R. (2012). Estimating Primary Demand for Substitutable Products from Sales Transaction Data. Operations Research 60(2). · Talluri K. & van Ryzin G. (2004). The Theory and Practice of Revenue Management. Springer.

Classical forecasting (statsforecast)

Forecasting clásico (statsforecast)

Nixtla statsforecast — Naive, MovingAverage, AutoETS, AutoTheta, AutoARIMA.

On-shelf availability (OSA) and lost sales

Disponibilidad en percha (OSA) y venta perdida

ToolsGroup — Cost of Stockouts vs Overstock · ECR Europe. (2003). On-shelf availability: reducing out-of-stocks in the fast moving consumer goods industry.