Matplotlib

A way to draw things from python, which is better than no way to draw things from python

2014-10-07 — 2026-10-01

quality 7.7

Wherein the Matplotlib Library Is Interrogated for Its Aesthetic Failings and Byzantine API, and Where Axes Objects Are Distinguished From Axis, Font Caches Are Purged, and Wrappers Like Plotnine Are Sought.

computers are awful
photon choreography
python
Figure 1

The classic python plotting tool is matplotlib. It can’t produce those modern, hipster graphs without hard labour and is awful at animations and interactions, and is fugly per default. It works OK out of the box. While other libraries use matplotlib as a backend to build more elaborate systems, these haven’t had much longevity so far, so I find myself falling back to plain old matplotlib. It is an acceptable default that handles most tasks, though it has plenty of weird edge cases when that punish insufficient cleverness.

Note some confusing terminology; An Axes object, which is constructed by an add_subplot command, contains two Axis objects, but is much more than a list of such objects, being the fundamental object upon which a graph is drawn.

But don’t listen to me describe it. Observe this lovely diagram which explains all.

Figure 2: Taxonomy of a matplotlib plot. I’ve lost the source for this image, sorry.

Read Jakevdp’s manual for some pedagogical advice.

1 Pro tips

2 Showing images

Traditionally annoying. There are colourbars everywhere, aspect ratios are horrible, and getting multiple images to plot is vexing.

There are helpers for this in modern matplotlib. The keyword to look for is mpl_toolkits.axes_grid1. See also the tutorial.

Alternatively, use proplot

3 Wrappers with better APIs

matplotlib will always be horrible to use because the designers were not prophets, so some of their early API design guesses were bad, but we are stuck with them for compatibility. There are various attempts to abstract away matplotlib’s byzantine API behind nicer ones.

3.1 seaborn.objects

The next-generation seaborn interface attempts to achieve a pythonic equivalent to ggplot, to some extent:

as seaborn has become more powerful, one has to write increasing amounts of matpotlib code to recreate what it is doing.

So the goal is to expose seaborn’s core features — integration with pandas, automatic mapping between data and graphics, statistical transformations — within an interface that is more compositional, extensible, and comprehensive.

One will note that the result looks a bit (a lot?) like ggplot. That’s not unintentional, but the goal is also not to “port ggplot2 to Python”. (If that’s what you’re looking for, check out the very nice plotnine package). There is an immense amount of wisdom in the grammar of graphics and in its particular implementation as ggplot2. But I think that, as languages, R and Python are just too different for idioms from one to feel natural when translated literally into the other. So while I have taken much inspiration from ggplot, I’ve also made plenty of choices differently, for better or for worse.

3.2 plotnine

Plotnine implements a best-effort clone of R’s ggplot2 library for matplotlib. I believe plotnine supersedes the abandoned(?) ggplot.py by yhat (ggplot source, plotnine source).

3.3 Proplot

Why proplot?:

Matplotlib is an extremely versatile plotting package used by scientists and engineers far and wide. However, matplotlib can be cumbersome or repetitive for users who…

  • Make highly complex figures with many subplots.
  • Want to finely tune their annotations and aesthetics.
  • Need to make new figures nearly every day.

Proplot’s core mission is to provide a smoother plotting experience for matplotlib’s most demanding users. We accomplish this by expanding upon matplotlib’s object-oriented interface. Proplot makes changes that would be hard to justify or difficult to incorporate into matplotlib itself, owing to differing design choices and backwards compatibility considerations.

This page enumerates these changes and explains how they address the limitations of matplotlib’s default interface. To start using these features, see the usage introduction and the user guide.

Seems to be on hiatus.

4 Matplotlib styling

The default matplotlib stylesheet aspires to look like 80s spreadsheet defaults, but unless you’re a retrofuturist, you’ll want to change the stylesheet. Some of the built-in stylesheets are OK.

Here is an ugly gallery of sometimes-beautiful graph styles. And here is an ugly gallery of sometimes-beautiful colour maps.

Seaborn is another vaunted extension, which I would describe as an “Edward Tufterizer,” originally by Michael Waskom. It extends matplotlib with a modern appearance and some missing plot types.

import seaborn as sns
sns.set_theme()

A cute hack to justify matplotlib’s existence: xkcd graphs.

4.1 Margins on saved graphics are too large

plt.savefig("image.png", dpi=300, bbox_inches='tight', pad_inches=0)

4.2 Axis labels

Suppressing?

ax = plt.gca()
ax.axes.xaxis.set_visible(False)
ax.axes.yaxis.set_visible(False)

5 Image montage

I have an array of images in arr. How can I plot them in a nice, simple montage? I need to do this all the time. If I have skimage installed, I can use the montage function. I do not always have that installed, though. Here is a snippet to do it by hand:

columns = 5
rows = 3
fsize = 6
fig = plt.figure(figsize=(fsize *columns/rows, fsize))

for i in range(1, columns*rows +1):
    img = arr[1,:,:,i]
    ax = fig.add_subplot(rows, columns, i)
    plt.imshow(img)
    ax.set_axis_off()
plt.tight_layout(pad = 1)
plt.show()

6 Math-friendly fonts

6.1 Help, matplotlib cannot find my font

Corrupt font cache? Some places we see this advice

import matplotlib.font_manager as fm
fm._rebuild()

That doesn’t work for me as of matplotlib 3.6. What does is finding the path thusly:

import matplotlib
matplotlib.get_cachedir()

and then removing it with rm -rf. Still cannot find that font? What if we are on the HPC?

Harriet Alexander advises setting up truetype fonts properly.

6.2 tikzplotlib

Agustinus Kristiadi, in The Last Mile of Creating Publication-Ready Plots introduces texworld/tikzplotlib,, which is a tikz plotting backend; why do we want this? For one, it can match fonts to the parent document.

6.3 Basic font stuff

Someone made the idiosyncratic choice that the default font is sans serif, even for mathematical text. We can change this by setting serif fonts also for mathtext.

from matplotlib import rc
rc(
  'font',
  family='serif',
  serif=['Palatino']
)
rc(
  'mathtext',
  fontset='dejavuserif'
)

Supported mathematical fonts are reputedly

  • dejavusans (the horrible default)
  • dejavuserif (beware of odd greek letters)
  • cm (“Computer Modern”. Classic, dated.)
  • stix (modern serif, looks OK)
  • stixsans (sounds like sans serif to me)

I am not aware of any publications that use those fonts in their style guides, but the difference between, say, Deja Vu Serif and Times New Roman is small enough that only font nerds notice.

Alternatively, I can render graph labels with TeX, which leads to some odd spacing but allows me to match fonts better. This approach is also fragile, and character set issues are terrible. Are these problems eased if I use XeLaTeX/LuaLaTeX?

Further suggestion: Automate these selection problems away using TUEplots.

TUEplots is a light-weight matplotlib extension that adapts your figure sizes to formats more suitable for scientific publications. It produces configurations that are compatible with matplotlib’s rcParams, and provides fonts, figure sizes, font sizes, color schemes, and more, for a number of publication formats.

import matplotlib.pyplot as plt

from tueplots import bundles

bundles.icml2022()
{'text.usetex': True, 'font.family': 'serif', 'text.latex.preamble': '\\usepackage{times} ', 'figure.figsize': (3.25, 2.0086104634371584), 'figure.constrained_layout.use': True, 'figure.autolayout': False, 'savefig.bbox': 'tight', 'savefig.pad_inches': 0.015, 'font.size': 8, 'axes.labelsize': 8, 'legend.fontsize': 6, 'xtick.labelsize': 6, 'ytick.labelsize': 6, 'axes.titlesize': 8}

# Plug any of those into either the rcParams or into an rc_context:

with plt.rc_context(bundles.icml2022()):
    pass

Alternative package garrettj403/SciencePlots: Matplotlib styles for scientific plotting also looks pretty good.

I am indebted to my former colleague Christian Walder for suggesting the following as a reliable initialization procedure for matplotlib plotting with equations across platforms.

import matplotlib
import sys, os

# GTK GTKAgg GTKCairo MacOSX Qt4Agg TkAgg WX WXAgg CocoaAgg
# GTK3Cairo GTK3Agg WebAgg agg cairo emf gdk pdf pgf ps svg template

is_mac = sys.platform == 'darwin'
if is_mac:
    _matplotlib_backend = 'MacOSX'
else:
    _matplotlib_backend = 'pdf'

matplotlib.rcParams['svg.fonttype'] = 'none'
matplotlib.rcParams['backend'] = _matplotlib_backend
matplotlib.rcParams['mathtext.fontset'] = 'stix'
matplotlib.rcParams['font.family'] = 'Times New Roman'

matplotlib.use(_matplotlib_backend)
import matplotlib.pyplot as plt
import matplotlib as mpl

plt.switch_backend(_matplotlib_backend)
# print(matplotlib.pyplot.get_backend())
try:
    import cairocffi as cairo
except:
    pass
    # logging.warning('import cairocffi failed')

_latex_preamble = [
    r'\usepackage{amsmath,bm}',
    r'\newcommand\what{\hat{\bm{w}}}',
    r'\newcommand\tr{^\top}',
    r'\newcommand\dt[1]{\left|#1\right|}',]

_latex_path = '/Library/TeX/texbin/'


def use_latex_mpl(
        latex_path=_latex_path,
        latex_preamble=_latex_preamble):
    mpl.rcParams['text.usetex'] = True
    mpl.rcParams['text.latex.preamble'] = latex_preamble
    if latex_path is not None:
        os.environ['PATH'] = '%s:%s' % (os.environ['PATH'], latex_path)

7 Interactivity

It’s confusing.

7.1 ipympl

ipympl “enables using the interactive features of matplotlib in Jupyter Notebooks, Jupyter Lab, Google Colab, VSCode notebooks.”

%matplotlib ipympl
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()


x = np.linspace(0, 2*np.pi, 100)
y = np.sin(3*x)
ax.plot(x, y)

7.2 Glue

An interactive exploratory matplotlib GUI toolkit/app is glue. They have solved many python gui problems, bless them, and have tried to make everything more-or-less interactive.

Glue is designed with “data-hacking” workflows in mind, and can be used in different ways. For instance, you can simply make use of the graphical Glue application as is, and never type a line of code. However, you can also interact with Glue via Python in different ways:

  • Using the IPython terminal built-in to the Glue application
  • Sending data in the form of NumPy arrays or Pandas DataFrames to Glue for exploration from a Python or IPython session.
  • Customising/hacking your Glue setup using config.py files, including automatically loading and clean data before starting Glue, writing custom functions to parse files in your favorite file format, writing custom functions to link datasets, or creating your own data viewers.

Glue thus blurs the boundary between GUI-centric and code-centric data exploration. In addition, it is also possible to develop your own plugin packages for Glue that you can distribute to users separately, and you can also make use of the Glue framework in your own application to provide data linking capabilities.

8 Yellowbrick

Yellowbrick is a matplotlib specialization for hyperparameter optimization.

Yellowbrick extends the Scikit-Learn API to make model selection and hyperparameter tuning easier. Under the hood, it’s using Matplotlib.