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
Prerequisites
pip install streamlit pandasStep 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.pyDrag 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
parse_dates=["date"] in read_csv is the fix (already in the code).resample and Streamlit sorts it correctly.st.date_input with one unique date returns a scalar; guard the unpack or pad the range.Key Concepts
What to Try Next
resample("YE") and a grouped bar chart.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.