Technical Lab · Synthetic demonstration

Intermittent demand

Intermittent-demand methods are useful when zeros are the normal week. They are not a reason to abandon a mean once demand becomes regular.

All technical examples

Synthetic demonstration · Independent educational implementation.

Forecast demand when many weeks are zero and the non-zero weeks come in irregular sizes.

A method that treats every week alike can keep ordering through a long drought, or it can forget the size of a demand when one finally occurs.

Watch a zero run

  • Demand; open dots are zero weeks
  • Historical mean
  • Classic Croston
  • TSB
In a run of zeros, classic Croston holds its rate while TSB lets the rate fall. Bars are synthetic weekly demand, including zeros. Lines are one-step forecasts made without seeing that week. Classified intermittent.

A 20% chance of demand produces an intermittent series: ADI 7.2, CV² 0.1, and 86% zeros. One-step mean absolute error on the scored weeks is mean 1.9, classic Croston 1.9, TSB 1.8. It ends with 5 zero weeks. Classic Croston’s next rate stays at 1.2 because zeros do not update it. TSB’s next rate is 1.0 because the occurrence probability falls during that run. These errors are for this synthetic series only.

The updates

Classic Croston (uncorrected) forecast = smoothed size / smoothed interval. Zeros leave both smoothers unchanged. This is not the Syntetos–Boylan bias correction. TSB forecast = probability × size. Probability moves every week. Size moves only when demand occurs.

Implementation

src/lib/lab/intermittent.ts View source on GitHub: src/lib/lab/intermittent.ts

Classic Croston updates only on a positive demand, then the rate is size divided by interval.src/lib/lab/intermittent.ts
export function croston(demand: number[], alpha: number): RateForecast {
  assertUnitInterval(alpha, 'Alpha');
  const first = assertDemand(demand);
  let size = demand[first];
  let interval = first + 1;
  let since = 0;
  const fitted = Array(demand.length).fill(0);
  for (let time = first + 1; time < demand.length; time += 1) {
    fitted[time] = size / interval;
    since += 1;
    if (demand[time] > 0) {
      size += alpha * (demand[time] - size);
      interval += alpha * (since - interval);
      since = 0;
    }
  }
  return { fitted, next: size / interval, size, intervalOrProbability: interval };
}
Show the TSB update
TSB smooths probability every period, including zeros.src/lib/lab/intermittent.ts
export function tsb(demand: number[], alpha: number, beta = alpha): RateForecast {
  assertUnitInterval(alpha, 'Alpha');
  assertUnitInterval(beta, 'Beta');
  const first = assertDemand(demand);
  let size = demand[first];
  let probability = 1 / (first + 1);
  const fitted = Array(demand.length).fill(0);
  for (let time = first + 1; time < demand.length; time += 1) {
    fitted[time] = probability * size;
    const occurred = demand[time] > 0 ? 1 : 0;
    probability += beta * (occurred - probability);
    if (occurred) size += alpha * (demand[time] - size);
  }
  return { fitted, next: probability * size, size, intervalOrProbability: probability };
}