Mathew K Analytics

Lesson 40 · Python for Retail E-commerce Analytics

Unlock Revenue Growth Opportunities in Retail E-Commerce with Python Analytics

In this lesson, we will solve a real-world business problem: finding ways to grow revenue in a retail or e-commerce environment. Revenue growth is crucial…

⬇ 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

Identifying Revenue Growth Opportunities in Retail and E-Commerce#

  • In this lesson, we will solve a real-world business problem: finding ways to grow revenue in a retail or e-commerce environment.
  • Revenue growth is crucial for retail and e-commerce companies, as it drives profits and supports expansion in a competitive market.
  • You will learn to analyze sales data, segment customers, evaluate product performance, and discover actionable opportunities to increase sales.
  • We will use real public datasets to build practical analytics workflows for sales, marketing, and inventory teams.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Core Retail Analytics Concepts#

  • Retail datasets track customer purchases, orders, product information, and transactions.
  • Key sales metrics include revenue, quantity sold, unit price, and order count.
  • Revenue usually equals Quantity multiplied by Price per transaction.
  • Beginners sometimes miscalculate revenue: forgetting to multiply by quantity or grouping incorrectly.
  • Be careful to always clarify the level of aggregation and the meaning of each column.
# Beginner Example 1: Load the Online Retail Transactions Dataset
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00502/online_retail_II.xlsx'
retail_df = pd.read_excel(url, sheet_name='Year 2010-2011')
retail_df['InvoiceDate'] = pd.to_datetime(retail_df['InvoiceDate'])
print(retail_df.shape)
print(retail_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 2: Find total revenue in the dataset
retail_df['Revenue'] = retail_df['Quantity'] * retail_df['Price']
total_revenue = retail_df['Revenue'].sum()
print(f"Total revenue in this period: GBP {total_revenue:,.2f}")
Total revenue in this period: GBP 9,747,765.93
# Beginner Example 3: Revenue by Product Description
revenue_by_product = retail_df.groupby('Description')['Revenue'].sum().sort_values(ascending=False)
print(revenue_by_product.head(5))
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: Revenue, dtype: float64
# Beginner Example 4: Revenue by Country
revenue_by_country = retail_df.groupby('Country')['Revenue'].sum().sort_values(ascending=False)
print(revenue_by_country.head(5))
Country
United Kingdom    8187806.364
Netherlands        284661.540
EIRE               263276.820
Germany            221698.210
France             197421.900
Name: Revenue, dtype: float64
# Beginner Example 5: Find number of unique customers
unique_customers = retail_df['Customer ID'].nunique()
print(f"Number of unique customers: {unique_customers}")
Number of unique customers: 4372
# Intermediate Example 1: Monthly Revenue Trend
monthly_revenue = retail_df.set_index('InvoiceDate').resample('M')['Revenue'].sum()
print(monthly_revenue)
InvoiceDate
2010-12-31     748957.020
2011-01-31     560000.260
2011-02-28     498062.650
2011-03-31     683267.080
2011-04-30     493207.121
2011-05-31     723333.510
2011-06-30     691123.120
2011-07-31     681300.111
2011-08-31     682680.510
2011-09-30    1019687.622
2011-10-31    1070704.670
2011-11-30    1461756.250
2011-12-31     433686.010
Freq: ME, Name: Revenue, dtype: float64
# Intermediate Example 2: Average Order Value (AOV)
retail_df['OrderValue'] = retail_df['Revenue']
aov = retail_df.groupby('Invoice')['OrderValue'].sum().mean()
print(f"Average order value (AOV): GBP {aov:.2f}")
Average order value (AOV): GBP 376.36
# Intermediate Example 3: High-Value Customer Identification
customer_revenue = retail_df.groupby('Customer ID')['Revenue'].sum()
top_customers = customer_revenue.sort_values(ascending=False).head(5)
print(top_customers)
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
# Intermediate Example 4: Product Category Revenue using Retail Product Catalog
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_df = pd.DataFrame({'ProductID':product_ids,'Category':product_categories,'Price':product_prices})
catalog_df['ProductID'] = catalog_df['ProductID'].astype(str)

# Merge with retail_df using StockCode as ProductID (string-match needed)
retail_df['ProductID'] = retail_df['StockCode'].astype(str)
merged_df = pd.merge(retail_df, catalog_df, left_on='ProductID', right_on='ProductID', how='left')

category_revenue = merged_df.groupby('Category')['Revenue'].sum().sort_values(ascending=False)
print(category_revenue)
Series([], Name: Revenue, dtype: float64)
# Intermediate Example 5: Time Series of a Product's Revenue
target_product = revenue_by_product.index[0]  # Use top seller
product_time_series = retail_df[retail_df['Description'] == target_product].set_index('InvoiceDate').resample('M')['Revenue'].sum()
print(f"Monthly revenue for {target_product}:")
print(product_time_series)
Monthly revenue for DOTCOM POSTAGE:
InvoiceDate
2010-12-31    24671.19
2011-01-31    13918.53
2011-02-28    10060.57
2011-03-31    11829.71
2011-04-30     7535.38
2011-05-31    10229.30
2011-06-30    11848.66
2011-07-31    12841.00
2011-08-31    13400.52
2011-09-30    15177.40
2011-10-31    17955.13
2011-11-30    36905.40
2011-12-31    19872.69
Freq: ME, Name: Revenue, dtype: float64
# Advanced Example 1: Calculate Revenue Growth Rate Month-over-Month
monthly_growth = monthly_revenue.pct_change().dropna() * 100
print("Monthly revenue growth rate (%) per month:")
print(monthly_growth.round(2))
Monthly revenue growth rate (%) per month:
InvoiceDate
2011-01-31   -25.23
2011-02-28   -11.06
2011-03-31    37.18
2011-04-30   -27.82
2011-05-31    46.66
2011-06-30    -4.45
2011-07-31    -1.42
2011-08-31     0.20
2011-09-30    49.37
2011-10-31     5.00
2011-11-30    36.52
2011-12-31   -70.33
Freq: ME, Name: Revenue, dtype: float64
# Advanced Example 2: Customer Segmentation by Revenue Quantiles
quantiles = customer_revenue.quantile([0.25, 0.5, 0.75]).to_dict()

def customer_segment(rev):
    if rev <= quantiles[0.25]:
        return 'Low Value'
    elif rev <= quantiles[0.5]:
        return 'Mid-Low Value'
    elif rev <= quantiles[0.75]:
        return 'Mid-High Value'
    else:
        return 'High Value'

customer_segments = customer_revenue.apply(customer_segment).value_counts()
print(customer_segments)
Revenue
Low Value         1093
High Value        1093
Mid-Low Value     1093
Mid-High Value    1093
Name: count, dtype: int64
# Advanced Example 3: Detect Revenue Loss from Missing Values
missing_revenue = retail_df[retail_df['Revenue'].isnull()]
num_missing = missing_revenue.shape[0]
print(f"Transactions missing revenue: {num_missing}")
Transactions missing revenue: 0
# Error Handling Example 1: Dropping Incomplete Records
clean_df = retail_df.dropna(subset=['Quantity','Price','Customer ID'])
print(f"Removed {len(retail_df) - len(clean_df)} incomplete records.")
print(f"Clean data now has {clean_df.shape[0]} rows.")
Removed 135080 incomplete records.
Clean data now has 406830 rows.
# Error Handling Example 2: Detecting Negative Sales
neg_qty = retail_df[retail_df['Quantity'] < 0]
print(f"Number of negative quantity transactions: {neg_qty.shape[0]}")
Number of negative quantity transactions: 10624
# Error Handling Example 3: Incorrect Aggregation Trap
incorrect_total = retail_df['Price'].sum()
correct_total = retail_df['Revenue'].sum()
print(f"Incorrect total (should NOT sum price alone): GBP {incorrect_total:,.2f}")
print(f"Correct total revenue: GBP {correct_total:,.2f}")
Incorrect total (should NOT sum price alone): GBP 2,498,821.97
Correct total revenue: GBP 9,747,765.93

Best Practices: Revenue Analysis Patterns#

  • Segment customers by their lifetime value to guide retention programs.
  • Analyze product categories to discover most promising growth opportunities.
  • Monitor order value and frequency to drive upsell and cross-sell strategies.
  • Always clean data and check your aggregations for accuracy.
  • Look for time trends and seasonal demand to plan inventory and marketing.
# Advanced Example 4: Simple Demand Forecasting (Moving Average)
monthly_revenue_ma = monthly_revenue.rolling(window=3).mean()
print("3-month moving average of revenue:")
print(monthly_revenue_ma)
3-month moving average of revenue:
InvoiceDate
2010-12-31             NaN
2011-01-31             NaN
2011-02-28    6.023400e+05
2011-03-31    5.804433e+05
2011-04-30    5.581790e+05
2011-05-31    6.332692e+05
2011-06-30    6.358879e+05
2011-07-31    6.985856e+05
2011-08-31    6.850346e+05
2011-09-30    7.945561e+05
2011-10-31    9.243576e+05
2011-11-30    1.184050e+06
2011-12-31    9.887156e+05
Freq: ME, Name: Revenue, dtype: float64
# Advanced Example 5: Market Basket Analysis (Simplified)
from itertools import combinations
retail_baskets = retail_df.groupby('Invoice')['Description'].unique().tolist()
basket_pairs = []
for basket in retail_baskets:
    if len(basket) > 1:
        basket_pairs.extend(combinations(sorted(basket), 2))
from collections import Counter
pair_counts = Counter(basket_pairs)
top_pairs = pair_counts.most_common(5)
print("Top 5 purchased product pairs:")
print(top_pairs)
Top 5 purchased product pairs:
[(('JUMBO BAG PINK POLKADOT', 'JUMBO BAG RED RETROSPOT'), 833), (('GREEN REGENCY TEACUP AND SAUCER', 'ROSES REGENCY TEACUP AND SAUCER '), 784), (('JUMBO BAG RED RETROSPOT', 'JUMBO STORAGE BAG SUKI'), 733), (('JUMBO BAG RED RETROSPOT', 'JUMBO SHOPPER VINTAGE RED PAISLEY'), 683), (('LUNCH BAG  BLACK SKULL.', 'LUNCH BAG RED RETROSPOT'), 648)]

Tiny End-to-End Example: Uncovering Revenue Growth Opportunities#

  • Step 1: Clean data by removing negative or invalid transactions.
  • Step 2: Identify product categories with the fastest revenue growth.
  • Step 3: Recommend focusing campaigns on high-growth categories.
  • This process takes you from raw data to a specific action item.
# Step 1: Clean negative and missing transactions
e2e_df = merged_df[(merged_df['Quantity'] > 0) & (~merged_df['Revenue'].isnull()) & (~merged_df['Category'].isnull())]
print(f"Rows after cleaning: {len(e2e_df)}")
Rows after cleaning: 0
# Step 2: Find fastest-growing categories (last 6 vs. prior 6 months)
e2e_df = e2e_df.set_index('InvoiceDate')
last_12m = e2e_df.sort_index().last('12M')
six_months_ago = last_12m.index.max() - pd.DateOffset(months=6)
cat_group = last_12m.groupby([pd.Grouper(freq='M'), 'Category'])['Revenue'].sum().reset_index()
growth_summary = cat_group.pivot(index='Category', columns='InvoiceDate', values='Revenue').fillna(0)
growth_summary['First6'] = growth_summary.iloc[:, :6].sum(axis=1)
growth_summary['Last6'] = growth_summary.iloc[:, -6:].sum(axis=1)
growth_summary['GrowthRate'] = ((growth_summary['Last6'] - growth_summary['First6']) / growth_summary['First6']) * 100
fastest = growth_summary['GrowthRate'].sort_values(ascending=False).head(3)
print('Fastest-growing categories (last 6 vs. first 6 months):')
print(fastest)
Fastest-growing categories (last 6 vs. first 6 months):
Series([], Name: GrowthRate, dtype: float64)
 

Found this useful?

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