Python matplotlib


  • Description: Plotting with matplotlib, pyplot vs the OO API, figures and axes, common plot types, subplots, labels and legends, colormaps and log scales, and saving figures
  • My Notion Note ID: K2A-D2-1
  • Created: 2022-09-15
  • Updated: 2026-05-11
  • License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io

Table of Contents


1. Two APIs: pyplot vs Object-Oriented

  • pyplot: stateful, MATLAB-like; plt.plot, plt.title act on the "current" figure/axes. Convenient in notebooks.
  • Object-oriented: explicit Figure/Axes. Preferred for scripts, libraries, multi-axes plots.
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 200)

# pyplot style
plt.plot(x, np.sin(x))
plt.title("sin")
plt.show()

# OO style (preferred)
fig, ax = plt.subplots()
ax.plot(x, np.sin(x))
ax.set_title("sin")
plt.show()
  • plt.subplots() returns OO handles, stay OO once axes are created

2. Figure and Axes

  • Figure: the entire window
  • Axes: one plotting area inside it
fig, ax = plt.subplots(figsize=(6, 4), dpi=100)

ax.plot(x, y)
ax.set_xlabel("time (s)")
ax.set_ylabel("value")
ax.set_xlim(0, 10)
ax.set_ylim(-1.5, 1.5)
ax.grid(True, alpha=0.3)
  • Useful Axes setters: set_title, set_xlabel/set_ylabel, set_xlim/set_ylim, set_xticks, set_xticklabels, set_xscale, axhline/axvline, tick_params

3. Common Plot Types

ax.plot(x, y, color="C0", linestyle="--", marker="o", label="sin")
ax.scatter(xs, ys, s=20, c=values, cmap="viridis")
ax.bar(categories, heights)
ax.barh(categories, heights)
ax.hist(samples, bins=50, density=True, alpha=0.6)
ax.boxplot([a, b, c], tick_labels=["A", "B", "C"])   # `labels=` was renamed in mpl 3.9
ax.imshow(image, cmap="gray")
ax.contour(X, Y, Z, levels=10)
ax.contourf(X, Y, Z, levels=20, cmap="plasma")
ax.errorbar(x, y, yerr=err, fmt="o", capsize=3)
ax.fill_between(x, y_low, y_high, alpha=0.3)
  • Colors: short codes ("r", "g", "b"), names ("steelblue"), hex ("#1f77b4"), or "C0""C9" (current cycle)
  • Line styles: "-", "--", "-.", ":"
  • Markers: "o", "s", "^", "x", "+", "."

4. Subplots

fig, axes = plt.subplots(2, 3, figsize=(12, 6), sharex=True, sharey=True)
for ax, name in zip(axes.flat, names):
    ax.plot(...)
    ax.set_title(name)
fig.tight_layout()         # avoid label overlap
# or fig.set_layout_engine("constrained") for newer mpl

Complex layouts, GridSpec or subplot_mosaic (3.3+):

fig, axd = plt.subplot_mosaic(
    """
    AAB
    CCB
    """
)
axd["A"].plot(...)
axd["B"].plot(...)
axd["C"].plot(...)

5. Labels, Legends, Titles

ax.plot(x, y1, label="signal")
ax.plot(x, y2, label="filtered")
ax.legend(loc="upper right", frameon=False)
ax.set_title("Time series")
fig.suptitle("Overall Title")

LaTeX-style math is supported in any text via $...$:

ax.set_xlabel(r"frequency $\omega$ (rad/s)")
ax.set_title(r"$y = \sin(\omega t + \phi)$")
  • Use a raw string (r"...") so backslashes don't need escaping

6. Scales and Log Plots

ax.set_yscale("log")            # log base 10
ax.set_xscale("symlog", linthresh=1e-3)   # log that handles negative + zero
ax.set_yscale("logit")

Asymmetric log color scaling (heatmap spanning many orders of magnitude):

import matplotlib.colors as mcolors
im = ax.contourf(X, Y, Z, levels=20,
                 norm=mcolors.LogNorm(vmin=Z.min(), vmax=Z.max()),
                 cmap="viridis")
fig.colorbar(im, ax=ax)
  • For data crossing zero, use SymLogNorm(linthresh=...)

7. Colormaps and Normalizations

Pick perceptually uniform maps; avoid jet (perceptually misleading):

  • Sequential: viridis, plasma, magma, inferno, cividis
  • Diverging (signed data): coolwarm, RdBu_r, seismic
  • Cyclic: twilight, hsv
im = ax.imshow(data, cmap="viridis", vmin=0, vmax=1, origin="lower")
fig.colorbar(im, ax=ax, label="intensity")

8. Styling and Themes

plt.style.use("seaborn-v0_8-whitegrid")   # available styles in plt.style.available
plt.rcParams["font.family"] = "Inter"
plt.rcParams["axes.spines.top"] = False
plt.rcParams["axes.spines.right"] = False

Ad-hoc style, with block:

with plt.style.context("dark_background"):
    fig, ax = plt.subplots()
    ax.plot(x, y)

9. Saving Figures

fig.savefig("plot.png", dpi=300, bbox_inches="tight")
fig.savefig("plot.pdf")                  # vector
fig.savefig("plot.svg", transparent=True)
  • Vector (PDF/SVG) for publication; raster (PNG) for web
  • bbox_inches="tight" trims surrounding whitespace

Headless scripts, pick a non-interactive backend before importing pyplot:

import matplotlib
matplotlib.use("Agg")        # before `import matplotlib.pyplot as plt`

10. References