Mathew K Analytics

Lesson 57 · Python for Retail E-commerce Analytics

Customer Segmentation Case Study: Python Training for Retail E-commerce Analytics

In this lesson, we will explore how to segment customers based on real retail transaction datasets. Segmenting customers helps retail businesses identify…

⬇ 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

Customer Segmentation Case Study: Retail Analytics in Action#

  • In this lesson, we will explore how to segment customers based on real retail transaction datasets.
  • Segmenting customers helps retail businesses identify high-value shoppers, loyal buyers, and target groups for promotions.
  • You will learn how to analyze transaction data, uncover purchase patterns, and extract actionable customer insights.
  • The goal is to gain the skills to inform smarter marketing, product, and sales decisions.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Key Retail Analytics Concepts#

  • Retail transaction datasets contain information about orders, customers, products, and sales.
  • Each row usually represents one transaction or line-item, including fields like price, quantity, and customer ID.
  • Revenue is calculated as price times quantity; it is important not to confuse it with the number of items sold.
  • Beginners often make mistakes by mixing up transaction and customer levels, or grouping incorrectly.
# Load the Online Retail Transactions Dataset
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00502/online_retail_II.xlsx'
df = pd.read_excel(url, sheet_name='Year 2010-2011')
df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])
print(df.shape)
print(df.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  
# Check missing values in customer IDs
missing_customers = df['Customer ID'].isnull().sum()
print('Number of missing customer IDs:', missing_customers)
Number of missing customer IDs: 135080
# Remove transactions that do not have a customer ID
df = df.dropna(subset=['Customer ID'])
print('Transactions remaining after cleaning:', df.shape[0])
Transactions remaining after cleaning: 406830
# Quick sales aggregation by customer
df['Revenue'] = df['Price'] * df['Quantity']
customer_sales = df.groupby('Customer ID')['Revenue'].sum().reset_index()
customer_sales = customer_sales.sort_values(by='Revenue', ascending=False)
print(customer_sales.head(5))
      Customer ID    Revenue
1703      14646.0  279489.02
4233      18102.0  256438.49
3758      17450.0  187482.17
1895      14911.0  132572.62
55        12415.0  123725.45
# Find the number of unique customers
n_customers = df['Customer ID'].nunique()
print('Unique customers:', n_customers)
Unique customers: 4372
# Count purchases per customer
customer_freq = df.groupby('Customer ID')['Invoice'].nunique().reset_index()
customer_freq = customer_freq.rename(columns={'Invoice':'Num_Purchases'})
print(customer_freq.sort_values(by='Num_Purchases',ascending=False).head(5))
      Customer ID  Num_Purchases
1895      14911.0            248
330       12748.0            224
4042      17841.0            169
1674      14606.0            128
2192      15311.0            118

Intermediate Example: Average Order Value by Customer#

  • Next, we will measure average order value (AOV) for each customer to find big-spenders and value-driven buyers.
  • AOV is a key retail metric that influences marketing and loyalty programs.
  • The formula is simple: Total revenue divided by number of unique orders for each customer.
# Compute average order value (AOV) per customer
aov_per_customer = customer_sales.merge(customer_freq, on='Customer ID')
aov_per_customer['AOV'] = aov_per_customer['Revenue'] / aov_per_customer['Num_Purchases']
print(aov_per_customer[['Customer ID','AOV']].sort_values('AOV',ascending=False).head())
     Customer ID          AOV
191      12357.0  6207.670000
34       15749.0  5383.975000
271      12688.0  4873.810000
4        12415.0  4758.671154
313      12752.0  4366.780000
# Segment customers by total revenue into quantiles
customer_sales['Segment'] = pd.qcut(customer_sales['Revenue'], q=4, labels=['Bronze','Silver','Gold','Platinum'])
print(customer_sales[['Customer ID','Revenue','Segment']].head(10))
      Customer ID    Revenue   Segment
1703      14646.0  279489.02  Platinum
4233      18102.0  256438.49  Platinum
3758      17450.0  187482.17  Platinum
1895      14911.0  132572.62  Platinum
55        12415.0  123725.45  Platinum
1345      14156.0  113384.14  Platinum
3801      17511.0   88125.38  Platinum
3202      16684.0   65892.08  Platinum
1005      13694.0   62653.10  Platinum
2192      15311.0   59419.34  Platinum

Intermediate Example: Customer Recency#

  • Recency analysis measures how recently a customer made their last purchase.
  • It is common in RFM (Recency, Frequency, Monetary) segmentation.
  • Recent buyers are often more likely to buy again soon.
# Calculate recency for each customer (in days)
latest_date = df['InvoiceDate'].max()
recency = df.groupby('Customer ID')['InvoiceDate'].max().reset_index()
recency['RecencyDays'] = (latest_date - recency['InvoiceDate']).dt.days
print(recency[['Customer ID','RecencyDays']].sort_values('RecencyDays').head())
      Customer ID  RecencyDays
61        12423.0            0
1273      14056.0            0
1268      14051.0            0
2527      15755.0            0
3407      16954.0            0
# Merge RFM metrics for one-table customer view
rfm = customer_sales.merge(customer_freq, on='Customer ID').merge(recency[['Customer ID','RecencyDays']], on='Customer ID')
print(rfm.head())
   Customer ID    Revenue   Segment  Num_Purchases  RecencyDays
0      14646.0  279489.02  Platinum             77            1
1      18102.0  256438.49  Platinum             62            0
2      17450.0  187482.17  Platinum             55            7
3      14911.0  132572.62  Platinum            248            0
4      12415.0  123725.45  Platinum             26           23
# Advanced: k-means clustering for customer segmentation
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
X = rfm[['Revenue','Num_Purchases','RecencyDays']]
X_scaled = StandardScaler().fit_transform(X)
kmeans = KMeans(n_clusters=4, random_state=42)
rfm['Cluster'] = kmeans.fit_predict(X_scaled)
print(rfm[['Customer ID','Revenue','Num_Purchases','RecencyDays','Cluster']].head())
   Customer ID    Revenue  Num_Purchases  RecencyDays  Cluster
0      14646.0  279489.02             77            1        2
1      18102.0  256438.49             62            0        2
2      17450.0  187482.17             55            7        2
3      14911.0  132572.62            248            0        3
4      12415.0  123725.45             26           23        3
# Advanced: Profile each customer cluster
cluster_profile = rfm.groupby('Cluster').agg({'Revenue':'mean','Num_Purchases':'mean','RecencyDays':'mean','Customer ID':'count'}).rename(columns={'Customer ID':'Num_Customers'})
print(cluster_profile)
               Revenue  Num_Purchases  RecencyDays  Num_Customers
Cluster                                                          
0          1781.504669       5.524575    39.017620           3235
1           460.508225       1.853922   244.994590           1109
2        241136.560000      64.666667     2.666667              3
3         52112.116400      82.720000     5.160000             25
# Error handling: What if a groupby fails due to a missing column?
try:
    rfm.groupby('MissingColumn').mean()
except KeyError as e:
    print('Error:', e)
Error: 'MissingColumn'
# Debugging: Make sure revenue is always non-negative
bad_revenue = df[df['Revenue'] < 0]
print('Transactions with negative revenue:', bad_revenue.shape[0])
Transactions with negative revenue: 8905
# Remove transactions with negative revenue for clean analysis
df = df[df['Revenue'] >= 0]
print('Transactions after removing negatives:', df.shape[0])
Transactions after removing negatives: 397925

Best Practices in Retail Customer Segmentation#

  • Segment by buying behavior, not only demographics.
  • Combine RFM (Recency, Frequency, Monetary) metrics for deeper insight.
  • Remove or separately analyze returns and incomplete transactions for accuracy.
  • Regularly update segments as customers' behaviors change.
# Tiny End-to-End Problem: Identify VIP Customers
vip = customer_sales[customer_sales['Segment']=='Platinum']
print('Number of Platinum (VIP) customers:', vip.shape[0])
print(vip[['Customer ID','Revenue']].head(10))
Number of Platinum (VIP) customers: 1093
      Customer ID    Revenue
1703      14646.0  279489.02
4233      18102.0  256438.49
3758      17450.0  187482.17
1895      14911.0  132572.62
55        12415.0  123725.45
1345      14156.0  113384.14
3801      17511.0   88125.38
3202      16684.0   65892.08
1005      13694.0   62653.10
2192      15311.0   59419.34
# Bonus: Export VIP list for the marketing team
vip[['Customer ID','Revenue']].to_csv('vip_customers.csv', index=False)
print('VIP customer list exported to vip_customers.csv')
VIP customer list exported to vip_customers.csv

Lesson Summary#

  • You learned how to segment customers based on order data and RFM metrics.
  • You saw how clustering can automatically group customers by behavioral traits.
  • You practiced handling missing or incorrect data for robust analysis.
  • These segmentation skills help grow retail revenue and improve customer loyalty.

Found this useful?

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