Project: sales analysis for a sweets chain
Let us put the workflow together on a realistic case. Madhuram Sweets (a fictional chain) has 12 stores across Jaipur, Indore and Nagpur. The owner asks: "Which products and stores should we focus on for the Diwali season, and how much stock should we plan?"
Step 1: frame the question
We narrow it to three answerable questions:
- Which categories drove revenue and gross margin in last year's festive period (Oct–Nov)?
- How does each store's festive uplift compare with its normal weeks?
- What daily demand should we plan for the top 10 products?
Step 2: load and check the data
The point-of-sale export has one row per bill line: bill_id, date, store, city, product, category, qty_kg,
price_per_kg, cost_per_kg.
import pandas as pd
sales = pd.read_csv("pos_lines.csv", parse_dates=["date"], dayfirst=True)
print(sales.shape, sales["date"].min(), sales["date"].max())
print(sales.isna().sum())
print(sales.duplicated(["bill_id", "product"]).sum())
sales = sales.drop_duplicates(["bill_id", "product"])
sales = sales[(sales["qty_kg"] > 0) & (sales["price_per_kg"] > 0)]
Check for zero or negative quantities (returns entered as sales), mixed city spellings and missing cost prices.
Step 3: engineer useful features
Raw columns rarely answer the question directly. Create features that match how the business thinks:
sales = sales.assign(
revenue=lambda d: d["qty_kg"] * d["price_per_kg"],
margin=lambda d: d["qty_kg"] * (d["price_per_kg"] - d["cost_per_kg"]),
weekday=lambda d: d["date"].dt.day_name(),
festive=lambda d: d["date"].between("2025-10-01", "2025-11-30"),
)
Other helpful features: bill size band, whether the bill contains a gift box, and days before Diwali.
Step 4: analyse
by_cat = (sales.groupby(["festive", "category"])
.agg(revenue=("revenue", "sum"), margin=("margin", "sum"))
.assign(margin_pct=lambda d: (d["margin"] / d["revenue"] * 100).round(1)))
daily = sales.groupby(["store", "festive", sales["date"].dt.date])["revenue"].sum()
uplift = (daily.groupby(level=["store", "festive"]).mean()
.unstack("festive")
.rename(columns={False: "normal", True: "festive"})
.assign(uplift=lambda d: d["festive"] / d["normal"]))
print(uplift.sort_values("uplift", ascending=False))
Suppose the results look like this:
| Category | Festive revenue share | Margin % |
|---|---|---|
| Dry-fruit sweets | 34% | 41% |
| Milk sweets | 29% | 28% |
| Gift boxes | 22% | 46% |
| Namkeen | 15% | 33% |
Step 5: plan demand
For the top products, take average daily kilograms sold during last year's festive period, adjust for this year's growth and add a safety buffer. Milk sweets have a short shelf life, so a smaller buffer and a daily re-order is better than one large batch.
Step 6: recommend
A good final answer is short and specific:
- Push gift boxes and dry-fruit sweets: highest margin and strong festive share.
- Two Indore stores show a 3x festive uplift; add counter staff and a pre-order desk there.
- Plan milk-sweet production daily from the demand table, with a 10% buffer.
Key takeaways
- Frame the question first; it decides which features and analyses matter.
- Cleaning and feature engineering usually take more time than the analysis itself.
- Always connect numbers back to an action someone can take.
Practice
Create a synthetic dataset of 5,000 bill lines for three stores and five products (use NumPy's random Generator). Run steps 2–4, then write a three-bullet recommendation the store owner could act on tomorrow.