Mathew K Analytics

Lesson 53 · Python for Retail E-commerce Analytics

Data Storytelling for Retail Decision Makers: Practical Training for Impactful Insights

In this lesson, we will learn how to translate retail analytics into actionable business stories. Data storytelling helps retailers understand key…

⬇ 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

Data Storytelling for Retail Decision Makers#

  • In this lesson, we will learn how to translate retail analytics into actionable business stories.
  • Data storytelling helps retailers understand key performance trends and make better sales, marketing, and inventory decisions.
  • We will use real retail datasets to find insights such as best-selling products, high-value customers, and sales trends.
  • You will practice building clear analyses and communicating findings with impact.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Core Retail Analytics Concepts#

  • Retail datasets often represent transactions, orders, products, and customers.
  • Important sales metrics include revenue, quantity sold, and price per unit.
  • Beginners often make mistakes such as counting quantities incorrectly, grouping by the wrong column, or mixing up revenue and quantity.
  • Carefully check each calculation and always review your aggregations.
# Beginner Example 1: Load Online Retail Transaction 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(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 2: View Unique Products
unique_products = df['Description'].nunique()
print('Number of unique products:', unique_products)
Number of unique products: 4223
# Beginner Example 3: Total Revenue for the Dataset
df['Revenue'] = df['Quantity'] * df['Price']
total_revenue = df['Revenue'].sum()
print('Total Revenue: {:,.2f}'.format(total_revenue))
Total Revenue: 9,747,765.93
# Intermediate Example 1: Monthly Sales Trend
df['Month'] = df['InvoiceDate'].dt.to_period('M')
monthly_revenue = df.groupby('Month')['Revenue'].sum()
print(monthly_revenue)
Month
2010-12     748957.020
2011-01     560000.260
2011-02     498062.650
2011-03     683267.080
2011-04     493207.121
2011-05     723333.510
2011-06     691123.120
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: Revenue, dtype: float64
# Intermediate Example 2: Top 5 Best-Selling Products
top_products = df.groupby('Description')['Revenue'].sum().sort_values(ascending=False).head(5)
print(top_products)
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
# Intermediate Example 3: Average Order Value (AOV)
invoice_revenue = df.groupby('Invoice')['Revenue'].sum()
aov = invoice_revenue.mean()
print('Average Order Value: {:.2f}'.format(aov))
Average Order Value: 376.36
# Intermediate Example 4: Customer Lifetime Value (LTV) Simplified
ltv = df.groupby('Customer ID')['Revenue'].sum().sort_values(ascending=False)
print(ltv.head(3))
Customer ID
14646.0    279489.02
18102.0    256438.49
17450.0    187482.17
Name: Revenue, dtype: float64
# Intermediate Example 5: Share of Revenue by Country
country_share = df.groupby('Country')['Revenue'].sum().sort_values(ascending=False)
total_revenue = country_share.sum()
country_share_perc = (country_share / total_revenue * 100).round(2)
print(country_share_perc.head(10))
Country
United Kingdom    84.00
Netherlands        2.92
EIRE               2.70
Germany            2.27
France             2.03
Australia          1.41
Switzerland        0.58
Spain              0.56
Belgium            0.42
Sweden             0.38
Name: Revenue, dtype: float64
# Advanced Example 1: Product Category Performance (using product catalog)
categories = ['Electronics','Clothing','Home','Sports','Beauty']
product_ids = list(range(1001,1101))
np.random.seed(42)
product_categories = np.random.choice(categories, 100)
df_catalog = pd.DataFrame({'StockCode': product_ids, 'Category': product_categories})
merged = df.merge(df_catalog, left_on='StockCode', right_on='StockCode', how='left')
category_revenue = merged.groupby('Category')['Revenue'].sum().sort_values(ascending=False)
print(category_revenue)
Series([], Name: Revenue, dtype: float64)
# Advanced Example 2: Customer Segmentation by Spend
merged['CustomerSpend'] = merged.groupby('Customer ID')['Revenue'].transform('sum')
high_value = merged[merged['CustomerSpend'] >= merged['CustomerSpend'].quantile(0.9)]
mid_value = merged[(merged['CustomerSpend'] < merged['CustomerSpend'].quantile(0.9)) & 
                  (merged['CustomerSpend'] >= merged['CustomerSpend'].quantile(0.5))]
low_value = merged[merged['CustomerSpend'] < merged['CustomerSpend'].quantile(0.5)]
print('High value customers:', high_value['Customer ID'].nunique())
print('Mid value customers:', mid_value['Customer ID'].nunique())
print('Low value customers:', low_value['Customer ID'].nunique())
High value customers: 29
Mid value customers: 610
Low value customers: 3733
# Advanced Example 3: Detecting Seasonal Sales Patterns
monthly_qty = merged.groupby('Month')['Quantity'].sum()
monthly_qty.plot(title='Total Quantity Sold Per Month', ylabel='Total Quantity', xlabel='Month', legend=False)
<Axes: title={'center': 'Total Quantity Sold Per Month'}, xlabel='Month', ylabel='Total Quantity'>
No description has been provided for this image
# Error Handling Example 1: Checking for Missing Values
missing = df.isnull().sum()
print(missing[missing > 0])
Description      1454
Customer ID    135080
dtype: int64
# Error Handling Example 2: Mistake in Grouping
incorrect = df.groupby('Invoice')['Quantity'].sum()
correct = df.groupby(['Invoice', 'Description'])['Quantity'].sum().groupby('Invoice').sum()
print('Incorrect total:', incorrect.head(3).sum())
print('Correct total:', correct.head(3).sum())
Incorrect total: 135
Correct total: 135
# Error Handling Example 3: Incorrect Revenue Calculation
df_err = df.copy()
df_err['WrongRevenue'] = df_err['Quantity'] + df_err['Price']
print(df_err[['Quantity','Price','WrongRevenue']].head(3))
   Quantity  Price  WrongRevenue
0         6   2.55          8.55
1         6   3.39          9.39
2         8   2.75         10.75
# Best Practice 1: Remove Refunds (Negative Quantity)
cleaned = df[df['Quantity'] > 0]
print('After removing refunds:', cleaned.shape)
After removing refunds: (531286, 10)
# Best Practice 2: Market Basket Analysis (Transaction-Product Matrix)
basket = pd.crosstab(df['Invoice'], df['Description'])
print(basket.head(3))
Description  20713   4 PURPLE FLOCK DINNER CANDLES  \
Invoice                                              
536365           0                               0   
536366           0                               0   
536367           0                               0   

Description   50'S CHRISTMAS GIFT BAG LARGE   DOLLY GIRL BEAKER  \
Invoice                                                           
536365                                    0                   0   
536366                                    0                   0   
536367                                    0                   0   

Description   I LOVE LONDON MINI BACKPACK   I LOVE LONDON MINI RUCKSACK  \
Invoice                                                                   
536365                                  0                             0   
536366                                  0                             0   
536367                                  0                             0   

Description   NINE DRAWER OFFICE TIDY   OVAL WALL MIRROR DIAMANTE   \
Invoice                                                              
536365                              0                            0   
536366                              0                            0   
536367                              0                            0   

Description   RED SPOT GIFT BAG LARGE   SET 2 TEA TOWELS I LOVE LONDON   ...  \
Invoice                                                                  ...   
536365                              0                                 0  ...   
536366                              0                                 0  ...   
536367                              0                                 0  ...   

Description  wrongly coded 20713  wrongly coded 23343  wrongly coded-23343  \
Invoice                                                                      
536365                         0                    0                    0   
536366                         0                    0                    0   
536367                         0                    0                    0   

Description  wrongly marked  wrongly marked 23343  \
Invoice                                             
536365                    0                     0   
536366                    0                     0   
536367                    0                     0   

Description  wrongly marked carton 22804  wrongly marked. 23343 in box  \
Invoice                                                                  
536365                                 0                             0   
536366                                 0                             0   
536367                                 0                             0   

Description  wrongly sold (22719) barcode  wrongly sold as sets  \
Invoice                                                           
536365                                  0                     0   
536366                                  0                     0   
536367                                  0                     0   

Description  wrongly sold sets  
Invoice                         
536365                       0  
536366                       0  
536367                       0  

[3 rows x 4223 columns]
# Best Practice 3: Forecasting with Rolling Mean
monthly_revenue_rolling = monthly_revenue.rolling(window=3).mean()
print(monthly_revenue_rolling.tail(6))
Month
2011-07    6.985856e+05
2011-08    6.850346e+05
2011-09    7.945561e+05
2011-10    9.243576e+05
2011-11    1.184050e+06
2011-12    9.887156e+05
Freq: M, Name: Revenue, dtype: float64
# End-to-End Problem: Identify Top-Selling Products and Recommend Action
top_products = df.groupby('Description')['Revenue'].sum().sort_values(ascending=False).head(3)
print('Top 3 products for promotion:')
for product, revenue in top_products.items():
    print(f'- {product}: {revenue:,.2f}')
print('Recommendation: Focus promotional campaigns on these products to maximize short-term sales growth.')
Top 3 products for promotion:
- DOTCOM POSTAGE: 206,245.48
- REGENCY CAKESTAND 3 TIER: 164,762.19
- WHITE HANGING HEART T-LIGHT HOLDER: 99,668.47
Recommendation: Focus promotional campaigns on these products to maximize short-term sales growth.
 

Found this useful?

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