📊 Section 3 · Data Viz 🟡 Intermediate MODULE 14

Matplotlib Fundamentals — Plots & Customization

⏱️ 32 min read
📖 Theory + Code
🧩 5 Quiz Questions
🏗️ 1 Challenge
Your progress in Section 320%
🎯 What you'll learn: Matplotlib is the library every other Python plotting tool is built on top of — including Seaborn, which you'll meet in the next lesson. In this lesson you'll build line, bar, scatter and histogram charts, understand the figure/axes model behind every Matplotlib chart, customize colors and line styles, lay out multiple charts in a grid with plt.subplots(), and save a finished chart to a file with plt.savefig().

The Figure/Axes Model

Every chart Matplotlib draws lives inside two objects: a Figure, which is the whole canvas — the window or image file — and one or more Axes, which are the individual plotting areas inside that canvas (confusingly, an "Axes" is a full chart with its own x-axis and y-axis, not a single axis line). The standard way to create both together is plt.subplots().

figure_axes.py
PYTHON
import matplotlib.pyplot as plt

# fig = the whole canvas, ax = one plotting area inside it
fig, ax = plt.subplots(figsize=(8, 5))

ax.plot([1, 2, 3, 4, 5], [10, 15, 13, 18, 22])
ax.set_title("Weekly Revenue")

plt.show()
📝
Object-oriented style vs. pyplot style
You'll also see code that skips fig, ax = plt.subplots() and calls plt.plot(...), plt.title(...) directly on the plt module itself — this is "pyplot style," and it quietly plots onto whatever the "current" figure and axes happen to be. It's fine for a single quick chart, but the object-oriented style used in this lesson — creating fig, ax explicitly and calling methods on ax — scales far better once you're building subplot grids or reusable plotting functions.

Two methods you'll use on almost every ax object: ax.set_title() and, as you'll see next, ax.set_xlabel() / ax.set_ylabel() for labeling each axis of the chart.

Line Plots with plt.plot()

ax.plot(x, y) connects a sequence of (x, y) points with a line — the natural choice whenever there's a meaningful order to the data, like values changing over time.

line_plot.py
PYTHON
weeks = [1, 2, 3, 4, 5, 6]
revenue = [210, 245, 198, 260, 288, 305]

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(weeks, revenue, color="#4f9eff", linewidth=2, marker="o", label="Revenue")

ax.set_title("Weekly Revenue")
ax.set_xlabel("Week")
ax.set_ylabel("Revenue (PKR '000)")
ax.legend()

plt.show()
🖼️ What plt.show() displays
A rising blue line with circular markers at each week, dipping once at week 3 before climbing to its highest point at week 6. The legend in the corner reads "Revenue," and the axis labels read "Week" along the bottom and "Revenue (PKR '000)" along the side.
plt.show() vs. a Jupyter notebook
In a plain .py script, plt.show() opens a window with the chart. In a Jupyter notebook, the chart usually renders automatically below the cell, but calling plt.show() explicitly is still good habit — it also suppresses the noisy <Axes: ...> object repr that would otherwise print above the chart.

Bar Charts with plt.bar()

Bar charts compare a numeric value across discrete categories — ax.bar(categories, values) draws one bar per category, with height equal to the value.

bar_chart.py
PYTHON
categories = ["Electronics", "Clothing", "Home", "Sports"]
values = [4200, 3100, 2600, 1800]

fig, ax = plt.subplots(figsize=(7, 5))
ax.bar(categories, values, color="#2de8c0")

ax.set_title("Sales by Category")
ax.set_ylabel("Units Sold")

plt.show()
📝
bar() vs. barh()
ax.bar() draws vertical bars; ax.barh() draws them horizontally, which is often easier to read when category names are long — no rotated x-axis labels needed.

Scatter Plots with plt.scatter()

Scatter plots show the relationship between two numeric variables as individual points — one point per row of data. They're the go-to chart for spotting correlation, clusters, or outliers.

scatter_plot.py
PYTHON
import numpy as np

rng = np.random.default_rng(42)
ad_spend = rng.uniform(10, 100, 40)
orders = ad_spend * 2.3 + rng.normal(0, 15, 40)

fig, ax = plt.subplots(figsize=(7, 5))
ax.scatter(ad_spend, orders, c="#a78bfa", alpha=0.75, edgecolors="white")

ax.set_title("Ad Spend vs. Orders")
ax.set_xlabel("Ad Spend (PKR '000)")
ax.set_ylabel("Orders")

plt.show()
alpha for overlapping points
Setting alpha below 1.0 makes each point semi-transparent, so areas where many points overlap look visibly darker — a cheap way to see point density without a completely different chart type.

Histograms with plt.hist()

A histogram shows how one numeric column is distributed — it buckets values into ranges ("bins") and draws a bar for how many values fall in each bin. It answers "what does this column's spread look like?" rather than comparing categories.

histogram.py
PYTHON
order_values = rng.normal(loc=500, scale=80, size=1000)

fig, ax = plt.subplots(figsize=(7, 5))
ax.hist(order_values, bins=30, color="#fbbf24", edgecolor="black")

ax.set_title("Distribution of Order Values")
ax.set_xlabel("Order Value (PKR)")
ax.set_ylabel("Frequency")

plt.show()
⚠️
bins changes what you see
Too few bins hides real structure in the data; too many makes the histogram noisy and hard to read. There's no single correct number — 20 to 40 bins is a reasonable starting point for a few hundred to a few thousand data points, then adjust by eye.

Titles, Labels, Legends & Styling

Every chart so far has used set_title(), set_xlabel()/set_ylabel(), and legend() — these plus color and line-style keyword arguments are what turn a bare plot into something someone else can actually read without you standing next to them explaining it.

customization.py
PYTHON
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
sales_2023 = [180, 195, 170, 210, 225, 240]
sales_2024 = [220, 230, 205, 260, 288, 305]

fig, ax = plt.subplots(figsize=(8, 5))

ax.plot(months, sales_2023, color="#8ca8d8", linestyle="--", linewidth=2, marker="s", label="2023")
ax.plot(months, sales_2024, color="#2de8c0", linestyle="-", linewidth=2.5, marker="o", label="2024")

ax.set_title("Sales: 2023 vs. 2024", fontsize=14, fontweight="bold")
ax.set_xlabel("Month")
ax.set_ylabel("Sales (PKR '000)")
ax.legend(loc="upper left")
ax.grid(True, alpha=0.3)

plt.show()
📝
Common style keyword arguments
color accepts named colors ("red"), hex codes ("#4f9eff"), or shorthand letters ("b" for blue). linestyle takes "-" (solid), "--" (dashed), "-." (dash-dot), or ":" (dotted). marker takes shorthand like "o" (circle), "s" (square), "^" (triangle). All three work on ax.plot(); ax.bar() and ax.scatter() use color/c the same way but don't take a line style.

Laying Out Multiple Charts with a Subplot Grid

plt.subplots() also accepts a number of rows and columns, returning a grid of Axes objects instead of just one — perfect for putting several related charts side by side.

subplot_grid.py
PYTHON
fig, axes = plt.subplots(2, 2, figsize=(10, 8))

axes[0, 0].plot(weeks, revenue, color="#4f9eff")
axes[0, 0].set_title("Line")

axes[0, 1].bar(categories, values, color="#2de8c0")
axes[0, 1].set_title("Bar")

axes[1, 0].scatter(ad_spend, orders, c="#a78bfa")
axes[1, 0].set_title("Scatter")

axes[1, 1].hist(order_values, bins=20, color="#fbbf24")
axes[1, 1].set_title("Histogram")

plt.tight_layout()
plt.show()
🖼️ What plt.show() displays
A 2×2 grid of four small charts sharing one figure — the line chart top-left, bar chart top-right, scatter bottom-left, histogram bottom-right — each with its own title but scaled to fit the overall figure size.
⚠️
axes indexing depends on the grid shape
With plt.subplots(2, 2), axes is a 2D NumPy array, indexed axes[row, col]. With a single row or column, e.g. plt.subplots(1, 3), axes is a flat 1D array, indexed just axes[i]. With plt.subplots() and no arguments, you get one plain Axes object, no indexing at all. plt.tight_layout() is worth calling before plt.show() on any multi-subplot figure — it fixes titles and labels overlapping between neighboring subplots.

Saving Figures with plt.savefig()

Once a chart looks right, fig.savefig() (or the equivalent plt.savefig()) writes it to a file — PNG, PDF, SVG, and more, chosen automatically from the file extension.

save_figure.py
PYTHON
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(months, sales_2024, color="#2de8c0", marker="o")
ax.set_title("2024 Sales")

# Save BEFORE plt.show() — some backends clear the figure after showing it
fig.savefig("sales_2024.png", dpi=300, bbox_inches="tight")

plt.show()
dpi and bbox_inches
dpi=300 (dots per inch) gives a crisp, print-quality image — the on-screen default is much lower resolution. bbox_inches="tight" trims excess whitespace around the chart so titles and labels aren't cut off at the edge of the saved file.

Lesson Summary

Let's recap everything you learned in this lesson:

fig, ax = plt.subplots() creates a Figure (the canvas) and Axes (a plotting area) together.
ax.plot(), ax.bar(), ax.scatter(), ax.hist() cover lines, category comparisons, relationships, and distributions.
set_title(), set_xlabel()/set_ylabel(), and legend() make a chart readable on its own.
color, linestyle, and marker customize how a line or set of points looks.
plt.subplots(rows, cols) lays out multiple charts in one figure.
fig.savefig() writes the chart to a PNG, PDF, or SVG file.
🧩 Knowledge Check — Lesson 14
Answer all 5 questions to test your understanding. Instant feedback on every answer.
1. What does fig, ax = plt.subplots() return?
2. Which method creates a scatter plot?
3. The bins keyword argument belongs to which chart function, and what does it control?
4. How do you save a Matplotlib figure to a PNG file?
5. What does ax.legend() do?
💪
Coding Challenge — Lesson 14
Apply what you learned · Intermediate Level

Put every chart type from this lesson into a single figure.

Challenge: Weekly Store Report 📈

Using weeks/revenue, categories/values, ad_spend/orders, and order_values from this lesson: (1) build a 2×2 subplot grid with plt.subplots(2, 2, figsize=(10, 8)), (2) put the line chart, bar chart, scatter plot, and histogram each in their own cell with a title, (3) call plt.tight_layout(), then (4) save the whole grid to "weekly_report.png" at 300 dpi before calling plt.show().

Rules: Use the object-oriented style throughout — call methods on the individual axes[row, col] objects, not on plt directly.
💡 Show hints if you're stuck
  • fig, axes = plt.subplots(2, 2, figsize=(10, 8))
  • Each cell: axes[0, 0].plot(...), axes[0, 1].bar(...), axes[1, 0].scatter(...), axes[1, 1].hist(...)
  • Save with fig.savefig("weekly_report.png", dpi=300, bbox_inches="tight") before plt.show()
Finished this lesson?
Mark it complete to track your progress.
🎉

Lesson 14 Complete!

You can build and customize line, bar, scatter, and histogram charts with Matplotlib, lay them out in a grid, and save them to a file. Next up: Seaborn — statistical charts that work directly with DataFrames.

Module 14 of 18 Section 3 — Data Visualization: Matplotlib & Seaborn