Mathew K Analytics

Lesson 52 · Python for Retail E-commerce Analytics

Designing Retail Analytics Dashboards with Python for E-commerce Insights

In this lesson, we solve real-world sales and product analysis problems using retail transactions data. Retail dashboards help businesses monitor sales…

⬇ 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

Designing Retail Analytics Dashboards#

  • In this lesson, we solve real-world sales and product analysis problems using retail transactions data.
  • Retail dashboards help businesses monitor sales performance, product trends, and customer behavior.
  • Dashboards are essential for inventory planning, marketing campaigns, and revenue forecasting.
  • By the end, you will know how to transform raw transaction data into actionable business insights.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')

What Is Retail Data and What Are We Measuring?#

  • Retail datasets track individual transactions, orders, products, and customers.
  • Key fields usually include product, price, quantity, date, and customer identifier.
  • Sales metrics are often structured as revenue (quantity price), quantity sold, and sales count.
  • Common mistakes include forgetting to aggregate quantities by invoice, or miscalculating total revenue.
  • Beginners sometimes group by the wrong column or ignore missing values.
# Beginner Example 1: Load Online Retail Transactions Data
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('Shape:', df.shape)
print(df.head(3))
Shape: (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: Explore Basic Column Types
print(df.dtypes)
print('Unique countries:', df['Country'].nunique())
Invoice                object
StockCode              object
Description            object
Quantity                int64
InvoiceDate    datetime64[ns]
Price                 float64
Customer ID           float64
Country                object
dtype: object
Unique countries: 38
# Beginner Example 3: Calculate Total Sales Revenue (Retail Transaction Dataset)
df['Revenue'] = df['Quantity'] * df['Price']
print('Revenue range:', df['Revenue'].min(), 'to', df['Revenue'].max())
print(df[['Quantity', 'Price', 'Revenue']].head(3))
Revenue range: -168469.6 to 168469.6
   Quantity  Price  Revenue
0         6   2.55    15.30
1         6   3.39    20.34
2         8   2.75    22.00
# Beginner Example 4: Retail Customer Orders DataFrame Setup
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')
orders = pd.DataFrame({'OrderID': order_ids,
                      'CustomerID': customer_ids,
                      'ProductID': product_ids,
                      'Quantity': quantities,
                      'OrderDate': order_dates})
print('Orders DataFrame shape:', orders.shape)
print(orders.head(3))
Orders DataFrame shape: (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 5: Summarize Customer Order Behavior
cust_order_counts = orders['CustomerID'].value_counts().reset_index()
cust_order_counts.columns = ['CustomerID', 'OrderCount']
print(cust_order_counts.head(3))
print('Max orders by a customer:', cust_order_counts['OrderCount'].max())
   CustomerID  OrderCount
0        1098           8
1        1143           7
2        1345           6
Max orders by a customer: 8
# Beginner Example 6: Group Orders by Product
prod_qty = orders.groupby('ProductID')['Quantity'].sum().reset_index()
prod_qty = prod_qty.sort_values('Quantity', ascending=False)
print(prod_qty.head(3))
    ProductID  Quantity
97       1098        54
25       1026        51
16       1017        50
# Intermediate Example 1: Generate Time-Based Sales Trends
orders['OrderDate'] = pd.to_datetime(orders['OrderDate'])
orders['OrderDay'] = orders['OrderDate'].dt.date
daily_sales = orders.groupby('OrderDay')['Quantity'].sum().reset_index()
plt.figure(figsize=(10,5))
sns.lineplot(data=daily_sales, x='OrderDay', y='Quantity')
plt.title('Total Quantity Sold Per Day')
plt.xlabel('Order Day')
plt.ylabel('Quantity Sold')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
No description has been provided for this image
# Intermediate Example 2: Plot Orders by Hour to Find Peak Times
orders['OrderHour'] = orders['OrderDate'].dt.hour
hourly_counts = orders.groupby('OrderHour').size().reset_index(name='OrderCount')
plt.figure(figsize=(8,4))
sns.barplot(data=hourly_counts, x='OrderHour', y='OrderCount', color='royalblue')
plt.title('Order Count by Hour of Day')
plt.xlabel('Hour of Day')
plt.ylabel('Number of Orders')
plt.show()
No description has been provided for this image
# Intermediate Example 3: Average Order Quantity per Customer
customer_mean = orders.groupby('CustomerID')['Quantity'].mean().reset_index()
customer_mean = customer_mean.sort_values('Quantity', ascending=False)
print(customer_mean.head(5))
     CustomerID  Quantity
4          1005       4.0
393        1462       4.0
387        1456       4.0
363        1426       4.0
46         1054       4.0
# Intermediate Example 4: Connect Orders to Product Catalog
categories = ['Electronics','Clothing','Home','Sports','Beauty']
product_ids_cat = list(range(1001,1101))
product_categories = np.random.choice(categories, 100)
product_prices = np.round(np.random.uniform(5,500,100),2)
catalog = pd.DataFrame({'ProductID': product_ids_cat, 'Category': product_categories, 'Price': product_prices})
orders_merged = orders.merge(catalog, on='ProductID', how='left')
print(orders_merged.head(3))
   OrderID  CustomerID  ProductID  Quantity           OrderDate    OrderDay  \
0        1        1102       1049         3 2023-01-01 00:00:00  2023-01-01   
1        2        1435       1011         1 2023-01-01 01:00:00  2023-01-01   
2        3        1348       1085         3 2023-01-01 02:00:00  2023-01-01   

   OrderHour     Category   Price  
0          0       Beauty  233.43  
1          1       Sports  211.00  
2          2  Electronics  314.21  
# Intermediate Example 5: Compute Revenue per Product Category
orders_merged['Revenue'] = orders_merged['Quantity'] * orders_merged['Price']
cat_revenue = orders_merged.groupby('Category')['Revenue'].sum().reset_index()
cat_revenue = cat_revenue.sort_values('Revenue', ascending=False)
print(cat_revenue)
plt.figure(figsize=(7,4))
sns.barplot(data=cat_revenue, x='Category', y='Revenue', palette='viridis')
plt.title('Revenue by Product Category')
plt.ylabel('Revenue')
plt.xlabel('Product Category')
plt.show()
      Category    Revenue
4       Sports  191575.92
1     Clothing  122116.40
0       Beauty  103516.93
3         Home   90647.86
2  Electronics   90631.93
No description has been provided for this image
# Intermediate Example 6: Identify Repeat Customers (Simple RFM)
order_counts = orders.groupby('CustomerID').size().reset_index(name='Frequency')
recent_order = orders.groupby('CustomerID')['OrderDate'].max().reset_index()
rfm = order_counts.merge(recent_order, on='CustomerID', how='left')
rfm = rfm.sort_values(['Frequency', 'OrderDate'], ascending=[False, False])
print(rfm.head(5))
     CustomerID  Frequency           OrderDate
78         1098          8 2023-02-03 08:00:00
119        1143          7 2023-02-08 23:00:00
186        1219          6 2023-02-10 11:00:00
356        1416          6 2023-02-10 08:00:00
320        1372          6 2023-02-09 12:00:00
# Advanced Example 1: Finding Top 5 High-Value Customers
high_value = orders_merged.groupby('CustomerID')['Revenue'].sum().reset_index()
high_value = high_value.sort_values('Revenue', ascending=False).head(5)
print('Top 5 high-value customers:')
print(high_value)
Top 5 high-value customers:
     CustomerID  Revenue
45         1053  6249.65
78         1098  6108.68
320        1372  5173.33
356        1416  5021.12
215        1251  4696.86
# Advanced Example 2: Monthly Sales Heatmap
orders_merged['YearMonth'] = orders_merged['OrderDate'].dt.to_period('M')
month_cat_sales = orders_merged.groupby(['YearMonth','Category'])['Revenue'].sum().reset_index()
pivot_tab = month_cat_sales.pivot(index='YearMonth', columns='Category', values='Revenue').fillna(0)
plt.figure(figsize=(10,6))
sns.heatmap(pivot_tab, annot=True, fmt='.0f', cmap='YlGnBu', cbar_kws={'label': 'Revenue'})
plt.title('Monthly Revenue Heatmap by Category')
plt.ylabel('Year-Month')
plt.xlabel('Product Category')
plt.tight_layout()
plt.show()
No description has been provided for this image
# Advanced Example 3: Market Basket Summary
products = ['Bread','Milk','Butter','Eggs','Apples','Chicken','Rice','Cheese']
transaction_ids = np.repeat(np.arange(1,301),3)
product_choices = np.random.choice(products,len(transaction_ids))
baskets = pd.DataFrame({'TransactionID': transaction_ids, 'Product': product_choices})
transaction_sets = baskets.groupby('TransactionID')['Product'].apply(list)
print(transaction_sets.head(3))
TransactionID
1    [Cheese, Cheese, Eggs]
2      [Milk, Butter, Milk]
3     [Rice, Bread, Cheese]
Name: Product, dtype: object
# Advanced Example 4: Error Handling - Missing Values
missing_counts = df.isnull().sum()
print('Missing values in retail transaction data:')
print(missing_counts)
Missing values in retail transaction data:
Invoice             0
StockCode           0
Description      1454
Quantity            0
InvoiceDate         0
Price               0
Customer ID    135080
Country             0
Revenue             0
dtype: int64
# Advanced Example 5: Debug Aggregation Mistakes - Incorrect Grouping
revenue_by_date = df.groupby('InvoiceDate')['Revenue'].sum()
print('Revenue grouped correctly by InvoiceDate.')

# Incorrect: group by Description only (could be misleading)
wrong_agg = df.groupby('Description')['Revenue'].sum().sort_values(ascending=False)
print('Revenue by product (top 3):')
print(wrong_agg.head(3))
Revenue grouped correctly by InvoiceDate.
Revenue by product (top 3):
Description
DOTCOM POSTAGE                        206245.48
REGENCY CAKESTAND 3 TIER              164762.19
WHITE HANGING HEART T-LIGHT HOLDER     99668.47
Name: Revenue, dtype: float64
# Advanced Example 6: Debug Revenue Calculation - Negative Quantities?
neg_qty = df[df['Quantity'] < 0]
print('Transactions with negative quantity:')
print(neg_qty[['Invoice', 'Quantity', 'Description', 'Revenue']].head())
Transactions with negative quantity:
     Invoice  Quantity                       Description  Revenue
141  C536379        -1                          Discount   -27.50
154  C536383        -1   SET OF 3 COLOURED  FLYING DUCKS    -4.65
235  C536391       -12    PLASTERS IN TIN CIRCUS PARADE    -19.80
236  C536391       -24  PACK OF 12 PINK PAISLEY TISSUES     -6.96
237  C536391       -24  PACK OF 12 BLUE PAISLEY TISSUES     -6.96

Best Practices: Retail Analytics Patterns#

  • Segment customers by how often, how recently, and how much they buy (RFM) for focused campaigns.
  • Monitor product category trends to optimize marketing and inventory decisions.
  • Analyze commonly-purchased product pairs for bundle deals and cross-selling.
  • Use seasonality and trend plots to prepare stock and campaigns ahead of demand spikes.
  • Avoid over-counting sales or customers through careful grouping by invoice, date, and product.
# End-to-End Example: Build a Simple Retail Sales Dashboard
category_sales = orders_merged.groupby('Category')['Revenue'].sum().reset_index()
date_sales = orders_merged.groupby('OrderDay')['Revenue'].sum().reset_index()
fig, axs = plt.subplots(1,2, figsize=(13,5))
sns.barplot(data=category_sales, x='Category', y='Revenue', ax=axs[0], palette='viridis')
axs[0].set_title('Total Revenue by Category')
axs[0].set_ylabel('Revenue')
axs[0].set_xlabel('Product Category')
sns.lineplot(data=date_sales, x='OrderDay', y='Revenue', ax=axs[1], color='teal')
axs[1].set_title('Revenue Trend Over Time')
axs[1].set_ylabel('Revenue')
axs[1].set_xlabel('Date')
plt.tight_layout()
plt.show()
No description has been provided for this image
 

Found this useful?

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