ENGINEERING THE NEXT GENERATION

Logo
Home/Blog/Croston, SBA and TSB in Python: Forecasting Intermittent Demand Step by Step
Demand PlanningForecastingPythonPredictive AnalyticsInventory

Croston, SBA and TSB in Python: Forecasting Intermittent Demand Step by Step

October 10, 2026
Code editor glowing beside sparse demand bars, representing intermittent demand forecasting in Python
Fifty lines of Python cover the three standard intermittent-demand methods. Judging them fairly takes a few more.

TL;DR & Quick Summary

Croston's method, the SBA correction and TSB are the three standard methods for intermittent demand, meaning items that sell nothing in most periods. All three take only a few lines of Python. This guide gives working code, checks it against the statsforecast library, and backtests it on 200 items. It also shows a result most tutorials leave out.

  • Croston: smooths demand size and the interval between sales separately. Forecast = size ÷ interval.

  • SBA: Croston × (1 − α/2). It removes most of Croston's upward bias.

  • TSB: smooths the probability of a sale every period, so the forecast falls when an item stops selling.

  • Verified: the hand-written Croston below matches statsforecast's CrostonClassic exactly.

  • The honest result: in our backtest, a 13-week moving average beat all three on MASE. Croston-type methods still won on bias and on fading items.

  • Key Takeaway: Implementing the methods is easy. Classifying items correctly, testing against a simple benchmark, and judging by bias as well as error is where the value is.

  • Get Started: For the concepts behind this code, read intermittent demand forecasting: Croston, SBA and TSB explained. To have it applied to your catalogue, schedule a strategy call with Cogniq AI.


What Each Method Does

Croston's method forecasts intermittent demand by smoothing two things separately: the size of each non-zero demand (z) and the number of periods between non-zero demands (p). Both are updated only when a sale happens. The forecast per period is z ÷ p. The method comes from Croston (1972).

SBA (Syntetos–Boylan Approximation) multiplies the Croston forecast by (1 − α/2), where α is the smoothing constant. Syntetos and Boylan (2005) showed this removes most of Croston's tendency to forecast too high.

TSB (Teunter–Syntetos–Babai) smooths the probability that a sale occurs in each period instead of the interval, and updates it every period, including periods with no sales. Its forecast falls during long runs of zeros, which suits items that may be dying out. It comes from Teunter, Syntetos and Babai (2011).

Step 1: Classify Your Items

Only apply these methods to items that actually need them. The standard classification uses two numbers per item:

  • ADI: average number of periods per non-zero demand
  • CV²: squared coefficient of variation of the non-zero demand sizes

Items with ADI ≥ 1.32 are intermittent (low CV²) or lumpy (high CV²). The cut-offs come from Syntetos, Boylan and Croston (2005).

python
import numpy as np

def classify(y):
    y = np.asarray(y, dtype=float)
    nz = y[y > 0]
    if len(nz) < 2:
        return np.nan, np.nan, "insufficient"
    adi = len(y) / len(nz)
    cv2 = (nz.std(ddof=1) / nz.mean()) ** 2
    if adi < 1.32:
        cat = "smooth" if cv2 < 0.49 else "erratic"
    else:
        cat = "intermittent" if cv2 < 0.49 else "lumpy"
    return adi, cv2, cat

Run it at the level you make stocking decisions, usually item by location. Remove stockout periods first: zero sales while out of stock is missing supply, not missing demand.

Step 2: Croston and SBA

python
def croston(y, alpha=0.1, variant="classic"):
    """Flat per-period forecast using Croston's method or the SBA variant."""
    y = np.asarray(y, dtype=float)
    nz = np.flatnonzero(y)
    if len(nz) == 0:
        return 0.0
    z = y[nz[0]]          # smoothed demand size
    p = nz[0] + 1         # smoothed interval (periods since start)
    last = nz[0]
    for t in nz[1:]:
        z = alpha * y[t] + (1 - alpha) * z
        p = alpha * (t - last) + (1 - alpha) * p
        last = t
    f = z / p
    if variant == "sba":
        f *= 1 - alpha / 2
    return f

The loop only visits periods with demand. That's the core idea: empty periods don't change the estimates.

Check it with the worked example from our intermittent demand guide. Demand of 5, then 3 four periods later, then 6 two periods later, with α = 0.2:

python
ex = [0, 0, 5, 0, 0, 0, 3, 0, 6]
croston(ex, 0.2)          # 1.649
croston(ex, 0.2, "sba")   # 1.484

Those match the hand calculation in that article: 1.65 and 1.48 units per period.

Step 3: TSB

python
def tsb(y, alpha_d=0.1, alpha_p=0.1):
    """Teunter-Syntetos-Babai: smooth demand probability every period."""
    y = np.asarray(y, dtype=float)
    nz = np.flatnonzero(y)
    if len(nz) == 0:
        return 0.0
    prob = (y > 0).mean()    # simple initialisation
    size = y[nz].mean()
    for v in y:
        if v > 0:
            prob = alpha_p + (1 - alpha_p) * prob
            size = alpha_d * v + (1 - alpha_d) * size
        else:
            prob = (1 - alpha_p) * prob
    return prob * size

The difference from Croston is in the else branch. Every empty period lowers the probability estimate, so the forecast decays when an item stops selling. Croston and SBA would hold their last value indefinitely.

The initialisation here is a simple choice. Libraries may start the estimates differently, which changes early forecasts slightly but matters less as history grows.

Step 4: Cross-Check Against statsforecast

Before trusting hand-written code, compare it with a maintained library. statsforecast from Nixtla includes CrostonClassic, CrostonSBA, CrostonOptimized and TSB:

python
import pandas as pd
from statsforecast import StatsForecast
from statsforecast.models import CrostonClassic, CrostonSBA, TSB

df = pd.DataFrame({
    "unique_id": "sku",
    "ds": pd.date_range("2024-01-01", periods=len(history), freq="W"),
    "y": history,
})
sf = StatsForecast(
    models=[CrostonClassic(), CrostonSBA(), TSB(alpha_d=0.1, alpha_p=0.1)],
    freq="W",
)
print(sf.forecast(df=df, h=4))

Three things are worth knowing about the library (checked against statsforecast 2.1.1):

  • CrostonClassic uses a fixed α of 0.1. On the same item, our croston(history) returned 3.220, identical to the library's 3.220164.
  • CrostonSBA applies a fixed factor of 0.95, which is 1 − 0.1/2. Our SBA gave 3.059, matching the library.
  • TSB has no defaults: you must pass alpha_d and alpha_p. CrostonOptimized chooses the smoothing constant for you.

In production, use the library. It's tested, vectorised across thousands of series, and supports prediction intervals. Hand-written versions are for understanding the method and checking assumptions.

Step 5: Backtest Fairly

The forecast is a rate, so it will never match any single week. Judge it on a held-out period with metrics that work when demand is zero:

python
def mase(actual, forecast, history):
    scale = np.mean(np.abs(np.diff(history)))  # in-sample naive error
    return np.mean(np.abs(actual - forecast)) / scale if scale > 0 else np.nan

bias = lambda actual, forecast: np.mean(forecast - actual)

MASE (Hyndman and Koehler, 2006) divides your error by the error of a naive forecast, so values below 1 beat that benchmark. Bias is the signed average error. For slow movers it often matters more than error size, because it turns directly into excess stock or stockouts.

What We Found on 200 Items

We generated 200 synthetic items over 104 weeks with a fixed random seed. Each item had its own sale probability (8–90% of weeks) and average order size, and one in ten items faded out during the second year. We held out the last 13 weeks, forecast with each method using α = 0.1, and also tested a plain 13-week moving average. 167 of the items classified as intermittent.

Method Mean MASE (intermittent items) Mean bias, units/week (intermittent) Mean bias, units/week (fading items)
Croston 0.913 +0.321 +2.337
SBA 0.901 +0.168 +2.220
TSB 0.831 +0.022 +0.981
13-week moving average 0.817 +0.016 +0.735

Three lessons:

  1. SBA roughly halved Croston's bias, as the theory predicts, at the cost of one multiplication.
  2. TSB was far less biased on fading items, because its forecast decays when sales stop, while Croston and SBA kept forecasting demand that no longer existed.
  3. The simple moving average did best on MASE. A recent-window average adapts naturally to items whose demand is changing. On this data, the more sophisticated methods didn't earn their complexity on accuracy alone.

This is synthetic data, and your results will differ. On catalogues with stable intermittent demand, Croston-type methods often perform better than they did here. The point is the process: always include a simple benchmark, and choose by accuracy and bias on your own history. If the simple method wins, use it.

Choosing the Smoothing Constant

The smoothing constant α controls how quickly the estimates react to new data. A low value, such as 0.05, produces stable forecasts that change slowly. A high value, such as 0.3, reacts quickly but also chases noise.

Practical guidance:

  • Start at 0.1, the value statsforecast uses for CrostonClassic and a common default in the literature
  • Use CrostonOptimized if you want the library to choose the value for each item from its history
  • For TSB, test alpha_p separately. It controls how quickly the forecast falls when an item stops selling. If it's too low, fading items keep receiving stock for months.
  • Pick by holdout performance, not by in-sample fit. Try three or four values and keep the one with the best combination of MASE and bias on held-out weeks.

Remember that SBA's correction depends on α. With α = 0.1 the factor is 0.95, and with α = 0.2 it's 0.9. If you change α, change the correction too. Our function does this automatically.

Common Pitfalls

  • Forecasting at the wrong level. An item can be smooth across the whole business and intermittent at every branch. Forecast where stock decisions are made.
  • Leaving stockouts in the history. Zero sales while out of stock make demand look rarer than it is, so every method will under-forecast.
  • Judging the forecast against weekly actuals on a chart. A rate of 1.5 units a week will never match a week that sold 0 or 6. Compare cumulative totals over the holdout instead.
  • Using MAPE. It's undefined whenever actual demand is zero. Use MASE and bias.
  • Treating the forecast as the order quantity. The forecast is an input to a stocking decision that also needs lead time, service target and the demand distribution.
  • Skipping the benchmark. As our backtest showed, the simplest method can win. Without a benchmark you'd never know.

Putting It Into Production

  • Run per segment: classify items, then apply the method that wins for each segment in backtesting
  • Use the forecast to set stock levels, not just to report. For intermittent items, a distribution-based method such as Poisson works better than mean plus normal safety stock. See safety stock calculation formulas
  • Reclassify quarterly, as items move between categories
  • Monitor bias after go-live, not only error

If your data is too short for a reliable backtest, see how much historical data demand forecasting needs.

Conclusion

Croston, SBA and TSB take about fifty lines of Python, and statsforecast gives you production-ready versions. The harder and more valuable part is classifying items, removing stockout periods, testing against a simple benchmark, and judging by bias as well as error. In our backtest, a moving average was the most accurate and TSB was the least biased on fading items. Your data decides which one belongs in production.

Schedule a strategy call with Cogniq AI to have your slow-moving catalogue backtested, or explore our predictive analytics services.

Frequently Asked Questions

Smooth two series with exponential smoothing, updating them only in periods where demand occurs: the size of each non-zero demand, and the number of periods since the previous one. The forecast per period is the smoothed size divided by the smoothed interval. The function in this article does this in about fifteen lines of NumPy, and its output matches the CrostonClassic model in the statsforecast library exactly when the same smoothing constant is used.

SBA is Croston multiplied by a bias-correction factor of one minus half the smoothing constant. With a smoothing constant of 0.1 that factor is 0.95, which is exactly what statsforecast's CrostonSBA applies. In code it is a single extra multiplication, and it reduces Croston's known tendency to forecast too high.

Nixtla's statsforecast includes TSB, along with CrostonClassic, CrostonSBA, CrostonOptimized, ADIDA and IMAPA. TSB requires two smoothing parameters, alpha_d for demand size and alpha_p for demand probability. You can also implement TSB yourself in a few lines, as shown in this article.

No. In the backtest in this article, a 13-week moving average achieved a lower MASE than Croston, SBA and TSB on the intermittent items. Croston-type methods have clear advantages, particularly lower bias with SBA and TSB and faster decay for fading items with TSB, but whether they win on your data has to be tested against a simple benchmark.

Use a holdout period and a scaled error such as MASE, which divides your error by the in-sample error of a naive forecast and works when actual demand is zero. Track bias separately, because a forecast that is consistently high or low creates excess stock or stockouts even when its average error looks acceptable. Do not use MAPE, which is undefined when actual demand is zero.

Values between about 0.05 and 0.3 are typical, with lower values giving more stable forecasts. statsforecast's CrostonClassic uses a fixed 0.1, and CrostonOptimized selects the value automatically. For TSB, a probability smoothing constant that is too low makes the forecast slow to react when an item stops selling, so test a few values on your own holdout data.