Lesson 8 · Real-World Data Analytics
Python Data Analytics #08: Product Category Performance & ABC Analysis in Python
Video eight of the hundred-video real-world data analytics series. Deriving real product categories straight from real description text, then running a real…
- CourseReal-World Data Analytics
- Lesson8 of 26
- Video28 min
- FormatJupyter notebook · 30 code cells
- Data1 dataset
What you'll learn
Datasets used in this lesson
Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.
- online_retail_clean.csv36.2 MB
📓 Full notebook
Download .ipynbData Analytics 100, Video 8: Product Category Performance and ABC Analysis#
- Video eight of the hundred-video real-world data analytics series.
- Deriving real product categories straight from real description text, then running a real classic ABC analysis on top of it.
- Let's get into it.
Part 1: This Real Dataset Has No Category Column#
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
clean = pd.read_csv('online_retail_clean.csv', parse_dates=['InvoiceDate'])
clean.shape
Part 2: Real Keyword List for Deriving Categories#
keywords = ['BAG', 'MUG', 'CANDLE', 'LIGHT', 'BOX', 'CARD', 'FRAME', 'CLOCK', 'BOTTLE', 'NECKLACE', 'BRACELET', 'CUSHION', 'LANTERN', 'JAR', 'TIN', 'BOWL', 'PLATE', 'APRON', 'NOTEBOOK', 'MIRROR']
len(keywords)
Part 3: Real Category Assignment Function#
desc_upper = clean['Description'].astype(str).str.upper()
def categorize(desc):
for kw in keywords:
if kw in desc:
return kw.title()
return 'Other'
Part 4: Applying the Real Categorization#
clean['Category'] = desc_upper.apply(categorize)
clean['Category'].value_counts().head(10)
Part 5: Being Real Honest About Coverage#
coverage = (clean['Category'] != 'Other').mean()
print(f'Real keyword categorization covers {round(coverage * 100, 1)}% of transactions; the rest fall into an honest Other bucket.')
Part 6: Real Revenue by Category#
category_revenue = clean.groupby('Category')['Revenue'].sum().sort_values(ascending=False)
category_revenue.head(10).round(0)
Part 7: Visualizing Real Category Revenue#
plt.figure(figsize=(10, 6))
top_categories = category_revenue[category_revenue.index != 'Other'].head(12)
plt.barh(top_categories.index[::-1], top_categories.values[::-1], color='teal')
plt.xlabel('Real Total Revenue (GBP)')
plt.title('Real Revenue by Derived Product Category')
plt.tight_layout()
plt.savefig('category_revenue.png', dpi=120)
plt.close()
Part 8: Real Average Order Value by Category#
category_stats = clean[clean['Category'] != 'Other'].groupby('Category').agg(AvgUnitPrice=('UnitPrice', 'mean'), TotalQty=('Quantity', 'sum'))
category_stats.sort_values('AvgUnitPrice', ascending=False).round(2)
Part 9: Switching to ABC Analysis at the Real Product Level#
product_revenue = clean.groupby('StockCode')['Revenue'].sum().sort_values(ascending=False)
product_revenue.shape[0]
Part 10: Real Cumulative Revenue Share#
cumulative_revenue = product_revenue.cumsum()
cumulative_pct = cumulative_revenue / product_revenue.sum() * 100
cumulative_pct.head(5).round(2)
Part 11: Assigning Real A, B, and C Tiers#
def assign_tier(pct):
if pct <= 80:
return 'A'
elif pct <= 95:
return 'B'
else:
return 'C'
tiers = cumulative_pct.apply(assign_tier)
tiers.value_counts()
Part 12: Real Revenue Share per Tier#
abc_table = pd.DataFrame({'Revenue': product_revenue, 'Tier': tiers})
abc_table.groupby('Tier')['Revenue'].sum().round(0)
(abc_table.groupby('Tier')['Revenue'].sum() / abc_table['Revenue'].sum() * 100).round(1)
Part 13: Validating the Real Pareto Principle#
pct_products_in_a = (tiers == 'A').mean() * 100
print(f'Just {round(pct_products_in_a, 1)}% of products in tier A generate 80% of total real revenue.')
Part 14: Visualizing the Real Pareto Curve#
plt.figure(figsize=(9, 6))
rank = np.arange(1, len(cumulative_pct) + 1)
plt.plot(rank, cumulative_pct.values, color='navy')
plt.axhline(80, color='red', linestyle='--', label='Real 80% revenue line')
plt.xlabel('Real Product Rank (by Revenue)')
plt.ylabel('Real Cumulative % of Total Revenue')
plt.title('Real Pareto Curve of Product Revenue')
plt.legend()
plt.tight_layout()
plt.savefig('pareto_curve.png', dpi=120)
plt.close()
Part 15: Real Top A-Tier Products#
desc_lookup = clean.groupby('StockCode')['Description'].agg(lambda s: s.mode().iloc[0])
a_tier_top = abc_table[abc_table['Tier'] == 'A'].head(10)
pd.DataFrame({'Description': desc_lookup.reindex(a_tier_top.index), 'Revenue': a_tier_top['Revenue']}).round(0)
Part 16: Real Long-Tail C-Tier Products#
c_tier = abc_table[abc_table['Tier'] == 'C']
c_tier.shape[0], c_tier['Revenue'].sum().round(0)
c_tier['Revenue'].mean().round(2)
Part 17: Real Category Mix Within Tier A#
a_tier_categories = clean[clean['StockCode'].isin(abc_table[abc_table['Tier'] == 'A'].index)]['Category']
a_tier_categories.value_counts().head(8)
Part 18: Real Order Frequency by Tier#
orders_per_product = clean.groupby('StockCode')['InvoiceNo'].nunique()
abc_table['Orders'] = abc_table.index.map(orders_per_product)
abc_table.groupby('Tier')['Orders'].mean().round(1)
Part 19: Real Inventory Implication#
avg_qty_per_order = clean.groupby('StockCode').agg(TotalQty=('Quantity', 'sum'), Orders=('InvoiceNo', 'nunique'))
avg_qty_per_order['QtyPerOrder'] = avg_qty_per_order['TotalQty'] / avg_qty_per_order['Orders']
abc_table['QtyPerOrder'] = abc_table.index.map(avg_qty_per_order['QtyPerOrder'])
abc_table.groupby('Tier')['QtyPerOrder'].mean().round(2)
Part 20: Real Sanity Check on the Tier Cutoffs#
boundary_product = abc_table[abc_table['Tier'] == 'A'].index[-1]
round(cumulative_pct[boundary_product], 2) <= 80
Part 21: Saving the Real ABC Classification#
abc_output = abc_table.copy()
abc_output['Description'] = desc_lookup.reindex(abc_output.index)
abc_output.round(2).to_csv('product_abc_classification.csv')
reloaded = pd.read_csv('product_abc_classification.csv')
reloaded.shape[0] == abc_output.shape[0]
Part 22: Real Recap Print#
print(f'Classified {len(abc_table)} real products into ABC tiers; tier A holds just {round(pct_products_in_a, 1)}% of products but drives 80% of real revenue.')
Part 23: Real Category Share Including the Other Bucket#
category_share = (category_revenue / category_revenue.sum() * 100).round(1)
category_share.head(6)
Part 24: Real Preview of Tier B Products#
b_tier_preview = abc_table[abc_table['Tier'] == 'B'].head(5)
pd.DataFrame({'Description': desc_lookup.reindex(b_tier_preview.index), 'Revenue': b_tier_preview['Revenue']}).round(0)
Part 25: Real Revenue Concentration at the Very Top#
top_1pct_count = max(1, int(len(product_revenue) * 0.01))
top_1pct_share = product_revenue.head(top_1pct_count).sum() / product_revenue.sum() * 100
print(f'The top {top_1pct_count} real products, just 1% of the catalog, generate {round(top_1pct_share, 1)}% of total real revenue.')
Part 26: Visualizing the Real Revenue Distribution#
plt.figure(figsize=(9, 5))
plt.hist(np.log10(product_revenue[product_revenue > 0]), bins=40, color='crimson', edgecolor='white')
plt.xlabel('Real Log10 of Product Revenue')
plt.ylabel('Real Number of Products')
plt.title('Real Distribution of Revenue Across the Product Catalog')
plt.tight_layout()
plt.savefig('product_revenue_distribution.png', dpi=120)
plt.close()
Part 27: Real Categories Entirely Missing from Tier A#
all_categories = set(clean[clean['Category'] != 'Other']['Category'].unique())
categories_in_a = set(a_tier_categories.unique())
all_categories - categories_in_a
Part 28: Real Median Revenue by Tier#
abc_table.groupby('Tier')['Revenue'].median().round(2)
Part 29: Real Countries Buying the Top Product#
top_product_code = product_revenue.index[0]
clean[clean['StockCode'] == top_product_code]['Country'].value_counts().head(5)
Part 30: Real Total Revenue Cross-Check#
abc_table['Revenue'].sum().round(0) == clean['Revenue'].sum().round(0)
Wrap-Up: What You Learned#
- When a real dataset has no category field, a real transparent keyword-based scheme can derive one honestly, coverage gaps and all.
- ABC analysis ranks real products by revenue and splits them into real A, B, and C tiers based on real cumulative contribution.
- The real Pareto pattern, a small share of products driving most revenue, held up directly against this real dataset's own numbers.
- Tier A products are genuinely both high-revenue and high-frequency, not just a few real lucky big sales.
- Real long-tail C-tier products individually contribute very little, informing real decisions about what to keep in stock.
- Next video: real scraping product prices for competitive analysis, comparing this catalog against real external pricing.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



