Mathew K Analytics

Lesson 7 · Pandas Projects

Retail Sales Analysis with Pandas: Find Trends & KPIs in Python

A complete, standalone tutorial: build a synthetic retail transactions dataset, then explore, clean, and analyze it with pandas. No prior pandas experience…

⬇ 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

Pandas for Retail: Analyzing Store Sales Data#

  • A complete, standalone tutorial: build a synthetic retail transactions dataset, then explore, clean, and analyze it with pandas.
  • No prior pandas experience needed. Let's jump straight in.

Before You Start#

  • Open a new Jupyter Notebook in VS Code and select your Python interpreter as the kernel.
  • If pandas isn't installed yet, open a terminal in VS Code and run: pip install pandas

Part 1: Building the Dataset#

import pandas as pd
import numpy as np
print(pd.__version__)
2.3.0

Generating Synthetic Sales Data#

rng = np.random.default_rng(seed=11)

stores = ['Downtown', 'Westside', 'Northgate', 'Eastview']
category_products = {
    'Grocery': ['Bread', 'Milk', 'Eggs', 'Coffee', 'Rice'],
    'Electronics': ['Headphones', 'Charger', 'Mouse', 'Webcam'],
    'Apparel': ['T-Shirt', 'Jeans', 'Jacket', 'Socks'],
    'Home & Garden': ['Lamp', 'Planter', 'Rug', 'Tool Kit'],
    'Toys': ['Puzzle', 'Board Game', 'Action Figure']
}
categories = list(category_products.keys())
print(stores)
print(categories)
['Downtown', 'Westside', 'Northgate', 'Eastview']
['Grocery', 'Electronics', 'Apparel', 'Home & Garden', 'Toys']
n_rows = 400
dates = pd.date_range('2026-01-01', periods=90, freq='D')

rows = []
for _ in range(n_rows):
    date = rng.choice(dates)
    store = rng.choice(stores)
    category = rng.choice(categories)
    product = rng.choice(category_products[category])
    quantity = int(rng.integers(1, 6))
    unit_price = round(float(rng.uniform(3, 120)), 2)
    rows.append([date, store, category, product, quantity, unit_price])

sales = pd.DataFrame(rows, columns=['date', 'store', 'category', 'product', 'quantity', 'unit_price'])
sales.insert(0, 'order_id', range(1001, 1001 + len(sales)))
print(sales.shape)
(400, 7)
sales.to_csv('retail_transactions.csv', index=False)
sales = pd.read_csv('retail_transactions.csv', parse_dates=['date'])
sales.head()
order_id date store category product quantity unit_price
0 1001 2026-01-13 Downtown Home & Garden Planter 3 6.36
1 1002 2026-02-24 Westside Grocery Eggs 5 11.24
2 1003 2026-02-18 Downtown Home & Garden Tool Kit 5 46.17
3 1004 2026-02-25 Downtown Apparel Jeans 4 35.21
4 1005 2026-03-19 Downtown Electronics Webcam 2 62.95

Part 2: First Look at the Data#

print(sales.shape)
print(sales.dtypes)
(400, 7)
order_id               int64
date          datetime64[ns]
store                 object
category              object
product               object
quantity               int64
unit_price           float64
dtype: object
sales.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 400 entries, 0 to 399
Data columns (total 7 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   order_id    400 non-null    int64         
 1   date        400 non-null    datetime64[ns]
 2   store       400 non-null    object        
 3   category    400 non-null    object        
 4   product     400 non-null    object        
 5   quantity    400 non-null    int64         
 6   unit_price  400 non-null    float64       
dtypes: datetime64[ns](1), float64(1), int64(2), object(3)
memory usage: 22.0+ KB
sales.describe()
order_id date quantity unit_price
count 400.000000 400 400.000000 400.000000
mean 1200.500000 2026-02-11 16:51:36.000000256 3.022500 60.683450
min 1001.000000 2026-01-01 00:00:00 1.000000 3.070000
25% 1100.750000 2026-01-18 18:00:00 2.000000 33.590000
50% 1200.500000 2026-02-12 00:00:00 3.000000 58.550000
75% 1300.250000 2026-03-06 00:00:00 4.000000 90.465000
max 1400.000000 2026-03-31 00:00:00 5.000000 119.860000
std 115.614301 NaN 1.442983 33.461407
sales['store'].value_counts()
store
Westside     109
Eastview     101
Downtown      99
Northgate     91
Name: count, dtype: int64

Part 3: Deriving Revenue#

sales['revenue'] = sales['quantity'] * sales['unit_price']
sales[['product', 'quantity', 'unit_price', 'revenue']].head()
product quantity unit_price revenue
0 Planter 3 6.36 19.08
1 Eggs 5 11.24 56.20
2 Tool Kit 5 46.17 230.85
3 Jeans 4 35.21 140.84
4 Webcam 2 62.95 125.90
print(f"Total revenue: ${sales['revenue'].sum():,.2f}")
print(f"Average order value: ${sales['revenue'].mean():,.2f}")
Total revenue: $74,515.35
Average order value: $186.29

Filtering and Sorting#

big_orders = sales[sales['revenue'] > 300]
print(len(big_orders))
big_orders.sort_values('revenue', ascending=False).head()
83
order_id date store category product quantity unit_price revenue
250 1251 2026-02-23 Eastview Electronics Headphones 5 119.51 597.55
366 1367 2026-03-13 Eastview Home & Garden Rug 5 117.94 589.70
181 1182 2026-01-18 Northgate Apparel T-Shirt 5 117.36 586.80
331 1332 2026-03-12 Eastview Home & Garden Lamp 5 116.83 584.15
191 1192 2026-02-06 Eastview Electronics Charger 5 116.72 583.60
electronics_orders = sales[(sales['category'] == 'Electronics') & (sales['store'] == 'Downtown')]
electronics_orders.head()
order_id date store category product quantity unit_price revenue
4 1005 2026-03-19 Downtown Electronics Webcam 2 62.95 125.90
6 1007 2026-01-13 Downtown Electronics Mouse 5 44.33 221.65
43 1044 2026-01-23 Downtown Electronics Charger 3 36.44 109.32
80 1081 2026-01-24 Downtown Electronics Webcam 4 41.84 167.36
91 1092 2026-01-03 Downtown Electronics Headphones 1 17.95 17.95

Part 4: Grouping and Aggregating#

revenue_by_category = sales.groupby('category')['revenue'].sum().sort_values(ascending=False)
revenue_by_category
category
Home & Garden    17360.23
Electronics      16761.78
Toys             13835.60
Grocery          13585.23
Apparel          12972.51
Name: revenue, dtype: float64
revenue_by_store = sales.groupby('store')['revenue'].sum().sort_values(ascending=False)
revenue_by_store
store
Westside     20909.68
Eastview     19050.99
Downtown     18132.55
Northgate    16422.13
Name: revenue, dtype: float64

Multiple Aggregations with agg()#

category_summary = sales.groupby('category').agg(
    total_revenue=('revenue', 'sum'),
    avg_unit_price=('unit_price', 'mean'),
    orders=('revenue', 'count')
).sort_values('total_revenue', ascending=False)
category_summary
total_revenue avg_unit_price orders
category
Home & Garden 17360.23 58.755745 94
Electronics 16761.78 63.298434 83
Toys 13835.60 60.487250 80
Grocery 13585.23 57.895588 68
Apparel 12972.51 62.942533 75

Pivot Table: Store by Category#

pivot = pd.pivot_table(sales, values='revenue', index='store', columns='category', aggfunc='sum', fill_value=0)
pivot
category Apparel Electronics Grocery Home & Garden Toys
store
Downtown 4027.50 3950.08 2700.35 4331.70 3122.92
Eastview 3251.28 5043.23 3133.23 4211.87 3411.38
Northgate 3047.07 3020.08 2727.07 4275.08 3352.83
Westside 2646.66 4748.39 5024.58 4541.58 3948.47

Top Products#

top_products = sales.groupby('product')['revenue'].sum().nlargest(5)
top_products
product
Puzzle           5550.78
Rug              5497.58
Action Figure    5293.85
Charger          4982.69
Headphones       4628.47
Name: revenue, dtype: float64

Part 5: Trends Over Time#

daily_revenue = sales.groupby('date')['revenue'].sum()
print(daily_revenue.head())
print(daily_revenue.sort_values(ascending=False).head(3))
date
2026-01-01    1147.22
2026-01-02     812.91
2026-01-03    1537.06
2026-01-04     705.25
2026-01-05    1300.08
Name: revenue, dtype: float64
date
2026-01-09    2038.09
2026-03-23    1965.76
2026-01-13    1888.35
Name: revenue, dtype: float64
rolling_avg = daily_revenue.rolling(window=7).mean()
print(rolling_avg.tail())
date
2026-03-27    859.095714
2026-03-28    723.104286
2026-03-29    721.898571
2026-03-30    461.215714
2026-03-31    352.660000
Name: revenue, dtype: float64

Part 6: Visualizing the Results#

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 5))
revenue_by_category.plot(kind='bar', ax=ax, color='steelblue')
ax.set_title('Total Revenue by Category')
ax.set_ylabel('Revenue ($)')
plt.tight_layout()
plt.savefig('revenue_by_category.png', dpi=150)
plt.close(fig)
print('Saved revenue_by_category.png')
Saved revenue_by_category.png
fig, ax = plt.subplots(figsize=(9, 5))
daily_revenue.plot(ax=ax, color='gray', alpha=0.5, label='Daily revenue')
rolling_avg.plot(ax=ax, color='crimson', linewidth=2, label='7-day rolling average')
ax.set_title('Daily Revenue with 7-Day Rolling Average')
ax.set_ylabel('Revenue ($)')
ax.legend()
plt.tight_layout()
plt.savefig('daily_revenue_trend.png', dpi=150)
plt.close(fig)
print('Saved daily_revenue_trend.png')
Saved daily_revenue_trend.png

Wrap-Up: What You Learned#

  • Generating a realistic synthetic dataset with NumPy, then saving and reloading it with to_csv and read_csv.
  • First-look exploration: shape, dtypes, info, describe, and value_counts.
  • Deriving new columns, filtering with boolean conditions, and sorting.
  • groupby, multi-statistic agg, and pivot_table for real business summaries.
  • Time-based grouping and a rolling average to reveal trends.
  • Saving clean, presentation-ready charts with matplotlib.
  • You went from raw synthetic transactions to a full revenue analysis with charts. If you want the next dataset in this series to land in your feed automatically, subscribing is the move see you in the next one.

Found this useful?

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