Mathew K Analytics

Lesson 25 · Python for Retail E-commerce Analytics

Master Interpreting Retail Performance Patterns Using Python for E-commerce Analytics

In this lesson, we will solve real-world retail analytics problems by interpreting sales patterns and customer behavior in transactional data. Understanding…

⬇ Download notebookOpen in Colab ↗

📓 Full notebook

Download .ipynb

Interpreting Retail Performance Patterns#

  • In this lesson, we will solve real-world retail analytics problems by interpreting sales patterns and customer behavior in transactional data.
  • Understanding sales and performance patterns helps retail businesses improve inventory management, marketing strategies, and profitability.
  • We will use real e-commerce datasets to uncover actionable insights such as top-performing products, customer segments, and time-based sales trends.
  • By the end, you will be able to turn raw transactional data into clear business recommendations.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Retail Data: Concepts and Common Pitfalls#

  • Retail datasets often describe transactions, orders, products, and customers.
  • Each row of a transactions dataset typically corresponds to a product sold in an order.
  • Key sales metrics include quantity (how many sold), price (unit price), and revenue (quantity x price).
  • Beginners often confuse product-level and order-level metrics, mix up gross and net revenue, or forget time granularity.
  • Careful grouping and aggregation is essential for interpreting performance patterns correctly.
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: Calculating Total Sales Revenue#

  • The first step to interpreting retail patterns is to calculate total sales revenue.
  • Revenue for each row is given by Quantity x Price.
  • Total sales revenue is the sum of this value across all transactions.
df['Revenue'] = df['Quantity'] * df['Price']
total_revenue = df['Revenue'].sum()
print(f'Total sales revenue: \u00a3{total_revenue:,.2f}')
Total sales revenue: £9,747,765.93

Beginner Example 2: Counting Number of Orders#

  • Retailers care about how many unique orders they receive in a period.
  • Each unique Invoice number typically represents one order.
  • Counting unique invoice numbers gives us the total number of orders.
num_orders = df['Invoice'].nunique()
print(f'Number of unique orders: {num_orders}')
Number of unique orders: 25900

Beginner Example 3: Identifying Top-Selling Products#

  • Top-selling products are those with the highest quantity sold across all orders.
  • Summing quantity by product description reveals which products are most popular.
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

Intermediate Example 1: Analyzing Average Order Value (AOV)#

  • A key metric in e-commerce is average order value: total sales revenue divided by the number of orders.
  • AOV helps businesses understand customer spending behavior.
aov = total_revenue / num_orders
print(f'Average order value (AOV): \u00a3{aov:,.2f}')
Average order value (AOV): £376.36

Intermediate Example 2: Sales by Country#

  • Retailers often sell to multiple countries, and country-wise analysis highlights key markets.
  • Summing revenue per country shows which regions contribute most to sales.
sales_by_country = df.groupby('Country')['Revenue'].sum().sort_values(ascending=False).head(5)
print('Top 5 countries by sales revenue:')
print(sales_by_country)
Top 5 countries by sales revenue:
Country
United Kingdom    8187806.364
Netherlands        284661.540
EIRE               263276.820
Germany            221698.210
France             197421.900
Name: Revenue, dtype: float64

Intermediate Example 3: Monthly Sales Patterns#

  • Businesses often want to know how sales change over time.
  • Summing revenue by month can highlight seasonal or trending patterns.
df['Month'] = df['InvoiceDate'].dt.to_period('M')
monthly_sales = df.groupby('Month')['Revenue'].sum()
print('Sales by month:')
print(monthly_sales.round(2))
Sales by month:
Month
2010-12     748957.02
2011-01     560000.26
2011-02     498062.65
2011-03     683267.08
2011-04     493207.12
2011-05     723333.51
2011-06     691123.12
2011-07     681300.11
2011-08     682680.51
2011-09    1019687.62
2011-10    1070704.67
2011-11    1461756.25
2011-12     433686.01
Freq: M, Name: Revenue, dtype: float64

Advanced Example 1: Identifying High-Value Customers#

  • Not all customers contribute equally to sales revenue.
  • Finding customers who spend the most can help with VIP marketing and retention.
top_customers = df.groupby('Customer ID')['Revenue'].sum().sort_values(ascending=False).head(5)
print('Top 5 customers by revenue:')
print(top_customers)
Top 5 customers by revenue:
Customer ID
14646.0    279489.02
18102.0    256438.49
17450.0    187482.17
14911.0    132572.62
12415.0    123725.45
Name: Revenue, dtype: float64

Advanced Example 2: Product Category Analysis (Synthetic Catalog)#

  • Sometimes, you will need to join transactions to product catalog data for richer analysis.
  • Let us create a synthetic product catalog and analyze which category drives most revenue.
# Create synthetic catalog
np.random.seed(42)
categories = ['Electronics','Clothing','Home','Sports','Beauty']
stock_codes = df['StockCode'].dropna().unique()
random_categories = np.random.choice(categories, size=len(stock_codes))
catalog = pd.DataFrame({'StockCode': stock_codes, 'Category': random_categories})
# Join to main data
df_cat = pd.merge(df, catalog, on='StockCode', how='left')
# Aggregate revenue per category
category_revenue = df_cat.groupby('Category')['Revenue'].sum().sort_values(ascending=False)
print('Revenue by product category:')
print(category_revenue)
Revenue by product category:
Category
Clothing       2178665.000
Electronics    2117509.370
Beauty         2014556.403
Sports         1724489.340
Home           1712545.821
Name: Revenue, dtype: float64

Advanced Example 3: Time-Based Sales Growth Rate#

  • Measuring percent change in monthly revenue shows sales growth or decline over time.
  • Growth rate helps managers react quickly to performance trends.
monthly_growth = monthly_sales.pct_change().dropna() * 100
print('Monthly sales growth rate (percentage):')
print(monthly_growth.round(2))
Monthly sales growth rate (percentage):
Month
2011-01   -25.23
2011-02   -11.06
2011-03    37.18
2011-04   -27.82
2011-05    46.66
2011-06    -4.45
2011-07    -1.42
2011-08     0.20
2011-09    49.37
2011-10     5.00
2011-11    36.52
2011-12   -70.33
Freq: M, Name: Revenue, dtype: float64

Error Handling: Dealing With Missing Customer IDs#

  • Real retail data often have missing or invalid customer IDs.
  • Ignoring missing customer IDs in customer-level analysis can mislead your conclusions.
missing_customers = df['Customer ID'].isnull().sum()
total_transactions = len(df)
missing_pct = 100 * missing_customers / total_transactions
print(f'Missing customer IDs: {missing_customers} ({missing_pct:.1f}% of transactions)')
Missing customer IDs: 135080 (24.9% of transactions)

Error Handling: Correcting Incorrect Aggregations#

  • Summing price instead of revenue is a common mistake.
  • Always multiply quantity by price before summing for transaction-level revenue.
# Incorrect aggregation: sum of price column
wrong_total = df['Price'].sum()
print(f'Incorrect total by summing Price only: \u00a3{wrong_total:,.2f}')
# Correct total revenue is much higher
print(f'Correct total revenue: \u00a3{total_revenue:,.2f}')
Incorrect total by summing Price only: £2,498,821.97
Correct total revenue: £9,747,765.93

Error Example: Incorrect Product or Category Grouping#

  • Failing to use the correct field to group by can mix up products or categories.
  • Always verify that grouping columns are uniquely identifying the product or category.
# Example: Group by 'StockCode' vs. 'Description'
group_by_code = df.groupby('StockCode')['Revenue'].sum().sort_values(ascending=False).head(3)
group_by_desc = df.groupby('Description')['Revenue'].sum().sort_values(ascending=False).head(3)
print('Grouped by StockCode:')
print(group_by_code)
print('\nGrouped by Description:')
print(group_by_desc)
Grouped by StockCode:
StockCode
DOT      206245.48
22423    164762.19
47566     98302.98
Name: Revenue, dtype: float64

Grouped by Description:
Description
DOTCOM POSTAGE                        206245.48
REGENCY CAKESTAND 3 TIER              164762.19
WHITE HANGING HEART T-LIGHT HOLDER     99668.47
Name: Revenue, dtype: float64

Best Practice: Customer Segmentation#

  • Segmenting customers by spending can help tailor marketing or loyalty efforts.
  • Use quantiles to group customers into tiers based on total revenue.
customer_revenue = df.groupby('Customer ID')['Revenue'].sum()
segments = pd.qcut(customer_revenue, q=4, labels=['Bronze','Silver','Gold','Platinum'])
segment_counts = segments.value_counts()
print('Customer segmentation by revenue tier:')
print(segment_counts)
Customer segmentation by revenue tier:
Revenue
Bronze      1093
Silver      1093
Gold        1093
Platinum    1093
Name: count, dtype: int64

Best Practice: Product Performance Analysis#

  • Always compare both total quantity sold and total revenue by product.
  • Some products are bestsellers by volume but not by revenue (and vice versa).
product_performance = df.groupby('Description').agg({'Quantity':'sum','Revenue':'sum'})
print('Sample product performance rows:')
print(product_performance.sort_values('Quantity', ascending=False).head(3))
print(product_performance.sort_values('Revenue', ascending=False).head(3))
Sample product performance rows:
                                   Quantity   Revenue
Description                                          
WORLD WAR 2 GLIDERS ASSTD DESIGNS     53847  13587.93
JUMBO BAG RED RETROSPOT               47363  92356.03
ASSORTED COLOUR BIRD ORNAMENT         36381  58959.73
                                    Quantity    Revenue
Description                                            
DOTCOM POSTAGE                           707  206245.48
REGENCY CAKESTAND 3 TIER               13033  164762.19
WHITE HANGING HEART T-LIGHT HOLDER     35317   99668.47

Best Practice: Detecting Seasonal Trends#

  • Identifying months with sales peaks or dips guides promotional campaigns.
  • Overlaying category or product sales by month can reveal product-specific seasonality.
# Example: Category revenue by month
df_cat['Month'] = df_cat['InvoiceDate'].dt.to_period('M')
cat_month = df_cat.groupby(['Category','Month'])['Revenue'].sum().unstack(0).fillna(0)
print('Revenue by category and month (sample):')
print(cat_month.head())
Revenue by category and month (sample):
Category      Beauty   Clothing  Electronics       Home     Sports
Month                                                             
2010-12   152217.160  175214.35    155779.50  109784.42  155961.59
2011-01   110265.550  109940.07    124756.36   96386.82  118651.46
2011-02   108216.420  101707.68    108180.93   86133.76   93823.86
2011-03   151379.980  142909.72    149007.71  119219.52  120750.15
2011-04   102635.601  102139.43    108732.03   93667.14   86032.92

End-to-End Example: From Transactions to Business Insight#

  • Let us answer a critical retail question: Which product category should the business prioritize for upcoming promotions?
  • We will combine revenue totals and seasonality to make a clear recommendation.
# Step 1: Find top revenue-driving category
top_category = category_revenue.idxmax()
print(f'Top category by total revenue: {top_category}')
# Step 2: Find peak month for this category
cat_peak_month = cat_month[top_category].idxmax()
print(f'Peak sales month for {top_category}: {cat_peak_month}')
Top category by total revenue: Clothing
Peak sales month for Clothing: 2011-11
 

Found this useful?

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