Retail, Demand and Supply Chain
Intermittent demand and mostly-zero series
Some products sell in bursts with long silent gaps between, and an ordinary moving average sees only the zeros and predicts nothing will sell.
- 9 min read
- 3 reading levels
- Published
Read these first
On this page 6
One lesson, three depths. Pick the one that fits you today — you can switch any time.
Beginner — No maths. Plain English.
Some products barely sell most days, then sell a bunch all at once. Ordinary forecasting tools misread that pattern as "nothing sells here."
Think of a hardware shop's shelf of a specific bolt size. Most days, nobody buys a single one. Then a contractor walks in needing two hundred of them for a job, buys the lot, and leaves. Weeks of silence, then a spike.
That bolt still has real, predictable demand. It does not arrive evenly.
Why it exists
Most forecasting methods were built for demand that shows up a little every day — milk, bread, phone recharge. Averages and smoothing work well there.
Spare parts, niche sizes, and B2B bulk items behave differently. Picture 60 days where a product sold on only 9 of them. A plain moving average of the last 10 days would look at that window, see mostly zeros, and forecast close to zero. That forecast is worse than useless — it tells the shop to stop stocking a product that customers do want, only not every day.
This pattern is called intermittent demand — intermittent meaning it starts and stops, rather than flowing steadily. It needs its own toolkit.
How it works
Ordinary demand (milk): 3 4 2 5 3 4 2 ... → averaging works fine
Intermittent demand (a bolt): 0 0 5 0 0 0 0 7 0 0 0 0 0 6 ...
^ ^ ^
a burst a burst a burstThe trick that actually works, called Croston's method after the statistician who invented it in 1972, separates two questions instead of averaging blindly:
- When a sale does happen, how big is it usually?
- How many days usually pass between sales?
Divide the first by the second, and you get a sensible daily rate — even though most days show zero.
Where you have already seen it
- A car workshop's rare spare part, like a specific model's headlight assembly — silent for weeks, then three needed after a batch of accidents.
- A pharmacy's less common medicine, prescribed occasionally but not daily.
- A wholesaler's bulk packaging material, ordered by shopkeepers a few times a month, in large quantities each time.
An honest warning
Intermittent demand is genuinely one of the harder forecasting problems in retail, and there is no trick that removes the uncertainty. A burst that happened last month might not repeat this month at all. Every method here gives you a better-informed guess, not a guarantee.
Remember this
- Mostly-zero sales does not mean mostly-zero demand — it can mean demand arrives in occasional bursts.
- A plain average over a short recent window sees mostly zeros and badly underforecasts.
- Purpose-built methods like Croston's separate "how big" from "how often" instead of averaging both together.
What to learn next
- Making store, region and national forecasts add up — the next layer once individual series are forecast.
- You measure sales, not demand — a stockout during a burst hides how big the burst really was.
- What is time series data? — the general forecasting ideas this builds on.
Developer — Code and libraries.
Setup
pip install numpyMinimal runnable code
We simulate 60 days of a spare part that only sells on some days, then compare a plain moving average against Croston's method.
import numpy as np
rng = np.random.default_rng(3)
# A slow-moving spare part: mostly zero days, occasional bulk orders.
n_days = 60
demand = np.zeros(n_days, dtype=int)
for t in range(n_days):
if rng.random() < 0.15: # a sale happens on ~15% of days
demand[t] = rng.integers(4, 12)
print("demand series:", demand.tolist())
print("share of zero days:", (demand == 0).mean())
print("plain moving average of last 10 days:", demand[-10:].mean())
print()
def croston(series, alpha=0.2):
"""Croston's method: smooth demand size and inter-arrival interval separately."""
first_nonzero = next(i for i, d in enumerate(series) if d > 0)
z = series[first_nonzero] # smoothed non-zero demand size
p = first_nonzero + 1 # smoothed interval between non-zero demands
since_last = 0
forecast = np.zeros(len(series))
for t in range(first_nonzero + 1, len(series)):
since_last += 1
if series[t] > 0:
z = alpha * series[t] + (1 - alpha) * z
p = alpha * since_last + (1 - alpha) * p
since_last = 0
forecast[t] = z / p
return forecast
forecast = croston(demand)
print("Croston forecast, last 10 days:", np.round(forecast[-10:], 2).tolist())
print("Croston forecast, average rate per day:", round(forecast[-1], 3))
print("true long-run average rate per day:", round(demand.mean(), 3))demand series: [5, 0, 0, 5, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 6, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] share of zero days: 0.8666666666666667 plain moving average of last 10 days: 0.0 Croston forecast, last 10 days: [1.29, 1.29, 1.29, 1.29, 1.29, 1.29, 1.29, 1.29, 1.29, 1.29] Croston forecast, average rate per day: 1.285 true long-run average rate per day: 0.95
What actually happened
The last 10 days of this series happen to be all zeros. A plain moving average over that window forecasts exactly 0.0 units per day — telling a buyer to stop ordering a product with real demand.
Croston's method forecasts 1.29 units per day instead, close to the true long-run rate of 0.95. It never resets to zero, because it is not averaging the raw series at all.
z tracks the typical size of a sale when one happens (updated only on non-zero days). p tracks the typical gap, in days, between sales. z / p converts "how big" and "how often" into one steady daily rate — the same trick as saying "if I get paid ₹8,000 every 4 days, that is ₹2,000 a day on average," even on the three days you get paid nothing.
Common mistakes
Judging Croston's forecast against a single day. It is a smoothed daily rate, meant for setting reorder points over weeks, not for predicting whether today specifically will have a sale.
Using a moving average window that is shorter than the typical gap between sales. A 10-day window on a product that sells every 20 days will regularly show all zeros, as seen above.
Picking alpha too high. A large alpha reacts hard to the most recent burst size, and the forecast swings wildly after every sale. Start near 0.1–0.3 and tune from there.
Forgetting this interacts with stockouts. A burst that got capped by low shelf stock looks like a small burst in the data. Combine this with You measure sales, not demand when both problems are present.
Try it yourself
Change the sale probability from 0.15 to 0.05, making sales rarer. Re-run, and watch the plain moving average sit at zero for even longer while Croston's forecast keeps producing a usable number.
What to learn next
- Making store, region and national forecasts add up
- Anomaly detection — telling a genuine demand burst apart from a data error.
- Exponential smoothing — the general technique Croston's method is built from.
Researcher — Mathematics and papers.
Croston's method, formally
Split the series y_t into non-zero demand sizes z_1, z_2, ... occurring at intervals p_1, p_2, ... (the number of periods since the previous non-zero observation). Both are smoothed independently with simple exponential smoothing:
z_i = alpha * y_t + (1 - alpha) * z_{i-1} (updated only when y_t > 0)
p_i = alpha * q_t + (1 - alpha) * p_{i-1} (updated only when y_t > 0)alpha— smoothing constant in (0, 1)q_t— periods elapsed since the last non-zero observation- The forecast at any time is
y_hat = z_i / p_i, held constant between non-zero observations
Croston (1972), Forecasting and Stock Control for Intermittent Demands, Operational Research Quarterly, is the origin.
The bias, and the standard correction
Syntetos and Boylan (2005), The Accuracy of Intermittent Demand Estimates, International Journal of Forecasting, proved Croston's estimator is biased upward under standard assumptions, because E[z/p] != E[z]/E[p] by Jensen's inequality when p is itself a random variable. The Syntetos-Boylan Approximation (SBA) applies a correction factor:
y_hat_SBA = (1 - alpha/2) * (z / p)SBA is the de facto default in production forecasting libraries over plain Croston.
Classifying a series before choosing a method
Syntetos, Boylan and Croston (2005) propose classifying each series along two axes before choosing a method:
- Average inter-demand interval (ADI) — mean number of periods between non-zero demands.
- Coefficient of variation squared (CV^2) of the non-zero demand sizes — variance divided by the squared mean.
low CV^2 high CV^2
ADI < 1.32 "smooth" "erratic"
ADI >= 1.32 "intermittent" "lumpy"Smooth series are well served by ordinary exponential smoothing. Lumpy series — high variability in both timing and size — are the hardest case, and even SBA degrades; quantile-based or bootstrap methods (Willemain et al., 2004) tend to outperform there.
Why this connects directly to inventory decisions
A point forecast of "1.29 units per day" is not by itself an ordering decision. Intermittent demand forecasting exists to feed a reorder-point or safety-stock calculation that needs the full distribution of demand over a lead time, not only its mean — see Turning a forecast into an order quantity. Evaluating an intermittent forecast with ordinary error metrics like RMSE is also misleading, since predicting zero every day scores deceptively well on a mostly-zero series; the field instead favours metrics built for this case, such as the Mean Absolute Scaled Error (Hyndman and Koehler, 2006) or a stock-out-cost-weighted evaluation tied directly to the business decision.
Key references
- Croston, J. (1972). Forecasting and Stock Control for Intermittent Demands. Operational Research Quarterly 23(3).
- Syntetos, A. & Boylan, J. (2005). The Accuracy of Intermittent Demand Estimates. International Journal of Forecasting 21(2).
- Willemain, T., Smart, C. & Schwarz, H. (2004). A New Approach to Forecasting Intermittent Demand for Service Parts Inventories. International Journal of Forecasting 20(3).