Mathew K Analytics

Lesson 24 · Python for Retail E-commerce Analytics

Visualizing Retail Sales Data with Python for E-commerce Analytics

In this lesson, we will solve real retail and e-commerce business questions using sales data visualizations. Visualizations help retailers and analysts…

⬇ Download notebookOpen in Colab ↗

📓 Full notebook

Download .ipynb

Visualizing Retail Sales Data#

  • In this lesson, we will solve real retail and e-commerce business questions using sales data visualizations.
  • Visualizations help retailers and analysts understand sales performance, spot demand trends, and make better marketing or inventory choices.
  • You will learn to analyze retail sales and create clear, actionable insights using charts in Python.
  • By the end, you will be able to explore key retail analytics problems visually and make practical recommendations for a business.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')

Core Retail Analytics Concepts#

  • Retail datasets often include transactions, products, customers, and sales dates.
  • Key metrics like revenue, quantity, and price show how products perform.
  • Analysts use groupings, aggregations, and visual summaries to discover insights.
  • Beginners may confuse invoice rows with product sales or miscalculate totals.
  • Always check data types and groupings to avoid mistakes in retail analysis.

Beginner Example 1: Load Real Retail Transactions and Explore#

  • Let us load a real-world retail transactions dataset.
  • We will explore the first few sales records to get familiar with the data.
  • Understanding the raw data is the first step in any visualization project.
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: Calculate Total Revenue in the Dataset#

  • We will compute total revenue from retail transactions.
  • This helps us understand overall store performance over a period.
  • Revenue is calculated as Quantity times Price for each row.
retail_df['Revenue'] = retail_df['Quantity'] * retail_df['Price']
total_revenue = retail_df['Revenue'].sum()
print('Total Revenue: {:,.2f}'.format(total_revenue))
Total Revenue: 9,747,765.93

Beginner Example 3: Basic Sales Visualization over Time#

  • We will visualize daily sales revenue using a simple line chart.
  • Line charts help retailers spot seasonality and detect changes in consumer demand.
  • Visualizing sales over time is essential for effective retail planning.
daily_sales = retail_df.groupby(retail_df['InvoiceDate'].dt.date)['Revenue'].sum()
plt.figure(figsize=(12,6))
plt.plot(daily_sales.index, daily_sales.values, marker='o', linestyle='-')
plt.title('Daily Retail Revenue Over Time')
plt.xlabel('Date')
plt.ylabel('Daily Revenue ()')
plt.grid(True)
plt.tight_layout()
plt.show()
No description has been provided for this image

Intermediate Example 1: Category-Level Sales Analysis#

  • Let us simulate a retail product catalog to analyze category performance.
  • Grouping sales by category reveals the strongest and weakest performers.
  • This information is key for inventory and marketing planning.
# Create a simulated product catalog with categories
np.random.seed(42)
categories = ['Electronics','Clothing','Home','Sports','Beauty']
prod_ids = list(range(1001, 1101))
prod_cats = np.random.choice(categories, 100)
prod_prices = np.round(np.random.uniform(5, 500, 100),2)
product_catalog = pd.DataFrame({'StockCode':prod_ids, 'Category':prod_cats, 'Price':prod_prices})
 
# Map StockCode in retail_df to Category
retail_df = pd.merge(retail_df, product_catalog[['StockCode','Category']], on='StockCode', how='left')
category_sales = retail_df.groupby('Category')['Revenue'].sum().sort_values(ascending=False)
print('Revenue by Product Category:')
print(category_sales)
Revenue by Product Category:
Series([], Name: Revenue, dtype: float64)
plt.figure(figsize=(8,6))
sns.barplot(x=category_sales.values, y=category_sales.index, palette='Blues_d')
plt.title('Revenue by Product Category')
plt.xlabel('Total Revenue ()')
plt.ylabel('Product Category')
plt.tight_layout()
plt.show()
No description has been provided for this image

Intermediate Example 2: Average Order Value by Country#

  • Calculating the mean order value helps businesses target high-value customers.
  • We will compute and visualize average order size for each country in our dataset.
  • This analysis aids international marketing and logistics decisions.
order_revenue = retail_df.groupby(['Invoice', 'Country'])['Revenue'].sum().reset_index()
country_avg_order = order_revenue.groupby('Country')['Revenue'].mean().sort_values(ascending=False)
print('Average Order Value by Country:')
print(country_avg_order.head(10))
Average Order Value by Country:
Country
Netherlands    2818.431089
Australia      1986.627101
Lebanon        1693.880000
Japan          1262.165000
Brazil         1143.600000
RSA            1002.310000
Singapore       912.039000
Denmark         893.720952
Norway          879.086500
Israel          878.646667
Name: Revenue, dtype: float64
plt.figure(figsize=(10,5))
sns.barplot(x=country_avg_order.values[:10], y=country_avg_order.index[:10], palette='viridis')
plt.title('Top 10 Countries by Average Order Value')
plt.xlabel('Average Order Value ()')
plt.ylabel('Country')
plt.tight_layout()
plt.show()
No description has been provided for this image

Intermediate Example 3: Sales Heatmap by Day of Week and Hour#

  • Retailers benefit by knowing when most sales occur during the week.
  • We will build a heatmap of total revenue by weekday and hour.
  • This reveals peak shopping times for staffing and promotions.
retail_df['DayOfWeek'] = retail_df['InvoiceDate'].dt.day_name()
retail_df['Hour'] = retail_df['InvoiceDate'].dt.hour
sales_pivot = retail_df.pivot_table(index='DayOfWeek', columns='Hour', values='Revenue', aggfunc='sum').fillna(0)
sales_pivot = sales_pivot.reindex(['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'])
plt.figure(figsize=(15,5))
sns.heatmap(sales_pivot, cmap='YlGnBu', linewidths=0.3)
plt.title('Retail Revenue by Day of Week and Hour')
plt.xlabel('Hour of Day')
plt.ylabel('Day of Week')
plt.show()
No description has been provided for this image

Advanced Example 1: Visualizing Product-Level Sales Distributions#

  • Analyzing which products sell most versus least can reveal inventory risks and opportunities.
  • We will plot a histogram and a top-k bar chart for product revenues.
  • These visualizations help retailers manage stock and prioritize promotions.
product_sales = retail_df.groupby('Description')['Revenue'].sum().sort_values(ascending=False)
plt.figure(figsize=(9,6))
sns.histplot(product_sales.values, bins=30, kde=True)
plt.title('Distribution of Product Sales Revenue')
plt.xlabel('Product Revenue ()')
plt.ylabel('Number of Products')
plt.tight_layout()
plt.show()
No description has been provided for this image
plt.figure(figsize=(10,5))
sns.barplot(x=product_sales.values[:10], y=product_sales.index[:10], palette='rocket')
plt.title('Top 10 Products by Revenue')
plt.xlabel('Total Revenue ()')
plt.ylabel('Product')
plt.tight_layout()
plt.show()
No description has been provided for this image

Advanced Example 2: Time Series Decomposition of Sales#

  • Time series decomposition helps uncover trend and seasonality in retail data.
  • We will apply a moving average and plot actual versus smoothed sales.
  • This lets businesses separate long-term growth from short-term fluctuations.
rolling_sales = daily_sales.rolling(window=7, min_periods=1).mean()
plt.figure(figsize=(12,6))
plt.plot(daily_sales.index, daily_sales.values, label='Actual Daily Sales', alpha=0.6)
plt.plot(daily_sales.index, rolling_sales, label='7-Day Moving Average', linewidth=3, color='red')
plt.title('Daily Retail Sales with Trend Smoothed (7-day MA)')
plt.xlabel('Date')
plt.ylabel('Revenue ()')
plt.legend()
plt.tight_layout()
plt.show()
No description has been provided for this image

Error Handling Example 1: Missing Quantity or Price Data#

  • Missing or zero values in retail sales data lead to incorrect revenue calculations.
  • We will find and count all rows where quantity or price is not positive.
  • Data quality checks are crucial before any visualization.
missing = retail_df[(retail_df['Quantity'] <= 0) | (retail_df['Price'] <= 0) | (retail_df['Quantity'].isnull()) | (retail_df['Price'].isnull())]
print('Number of problematic transactions:', missing.shape[0])
print(missing.head(2))
Number of problematic transactions: 11805
     Invoice StockCode                      Description  Quantity  \
141  C536379         D                         Discount        -1   
154  C536383    35004C  SET OF 3 COLOURED  FLYING DUCKS        -1   

            InvoiceDate  Price  Customer ID         Country  Revenue Category  \
141 2010-12-01 09:41:00  27.50      14527.0  United Kingdom   -27.50      NaN   
154 2010-12-01 09:49:00   4.65      15311.0  United Kingdom    -4.65      NaN   

     DayOfWeek  Hour  
141  Wednesday     9  
154  Wednesday     9  

Error Handling Example 2: Incorrect Grouping for Sales Aggregation#

  • Grouping by the wrong field when aggregating revenue can yield misleading business conclusions.
  • We will demonstrate an incorrect and then a correct way to summarize sales by product.
  • Accurate groupings are key for trustworthy insight.
# INCORRECT: Aggregating by Invoice only loses product detail
invoice_sales = retail_df.groupby('Invoice')['Revenue'].sum().head(5)
print(invoice_sales)
Invoice
536365    139.12
536366     22.20
536367    278.73
536368     70.05
536369     17.85
Name: Revenue, dtype: float64
# CORRECT: Group by both Invoice and Product Description
invoice_product_sales = retail_df.groupby(['Invoice','Description'])['Revenue'].sum().reset_index().head(5)
print(invoice_product_sales)
  Invoice                          Description  Revenue
0  536365       CREAM CUPID HEARTS COAT HANGER    22.00
1  536365    GLASS STAR FROSTED T-LIGHT HOLDER    25.50
2  536365  KNITTED UNION FLAG HOT WATER BOTTLE    20.34
3  536365       RED WOOLLY HOTTIE WHITE HEART.    20.34
4  536365         SET 7 BABUSHKA NESTING BOXES    15.30

Best Practices: Common Patterns in Retail Sales Analysis#

  • Segment customers based on order frequency and value for targeted promotions.
  • Analyze top and bottom-performing products to guide stock and marketing.
  • Use time-based plots and smoothed trends to see seasonality and campaigns.
  • Always clean and check for missing or unusual data values.
  • Visualizations are more effective when clearly labeled and matched to the business question.

Practice: A Tiny End-to-End Retail Sales Problem#

  • Let us identify the top-selling products of the month and generate a recommendation.
  • From raw transaction data, we will make an actionable business insight.
  • Try customizing the code below to fit your own store or retail scenario.
retail_df['Month'] = retail_df['InvoiceDate'].dt.to_period('M')
latest_month = retail_df['Month'].max()
monthly_top_products = retail_df[retail_df['Month'] == latest_month].groupby('Description')['Revenue'].sum().sort_values(ascending=False).head(5)
print(f'Top 5 Selling Products in {latest_month}:')
print(monthly_top_products)
Top 5 Selling Products in 2011-12:
Description
DOTCOM POSTAGE                     19872.69
RABBIT NIGHT LIGHT                  9618.01
PAPER CHAIN KIT 50'S CHRISTMAS      6870.71
REGENCY CAKESTAND 3 TIER            5902.92
BLACK RECORD COVER FRAME            5582.43
Name: Revenue, dtype: float64
 

Found this useful?

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