Matplotlib is a Python library to make plots (or graphs or charts).
On linux, you can install it with a package manager (unless you prefer to always work in a virtual environment). Otherwise use pip install matplotlib
.
object oriented (OO) API.
The main differences between these two are
pyplot
takes care of low level things like configuring the drawing "back-end".pyplot
maintains an internal state (e.g. a list of open figures).If you are writing scripts, these are both nice things. If you are writing applications, you'll often want to configure the back-end manually, and will want to avoid having any state outside of your own program's.
Here's an example using pyplot:
import matplotlib.pyplot as plt
# Create a figure, stored inside the pyplot module:
plt.figure()
# Plot something on the axes of our figure
plt.xlabel('x')
plt.ylabel('y')
plt.plot([1, 2, 3], [4, 2, 7])
# Show all open figures on the screen
plt.show()
After running this (in jupyter notebook), the figure is closed:
# We can try to show() again, but nothing happens
plt.show()
# Similarly, nothing happens if we close before showing
plt.figure()
plt.xlabel('x')
plt.ylabel('y')
plt.plot([1, 2, 3], [4, 2, 7])
plt.close('all')
plt.show()
If this smells a bit error-prone to you, you can use the object oriented interface instead. (It's also just nicer.)
Unfortunately, figure creation without pyplot
is a bit low-level:
# Load a back-end module and import its Canvas implementation
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
# Import the Figure class
from matplotlib.figure import Figure
# Create a figure without a "canvas"
fig = Figure()
# Attach a canvas to the figure
FigureCanvas(fig)
# Plot some things
ax = fig.add_subplot()
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.plot([1, 2, 3], [4, 2, 7])
# But how do we show?
[<matplotlib.lines.Line2D at 0x7f3bffeb5660>]
How do we know what backend to use? And what is a backend anyway? If you're using matplotlib from a script you probably don't want to think about this kind of stuff.
And how do we get jupyter notebook to show this figure? It has nice magical ways to interact with pyplot, but now we'll need to do this manually instead. Some online searching reveals it's like this:
from IPython.core.display import display
display(fig)
So, we've got 2 APIs one of which is newer and more scalable, but too low level when it comes to figure creation and display. We also want to avoid learning 2 APIs to the same software.
The best solution is probably to mix the APIs: use pyplot to create and display figures, but do everything else via the object oriented interface.
A full example follows below:
# Import pyplot (and let it load a backend)
import matplotlib.pyplot as plt
# Create a figure, attached to a canvas and stored inside the pyplot module,
# but also store a handle locally
fig = plt.figure()
# Add a set of axes
ax = fig.add_subplot()
ax.set_xlabel('x')
ax.set_ylabel('y')
# Plot something on the axes
ax.plot([1, 2, 3], [4, 2, 7])
# Show all open figures on the screen
plt.show()
Even within the OO API, matplotlib's naming is massively inconsistent.
Sometimes you need underscores (fig.add_suplot
) sometimes you don't (ax.vline()
, ax.legend(ncols=3)
) and sometimes you camel case (ax.transData
).
Some things are partially abbreviated (ax.set_xlim
, ax.transData
) many are not.
Several keyword arguments have more than one form (ax.plot([1,2,3], lw=2)
is the same as ax.plot([1,2,3], linewidth=2)
).
This makes everything difficult to remember, so a lot of searching in the docs is required.
Matplotlib is well documented, and there are lots of tutorials and examples online (from "official" and unofficial sources).
Two things are worth knowing when you navigate the docs:
pyplot.plot
coexist with the docs for Axes.plot
. To look stuff up about the OO version of plot
, you need to look at the docs for Axes
.Axes.plot
has only 6 arguments. When we call ax.plot([1,2,3], alpha=3)
it creates a Line2D
object and passes the alpha=3
part to its constructor. This means the detailed information about keyword arguments to ax.plot
are found under matplotlib.lines.Line2D
! In many cases this is mitigated by duplicating part of the documentation.As the examples above show, jupyter renders matplotlib figures a bit small by default.
To see how this happens, consider the steps involved:
For whatever reason, jupyter's matplotlib backend has also chosen to render plots at 72 PPI (or DPI, in matplotlib's terminology), which will result in small figures on most modern screens.
I tried a few things to compensate this, but all have drawbacks :-(
Below is a minimal example of a figure in matplotlib:
Next,
Do these things while designing. Only once you're finished you can think about leaving out some labels, legends, etc.
# Import matplotlib and numpy, generate some data
import matplotlib.pyplot as plt
import numpy as np
t = np.arange(0, 1000)
c1 = np.sin((t - 30) * 0.02)
c2 = np.sin((t - 40) * 0.021)
# Create a figure of 7 by 3 inches
fig = plt.figure(figsize=(7, 3))
# Add an axes object --- always set labels!
ax = fig.add_subplot()
ax.set_xlabel('Time (s)')
ax.set_ylabel('Current ($\mu$A)')
# Plot the data
ax.plot(t, c1, label='First recording')
ax.plot(t, c2, label='Second recording')
# Add a legend
ax.legend()
# Show the figure
plt.show()
To save your work, simply use savefig
:
# Create a figure of 7 by 3 inches
fig = plt.figure(figsize=(7, 3))
# Add an axes object --- always set labels!
ax = fig.add_subplot()
ax.set_xlabel('Time (s)')
ax.set_ylabel('Current ($\mu$A)')
# Plot the data
ax.plot(t, c1, label='First recording')
ax.plot(t, c2, label='Second recording')
# Add a legend
ax.legend()
# Store the figure
fig.savefig('figures/first-example.png') # Save as png
fig.savefig('figures/first-example-hi-res.png', dpi=600) # Save as png with higher DPI
fig.savefig('figures/first-example.pdf') # Save as PDF (best format for latex)
plt.close()
We can also save as EPS, which throws up a warning:
# Create a figure of 7 by 3 inches
fig = plt.figure(figsize=(7, 3))
# Add an axes object --- always set labels!
ax = fig.add_subplot()
ax.set_xlabel('Time (s)')
ax.set_ylabel('Current ($\mu$A)')
# Plot the data
ax.plot(t, c1, label='First recording')
ax.plot(t, c2, label='Second recording')
# Add a legend
ax.legend()
# Store the figure
fig.savefig('figures/first-example.eps') # Save as EPS (for some journals)
plt.close()
The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.
We didn't ask for any transparency, but the current (2022-05-09) matplotlib makes legends semi-transparent by default. We can disable this to get a consistent result:
# Create a figure of 7 by 3 inches
fig = plt.figure(figsize=(7, 3))
# Add an axes object --- always set labels!
ax = fig.add_subplot()
ax.set_xlabel('Time (s)')
ax.set_ylabel('Current ($\mu$A)')
# Plot the data
ax.plot(t, c1, label='First recording')
ax.plot(t, c2, label='Second recording')
# Add a legend, set the alpha (transparency) values of its "frame" to 1 (fully opaque)
ax.legend(framealpha=1)
# Store the figure
fig.savefig('figures/first-example.eps') # Save as EPS (for some journals)
plt.close()