Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
🧹 Week 3 – Data Cleaning & EDA  ·  Day 19

Day 19: Mini Project: Retail Sales EDA

⏱ 3–4 hours📶 Project🎯 100% self-study
Day 19 / 60

✅ Today you will learn

  • Turn a vague brief into 6 business questions
  • Clean and enrich data with a reusable function
  • Compute KPIs: revenue, AOV, orders, rating
  • Answer each question with a table + chart
  • Write a one-page management summary

🏢 In the real world

The CEO of BazaarKart writes: “We are planning 2025. Tell me what happened in 2024 and where we should invest.” That is all you get. This project shows exactly how a data analyst turns such a message into numbers, charts and recommendations – the kind of project that wins interviews.

Step 1: turn the brief into questions

#Business questionAnalysis
Q1How big was 2024?KPIs
Q2How did sales move through the year?Monthly trend
Q3Which cities and categories drive revenue?groupby + pivot
Q4Do discounts help or hurt?Discount bands vs revenue and rating
Q5Who are our customers?Age groups, gender, payment
Q6Where are customers unhappy?Rating analysis

Step 2: load, clean, enrich

Python
import pandas as pd

def load_sales(path="sales_data.csv"):
    df = pd.read_csv(path, parse_dates=["order_date"]).drop_duplicates()
    df["customer_age"] = df["customer_age"].fillna(df["customer_age"].median())
    df["revenue"] = df["quantity"] * df["unit_price"] * (1 - df["discount"])
    df["month"] = df["order_date"].dt.to_period("M")
    df["age_group"] = pd.cut(df["customer_age"], [17, 24, 34, 44, 60], labels=["18-24", "25-34", "35-44", "45+"])
    df["disc_band"] = pd.cut(df["discount"], [-0.01, 0, 0.1, 0.2, 0.3], labels=["None", "1-10%", "11-20%", "21-30%"])
    return df

df = load_sales()
df.to_pickle("sales_ready.pkl")                 # reuse in the next cells
print(df.shape, "| rating missing:", df["rating"].isna().sum())
print(df[["order_date", "city", "product", "revenue", "age_group", "disc_band"]].head(3))
(2000, 17) | rating missing: 35 order_date city product revenue age_group disc_band 0 2024-01-01 Delhi Jeans 2440.0 45+ None 1 2024-01-01 Mumbai Sneakers 2094.3 35-44 1-10% 2 2024-01-01 Mumbai Tea 1kg 2625.0 18-24 None

Q1: KPIs

Python
import pandas as pd
df = pd.read_pickle("sales_ready.pkl")
kpi = {"Revenue (₹ lakh)": round(df["revenue"].sum() / 1e5, 2),
       "Orders": len(df),
       "Units sold": int(df["quantity"].sum()),
       "Avg order value (₹)": round(df["revenue"].mean()),
       "Avg rating": round(df["rating"].mean(), 2),
       "Prepaid share %": round((df["payment_mode"] != "Cash on Delivery").mean() * 100, 1)}
for k, v in kpi.items():
    print(f"{k:22s} {v}")
Revenue (₹ lakh) 68.8 Orders 2000 Units sold 3890 Avg order value (₹) 3440 Avg rating 4.06 Prepaid share % 80.4

Q2: monthly trend

Python
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_pickle("sales_ready.pkl")
m = df.groupby("month").agg(revenue=("revenue", "sum"), orders=("order_id", "count"))
m["revenue_lakh"] = (m["revenue"] / 1e5).round(2)
print(m[["orders", "revenue_lakh"]].to_string())
fig, ax = plt.subplots(figsize=(10, 4))
ax.bar(m.index.strftime("%b"), m["revenue_lakh"], color=["#dc2626" if x in (10, 11) else "#818cf8" for x in m.index.month])
ax.set_title("Monthly revenue 2024 – the festive season (Oct–Nov) is the business"); ax.set_ylabel("₹ lakh")
for i, v in enumerate(m["revenue_lakh"]):
    ax.text(i, v + 0.1, f"{v:.1f}", ha="center", fontsize=8)
plt.show()
orders revenue_lakh month 2024-01 166 7.03 2024-02 128 5.24 2024-03 169 5.90 2024-04 139 4.32 2024-05 133 4.08 2024-06 134 5.02 2024-07 123 4.44 2024-08 180 4.92 2024-09 139 4.45 2024-10 249 8.61 2024-11 285 10.00 2024-12 155 4.80
Monthly revenue bar chart with October and November highlighted
Oct–Nov stand out – stock, staff and ad budgets must be planned for these two months.
Python
import pandas as pd
df = pd.read_pickle("sales_ready.pkl")
m = df.groupby("month")["revenue"].sum()
festive = m[m.index.month.isin([10, 11])].sum()
print(f"Oct+Nov = {festive / m.sum() * 100:.1f}% of yearly revenue in {2 / 12 * 100:.0f}% of the time")
print("Best month:", m.idxmax(), "| weakest month:", m.idxmin())
Oct+Nov = 27.0% of yearly revenue in 17% of the time Best month: 2024-11 | weakest month: 2024-05

Q3: cities and categories

Python
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_pickle("sales_ready.pkl")
city = df.groupby("city").agg(revenue=("revenue", "sum"), orders=("order_id", "count"), aov=("revenue", "mean"))
city["share_%"] = (city["revenue"] / city["revenue"].sum() * 100).round(1)
city = city.sort_values("revenue")
print(city.round(0).sort_values("revenue", ascending=False).head(5))
fig, ax = plt.subplots(1, 2, figsize=(12, 4.2))
(city["revenue"] / 1e5).plot(kind="barh", ax=ax[0], color="#4f46e5"); ax[0].set_title("Revenue by city (₹ lakh)")
cat = df.groupby("category")["revenue"].sum().sort_values()
ax[1].pie(cat, labels=cat.index, autopct="%1.0f%%", startangle=90, wedgeprops=dict(width=0.45))
ax[1].set_title("Revenue share by category")
plt.tight_layout(); plt.show()
revenue orders aov share_% city Mumbai 1043230.0 252 4140.0 15.0 Bengaluru 964020.0 299 3224.0 14.0 Delhi 945592.0 271 3489.0 14.0 Kolkata 839885.0 193 4352.0 12.0 Hyderabad 572190.0 200 2861.0 8.0
Bar chart of revenue by city and donut of category share
Mumbai, Bengaluru and Delhi bring over 40% of revenue; Electronics alone is more than half of all revenue.
Python
import pandas as pd
df = pd.read_pickle("sales_ready.pkl")
pv = df.pivot_table(index="category", values=["revenue", "quantity"], aggfunc="sum")
pv["orders"] = df.groupby("category").size()
pv["aov"] = (pv["revenue"] / pv["orders"]).round()
pv["rev_share_%"] = (pv["revenue"] / pv["revenue"].sum() * 100).round(1)
pv["order_share_%"] = (pv["orders"] / pv["orders"].sum() * 100).round(1)
print(pv[["orders", "order_share_%", "rev_share_%", "aov"]].sort_values("rev_share_%", ascending=False))
orders order_share_% rev_share_% aov category Electronics 439 22.0 55.8 8746.0 Fashion 486 24.3 22.0 3117.0 Grocery 528 26.4 11.5 1496.0 Home & Kitchen 325 16.2 6.2 1309.0 Books & Stationery 222 11.1 4.5 1396.0
🔎 Reading this table

Grocery has many orders but a small revenue share (low AOV) – it brings customers back often. Electronics has fewer orders but most of the money. Both matter, for different reasons: frequency vs value.

Q4: do discounts help?

Python
import pandas as pd
df = pd.read_pickle("sales_ready.pkl")
d = df.groupby("disc_band", observed=True).agg(orders=("order_id", "count"), avg_qty=("quantity", "mean"),
                                               aov=("revenue", "mean"), rating=("rating", "mean"))
d["discount_given_₹"] = df.groupby("disc_band", observed=True).apply(
    lambda g: (g["quantity"] * g["unit_price"] * g["discount"]).sum(), include_groups=False).round()
print(d.round(2))
orders avg_qty aov rating discount_given_₹ disc_band None 794 1.95 3463.30 4.05 0.0 1-10% 529 1.98 3742.54 4.08 162403.0 11-20% 584 1.92 3162.97 4.05 422572.0 21-30% 93 1.86 3257.48 4.10 129834.0
💡 Insight

Quantity per order and rating barely change with bigger discounts, yet the money given away rises sharply. In this data, deep discounts mostly reduce margin. Recommendation: test smaller, targeted offers (Day 31 – A/B testing).

Q5: who are our customers?

Python
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_pickle("sales_ready.pkl")
fig, ax = plt.subplots(1, 2, figsize=(11, 3.8))
df.groupby("age_group", observed=True)["revenue"].sum().div(1e5).plot(kind="bar", ax=ax[0], color="#0891b2", rot=0)
ax[0].set_title("Revenue by age group (₹ lakh)")
pm = pd.crosstab(df["age_group"], df["payment_mode"], normalize="index") * 100
pm.plot(kind="bar", stacked=True, ax=ax[1], rot=0, colormap="Set2")
ax[1].set_title("Payment mode by age (%)"); ax[1].legend(fontsize=7, bbox_to_anchor=(1.01, 1), loc="upper left")
plt.tight_layout(); plt.show()
print(df.groupby("customer_gender")["revenue"].agg(["count", "mean"]).round())
count mean customer_gender Female 924 3244.0 Male 1076 3608.0
Revenue by age group and payment mode share by age group
25–34 year-olds are the core customer; UPI dominates in every age group.

Q6: where are customers unhappy?

Python
import pandas as pd
df = pd.read_pickle("sales_ready.pkl")
r = df.dropna(subset=["rating"])
low = r.assign(low=r["rating"] <= 3).groupby("product")["low"].agg(["mean", "count"])
low = low[low["count"] >= 30].sort_values("mean", ascending=False)
low["mean"] = (low["mean"] * 100).round(1)
print(low.rename(columns={"mean": "low_rating_%", "count": "orders"}).head(5))
print("Overall low-rating %:", round((r["rating"] <= 3).mean() * 100, 1))
low_rating_% orders product Power Bank 17.3 81 T-Shirt 17.3 110 Mobile Phone 16.3 104 Jeans 16.0 94 NCERT Book Set 15.8 38 Overall low-rating %: 12.4

Step 3: the management summary

📈
Festive = the yearOct–Nov bring 27% of revenue in 17% of the year. Plan stock, delivery staff and ads 6 weeks earlier.
🏙️
Metros firstMumbai, Bengaluru and Delhi bring 43% of revenue. Same-day delivery there protects the core.
💸
Cut blanket discountsBigger discounts did not raise quantity or rating. Move budget to targeted offers.
🧑‍💻
25–34 + UPIThe core customer is young and pays by UPI – UPI cashback beats COD promotions.
Four recommendations for 2025
💼 How to present this project

Put it on GitHub (Day 53) with: the questions, the notebook, 4–5 charts and the four recommendations at the top of the README. Recruiters read the summary first – make it business-focused, not code-focused.

🏅 Job skills unlocked today

✔ Frame business questions✔ Build a KPI table✔ Combine groupby, pivot and charts into insights✔ Write recommendations for management

Practice tasks

🏋️ Practice – do it yourself

Task 1: Extend the project: which region grew most from Q3 to Q4?
Show solution
Python
import pandas as pd
df = pd.read_pickle("sales_ready.pkl")
q = df.pivot_table(index="region", columns=df["order_date"].dt.quarter, values="revenue", aggfunc="sum")
print(((q[4] / q[3] - 1) * 100).round(1).sort_values(ascending=False))
region East 106.6 South 99.2 West 55.5 North 37.4 dtype: float64
Task 2: Find the top 3 products in each city by revenue.
Show solution
Python
import pandas as pd
df = pd.read_pickle("sales_ready.pkl")
t = df.groupby(["city", "product"])["revenue"].sum().reset_index()
print(t.sort_values(["city", "revenue"], ascending=[True, False]).groupby("city").head(3).head(9).round(0).to_string(index=False))
city product revenue Ahmedabad Mobile Phone 112986.0 Ahmedabad Laptop 106274.0 Ahmedabad Sneakers 63819.0 Bengaluru Mobile Phone 264752.0 Bengaluru Laptop 141908.0 Bengaluru Sneakers 98781.0 Chennai Mobile Phone 160087.0 Chennai Laptop 114432.0 Chennai Sneakers 45284.0
Task 3: Write your own fifth recommendation backed by one number from the data.

Quick quiz

🧠 Quick Quiz – test yourself

1. AOV means…
Revenue ÷ number of orders.
2. Why save the cleaned data as a pickle?
CSV would turn dates and categories back into text.
3. A good management summary is…
Each point: finding + number + action.
4. Grocery has high order share but low revenue share. This means…
Many small baskets.
5. To check if discounts “work”, compare…
Benefit must be weighed against cost. A/B tests prove cause.

Interview corner

💼 Interview Corner

Tell me about a data analysis project you did.

Use STAR: Situation (CEO wanted a 2024 review), Task (find drivers and opportunities), Action (cleaned 2,000 orders, built KPIs, trend, city/category/discount/customer analysis in pandas), Result (four recommendations, e.g. festive season = X% of revenue, deep discounts did not raise quantity).

Can you say discounts caused no change?

No – this is observational data; discounts were not random. It suggests a hypothesis. A controlled A/B test is needed to prove cause.

Summary

  • Start from business questions, not from charts.
  • One load/clean function + saved pickle keeps analysis consistent.
  • Each question → table → chart → one-line insight.
  • Finish with 3–5 actionable, numeric recommendations.
➡️ Next

Day 20: Pandas practice test – 15 real tasks to check you are job-ready with pandas.

Frequently Asked Questions

Can I use this project in my portfolio?
Yes – but add your own question and recommendation so it is genuinely yours, and explain your choices in the README.
Notebook or script?
Use a notebook for exploration and presentation; move reusable code (load_sales) into a .py file as the project grows.
← Data Science (60 Days)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicDay 20: Revision Test: Python + Pandas Your progress: 19 / 60 (32%) · Lesson 20 / 60 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free