Project Goals
3 min- State one specific question in one sentence.
- Pick three charts that each answer a sub-question.
- Compose them into a single PNG with a story-arc headline.
- Write a four-sentence conclusion in a README.
Warm-Up · The Three-Chart Arc
5 minChart 1 — what happened? overall trend / total / size Chart 2 — who / where / what? broken down by category Chart 3 — what's interesting? surprise, outliers, correlation, comparison
Three charts. One question. One arc. Every good report follows this shape — Vox explainers, FT Visual Capitalist, the data section of any newspaper.
A data report stands or falls on the headline question. Write it first. The charts come next.
Plan · One Question → Three Charts
14 minSample question: "What does our shop's May 2026 look like?"
- Chart 1 — Daily revenue line with 3-day rolling average. Story: "the month overall".
- Chart 2 — Bar of revenue by product. Story: "who's driving it".
- Chart 3 — Pie of share by customer (or scatter of qty × price). Story: "a takeaway".
Skeleton
# data_story.py import pandas as pd import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec df = pd.read_csv("clean.csv", parse_dates=["date"]) df["total"] = df["quantity"] * df["price"] # --- compute ---------------------------------------------------- daily = (df.groupby(df["date"].dt.date)["total"].sum() .rename("rev").to_frame()) daily["roll"] = daily["rev"].rolling(3).mean() by_product = df.groupby("product")["total"].sum().sort_values(ascending=False) by_customer = df.groupby("customer")["total"].sum().sort_values(ascending=False) # --- draw ------------------------------------------------------- fig = plt.figure(figsize=(12, 7), constrained_layout=True) gs = GridSpec(2, 3, figure=fig) # Chart 1: trend a1 = fig.add_subplot(gs[0, :2]) a1.plot(daily.index, daily["rev"], alpha=0.4, label="daily") a1.plot(daily.index, daily["roll"], linewidth=2, label="3-day avg") a1.set_title("1. Revenue trend") a1.set_ylabel("RM") a1.legend() # Chart 2: bars a2 = fig.add_subplot(gs[0, 2]) a2.bar(by_product.index, by_product.values, color="#4a90e2") a2.set_title("2. By product") # Chart 3: pie a3 = fig.add_subplot(gs[1, :]) a3.pie(by_customer.head(5), labels=by_customer.head(5).index, autopct="%1.0f%%", startangle=90, counterclock=False) a3.set_title("3. Top-5 customer share") fig.suptitle("Sales · May 2026 — total RM {:.0f} across {} orders" .format(daily["rev"].sum(), len(df)), fontsize=15, weight="bold") fig.savefig("data_story.png", dpi=200)
Build Your Own
12 minPick from your homework dataset. Spend the first 5 minutes writing your question on paper. Then sketch the three charts. Then write the code.
The README
# Data story — <your title> **Question:** What does <dataset> tell us about <topic>? **Chart 1:** <what we show> **Chart 2:** <what we show> **Chart 3:** <what we show> ## Conclusion Three or four sentences. State the surprise / pattern / decision the data supports. Mention one caveat (data source, time range, missing values).
Extensions
13 minAdd three KPI text-cards at the top of the figure (Lesson 36 trick): total, count, average.
On the trend chart, annotate the day with the highest revenue using ax.annotate.
Apply a consistent color palette across all three charts (e.g., shades of one colour). Add a small subtle source line at the bottom-right with fig.text(0.99, 0.01, "source: ...", ha="right", alpha=0.5).
Stretch · Make It a PDF
8 minUse matplotlib.backends.backend_pdf.PdfPages to put your figure + a second figure (e.g., methodology) into a single multi-page PDF. Now your "data story" is a one-click deliverable.
Show the API
from matplotlib.backends.backend_pdf import PdfPages with PdfPages("data_story.pdf") as pdf: pdf.savefig(fig_story) pdf.savefig(fig_method)
Recap
3 minYou shipped your first portfolio-worthy data story. The arc — question, three charts, conclusion — works for school reports, blog posts, work decks. Tomorrow we shift to web apps: Flask.
Homework
4 minPolish your data story to portfolio quality: one PNG + one README, both committed to a folder. Tweet the headline + image (optional!) — feedback from the world is the best teacher.