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)
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())
(1205, 6) 15 5
Cleaning, enriching and answering the business questions
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)
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
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()))
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.