Python matplotlib
- Description: Plotting with matplotlib,
pyplotvs 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:
pyplotvs Object-Oriented - 2. Figure and Axes
- 3. Common Plot Types
- 4. Subplots
- 5. Labels, Legends, Titles
- 6. Scales and Log Plots
- 7. Colormaps and Normalizations
- 8. Styling and Themes
- 9. Saving Figures
- 10. References
1. Two APIs: pyplot vs Object-Oriented
pyplot: stateful, MATLAB-like;plt.plot,plt.titleact 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
Axessetters: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`