Forecasting in Conditions of Uncertainty
Problem
Use Croston’s method when a time series has many zero periods interspersed with sporadic non-zero demand. Standard exponential smoothing collapses because zeros dilute estimates and ARIMA struggles with the structural zeros.
Classic symptoms:
- ADI (Average Demand Interval) > 1.32
- CV² (squared coefficient of variation of non-zero demand) < 0.49
Assumptions & Conditions
- Demand arrivals follow a Bernoulli process (independent across periods).
- Non-zero demand sizes are i.i.d. (no trend, no seasonality within bursts).
- Demand interval and demand size are independent of each other.
Breaks down when: demand has a trend, sizes cluster seasonally, or there’s a long-run structural shift.
Approach
Croston splits the series into two separate ES processes:
where is the non-zero demand size at occurrence , is the interval between occurrences, and is the smoothing parameter.
Forecast per period:
Bias correction (Syntetos-Boylan, SBA): Croston’s estimator is positively biased. SBA fixes this:
Implementation
import numpy as np
from dataclasses import dataclass
@dataclass
class CrostonResult:
forecast: float
z_hat: float # smoothed demand size
p_hat: float # smoothed interval
def croston(demand: np.ndarray, alpha: float = 0.1, variant: str = "sba") -> CrostonResult:
"""
Croston's method for intermittent demand forecasting.
Parameters
----------
demand : 1-D array of demand values (zeros allowed)
alpha : smoothing parameter in (0, 1)
variant: "classic" or "sba" (Syntetos-Boylan bias correction)
Returns
-------
CrostonResult with per-period rate forecast
"""
nonzero_idx = np.where(demand > 0)[0]
if len(nonzero_idx) == 0:
return CrostonResult(forecast=0.0, z_hat=0.0, p_hat=1.0)
# Bootstrap from first occurrence
z_hat = demand[nonzero_idx[0]]
p_hat = float(nonzero_idx[0] + 1) # periods until first demand
for i in range(1, len(nonzero_idx)):
interval = nonzero_idx[i] - nonzero_idx[i - 1]
z_hat = alpha * demand[nonzero_idx[i]] + (1 - alpha) * z_hat
p_hat = alpha * interval + (1 - alpha) * p_hat
rate = z_hat / p_hat
if variant == "sba":
rate *= 1 - alpha / 2
return CrostonResult(forecast=rate, z_hat=z_hat, p_hat=p_hat)
Results & Tradeoffs
| Property | Croston | SBA |
|---|---|---|
| Bias | Positive bias ~5–30% | Near-unbiased |
| Variance | Lower | Slightly higher |
| Best for | Never outperforms SBA | ADI > 1.32, CV² < 0.49 |
When to prefer alternatives:
- TSB (Teunter-Syntetos-Babai): Demand can become extinct (e.g., end-of-life parts). TSB updates probability of non-zero demand each period.
- ADIDA aggregation: Aggregate to non-zero-free bucket, forecast normally, then disaggregate. Simple and often competitive.
- ML on features: If you have rich features (promotions, seasonality), a gradient-boosted model on binned demand usually beats smoothing-based methods.
Typical error metrics: CSL (Cycle Service Level), Fill Rate, MAE on non-zero periods. Avoid MAPE — division by zero on empty periods.
References
- Croston, J.D. (1972). Forecasting and Stock Control for Intermittent Demands. Operational Research Quarterly.
- Syntetos, A.A. & Boylan, J.E. (2005). The Accuracy of Intermittent Demand Estimates. International Journal of Forecasting.
- Teunter, R., Syntetos, A.A. & Babai, M.Z. (2011). Intermittent demand: Linking forecasting to inventory obsolescence. EJOR.