TL;DR & Quick Summary
ABC-XYZ analysis sorts inventory by value (ABC) and predictability (XYZ) so you can give each group a different stocking policy. It's simple enough to run in a few lines of Python. We tested it on 500 simulated items, and two results matter more than the method itself:
Cutoffs change the answer. Moving from 70/20/10 to 80/15/5 raised A items from 77 to 123. Tighter variability cutoffs doubled Z items from 153 to 304.
Raw variability misjudges seasonal items. 130 of 132 seasonal items were labelled Y and none X. Using forecast error instead put 121 of them in X.
129 of 500 items changed class when we switched to forecast error.
Service targets by group used about 1% less safety stock value while raising value-weighted service targets from 95.0% to 95.9%.
Key Takeaway: Classify predictability by forecast error, not raw variability. Otherwise your planners will spend their time on seasonal items that were never a problem.
Get Started: Want your catalogue classified properly? Schedule a strategy call with Cogniq AI or see our predictive analytics services.
What ABC-XYZ Analysis Does
Most businesses can't give every item the same attention. ABC-XYZ analysis decides where attention pays off, using two questions.
ABC: how much does this item matter? Rank items by value, usually annual sales or consumption value. Add up the cumulative share. The top items making up most of the value are A, the middle are B, and the long tail is C.
XYZ: how predictable is it? Measure how much demand varies. X items are steady, Y items are moderately variable, and Z items are erratic.
Combined, you get a 3×3 grid:
| X (predictable) | Y (variable) | Z (erratic) | |
|---|---|---|---|
| A (high value) | AX: automate tightly | AY: forecast and review | AZ: human attention |
| B (medium value) | BX: automate | BY: standard review | BZ: buffer or review |
| C (low value) | CX: simple rules | CY: simple rules, low effort | CZ: rethink stocking |
The point isn't the labels. It's that AX and CZ items should never be managed the same way.
The Code
This function takes a table with one row per item, with a value column and a variability column, and returns the classes.
import numpy as np
import pandas as pd
def abc_xyz(df, a=0.80, b=0.95, x=0.5, y=1.0, cv_col="cv"):
df = df.sort_values("value", ascending=False).copy()
share = df["value"].cumsum() / df["value"].sum()
df["abc"] = np.where(share <= a, "A", np.where(share <= b, "B", "C"))
df["xyz"] = np.where(df[cv_col] <= x, "X", np.where(df[cv_col] <= y, "Y", "Z"))
df["cell"] = df["abc"] + df["xyz"]
return df
The usual way to get cv is the coefficient of variation of weekly sales:
cv = sales.std(ddof=1) / sales.mean()
That's the version most guides use, and it's the one with the problem we'll come to.
What We Tested
We simulated 500 items over two years of weekly sales with a fixed random seed. Item values followed a long-tailed pattern, as real catalogues do. Each item had one of three demand types:
- Stable (45%): steady demand with ordinary noise
- Seasonal (25%): a strong yearly pattern
- Erratic (30%): genuinely unpredictable swings
Because we set the demand type ourselves, we can check whether the classification got each item right. That's something real data can't tell you. These are simulated results, so your numbers will differ, but the behaviour of the method carries over.
With 80/15/5 and CV cutoffs of 0.5 and 1.0, 123 items (25% of the catalogue) made up 80% of value. That's the familiar Pareto shape.
Finding 1: Your Cutoffs Are a Decision
There's no standard set of cutoffs. Published guides use different ABC splits and CV thresholds, and we found the choice changes the result substantially:
| Setting | Result |
|---|---|
| ABC at 70/20/10 | 77 A items, 129 B, 294 C |
| ABC at 80/15/5 | 123 A items, 160 B, 217 C |
| XYZ at CV 0.5 and 1.0 | 196 X, 151 Y, 153 Z |
| XYZ at CV 0.25 and 0.5 | 121 X, 75 Y, 304 Z |
Same items, same data. Under one convention 61% of the catalogue is "erratic". Under the other, 31% is.
So don't copy a cutoff from a blog. Choose based on how many items your team can realistically review. If planners can give close attention to 80 items, set the A cutoff so that roughly 80 items land there. Write the choice down, so results stay comparable between reviews.
Finding 2: Raw Variability Gets Seasonal Items Wrong
This is the most useful result. The coefficient of variation measures how much demand moves, not how predictable it is. A seasonal item moves a great deal, but in a pattern you already know.
On raw CV, our 132 seasonal items were classified like this: 0 in X, 130 in Y, 2 in Z. Not one was recognised as predictable.
The fix is to measure the variability of forecast errors, not sales. Here we used a smoothed version of last year's weekly pattern as a simple forecast:
last_year, this_year = sales[:52], sales[52:]
profile = pd.Series(last_year).rolling(5, center=True, min_periods=1).mean().values
errors = this_year - profile
cv_err = errors.std(ddof=1) / this_year.mean()
Then pass cv_col="cv_err" to the same function. The seasonal items moved to 121 in X, 10 in Y, 1 in Z, which correctly reflects that a yearly pattern is predictable.
The check that matters is whether it still catches items that really are erratic, and it did. Of 152 erratic items, 151 were in Z on raw CV and 150 on forecast error. Overall, 129 of the 500 items changed class, most of them seasonal items moving to where they belong.
Why this matters in practice: Y and Z items usually get more human review and more safety stock. Labelling predictable seasonal items as variable sends planners' time and working capital to the wrong place. If you use a better forecast than this simple one, the classification gets better too. Our guide to forecast accuracy metrics in Python shows how to measure it.
Finding 3: Different Targets per Group Pay Off
We then compared two safety stock policies with a two-week lead time:
- Uniform: 95% service target for every item
- By group: 98% for AX, down to 85% for CZ, with higher targets on A items and lower on C items
| Policy | Safety stock value | Value-weighted service target |
|---|---|---|
| Uniform 95% | 948,609 | 95.0% |
| By group | 939,129 (−1.0%) | 95.9% |
The saving is modest in this test. The more important result is the shift: slightly less stock, with protection moved towards the items that carry the value. Note that these are service targets, not simulated outcomes. Use the method in safety stock and reorder point in Python to check what a target really delivers.
What to Do With Each Group
| Group | Policy |
|---|---|
| AX | Automated replenishment, high service target, frequent small orders |
| AY | Forecast-driven, regular planner review |
| AZ | Human attention, higher buffers, talk to customers about upcoming demand |
| BX, BY | Automate with standard targets; review exceptions |
| BZ | Buffer stock or make to order; check for causes |
| CX, CY | Simple min-max rules, little human time |
| CZ | Make to order, stock centrally, or discontinue |
AZ deserves special attention. These items are valuable and hard to forecast, so they're where a stockout hurts most. Before accepting that an item is Z, check whether stockouts have distorted its history. Sales that drop to zero because you ran out look like erratic demand. Our guide to estimating lost sales from stockouts shows how to correct that first. For intermittent Z items, methods such as Croston, SBA and TSB fit better than standard forecasting.
Common Mistakes
- Classifying on revenue alone when margin matters. A high-revenue, low-margin item may deserve less protection than its ABC class suggests. You can rank on margin instead, or add a criticality flag for items that stop a line or a contract.
- Using too little history. Less than a year can miss seasonality. See how much data demand forecasting needs.
- Ignoring new items. Items with short history get misleading classes. Treat them as a separate group until they have enough data.
- Classifying across the whole network. An item can be steady in total and erratic at each branch. Classify at the level you stock.
- Reclassifying too often. Policy churn creates more work than it saves. Quarterly is usually enough.
- Stopping at the grid. A classification only pays off if each group actually gets a different policy in your system.
How to Roll It Out
- Pull 12–24 months of sales by item and location, with stockouts flagged
- Choose cutoffs based on your team's capacity, and write them down
- Classify on forecast error, not raw variability
- Have planners review the results. They'll quickly spot items that look wrong, which often reveals data problems
- Assign a policy to each group and load the settings into your inventory system
- Review quarterly and track stock value and service by group
Step 5 is where most of the benefit is won or lost. Our guide to connecting inventory systems to predictive reordering covers how these policies get into purchasing.
Conclusion
ABC-XYZ analysis is a simple way to decide where attention and stock should go, and the code takes a few lines. Two details decide whether it helps: choosing cutoffs deliberately, and measuring predictability by forecast error rather than raw variability. In our test, the raw method missed every predictable seasonal item, and fixing it moved 129 of 500 items to a better group.
Schedule a strategy call with Cogniq AI to classify your catalogue and set policies by group, or explore our predictive analytics services.



