Courses/Streamlit: 20 Real Apps/Module 4: Data Apps
Module 4 · Lesson 435 minIntermediate

Project 12: Sales Data Analyzer

What you'll build
Answer real business questions: revenue trends, top products, regional breakdowns, growth.

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

  • Python 3.8+ — from python.org.
  • Dependencies:
  • code
    pip install streamlit pandas

    Step 1: Prepare Sample Data

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

    code
    # 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:

    code
    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

    code
    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 filesst.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.

    Adapted from: Sales Data Analyzer using Python, Pandas, and Streamlit

    Checkpoint
    The date-range filter recomputes KPIs, trend, and leaderboards consistently.
    What you learned
    • resample for time grouping
    • nlargest for leaderboards
    • st.stop for empty states