Learning Goals
3 min- Create a grid of subplots with
plt.subplots(nrows, ncols). - Add a figure-level title with
fig.suptitle. - Tweak layouts with
tight_layoutandconstrained_layout. - Use
GridSpecfor unequal-size panels (1 big chart + 2 small).
Warm-Up · 2 × 2 Grid
5 minimport matplotlib.pyplot as plt fig, axes = plt.subplots(2, 2, figsize=(10, 8)) axes[0, 0].plot([1, 2, 3], [3, 1, 2]) axes[0, 1].bar(["A", "B"], [5, 3]) axes[1, 0].scatter([1, 2, 3], [3, 5, 1]) axes[1, 1].pie([1, 2, 3]) fig.suptitle("Four chart types") fig.tight_layout() plt.show()
axes is a 2-D numpy array of Axes objects — same indexing as a 2-D list. axes.flat flattens it if you want to iterate.
A dashboard is one figure with multiple coordinated panels. Subplots share figure-level metadata (title, color scheme) so the story stays consistent.
New Concept · subplots & gridspec
14 minOne row, many columns
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 4))
Share axes
fig, axes = plt.subplots(2, 2, sharex=True, sharey=True)
Shared axes are great for small multiples — same scale, same range, easy to compare.
Custom layouts with GridSpec
from matplotlib.gridspec import GridSpec fig = plt.figure(figsize=(12, 6)) gs = GridSpec(2, 3, figure=fig) big = fig.add_subplot(gs[:, 0:2]) # rows 0-1, cols 0-1 small1 = fig.add_subplot(gs[0, 2]) # row 0, col 2 small2 = fig.add_subplot(gs[1, 2]) # row 1, col 2
That gives you one big chart on the left and two small ones stacked on the right — a common dashboard shape.
constrained_layout — the modern alternative to tight_layout
fig, axes = plt.subplots(2, 2, constrained_layout=True)
constrained_layout=True packs subplots smartly and avoids overlapping titles/labels. Set it once on figure creation.
Worked Example · One-Page Sales Dashboard
12 min# dashboard.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"] fig = plt.figure(figsize=(12, 7), constrained_layout=True) gs = GridSpec(2, 3, figure=fig) big = fig.add_subplot(gs[:, 0:2]) small_top = fig.add_subplot(gs[0, 2]) small_bot = fig.add_subplot(gs[1, 2]) # Big: daily revenue trend daily = (df.groupby(df["date"].dt.date)["total"].sum() .rename("daily").to_frame()) daily["roll"] = daily["daily"].rolling(3).mean() big.plot(daily.index, daily["daily"], alpha=0.4, label="daily") big.plot(daily.index, daily["roll"], linewidth=2, label="3-day avg") big.set_title("Daily revenue") big.set_ylabel("RM") big.legend() big.grid(axis="y", linestyle="--", alpha=0.4) # Small top: revenue by product (bar) rev = df.groupby("product")["total"].sum().sort_values(ascending=False) small_top.bar(rev.index, rev.values, color="#4a90e2") small_top.set_title("Revenue by product") small_top.set_ylabel("RM") # Small bottom: share (pie) small_bot.pie(rev.values, labels=rev.index, autopct="%1.0f%%", startangle=90, counterclock=False) small_bot.set_title("Share") fig.suptitle("Sales · last 30 days", fontsize=16, weight="bold") fig.savefig("dashboard.png", dpi=200) plt.show()
Open dashboard.png — one image with three coordinated panels and a single overarching title. Print it and pin it up; you just shipped a report.
Try It Yourself
13 minPick any four aggregates / charts from your data. Put one in each cell of a 2 × 2 grid.
Show the same line chart for four different groups (states, products, classes) on four subplots with shared axes.
Hint
fig, axes = plt.subplots(2, 2, sharex=True, sharey=True, figsize=(10, 6)) for ax, (name, g) in zip(axes.flat, df.groupby("product")): ax.plot(g["date"], g["total"].rolling(3).mean()) ax.set_title(name) fig.suptitle("Daily revenue by product")
Re-create the worked example with your own data and three meaningful panels.
Mini-Challenge · Annotated KPI Header
8 minAdd a top row of three text-only "KPI cards" (total revenue, # orders, avg order) to your dashboard. Use ax.text and ax.axis("off") on small subplots.
Show the trick
def kpi(ax, label, value): ax.axis("off") ax.text(0.5, 0.7, label, ha="center", va="center", fontsize=10, color="#666") ax.text(0.5, 0.3, value, ha="center", va="center", fontsize=22, weight="bold") fig = plt.figure(figsize=(12, 8), constrained_layout=True) gs = GridSpec(3, 3, figure=fig, height_ratios=[1, 4, 4]) k1, k2, k3 = (fig.add_subplot(gs[0, i]) for i in range(3)) kpi(k1, "Revenue", "RM 980") kpi(k2, "Orders", "143") kpi(k3, "Avg", "RM 6.85") # … (charts in row 1 and 2 as before)
Recap
3 minSubplots compose stories. plt.subplots for regular grids, GridSpec for custom shapes. constrained_layout = no overlapping labels. Add a suptitle to tie the panels together. Tomorrow: the data-story project pulls it all together.
Homework
4 minBuild a one-page dashboard PNG with at least three subplots and a suptitle. Use your own data. Save it. (You'll use it as the foundation for tomorrow's project.)