ENGINEERING THE NEXT GENERATION

Logo
Home/Blog/ABC-XYZ Inventory Analysis in Python: Which Items Deserve Your Attention
InventoryABC AnalysisPythonPredictive AnalyticsDemand Planning

ABC-XYZ Inventory Analysis in Python: Which Items Deserve Your Attention

November 1, 2026
Three by three grid of glowing inventory blocks with the most valuable, predictable cell lit brightest
Nine cells, nine different stocking policies. The value comes from treating them differently.

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.

python
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:

python
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:

python
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

  1. Pull 12–24 months of sales by item and location, with stockouts flagged
  2. Choose cutoffs based on your team's capacity, and write them down
  3. Classify on forecast error, not raw variability
  4. Have planners review the results. They'll quickly spot items that look wrong, which often reveals data problems
  5. Assign a policy to each group and load the settings into your inventory system
  6. 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.

Frequently Asked Questions

ABC-XYZ analysis sorts inventory items on two measures. ABC ranks items by value, usually annual sales or consumption value, so A items are the few that make up most of the value. XYZ ranks items by how predictable their demand is, so X is steady and Z is erratic. Combining them gives nine groups, such as AX for high-value predictable items and CZ for low-value erratic items, and each group can have its own stocking policy.

There are no universal cutoffs. Common ABC splits are 80/15/5 and 70/20/10 of cumulative value. XYZ thresholds on the coefficient of variation also vary, for example 0.25 and 0.5, or 0.5 and 1.0. In our 500-item test, the choice mattered: moving from 70/20/10 to 80/15/5 raised A items from 77 to 123, and the tighter CV cutoffs doubled Z items from 153 to 304. Pick a convention that fits your data and document it.

Calculate the coefficient of variation for each item, which is the standard deviation of demand divided by the mean, then assign classes using your thresholds with numpy.where. For better results, calculate the coefficient of variation of forecast errors rather than raw sales, so seasonal patterns you can predict are not counted as variability.

Raw coefficient of variation measures how much demand moves, not how predictable it is. A seasonal item moves a lot but in a known pattern. In our test, 130 of 132 seasonal items landed in class Y on raw variability and none in X. Measuring the variability of forecast errors instead put 121 of them in X, correctly showing they are predictable.

Give AX items tight, automated replenishment and high service targets. Watch AZ items closely, because they are valuable and hard to predict, so they need human review and higher buffers. Automate BX and CX items with simple rules. Consider making CZ items to order, stocking them centrally or discontinuing them, because they tie up attention for little value.

Quarterly is a sensible default, using the last 12 months so seasonal effects are included. Reclassify sooner after a major range change. Avoid changing policies for items that move between neighbouring groups only briefly, because constant policy changes create more work than they save.