Mathew K Analytics

Lesson 32 · Python for Retail E-commerce Analytics

Top Selling and Low-Performing Products Analysis with Python for Retail E-commerce

In this lesson, we will learn how to identify top-selling and low-performing products in a retail or e-commerce business. This analysis helps companies…

⬇ 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

Top-Selling and Low-Performing Products in Retail Analytics#

  • In this lesson, we will learn how to identify top-selling and low-performing products in a retail or e-commerce business.
  • This analysis helps companies decide which products to promote, discontinue, or restock.
  • The insights we produce will help drive smarter sales, marketing, and inventory strategies.
  • We will use real retail datasets to analyze sales performance at the product and category level.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Key Concepts in Retail Analytics#

  • Retail datasets commonly include product, transaction, order, and customer information.
  • Sales metrics include quantity (units sold), revenue (sales value), and price (unit cost).
  • Beginners often group by the wrong field or forget to multiply price by quantity.
  • Always check for missing or zero values that can skew your analysis.
  • Correctly linking products to sales data helps produce trustworthy insights.
# Load sample online retail transaction dataset from UCI
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00502/online_retail_II.xlsx'
df_retail = pd.read_excel(url, sheet_name='Year 2010-2011')
df_retail['InvoiceDate'] = pd.to_datetime(df_retail['InvoiceDate'])
print(df_retail.shape)
print(df_retail.head(3))
(541910, 8)
  Invoice StockCode                         Description  Quantity  \
0  536365    85123A  WHITE HANGING HEART T-LIGHT HOLDER         6   
1  536365     71053                 WHITE METAL LANTERN         6   
2  536365    84406B      CREAM CUPID HEARTS COAT HANGER         8   

          InvoiceDate  Price  Customer ID         Country  
0 2010-12-01 08:26:00   2.55      17850.0  United Kingdom  
1 2010-12-01 08:26:00   3.39      17850.0  United Kingdom  
2 2010-12-01 08:26:00   2.75      17850.0  United Kingdom  
# Load product catalog (synthetic, reproducible)
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)
df_catalog = pd.DataFrame({'ProductID': product_ids, 'Category': product_categories, 'Price': product_prices})
print(df_catalog.shape)
print(df_catalog.head(3))
(100, 3)
   ProductID Category   Price
0       1001   Sports  457.91
1       1002   Beauty  425.77
2       1003     Home  227.48
# Load retail customer orders (synthetic orders, reproducible)
np.random.seed(42)
n_orders = 1000
order_ids = list(range(1, n_orders + 1))
customer_ids = np.random.randint(1000, 1500, n_orders)
product_ids = np.random.randint(1001, 1100, n_orders)
quantities = np.random.randint(1, 5, n_orders)
order_dates = pd.date_range('2023-01-01', periods=n_orders, freq='h')
df_orders = pd.DataFrame({'OrderID': order_ids, 'CustomerID': customer_ids, 'ProductID': product_ids, 'Quantity': quantities, 'OrderDate': order_dates})
print(df_orders.shape)
print(df_orders.head(3))
(1000, 5)
   OrderID  CustomerID  ProductID  Quantity           OrderDate
0        1        1102       1049         3 2023-01-01 00:00:00
1        2        1435       1011         1 2023-01-01 01:00:00
2        3        1348       1085         3 2023-01-01 02:00:00
# Beginner Example 1: Find total quantity sold per product
product_sales = df_orders.groupby('ProductID')['Quantity'].sum().reset_index()
product_sales = product_sales.sort_values('Quantity', ascending=False)
print(product_sales.head(10))
    ProductID  Quantity
97       1098        54
25       1026        51
16       1017        50
58       1059        48
39       1040        45
50       1051        43
37       1038        42
61       1062        40
28       1029        39
4        1005        39
# Beginner Example 2: Find total revenue per product
merged_orders = pd.merge(df_orders, df_catalog, left_on='ProductID', right_on='ProductID', how='left')
merged_orders['Revenue'] = merged_orders['Quantity'] * merged_orders['Price']
product_revenue = merged_orders.groupby('ProductID')['Revenue'].sum().reset_index()
product_revenue = product_revenue.sort_values('Revenue', ascending=False)
print(product_revenue.head(10))
    ProductID   Revenue
39       1040  19899.90
0        1001  14195.21
24       1025  13272.64
32       1033  13224.90
93       1094  12576.70
62       1063  12238.45
96       1097  12234.56
65       1066  11973.12
13       1014  11954.58
80       1081  11673.95
# Beginner Example 3: Locate low-performing products (lowest sales quantity)
low_sales = product_sales.tail(10).reset_index(drop=True)
print(low_sales)
   ProductID  Quantity
0       1035        15
1       1044        14
2       1071        13
3       1028        13
4       1076        13
5       1091        12
6       1065        12
7       1031        10
8       1080         9
9       1058         4
# Intermediate Example 1: Calculate total sales by product category
category_sales = pd.merge(product_sales, df_catalog, left_on='ProductID', right_on='ProductID', how='left')
category_sales = category_sales.groupby('Category')['Quantity'].sum().reset_index().sort_values('Quantity', ascending=False)
print(category_sales)
      Category  Quantity
4       Sports       685
1     Clothing       511
0       Beauty       487
2  Electronics       408
3         Home       399
# Intermediate Example 2: Revenue distribution by category
category_revenue = pd.merge(product_revenue, df_catalog, on='ProductID', how='left')
category_revenue = category_revenue.groupby('Category')['Revenue'].sum().reset_index().sort_values('Revenue', ascending=False)
print(category_revenue)
      Category    Revenue
4       Sports  147783.07
0       Beauty  134105.61
1     Clothing  121487.00
3         Home   98098.72
2  Electronics   92256.84
# Intermediate Example 3: Identify products with zero orders (not purchased)
ordered_products = set(df_orders['ProductID'])
all_products = set(df_catalog['ProductID'])
unordered_products = all_products - ordered_products
print('Products never ordered:', unordered_products)
Products never ordered: {1100}
# Intermediate Example 4: Top products for a specific customer
sample_cust = df_orders['CustomerID'].iloc[0]
cust_products = df_orders[df_orders['CustomerID'] == sample_cust].groupby('ProductID')['Quantity'].sum().sort_values(ascending=False).reset_index()
print(f'Top products for Customer {sample_cust}:')
print(cust_products.head(5))
Top products for Customer 1102:
   ProductID  Quantity
0       1027         3
1       1049         3
2       1057         3
# Advanced Example 1: Monthly sales trend for top 3 products
top3 = product_sales.head(3)['ProductID'].tolist()
merged_orders['Month'] = merged_orders['OrderDate'].dt.to_period('M')
trend = merged_orders[merged_orders['ProductID'].isin(top3)].groupby(['Month', 'ProductID'])['Quantity'].sum().unstack(fill_value=0)
print(trend)
ProductID  1017  1026  1098
Month                      
2023-01      38    45    29
2023-02      12     6    25
# Advanced Example 2: Identify products with declining sales over time
monthly_trend = merged_orders.groupby(['ProductID', merged_orders['OrderDate'].dt.to_period('M')])['Quantity'].sum().reset_index()
monthly_trend = monthly_trend.sort_values(['ProductID', 'OrderDate'])
declining_products = []
for pid in merged_orders['ProductID'].unique():
    sales = monthly_trend[monthly_trend['ProductID'] == pid]['Quantity'].values
    if len(sales) > 1 and all(earlier >= later for earlier, later in zip(sales, sales[1:])):
        declining_products.append(pid)
print('Products with strictly declining sales:', declining_products)
Products with strictly declining sales: [np.int32(1049), np.int32(1011), np.int32(1085), np.int32(1026), np.int32(1063), np.int32(1089), np.int32(1086), np.int32(1027), np.int32(1077), np.int32(1033), np.int32(1098), np.int32(1099), np.int32(1021), np.int32(1055), np.int32(1006), np.int32(1092), np.int32(1081), np.int32(1069), np.int32(1095), np.int32(1005), np.int32(1003), np.int32(1053), np.int32(1023), np.int32(1037), np.int32(1074), np.int32(1083), np.int32(1017), np.int32(1073), np.int32(1045), np.int32(1004), np.int32(1062), np.int32(1065), np.int32(1032), np.int32(1034), np.int32(1072), np.int32(1039), np.int32(1054), np.int32(1050), np.int32(1012), np.int32(1094), np.int32(1057), np.int32(1047), np.int32(1079), np.int32(1014), np.int32(1066), np.int32(1038), np.int32(1082), np.int32(1030), np.int32(1091), np.int32(1052), np.int32(1097), np.int32(1029), np.int32(1010), np.int32(1056), np.int32(1084), np.int32(1043), np.int32(1008), np.int32(1025), np.int32(1067), np.int32(1096), np.int32(1061), np.int32(1019), np.int32(1042), np.int32(1070), np.int32(1090), np.int32(1046), np.int32(1028), np.int32(1087), np.int32(1007), np.int32(1018), np.int32(1015), np.int32(1068), np.int32(1040), np.int32(1041), np.int32(1024), np.int32(1009), np.int32(1031), np.int32(1020), np.int32(1035), np.int32(1060), np.int32(1076), np.int32(1071), np.int32(1058)]
# Advanced Example 3: Calculate average order value for each product
order_values = merged_orders.groupby('OrderID')['Revenue'].sum()
avg_order_value = merged_orders.groupby('ProductID')['Revenue'].sum() / merged_orders.groupby('ProductID')['OrderID'].nunique()
avg_order_value = avg_order_value.reset_index().rename(columns={0: 'AvgOrderValue'})
print(avg_order_value.head(10))
   ProductID  AvgOrderValue
0       1001    1091.939231
1       1002    1064.425000
2       1003     682.440000
3       1004     113.956364
4       1005     432.578824
5       1006     824.923636
6       1007    1003.890000
7       1008     661.533333
8       1009     320.431818
9       1010     785.611111
# Error Example 1: Aggregating revenue without multiplying by quantity (wrong!)
product_wrong = pd.merge(df_orders, df_catalog, left_on='ProductID', right_on='ProductID', how='left')
# Total revenue error: just summing price, not considering quantities
product_wrong_revenue = product_wrong.groupby('ProductID')['Price'].sum().reset_index()
print(product_wrong_revenue.head(3))
   ProductID    Price
0       1001  5952.83
1       1002  3406.16
2       1003  2502.28
# Error Example 2: Missing value handling in product sales
df_orders_with_nan = df_orders.copy()
df_orders_with_nan.loc[0, 'Quantity'] = np.nan
total_quant_nan = df_orders_with_nan['Quantity'].sum()
print(f'Total quantity with missing value: {total_quant_nan}')
Total quantity with missing value: 2487.0
# Error Example 3: Grouping by the wrong field (by CustomerID instead of ProductID)
wrong_group = df_orders.groupby('CustomerID')['Quantity'].sum().head()
print(wrong_group)
CustomerID
1000     9
1001     8
1003     5
1004    11
1005     4
Name: Quantity, dtype: int32

Retail Analytics Best Practices#

  • Always double-check sales metrics (quantity, price, revenue) for calculation accuracy.
  • Use data merges to connect products, orders, and catalog data.
  • Analyze both top and low performers for business action.
  • Segment customers to identify trends and patterns.
  • Review results for seasonal or time-based trends.
  • Validate your aggregation logic and check for missing or zero data.
  • Market basket analysis can help uncover cross-sell and up-sell opportunities.
# End-to-End Analysis: Identify and recommend top and low-performing products
performance = pd.merge(product_sales, product_revenue, on='ProductID')
performance = pd.merge(performance, df_catalog, on='ProductID')
performance = performance.sort_values('Revenue', ascending=False)
top_n = 5
top_products = performance.head(top_n)[['ProductID', 'Category', 'Quantity', 'Revenue']]
low_products = performance.tail(top_n)[['ProductID', 'Category', 'Quantity', 'Revenue']]
print('Top 5 products to promote:')
print(top_products)
print('\nLow-performing products to review or optimize:')
print(low_products)
Top 5 products to promote:
    ProductID     Category  Quantity   Revenue
4        1040     Clothing        45  19899.90
24       1001       Sports        31  14195.21
10       1025  Electronics        38  13272.64
25       1033       Sports        30  13224.90
22       1094         Home        31  12576.70

Low-performing products to review or optimize:
    ProductID  Category  Quantity  Revenue
75       1096      Home        18   946.98
95       1065  Clothing        12   939.60
79       1060  Clothing        17   631.04
46       1070    Sports        24   512.64
16       1047  Clothing        33   173.58

Keep Practicing and Exploring!#

  • Try these analyses with your own retail or e-commerce data to find actionable insights.
  • For more detailed product analytics, explore related YouTube tutorials on market basket analysis and retail performance.
# Save product performance to CSV for further sharing or visualization
performance.to_csv('product_performance.csv', index=False)
print('File saved: product_performance.csv')
File saved: product_performance.csv
 

Found this useful?

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