Tech With Galvan
Back to Articles
Development7/28/2025 • 3 min read

Sales Data Analyzer using Python, Pandas, and Streamlit

Analyze sales data with Python, Pandas, and Streamlit — revenue trends, top products, regional breakdowns, and month-over-month growth.

Galvan
Galvan

Founder & Creator

Introduction

Sales analysis is the business question engine: which products win, which regions lag, are we growing? This app answers all three from one transactions CSV — revenue trend lines, product leaderboards, regional breakdowns, and month-over-month growth — using nothing but groupby and the charting patterns from the Pandas dashboard.

It assumes a simple schema you likely already have: a row per sale with date, product, region, and amount. Everything else is aggregation.

Features

  • KPI row — total revenue, order count, average order value.
  • Monthly revenue trend — line chart with month-over-month growth labels.
  • Top products — ranked revenue bar chart with a configurable N.
  • Regional breakdown — side-by-side bar chart per region.
  • Date range filter — narrow every metric to a custom window.

Prerequisites

pip install streamlit pandas

Step 1: Prepare Sample Data

The app expects columns date, product, region, amount. Generate test data:

# make_sample_data.py
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
n = 2000
products = ["Laptop", "Phone", "Headphones", "Keyboard", "Monitor"]
regions = ["North", "South", "East", "West"]

df = pd.DataFrame({
    "date": pd.to_datetime("2024-01-01") + pd.to_timedelta(rng.integers(0, 540, n), unit="D"),
    "product": rng.choice(products, n),
    "region": rng.choice(regions, n),
    "amount": np.round(rng.uniform(20, 2000, n), 2),
})
df.to_csv("sales.csv", index=False)
print(df.head())

Step 2: Create the Analyzer

Save as sales_analyzer.py:

import streamlit as st
import pandas as pd

st.set_page_config(page_title="Sales Analyzer", page_icon="💼", layout="wide")
st.title("💼 Sales Data Analyzer")

df = pd.read_csv("sales.csv", parse_dates=["date"])

start, end = st.sidebar.date_input("Date range", [df["date"].min(), df["date"].max()])
mask = df["date"].between(pd.Timestamp(start), pd.Timestamp(end))
data = df[mask]

if data.empty:
    st.warning("No sales in this range.")
    st.stop()

revenue = data["amount"].sum()
c1, c2, c3 = st.columns(3)
c1.metric("Total revenue", f"₹{revenue:,.0f}")
c2.metric("Orders", f"{len(data):,}")
c3.metric("Avg order", f"₹{data['amount'].mean():,.0f}")

monthly = data.set_index("date").resample("ME")["amount"].sum()
st.line_chart(monthly, height=250)

if len(monthly) >= 2:
    growth = (monthly.iloc[-1] / monthly.iloc[-2] - 1) * 100
    st.caption(f"Month-over-month: {'📈 +' if growth >= 0 else '📉 '}{growth:.1f}%")

left, right = st.columns(2)
with left:
    top_n = st.slider("Top products", 3, len(data["product"].unique()), 5)
    top = data.groupby("product")["amount"].sum().nlargest(top_n)
    st.bar_chart(top)
with right:
    st.bar_chart(data.groupby("region")["amount"].sum())
    st.caption("Revenue by region")

with st.expander("🔍 Drill down"):
    product = st.selectbox("Product", ["All"] + sorted(data["product"].unique()))
    view = data if product == "All" else data[data["product"] == product]
    st.dataframe(view.sort_values("date", ascending=False).head(25), hide_index=True)

Step 3: Run the App

python make_sample_data.py
streamlit run sales_analyzer.py

Drag the date range and watch every metric recompute — then drill into a single product.

How It Works

Three Pandas idioms do all the work. resample("ME") converts timestamped rows into month buckets in one call — the time-grouping cousin of the to_period trick from the expense tracker. groupby(...).sum() produces the product and region aggregations, and nlargest(n) is a sorted-top-N in one method, replacing a sort-then-head two-step.

The sidebar date filter runs first and everything downstream reads the filtered data — one source of truth, so KPIs, charts, and the drill-down table can never disagree. st.stop() halts execution cleanly when the range is empty, avoiding a wall of downstream errors.

Month-over-month growth is just last / previous − 1 on the resampled series — with a signed caption so a bad month is visible at a glance.

Common Errors & Fixes

  • resample throws on object dtype — the date column wasn’t parsed; parse_dates=["date"] in read_csv is the fix (already in the code).
  • Chart months out of order — you grouped on a string month name; keep the datetime index from resample and Streamlit sorts it correctly.
  • nlargest includes ties oddly — it’s deterministic on first occurrence; for exact tie handling, sort by two columns instead.
  • Date input crashes on single-date files — st.date_input with one unique date returns a scalar; guard the unpack or pad the range.

Key Concepts

  • resample — time-based grouping with one method.
  • Filter once, use everywhere — a single filtered frame feeds all views.
  • nlargest — top-N without the sort ceremony.
  • st.stop() — early exit for degenerate states.

What to Try Next

  • Add profit margin columns (amount × margin rate) and rank by profit instead of revenue.
  • Add a year-over-year comparison using resample("YE") and a grouped bar chart.
  • Forecast next month with a rolling mean or the time series tutorial.
  • Export the filtered view to Excel via the report generator.

FAQ

My CSV has extra columns — will it break?

No — the app reads only the four it needs. Extra columns ride along harmlessly and appear in the drill-down table.

How do I handle returns/refunds?

Model them as negative amounts; sums and averages handle them correctly, and you can add a filter to exclude them from order counts.

Can this handle millions of rows?

Pandas can, but the drill-down table and date filter recompute each interaction. Pre-aggregate to monthly for display and drill down on a filtered subset — or move to DuckDB.