Mathew K Analytics

Lesson 22 · Python for Retail E-commerce Analytics

Analyzing Sales by Product and Category in Python for Retail E-commerce

In retail and e-commerce, understanding which products and categories drive sales is vital. Retail managers need to identify best-selling products and most…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Analyzing Sales by Product and Category#

  • In retail and e-commerce, understanding which products and categories drive sales is vital.
  • Retail managers need to identify best-selling products and most valuable categories to optimize inventory, plan promotions, and grow revenue.
  • In this lesson, you will analyze real and realistic datasets to summarize sales by product and category, spot best and worst performers, and learn to avoid common mistakes.
  • You will produce insights that help make data-driven decisions in retail and e-commerce businesses.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Core Concepts: Retail Analytics Data and Metrics#

  • Retail datasets often represent transactions, orders, products, or customers.
  • Key sales metrics include revenue (total money earned), quantity (units sold), and price (per unit).
  • Beginner mistakes include summing prices instead of revenue, forgetting to group by product or category, or misinterpreting missing values.
  • Accurate sales aggregation is critical for making good decisions.
# Beginner Example 1: Load product catalog (synthetic)
np.random.seed(42)
categories = ['Electronics','Clothing','Home','Sports','Beauty']
product_ids = list(range(1001,1101))
product_categories = np.random.choice(categories,100)
product_prices = np.round(np.random.uniform(5,500,100),2)
catalog = pd.DataFrame({'ProductID':product_ids,'Category':product_categories,'Price':product_prices})
print(catalog.shape)
print(catalog.head(3))
(100, 3)
   ProductID Category   Price
0       1001   Sports  457.91
1       1002   Beauty  425.77
2       1003     Home  227.48
# Beginner Example 2: Simulate orders linked to products
np.random.seed(42)
n_orders = 1000
order_ids = list(range(1, n_orders+1))
customer_ids = np.random.randint(1000,1500,n_orders)
order_product_ids = np.random.randint(1001,1101,n_orders)
quantities = np.random.randint(1,5,n_orders)
order_dates = pd.date_range('2023-01-01', periods=n_orders, freq='h')
orders = pd.DataFrame({'OrderID':order_ids,'CustomerID':customer_ids,'ProductID':order_product_ids,'Quantity':quantities,'OrderDate':order_dates})
print(orders.shape)
print(orders.head(3))
(1000, 5)
   OrderID  CustomerID  ProductID  Quantity           OrderDate
0        1        1102       1049         2 2023-01-01 00:00:00
1        2        1435       1011         4 2023-01-01 01:00:00
2        3        1348       1085         3 2023-01-01 02:00:00
# Beginner Example 3: Merge orders and products
merged = pd.merge(orders, catalog, on='ProductID', how='left')
print(merged.shape)
print(merged[['OrderID','ProductID','Category','Quantity','Price']].head(3))
(1000, 7)
   OrderID  ProductID     Category  Quantity   Price
0        1       1049     Clothing         2  155.87
1        2       1011       Sports         4  194.55
2        3       1085  Electronics         3  409.13
# Beginner Example 4: Calculate revenue per order item
merged['Revenue'] = merged['Quantity'] * merged['Price']
print(merged[['ProductID','Category','Quantity','Price','Revenue']].head())
   ProductID     Category  Quantity   Price  Revenue
0       1049     Clothing         2  155.87   311.74
1       1011       Sports         4  194.55   778.20
2       1085  Electronics         3  409.13  1227.39
3       1026         Home         3   73.97   221.91
4       1063       Beauty         2  349.67   699.34
# Beginner Example 5: Sum total sales revenue
total_revenue = merged['Revenue'].sum()
print('Total Revenue across all sales: $', round(total_revenue,2))
Total Revenue across all sales: $ 601947.81
# Intermediate Example 1: Aggregate sales by product
sales_by_product = merged.groupby('ProductID').agg({'Revenue':'sum', 'Quantity':'sum'}).reset_index()
sales_by_product = sales_by_product.merge(catalog[['ProductID','Category']], on='ProductID', how='left')
print(sales_by_product.head(5))
   ProductID   Revenue  Quantity Category
0       1001  15568.94        34   Sports
1       1002   7238.09        17   Beauty
2       1003   5687.00        25     Home
3       1004   1671.36        32   Beauty
4       1005   8485.20        45   Beauty
# Intermediate Example 2: Aggregate sales by category
sales_by_category = merged.groupby('Category').agg({'Revenue':'sum', 'Quantity':'sum'}).reset_index()
print(sales_by_category.sort_values('Revenue', ascending=False))
      Category    Revenue  Quantity
4       Sports  146264.43       683
0       Beauty  127703.81       478
1     Clothing  123378.34       508
3         Home  102829.31       392
2  Electronics  101771.92       437
# Intermediate Example 3: Top 5 products by revenue
top_products = sales_by_product.sort_values('Revenue', ascending=False).head(5)
print(top_products[['ProductID','Category','Revenue','Quantity']])
    ProductID     Category   Revenue  Quantity
39       1040     Clothing  21668.78        49
0        1001       Sports  15568.94        34
93       1094         Home  15010.90        37
84       1085  Electronics  13910.42        34
89       1090         Home  13756.48        32
# Intermediate Example 4: Average order value (AOV)
aov = merged.groupby('OrderID')['Revenue'].sum().mean()
print('Average Order Value: $', round(aov,2))
Average Order Value: $ 601.95
# Intermediate Example 5: Sales over time by category
merged['OrderDate'] = pd.to_datetime(merged['OrderDate'])
time_cat = merged.groupby([merged['OrderDate'].dt.date, 'Category'])['Revenue'].sum().unstack('Category', fill_value=0)
print(time_cat.tail())
Category     Beauty  Clothing  Electronics     Home   Sports
OrderDate                                                   
2023-02-07  4088.29   2015.73      2736.05  1444.28  3297.19
2023-02-08  2014.63   4940.95      1385.74  1115.76  3445.67
2023-02-09  3238.57   3823.30      3079.65   704.95  2120.90
2023-02-10  2011.45   6793.80      3072.10  3081.68  1428.08
2023-02-11  2666.49   3532.33       544.49     0.00  4145.49
# Advanced Example 1: Identify top customers by revenue
customer_sales = merged.groupby('CustomerID')['Revenue'].sum().reset_index().sort_values('Revenue', ascending=False)
print(customer_sales.head(5))
     CustomerID  Revenue
45         1053  5815.86
312        1364  5397.97
193        1226  5204.68
134        1159  5126.64
356        1416  4891.88
# Advanced Example 2: Cross-tab products vs categories by revenue
product_cat_sales = pd.pivot_table(merged, values='Revenue', index='ProductID', columns='Category', aggfunc='sum', fill_value=0)
print(product_cat_sales.head(8))
Category    Beauty  Clothing  Electronics     Home    Sports
ProductID                                                   
1001          0.00      0.00          0.0     0.00  15568.94
1002       7238.09      0.00          0.0     0.00      0.00
1003          0.00      0.00          0.0  5687.00      0.00
1004       1671.36      0.00          0.0     0.00      0.00
1005       8485.20      0.00          0.0     0.00      0.00
1006          0.00  10418.48          0.0     0.00      0.00
1007          0.00      0.00          0.0  6692.60      0.00
1008          0.00      0.00          0.0  7442.25      0.00
# Advanced Example 3: Calculate category revenue share
total_rev = sales_by_category['Revenue'].sum()
sales_by_category['RevenueShare'] = round(100 * sales_by_category['Revenue'] / total_rev, 2)
print(sales_by_category[['Category','RevenueShare']])
      Category  RevenueShare
0       Beauty         21.22
1     Clothing         20.50
2  Electronics         16.91
3         Home         17.08
4       Sports         24.30
# Advanced Example 4: Detect products with sales variance across categories
variance = sales_by_product.groupby('Category')['Revenue'].var().reset_index()
print('Sales Revenue Variance by Category:')
print(variance)
Sales Revenue Variance by Category:
      Category       Revenue
0       Beauty  1.055425e+07
1     Clothing  2.742142e+07
2  Electronics  1.241573e+07
3         Home  1.851252e+07
4       Sports  1.547955e+07
# Error Handling Example 1: Handling missing values
merged_missing = merged.copy()
merged_missing.loc[merged_missing.sample(frac=0.02, random_state=42).index, 'Revenue'] = np.nan
missing_count = merged_missing['Revenue'].isna().sum()
print('Rows with missing Revenue:', missing_count)
merged_missing['Revenue'].fillna(0, inplace=True)
print('Missing revenues set to 0. Check:', merged_missing['Revenue'].isna().sum())
Rows with missing Revenue: 20
Missing revenues set to 0. Check: 0
# Error Handling Example 2: Check for negative quantities or prices
neg_qty = (merged['Quantity'] < 0).sum()
neg_price = (merged['Price'] < 0).sum()
print('Negative Quantities:', neg_qty)
print('Negative Prices:', neg_price)
Negative Quantities: 0
Negative Prices: 0
# Error Handling Example 3: Trap incorrect groupings
incorrect_group = merged.groupby('Price').agg({'Revenue':'sum'})
print('Sales grouped only by Price (incorrect):')
print(incorrect_group.head(3))
correct_group = merged.groupby('ProductID').agg({'Revenue':'sum'})
print('Sales grouped by ProductID (correct):')
print(correct_group.head(3))
Sales grouped only by Price (incorrect):
       Revenue
Price         
5.26    173.58
21.36   640.80
25.01  1100.44
Sales grouped by ProductID (correct):
            Revenue
ProductID          
1001       15568.94
1002        7238.09
1003        5687.00

Best Practices and Common Retail Analytics Patterns#

  • Segment your customers and products to tailor promotions.
  • Analyze product performance to optimize inventory and pricing.
  • Use market basket analysis to surface cross-sell opportunities.
  • Forecast demand, and identify seasonal or promotional effects.
  • Regularly check for data quality issues before any analysis.
# End-to-End Problem: Identify top category and product with actionable insight
top_cat = sales_by_category.sort_values('Revenue', ascending=False).iloc[0]
top_prod = sales_by_product.sort_values('Revenue', ascending=False).iloc[0]
print('TOP-PERFORMING CATEGORY: {} (${:.2f} revenue)'.format(top_cat['Category'], top_cat['Revenue']))
print('TOP-PERFORMING PRODUCT: {} (${:.2f} revenue)'.format(top_prod['ProductID'], top_prod['Revenue']))
print('Business recommendation: Focus your next promotion on {} products and feature ProductID {} prominently.'.format(top_cat['Category'], top_prod['ProductID']))
TOP-PERFORMING CATEGORY: Sports ($146264.43 revenue)
TOP-PERFORMING PRODUCT: 1040 ($21668.78 revenue)
Business recommendation: Focus your next promotion on Sports products and feature ProductID 1040 prominently.
# Bonus: Plot category revenue with Matplotlib
import matplotlib.pyplot as plt
plt.figure(figsize=(7,5))
plt.bar(sales_by_category['Category'], sales_by_category['Revenue'], color='skyblue')
plt.title('Total Revenue by Category')
plt.ylabel('Revenue ($)')
plt.xlabel('Category')
plt.show()
No description has been provided for this image
 

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.