Mathew K Analytics

Lesson 23 · Python for Retail E-commerce Analytics

Customer Purchase Behavior Analysis Training with Python for Retail E-commerce Analytics

In this lesson, we will analyze customer purchase behavior using real online retail transaction data. Understanding customer buying patterns helps…

⬇ 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 Purchase Behavior Analysis#

  • In this lesson, we will analyze customer purchase behavior using real online retail transaction data.
  • Understanding customer buying patterns helps businesses improve targeted marketing and increase sales.
  • We will generate actionable insights such as top customers, purchase frequencies, and revenue trends.
  • The skills you learn can help teams optimize product offerings and boost customer retention.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Retail Analytics Concepts#

  • Retail transaction datasets record purchases made by customers.
  • Each transaction commonly includes product, quantity, date, and customer information.
  • Metrics like quantity, revenue, and unit price reveal sales and customer behavior trends.
  • Common mistakes include double counting quantities, grouping data incorrectly, and missing data handling.
  • Always check data types and handle missing or duplicate entries before analysis.
# Load the Online Retail Transactions Dataset from UCI
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  
# Beginner Example 1: Counting Total Unique Customers
num_customers = df['Customer ID'].nunique()
print('Total unique customers:', num_customers)
Total unique customers: 4372
# Beginner Example 2: What is the average purchase quantity per invoice?
avg_qty_per_invoice = df.groupby('Invoice')['Quantity'].sum().mean()
print('Average quantity per invoice:', round(avg_qty_per_invoice,2))
Average quantity per invoice: 199.86
# Beginner Example 3: Top 5 best-selling products (by quantity sold)
top_products = df.groupby('Description')['Quantity'].sum().sort_values(ascending=False).head(5)
print('Top 5 products by total quantity sold:')
print(top_products)
Top 5 products by total quantity sold:
Description
WORLD WAR 2 GLIDERS ASSTD DESIGNS    53847
JUMBO BAG RED RETROSPOT              47363
ASSORTED COLOUR BIRD ORNAMENT        36381
POPCORN HOLDER                       36334
PACK OF 72 RETROSPOT CAKE CASES      36039
Name: Quantity, dtype: int64
# Beginner Example 4: What is the total sales revenue generated?
df['TotalPrice'] = df['Quantity'] * df['Price']
total_revenue = df['TotalPrice'].sum()
print('Total sales revenue:', round(total_revenue,2))
Total sales revenue: 9747765.93
# Beginner Example 5: Distribution of purchases by country
country_counts = df['Country'].value_counts().head(5)
print('Top 5 countries by number of purchases:')
print(country_counts)
Top 5 countries by number of purchases:
Country
United Kingdom    495478
Germany             9495
France              8558
EIRE                8196
Spain               2533
Name: count, dtype: int64
# Beginner Example 6: How many repeat customers do we have?
repeat_customers = df.groupby('Customer ID').size().loc[lambda x: x > 1].count()
print('Number of customers with multiple purchases:', repeat_customers)
Number of customers with multiple purchases: 4293
# Beginner Example 7: Which day has the highest number of purchases?
most_active_day = df['InvoiceDate'].dt.date.value_counts().idxmax()
print('Day with most purchases:', most_active_day)
Day with most purchases: 2011-12-05
# Intermediate Example 1: Revenue by country
country_revenue = df.groupby('Country')['TotalPrice'].sum().sort_values(ascending=False).head(5)
print('Top 5 countries by revenue:')
print(country_revenue)
Top 5 countries by revenue:
Country
United Kingdom    8187806.364
Netherlands        284661.540
EIRE               263276.820
Germany            221698.210
France             197421.900
Name: TotalPrice, dtype: float64
# Intermediate Example 2: Who are the top 10 customers by lifetime value (LTV)?
top_customers = df.groupby('Customer ID')['TotalPrice'].sum().sort_values(ascending=False).head(10)
print('Top 10 customers by total spend:')
print(top_customers)
Top 10 customers by total spend:
Customer ID
14646.0    279489.02
18102.0    256438.49
17450.0    187482.17
14911.0    132572.62
12415.0    123725.45
14156.0    113384.14
17511.0     88125.38
16684.0     65892.08
13694.0     62653.10
15311.0     59419.34
Name: TotalPrice, dtype: float64
# Intermediate Example 3: Orders over time (monthly revenue trend)
df['Month'] = df['InvoiceDate'].dt.to_period('M')
monthly_revenue = df.groupby('Month')['TotalPrice'].sum()
print('Monthly revenue breakdown:')
print(monthly_revenue.tail(6))
Monthly revenue breakdown:
Month
2011-07     681300.111
2011-08     682680.510
2011-09    1019687.622
2011-10    1070704.670
2011-11    1461756.250
2011-12     433686.010
Freq: M, Name: TotalPrice, dtype: float64
# Intermediate Example 4: What is the average order value (AOV)?
invoice_totals = df.groupby('Invoice')['TotalPrice'].sum()
aov = invoice_totals.mean()
print('Average order value (AOV):', round(aov, 2))
Average order value (AOV): 376.36
# Intermediate Example 5: Which products generate the most revenue?
product_revenue = df.groupby('Description')['TotalPrice'].sum().sort_values(ascending=False).head(5)
print('Top 5 products by revenue:')
print(product_revenue)
Top 5 products by revenue:
Description
DOTCOM POSTAGE                        206245.48
REGENCY CAKESTAND 3 TIER              164762.19
WHITE HANGING HEART T-LIGHT HOLDER     99668.47
PARTY BUNTING                          98302.98
JUMBO BAG RED RETROSPOT                92356.03
Name: TotalPrice, dtype: float64
# Intermediate Example 6: Customer frequency - how often do customers buy?
customer_freq = df.groupby('Customer ID')['InvoiceDate'].nunique().sort_values(ascending=False).head(5)
print('Top 5 customers by the number of distinct purchase days:')
print(customer_freq)
Top 5 customers by the number of distinct purchase days:
Customer ID
14911.0    248
12748.0    225
17841.0    168
14606.0    129
15311.0    118
Name: InvoiceDate, dtype: int64
# Intermediate Example 7: Which customer segment spends the most?
df['OrderSize'] = pd.cut(df['TotalPrice'], bins=[-1,20,100,500,np.inf], labels=['Small','Medium','Large','ExtraLarge'])
segment_counts = df.groupby('OrderSize')['TotalPrice'].mean().sort_values(ascending=False)
print('Average spend by order size segment:')
print(segment_counts)
Average spend by order size segment:
OrderSize
ExtraLarge    1339.800190
Large          184.775279
Medium          37.725271
Small            8.218086
Name: TotalPrice, dtype: float64
# Advanced Example 1: Identify customers with high recency, frequency, and monetary value (RFM)
snapshot_date = df['InvoiceDate'].max() + pd.Timedelta(days=1)
rfm = df.groupby('Customer ID').agg({
    'InvoiceDate': lambda x: (snapshot_date - x.max()).days,
    'Invoice': 'nunique',
    'TotalPrice': 'sum'
})
rfm.columns = ['Recency', 'Frequency', 'Monetary']
print('Sample RFM table:')
print(rfm.head())
Sample RFM table:
             Recency  Frequency  Monetary
Customer ID                              
12346.0          326          2      0.00
12347.0            2          7   4310.00
12348.0           75          4   1797.24
12349.0           19          1   1757.55
12350.0          310          1    334.40
# Advanced Example 2: Segmenting customers by RFM scores
rfm['R'] = pd.qcut(rfm['Recency'], 4, labels=[4,3,2,1])
rfm['F'] = pd.qcut(rfm['Frequency'].rank(method='first'), 4, labels=[1,2,3,4])
rfm['M'] = pd.qcut(rfm['Monetary'], 4, labels=[1,2,3,4])
rfm['RFM_Score'] = rfm[['R', 'F', 'M']].astype(int).sum(axis=1)
print('Customer segmentation based on RFM score:')
print(rfm[['Recency','Frequency','Monetary','RFM_Score']].head(5))
Customer segmentation based on RFM score:
             Recency  Frequency  Monetary  RFM_Score
Customer ID                                         
12346.0          326          2      0.00          4
12347.0            2          7   4310.00         12
12348.0           75          4   1797.24          9
12349.0           19          1   1757.55          8
12350.0          310          1    334.40          4
# Advanced Example 3: Detecting returns and cancelled transactions
cancelled = df[df['Invoice'].astype(str).str.contains('C')]
print('Number of cancelled/returned transactions:', cancelled.shape[0])
print('Sample cancelled transactions:')
print(cancelled[['Invoice', 'Customer ID', 'TotalPrice']].head())
Number of cancelled/returned transactions: 9288
Sample cancelled transactions:
     Invoice  Customer ID  TotalPrice
141  C536379      14527.0      -27.50
154  C536383      15311.0       -4.65
235  C536391      17548.0      -19.80
236  C536391      17548.0       -6.96
237  C536391      17548.0       -6.96
# Advanced Example 4: Visualizing sales trend over time (requires matplotlib)
import matplotlib.pyplot as plt
plt.figure(figsize=(12,5))
monthly_revenue.plot(kind='bar')
plt.title('Monthly Sales Revenue Trend')
plt.xlabel('Month')
plt.ylabel('Revenue')
plt.tight_layout()
plt.show()
No description has been provided for this image
# Error Handling 1: Checking for missing values
missing_counts = df.isnull().sum()
print('Missing value counts per column:')
print(missing_counts)
Missing value counts per column:
Invoice             0
StockCode           0
Description      1454
Quantity            0
InvoiceDate         0
Price               0
Customer ID    135080
Country             0
TotalPrice          0
Month               0
OrderSize        8994
dtype: int64
# Error Handling 2: Handling negative quantities (returns)
negative_quantities = df[df['Quantity'] < 0]
print('Number of transactions with negative quantity (returns):', negative_quantities.shape[0])
Number of transactions with negative quantity (returns): 10624
# Error Handling 3: What happens if you try to group by a column with missing values?
try:
    problem_group = df.groupby('Customer ID')['TotalPrice'].sum()
    print('Grouping succeeded!')
except Exception as e:
    print('Error during grouping:', e)
Grouping succeeded!
# Error Handling 4: Double counting errors - grouping by the wrong column
wrong_count = df.groupby('Invoice')['Description'].count().sum()
right_count = df.shape[0]
print('Wrong count if grouping then summing:', wrong_count)
print('Actual transaction count:', right_count)
Wrong count if grouping then summing: 540456
Actual transaction count: 541910
# Best Practice 1: Removing outliers for better purchase analysis
q_low = df['TotalPrice'].quantile(0.01)
q_high = df['TotalPrice'].quantile(0.99)
filtered = df[(df['TotalPrice'] >= q_low) & (df['TotalPrice'] <= q_high)]
print('Shape after removing outliers:', filtered.shape)
Shape after removing outliers: (531122, 11)
# Best Practice 2: Segmenting by product category using product codes
category_counts = df.groupby('StockCode')['TotalPrice'].sum().sort_values(ascending=False).head(5)
print('StockCodes with highest revenue:')
print(category_counts)
StockCodes with highest revenue:
StockCode
DOT       206245.48
22423     164762.19
47566      98302.98
85123A     97894.50
85099B     92356.03
Name: TotalPrice, dtype: float64
# Best Practice 3: Checking for seasonality
df['Weekday'] = df['InvoiceDate'].dt.day_name()
weekday_sales = df.groupby('Weekday')['TotalPrice'].sum()
weekday_sales = weekday_sales.reindex(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'])
print('Total revenue by weekday:')
print(weekday_sales)
Total revenue by weekday:
Weekday
Monday       1588609.431
Tuesday      1966182.791
Wednesday    1734147.010
Thursday     2112519.000
Friday       1540628.811
Saturday             NaN
Sunday        805678.891
Name: TotalPrice, dtype: float64
# Best Practice 4: Market basket analysis - Products purchased together
basket = df.groupby(['Invoice', 'Description'])['Quantity'].sum().unstack().fillna(0)
top_pairs = (basket > 0).sum(axis=0).sort_values(ascending=False).head(3)
print('Most frequently purchased products in baskets:')
print(top_pairs.index.tolist())
Most frequently purchased products in baskets:
['WHITE HANGING HEART T-LIGHT HOLDER', 'JUMBO BAG RED RETROSPOT', 'REGENCY CAKESTAND 3 TIER']
# End-to-End Example: Find high-value customers for a holiday email campaign
valid_sales = df[(~df['Invoice'].astype(str).str.contains('C')) & (df['TotalPrice'] > 0)]
customer_spend = valid_sales.groupby('Customer ID')['TotalPrice'].sum()
customer_email_targets = customer_spend[customer_spend > customer_spend.quantile(0.95)].index.tolist()
print('Number of top-spending customers to target:', len(customer_email_targets))
print('Sample Customer IDs:', customer_email_targets[:5])
Number of top-spending customers to target: 217
Sample Customer IDs: [12346.0, 12357.0, 12359.0, 12409.0, 12415.0]
 

Found this useful?

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