Course topics

By WebNest Studio

Python Tutorial

Project: Exploratory Data Analysis of Sales Data

This project brings the whole data module together in the workflow analysts use every day: load → inspect → clean → enrich → analyse → visualise → report. You will analyse a year of orders for an online store and answer business questions: How are sales trending? Which cities and categories matter most? Who are the best customers? Is there a best day to run promotions?

The dataset is generated with a fixed random seed so your results match the ones shown. Replace the generation step with pd.read_csv("orders.csv", parse_dates=["date"]) to run the same analysis on real data.

The EDA Workflow

  • Ask questions first — what decisions should this analysis support?
  • Inspect — shape, types, missing values, duplicates, ranges.
  • Clean and enrich — fix types and values, add derived columns such as revenue, month and weekday.
  • Analyse — groupby summaries, rankings, trends and comparisons.
  • Visualise — one clear chart per key finding.
  • Report — a few plain-language conclusions backed by numbers, plus caveats about data quality.

Going Further

Turn the analysis into a reusable script or notebook, schedule it to regenerate the report monthly, or publish it as an interactive dashboard with Streamlit or Plotly Dash. The same cleaned DataFrame is also the starting point for machine learning — for example predicting next month's sales or which customers are likely to return.

Examples

Generating the dataset (replace with read_csv for real data)

Python
import numpy as np
import pandas as pd

rng = np.random.default_rng(2026)
n = 1200
products = pd.DataFrame({
    "product": ["Pen Set", "Notebook", "Backpack", "Headphones", "Mouse", "Desk Lamp"],
    "category": ["stationery", "stationery", "bags", "electronics", "electronics", "home"],
    "unit_price": [150, 60, 1250, 1999, 699, 899],
})
orders = pd.DataFrame({
    "order_id": np.arange(1, n + 1),
    "date": pd.to_datetime("2025-01-01") + pd.to_timedelta(rng.integers(0, 365, n), unit="D"),
    "customer_id": rng.integers(1, 301, n),
    "city": rng.choice(["Pune", "Mumbai", "Delhi", "Bengaluru"], n, p=[0.35, 0.3, 0.2, 0.15]),
    "product": rng.choice(products["product"], n, p=[0.25, 0.25, 0.1, 0.1, 0.2, 0.1]),
    "quantity": rng.integers(1, 5, n),
})
orders.loc[rng.choice(n, 15, replace=False), "quantity"] = np.nan    # some missing values
orders = pd.concat([orders, orders.sample(5, random_state=1)])        # some duplicates
orders.to_csv("orders.csv", index=False)
products.to_csv("products.csv", index=False)
print(orders.shape, orders["quantity"].isna().sum(), orders.duplicated().sum())
Output
(1205, 6) 15 5

Cleaning, enriching and answering the business questions

Python
import pandas as pd

orders = pd.read_csv("orders.csv", parse_dates=["date"])
products = pd.read_csv("products.csv")

# 1. Inspect and clean
print("raw:", orders.shape, "| missing quantity:", orders["quantity"].isna().sum(),
      "| duplicates:", orders.duplicated().sum())
orders = orders.drop_duplicates().dropna(subset=["quantity"])
orders["quantity"] = orders["quantity"].astype(int)

# 2. Enrich
df = orders.merge(products, on="product", how="left", validate="many_to_one")
df["revenue"] = df["quantity"] * df["unit_price"]
df["month"] = df["date"].dt.to_period("M")
df["weekday"] = df["date"].dt.day_name()
print("clean:", df.shape, "| total revenue: Rs", f"{df['revenue'].sum():,}")

# 3. Trend: monthly revenue and growth
monthly = df.groupby("month")["revenue"].sum()
print("best month:", monthly.idxmax(), "| worst month:", monthly.idxmin())

# 4. Where does revenue come from?
by_city = df.groupby("city")["revenue"].sum().sort_values(ascending=False)
print((by_city / by_city.sum() * 100).round(1).to_dict())
by_category = df.groupby("category").agg(orders=("order_id", "count"), revenue=("revenue", "sum"))
print(by_category.sort_values("revenue", ascending=False))

# 5. Best customers (top 5 by spend)
top = df.groupby("customer_id").agg(orders=("order_id", "count"), spend=("revenue", "sum")).nlargest(5, "spend")
print(top)
print("top 10% of customers bring",
      round(df.groupby("customer_id")["revenue"].sum().nlargest(30).sum() / df["revenue"].sum() * 100, 1), "% of revenue")

# 6. Best weekday for orders
print(df["weekday"].value_counts().head(3).to_dict())

df.to_csv("orders_clean.csv", index=False)
Output
raw: (1205, 6) | missing quantity: 15 | duplicates: 5
clean: (1185, 11) | total revenue: Rs 1,830,625
best month: 2025-05 | worst month: 2025-08
{'Pune': 39.4, 'Mumbai': 28.0, 'Delhi': 16.8, 'Bengaluru': 15.8}
             orders  revenue
category
electronics     329   991865
bags            119   413750
home            118   269700
stationery      619   155310
             orders  spend
customer_id
47               10  25121
298               9  21290
110               5  20466
246               7  20336
31                8  20237
top 10% of customers bring 26.6 % of revenue
{'Sunday': 183, 'Friday': 180, 'Saturday': 168}

Visualising the findings in a one-page report

Python
import matplotlib.pyplot as plt
import pandas as pd

df = pd.read_csv("orders_clean.csv", parse_dates=["date"])
monthly = df.set_index("date")["revenue"].resample("ME").sum()
by_city = df.groupby("city")["revenue"].sum().sort_values()
by_category = df.groupby("category")["revenue"].sum().sort_values()

fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes[0, 0].plot(monthly.index.strftime("%b"), monthly.values / 1000, marker="o")
axes[0, 0].set(title="Monthly revenue", ylabel="Revenue (thousand Rs)")
axes[0, 1].barh(by_city.index, by_city.values / 1000)
axes[0, 1].set(title="Revenue by city", xlabel="thousand Rs")
axes[1, 0].barh(by_category.index, by_category.values / 1000, color="tab:orange")
axes[1, 0].set(title="Revenue by category", xlabel="thousand Rs")
axes[1, 1].hist(df["revenue"], bins=20, edgecolor="white")
axes[1, 1].set(title="Order value distribution", xlabel="Rs per order", ylabel="Orders")
fig.suptitle("Store performance 2025", fontsize=14)
fig.tight_layout()
fig.savefig("sales_report.png", dpi=120)
plt.close(fig)

print("saved sales_report.png")
print("average order value: Rs", round(df["revenue"].mean()))
print("median order value:  Rs", round(df["revenue"].median()))
Output
saved sales_report.png
average order value: Rs 1545
median order value:  Rs 600

Common Mistakes

  • Jumping into charts before checking data quality.
  • Reporting averages only, when medians or distributions tell a different story.
  • Presenting many charts without stating what each one shows.
  • Forgetting to note caveats such as removed rows or estimated values.
  • Keeping analysis in an unrepeatable series of notebook cells instead of a script that can be rerun.

Key Points to Remember

  • EDA workflow: questions → inspect → clean → enrich → analyse → visualise → report.
  • merge adds reference data; derived columns (revenue, month, weekday) unlock analysis.
  • groupby, nlargest and value_counts answer most business questions.
  • A small set of clear charts communicates the findings.
  • Save the cleaned data so later analysis and machine learning start from it.

Practice the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

Use your local project environment for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.