Why Your Sales Forecast Is Always 20% Wrong (And How To Make It 12% Wrong)
Author(s): Kamrun Nahar Originally published on Towards AI. Real World Sales Forecasting Playbook The Single Most Useful Picture I Have Ever Seen The thing that changed how I worked was not a model. It was a 2×2 grid. Two professors put it on paper in 2005. The grid has saved me from picking the wrong model approximately 600 times. The Forecastability Map. Every product on your shelf belongs to one of these four boxes. Pick the wrong box, pick the wrong model. This is the Syntetos-Boylan classification. Two axes. Two numbers per SKU. The x-axis is the Average Demand Interval (ADI). Take all the time periods in your history. Count how many had at least one sale. Now divide the total number of periods by that. If you sell something every day, ADI is 1. If you sell it every other day on average, ADI is 2. The bigger ADI gets, the rarer the SKU. The y-axis is the squared coefficient of variation (CV²) of the non-zero demand sizes. Take only the periods where you sold something. Compute the standard deviation of the quantities. Divide by the mean. Square the result. This tells you how variable the size of demand is when it does happen. The thresholds are 1.32 for ADI and 0.49 for CV². Why those exact numbers. They came from empirical analysis of real industrial data. The math is in a 2005 paper. Don’t go chasing the original PDF, the boundaries are good enough as rules of thumb. Four boxes pop out of this. SMOOTH | INTERMITTENT |Frequent. | Rare.Steady. | Steady when it happens. |------------|------------ |ERRATIC | LUMPY |Frequent. | Rare AND variable.Variable. | The nightmare. Every product on your shelf needs a different forecasting approach. Treating them all the same is why your one big model fails on the long tail. Let me walk you through each box. Pour yourself something. We’ll be a while. Box One. Smooth. This is the dream. Sells every day. Roughly the same quantity. The textbook stuff. A small grocery store sells milk like this. So does a hospital pharmacy with basic painkillers. So does a power company billing residential customers. Frequent. Steady. The data scientist’s friend. For smooth demand, almost any classical method works. AutoARIMA. Exponential Smoothing (ETS). A simple regression with calendar features and a couple of lags. The fancy methods barely beat the simple methods. You will see WAPE around 8 to 15 percent and you will look like a magician. The model will not save you. The data is already easy. The mistake people make on smooth data is over-engineering. They reach for an LSTM. They tune hyperparameters for two weeks. They get a 0.4 percent improvement and call a meeting. import pandas as pdfrom statsforecast import StatsForecastfrom statsforecast.models import AutoETS, AutoARIMA, SeasonalNaivedf = pd.read_csv("smooth_skus.csv", parse_dates=["ds"]) # cols unique_id ds ysf = StatsForecast( models=[AutoETS(season_length=7), AutoARIMA(season_length=7), SeasonalNaive(season_length=7)], freq="D", n_jobs=-1) # daily data, 7-day weekly cycle, all coressf.fit(df) # fits one of each model per unique_idfcst = sf.predict(h=28) # 4 weeks outprint(fcst.head()) # check it. should look boring. boring is good. Line by line, because the Reddit poster asked for thoroughness. import pandas as pd. Pandas is the spreadsheet library every Python data person uses. The as pd is a community nickname so we never have to type the long name. from statsforecast import StatsForecast. The orchestrator class from Nixtla's library. It is fast because it parallelizes across SKUs without you having to write a loop. from statsforecast.models import AutoETS, AutoARIMA, SeasonalNaive. Three classical models. AutoETS picks the best exponential smoothing variant for you. AutoARIMA does the (p,d,q) search you used to do by hand at 2 AM. SeasonalNaive is the dumb baseline that says "last week's same weekday." Always include the dumb baseline. df = pd.read_csv(...). Reads the CSV. The library expects three columns. unique_id (which SKU), ds (which date), y (how many sold). StatsForecast(models=[...], freq="D", n_jobs=-1). We instantiate. freq="D" is daily. n_jobs=-1 means "use every CPU." The library is genuinely fast. sf.fit(df). Behind the scenes, it groups by unique_id and fits each model to each SKU's history. No loop. You're welcome. fcst = sf.predict(h=28). Predict 28 days ahead. Each model produces its own column. print(fcst.head()). Eyeball test. For a smooth SKU, the three forecasts should agree closely. If they wildly disagree, your data isn't actually smooth, and you're in the wrong box. Why this matters. If your SKU is genuinely smooth, this snippet is your whole pipeline. You don’t need LightGBM. You don’t need Prophet. You don’t need a paper from NeurIPS. The smooth demand dream. Live it while you can. Box Two. Intermittent. Now it gets interesting. A roofing supply store sells a particular size of slate tile maybe four times a month. When it sells, it sells two or three boxes at a time. Always two or three. Never twenty. Never zero point seven. Frequency is low. Quantity is consistent. For demand that is rare but steady-quantity, the classical models go quiet. ARIMA wants regular data. ETS wants a trend or a season. There isn’t one. There are just zeros, interrupted by a normal number, then more zeros. Enter John Croston. 1972. Yorkshire. He probably had a slide rule. He had a brilliant idea. Forecast two things separately. How big an order is when it happens. How often orders happen. Divide the first by the second. That’s the whole method. From Watergate-era. And it still beats every neural network on sparse data most of the time. Most of the time. We will get to the exceptions. Croston’s method has a known problem. It is positively biased. It tends to over-forecast. The smoothing parameter beta makes it worse. In 2005, the same Syntetos and Boylan who made the quadrant figured out a fix. Multiply by (1 - alpha/2). That's it. That fix is now called the Syntetos-Boylan Approximation (SBA) and it is the default people should be using. There is a further variant called TSB (Teunter-Syntetos-Babai). This one solves a different problem. Croston only updates […]
