The Two APIs: pyplot vs. Object-Oriented
Why Matplotlib Still Dominates Scientific Plotting
Despite newer libraries like Plotly and Bokeh, Matplotlib remains the
backbone of Python scientific visualization. Every journal-quality figure
pipeline, every automated reporting script, and most textbook plots are
built with Matplotlib. Understanding its two APIs β the MATLAB-like
pyplot interface and the object-oriented (OO) interface β is the key
to writing both quick exploratory code and production-quality figure
scripts.
Definition: Figure and Axes Objects
Figure and Axes Objects
In Matplotlib's object model:
- A Figure (
matplotlib.figure.Figure) is the top-level container representing the entire image. It holds one or more Axes. - An Axes (
matplotlib.axes.Axes) is the rectangular region where data is plotted. It contains axis lines, tick labels, title, and all plotted elements (artists).
import matplotlib.pyplot as plt
fig, ax = plt.subplots() # one Figure, one Axes
fig, axes = plt.subplots(2, 3) # one Figure, 6 Axes in a 2x3 grid
An Axes is not the plural of "axis." In Matplotlib, ax.xaxis and
ax.yaxis are the two Axis objects living inside an Axes object.
Definition: Artist β The Base Class of All Visual Elements
Artist β The Base Class of All Visual Elements
Every visual element in a Matplotlib figure is an Artist: lines, text, patches, images, tick labels, and the axes frame itself. Artists form a tree with the Figure at the root:
You rarely create Artists directly. Instead, Axes methods like
ax.plot(), ax.scatter(), and ax.set_title() create and add them.
Definition: pyplot (Stateful) vs. Object-Oriented (Explicit) API
pyplot (Stateful) vs. Object-Oriented (Explicit) API
pyplot API β a stateful MATLAB-like interface using plt.* functions.
It maintains a "current figure" and "current axes" implicitly:
plt.plot(x, y) # operates on current axes
plt.xlabel("Time (s)") # modifies current axes
plt.title("Signal")
plt.show()
OO API β you create Figure and Axes objects explicitly and call methods on them:
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xlabel("Time (s)")
ax.set_title("Signal")
fig.savefig("signal.pdf")
The OO API is always preferred for scripts, functions, and multi-panel figures. Use pyplot only in throwaway REPL explorations.
Theorem: Composability of the OO API
Any plotting function that accepts an ax parameter can be composed
with any layout. If draws a BER curve and
draws a constellation diagram, then:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
f_1(ax1)
f_2(ax2)
produces a two-panel figure with no global state conflicts.
The OO API avoids the "which figure am I modifying?" ambiguity that plagues the stateful pyplot interface in complex scripts.
Theorem: Backend Architecture Theorem
Matplotlib separates frontend (Artist tree construction) from backend (rendering). The same Python code produces identical output regardless of backend:
- Interactive backends (Qt5Agg, TkAgg, nbAgg) render to a GUI window.
- Non-interactive backends (Agg, PDF, SVG, PGF) render to files.
Switching is done before any import:
import matplotlib
matplotlib.use("Agg") # file-only backend, no GUI needed
This separation is why Matplotlib works on headless servers, in Docker containers, and in Jupyter notebooks equally well.
Example: The Same Plot in Both APIs
Plot for using both the pyplot and OO APIs.
pyplot API
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 1, 500)
y = np.sin(2 * np.pi * 3 * t)
plt.figure(figsize=(8, 3))
plt.plot(t, y, color='#2563EB')
plt.xlabel("Time (s)")
plt.ylabel("Amplitude")
plt.title("3 Hz Sinusoid (pyplot)")
plt.tight_layout()
plt.show()
OO API (preferred)
fig, ax = plt.subplots(figsize=(8, 3))
ax.plot(t, y, color='#2563EB')
ax.set(xlabel="Time (s)", ylabel="Amplitude",
title="3 Hz Sinusoid (OO)")
fig.tight_layout()
fig.savefig("sinusoid.pdf")
Key Differences
- The OO version is explicit:
ax.plot()vs.plt.plot(). - Setter syntax:
ax.set_xlabel()vs.plt.xlabel(). fig.savefig()targets the specific figure.
Example: Writing a Reusable Plotting Function
Write a function plot_ber(snr_db, ber, ax=None) that plots BER vs. SNR
on a log scale and works both standalone and embedded in multi-panel figures.
Implementation
def plot_ber(snr_db, ber, label="Simulated", ax=None):
if ax is None:
fig, ax = plt.subplots()
ax.semilogy(snr_db, ber, 'o-', label=label)
ax.set_xlabel(r" (dB)")
ax.set_ylabel("BER")
ax.set_ylim(1e-6, 1)
ax.grid(True, which="both", alpha=0.3)
ax.legend()
return ax
Usage
# Standalone
plot_ber(snr, ber_bpsk)
# In a multi-panel figure
fig, (ax1, ax2) = plt.subplots(1, 2)
plot_ber(snr, ber_bpsk, label="BPSK", ax=ax1)
plot_ber(snr, ber_qpsk, label="QPSK", ax=ax2)
Example: Temporary Style Changes with Context Managers
Apply the seaborn-v0_8-whitegrid style for one figure without
affecting the rest of your script.
Using plt.style.context
with plt.style.context('seaborn-v0_8-whitegrid'):
fig, ax = plt.subplots()
ax.plot(t, y)
ax.set_title("Styled Plot")
fig.savefig("styled.pdf")
# Style reverts here
Available styles
print(plt.style.available)
# ['seaborn-v0_8', 'ggplot', 'bmh', 'dark_background', ...]
pyplot vs. OO API Explorer
Compare how changing frequency and amplitude affects a sinusoid. Both APIs produce identical output β the difference is purely in code style.
Parameters
Common Mistake: pyplot State Leaks Between Cells
Mistake:
Using plt.plot() in Jupyter without creating a new figure per cell.
Previous plots "leak" into the current cell's output.
Correction:
Always start a cell with fig, ax = plt.subplots() or use
plt.figure() to ensure a fresh canvas. Better yet, use the OO API
exclusively.
Common Mistake: Calling plt.show() Before plt.savefig()
Mistake:
Calling plt.show() before plt.savefig() results in a blank saved
file because show() clears the figure in non-interactive backends.
Correction:
Always call fig.savefig(...) before plt.show(), or better,
use the OO API where fig.savefig() operates on a specific figure
object and is not affected by show().
Quick Check
In Matplotlib, what is an 'Axes' object?
The x and y axis lines
The rectangular region where data is plotted
The entire window
A synonym for 'subplot'
An Axes is the plot area containing data, labels, and tick marks.
Axes
A rectangular region within a Figure where data is plotted, containing axis lines, labels, title, and data artists.
Related: Figure
Artist
The base class for all visual elements in Matplotlib. Everything you see in a figure is an Artist.
Backend
The rendering engine that converts the Artist tree to pixels or vector graphics. Interactive backends show windows; non-interactive backends write files.
Historical Note: Matplotlib's Origin Story
2003John Hunter created Matplotlib in 2003 as a free alternative to MATLAB's plotting. The pyplot API was intentionally designed to mirror MATLAB syntax so that scientists could switch without relearning. After Hunter's passing in 2012, the community maintained and modernized the library, adding the OO API as the recommended approach.
Historical Note: The MATLAB Connection
2003-2010Matplotlib's pyplot commands (plot, xlabel, title, subplot)
are named identically to MATLAB functions. This was a deliberate
design choice β Hunter wanted neuroimaging researchers at his lab to
migrate from MATLAB without friction. The pylab module (now
deprecated) went further, importing NumPy and pyplot into a single
namespace to emulate MATLAB's workspace.