Matplotlib
In one line: a drawing library where every mark on the page is an object you can reach and change.
Version at time of writing: 3.11.x · import matplotlib.pyplot as plt
The mental model
Three nested containers, and one rule.
Figure ──▶ the whole canvas; owns the size, the DPI, the saving
└─ Axes ──▶ one plotting panel; owns the data area, ticks, labels
└─ Artist ──▶ every drawn thing: Line2D, Text, Patch, Collection
The rule: everything you see is an Artist attached to an Axes, and every Artist has getters and setters. If you can see it, you can change it. That is why matplotlib is verbose and why it never dead-ends — unlike higher-level libraries, there is no plot you cannot eventually produce.
"Axes" means a panel, not an axis. A 2×2 grid has four Axes. The x-axis of one panel is ax.xaxis. This naming causes real confusion; see Figure and Axes.
The one habit that matters
Use the object-oriented API, not the implicit plt. state machine:
fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(x, y)
ax.set_xlabel("log2 fold change")
fig.savefig("plot.pdf", bbox_inches="tight")
not
plt.scatter(x, y)
plt.xlabel("log2 fold change") # ← which Axes did this hit?
plt.savefig("plot.pdf")
The plt. form writes to whatever the "current" Axes happens to be, which breaks the moment you have subplots, a loop, or a seaborn call in between. See pyplot vs Object-Oriented API.
Why bioinformatics cares
Journals want vector figures at specific column widths with editable text. Only matplotlib gives you that control: exact figure sizes in inches, fonts embedded as text rather than paths, colourblind-safe palettes, and per-Artist tweaking for the one gene label that overlaps another. Seaborn gets you 90% of the way in one line, and then you reach through to matplotlib for the last 10%.
Core notes
Structure Figure and Axes · pyplot vs Object-Oriented API · Matplotlib Artists · Subplots and Layout
Content Common Plot Types · Axes Styling and Annotation · Colormaps and Color
Output Saving Figures · rcParams and Style Sheets
Applied
Applied - Differential Expression Volcano Plot · Applied - Heatmaps and Clustermaps · Applied - Quality Control Plots
Install and check
pip install "matplotlib>=3.10,<4"
python -c "import matplotlib; print(matplotlib.__version__, matplotlib.get_backend())"
If figures do not appear, the backend is usually the culprit. In a headless script or container use matplotlib.use("Agg") before importing pyplot and save to file rather than showing.
The 20% that gets 80% of the work done
fig, axes = plt.subplots(1, 2, figsize=(10, 4), sharey=True, layout="constrained")
axes[0].scatter(x, y, s=8, alpha=0.5, c=colours)
axes[0].axhline(0, ls="--", lw=1, color="grey")
axes[0].set(xlabel="log2FC", ylabel="-log10 p", title="Volcano")
axes[1].hist(y, bins=50)
axes[1].set_xlabel("-log10 p")
fig.savefig("figure.pdf", bbox_inches="tight")
plt.close(fig)
layout="constrained" (the modern replacement for tight_layout()) fixes overlapping labels automatically. plt.close(fig) in loops prevents the memory leak that produces the "More than 20 figures have been opened" warning.
Gotchas that bite newcomers
- Figures never close in a loop → memory grows until the process dies. Always
plt.close(fig). - Text saved as paths instead of glyphs, so editors cannot change it. Set
rcParams["pdf.fonttype"] = 42. See Saving Figures. - Jet / rainbow colormaps. They are perceptually non-uniform and invent structure that is not there. Use
viridis. See Colormaps and Color. plt.show()inside a script blocks; inside a notebook it is usually redundant.- Resizing after saving does nothing — the save captured the state at that moment.
See also
Seaborn · Ecosystem Map · Pandas