Why ProPlot?

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

  • Make very complex figures with multiple subplots.

  • Want to finely tune their figure 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 heaviest 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.

Less typing, more plotting

Limitation

Matplotlib users often need to change lots of plot settings all at once. With the default interface, this requires calling a series of one-liner setter methods.

This workflow is quite verbose – it tends to require “boilerplate code” that gets copied and pasted a hundred times. It can also be confusing – it is often unclear whether properties are applied from an Axes setter (e.g. set_xlabel and set_xticks), an XAxis or YAxis setter (e.g. set_major_locator and set_major_formatter), a Spine setter (e.g. set_bounds), or a “bulk” property setter (e.g. tick_params), or whether one must dig into the figure architecture and apply settings to several different objects. While this is in the spirit of object-oriented design, it seems like there should be a more unified, straightforward way to change settings for day-to-day matplotlib usage.

Solution

ProPlot introduces the format method to resolve this. Think of this as an expanded and thoroughly documented version of the matplotlib.artist.Artist.update method. format can modify things like axis labels and titles and update existing axes with new “rc” settings. It also integrates with various constructor functions to help keep things succinct. Further, subplot containers can be used to invoke format on several subplots at once.

Together, these features significantly reduce the amount of code needed to create highly customized figures. As an example, it is trivial to see that…

import proplot as pplt
fig, axs = pplt.subplots(ncols=2)
axs.format(linewidth=1, color='gray')
axs.format(xlim=(0, 100), xticks=10, xtickminor=True, xlabel='foo', ylabel='bar')

is much more succinct than…

import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import matplotlib as mpl
with mpl.rc_context(rc={'axes.linewidth': 1, 'axes.color': 'gray'}):
    fig, axs = plt.subplots(ncols=2, sharey=True)
    axs[0].set_ylabel('bar', color='gray')
    for ax in axs:
        ax.set_xlim(0, 100)
        ax.xaxis.set_major_locator(mticker.MultipleLocator(10))
        ax.tick_params(width=1, color='gray', labelcolor='gray')
        ax.tick_params(axis='x', which='minor', bottom=True)
        ax.set_xlabel('foo', color='gray')

Class constructor functions

Limitation

Matplotlib and cartopy define several classes with verbose names like MultipleLocator, FormatStrFormatter, and LambertAzimuthalEqualArea. They also keep them out of the top-level package namespace. Since plotting code has a half life of about 30 seconds, typing out these extra class names and import statements can be a major drag.

Parts of matplotlib’s interface were actually designed with this in mind. Backend classes, native axes projections, axis scales, box styles, arrow styles, and arc styles are referenced with “registered” string names, as are basemap projections. So, why not “register” everything else?

Solution

In ProPlot, tick locators, tick formatters, axis scales, cartopy projections, colormaps, and property cyclers are all “registered”. ProPlot does this by defining constructor functions and passing various keyword arguments through these functions. This may seem “unpythonic” but it is absolutely invaluable for writing plotting code.

The constructor functions also accept flexible inputs. For example, a scalar passed to Locator returns a MultipleLocator, a lists of strings passed to Formatter returns a FixedFormatter, and Colormap and Cycle accept colormap names, individual colors, and lists of colors. Passing the relevant class instance to a constructor function simply returns the instance.

See the user guide sections on Cartesian plots, colormaps, and color cycles for details. The below table lists the constructor functions and the keyword arguments that use them.

Function

Return type

Used by

Keyword argument(s)

Locator

Locator

format and colorbar

locator=, xlocator=, ylocator=, minorlocator=, xminorlocator=, yminorlocator=, ticks=, xticks=, yticks=, minorticks=, xminorticks=, yminorticks=

Formatter

Formatter

format and colorbar

formatter=, xformatter=, yformatter=, ticklabels=, xticklabels=, yticklabels=

Scale

ScaleBase

format

xscale=, yscale=

Cycle

Cycler

1D plotting methods

cycle=

Colormap

Colormap

2D plotting methods

cmap=

Norm

Normalize

2D plotting methods

norm=

Proj

Projection or Basemap

subplots

proj=

Note that set_xscale and set_yscale now accept instances of ScaleBase thanks to a monkey patch applied by ProPlot.

Automatic dimensions and spacing

Limitation

Matplotlib plots tend to require lots of “tweaking” when you have more than one subplot in the figure. This is partly because you must specify the physical dimensions of the figure, despite the fact that…

  1. The subplot aspect ratio is generally more relevant than the figure aspect ratio. An aspect ratio of 1 is desirable for most plots, and the aspect ratio must be held fixed for geographic and polar projections and most imshow plots.

  2. The physical width and height of the subplot controls the “evident” thickness of text, lines, and other content plotted inside the subplot. The effect of the figure size on this “evident” thickness depends on the number of subplot tiles in the figure.

Also, while matplotlib’s tight layout algorithm helps to avoid tweaking the spacing, the algorithm cannot apply different amounts of spacing between different subplot row and column boundaries.

Solution

In ProPlot, you can specify the physical dimensions of a reference subplot instead of the figure by passing refwidth, refheight, and/or refaspect to Figure. The default behavior is refaspect=1 and refwidth=2 (inches). If the aspect ratio mode for the reference subplot is set to 'equal', as with geographic and polar plots and imshow plots, the imposed aspect ratio will be used instead.

The width or height of the figure can also be constrained independently with the figwidth and figheight parameters. If only one is specified, the other will be adjusted to preserve subplot aspect ratios. You can select a figwidth and/or figheight suitable for submission to various publications using the journal parameter.

ProPlot also uses its own “tight layout” algorithm to automatically determine the left, right, bottom, top, wspace, and hspace GridSpec parameters. This algorithm has the following advantages:

  • Spacing between rows and columns is now variable thanks to the GridSpec subclass. This is critical for putting colorbars and legends or axes panels outside of subplots without “stealing space” from the parent subplot.

  • The “tight layout” algorithm is fairly simple because figures are restricted to have just one GridSpec. This is done by requiring users to draw all of their subplots at once with subplots (although in a future version, there will be a proplot.figure function that allows users to add subplots one-by-one while retaining the single-gridspec restriction).

See the user guide for details.

Working with multiple subplots

Limitation

For many matplotlib users, figures with one subplot are a rarity. These users need multiple subplots to communicate complex ideas. Unfortunately, the path of least resistance often leads to redundant figure elements when drawing multiple subplots, namely…

  • Repeated axis tick labels.

  • Repeated axis labels.

  • Repeated colorbars.

  • Repeated legends.

These sorts of redundancies are very common even in publications, where they waste valuable page space. It is also generally necessary to add “a-b-c” labels to figures with multiple subplots before submitting them to publications, but matplotlib has no built-in way of doing this.

Solution

ProPlot makes it easier to work with multiple subplots and create clear, concise figures.

Simpler colorbars and legends

Limitation

In matplotlib, it can be difficult to draw legends along the outside of subplots. Generally, you need to position the legend manually and tweak the spacing to make room for the legend.

Also, colorbars drawn along the outside of subplots with e.g. fig.colorbar(..., ax=ax) need to “steal” space from the parent subplot. This can cause asymmetry in figures with more than one subplot. It is also generally difficult to draw “inset” colorbars in matplotlib.

Solution

ProPlot includes a simple framework for drawing colorbars and legends that reference individual subplots and multiple contiguous subplots.

Since GridSpec permits variable spacing between subplot rows and columns, “outer” colorbars and legends do not alter subplot spacing or add whitespace. This is critical e.g. if you have a colorbar between columns 1 and 2 but nothing between columns 2 and 3. Also, Figure and Axes colorbar widths are now specified in physical units rather than relative units, which makes colorbar thickness independent of subplot size and easier to get just right.

There are also several useful new colorbar and legend features described in the user guide.

Improved plotting methods

Limitation

A few common plotting tasks take a lot of work using matplotlib alone. The seaborn, xarray, and pandas packages offer improvements, but it would be nice to have this functionality built right into matplotlib’s interface.

Solution

ProPlot adds various seaborn, xarray, and pandas features to the Axes plotting methods along with several additional features designed to make your life easier.

The following features are relevant for “1D” plotting methods like plot and scatter:

The following features are relevant for “2D” plotting methods like pcolor and contour:

  • The new heatmap method invokes pcolormesh and draws ticks at the center of each box. This is more convenient for things like covariance matrices.

  • Wherever colormaps are used, they can be divided into :ref:` discrete levels <ug_discrete>` using keyword arguments like N and levels – similar to contourf. This is accomplished by applying DiscreteNorm as the new default colormap normalizer. This feature can be disabled by setting rc[‘image.discretenorm’] to False.

  • The new DivergingNorm normalizer is perfect for data with a natural midpoint and offers both “fair” and “unfair” scaling, and the new LinearSegmentedNorm normalizer can generate the uneven color gradations useful for unusually distributed data.

  • The contour, contourf, pcolormesh, and pcolor commands all accept a labels keyword argument to draw contour labels and grid box labels on-the-fly. Labels are colored black or white according to the luminance of the underlying filled contour or grid box color.

  • Matplotlib requires coordinate “centers” for contour plots and “edges” for pcolor plots. If you pass centers to pcolor, matplotlib treats them as edges, then silently trims one row/column of your data. ProPlot changes this behavior so that your data is not trimmed.

  • ProPlot fixes an irritating issue with saved vector graphics where white lines appear between filled contours, pcolor patches, and colorbar patches.

  • All 2D plotting methods methods accept cmap and norm keyword arguments interpreted by Colormap and Norm and a colorbar keyword argument for drawing colorbars at the specified location.

Cartopy and basemap integration

Limitation

There are two widely-used engines for working with geophysical data in matplotlib: cartopy and basemap. Using cartopy tends to be verbose and involve boilerplate code, while using basemap requires you to use plotting commands on a separate Basemap object rather than an axes object. They both require separate import statements and extra lines of code to configure the projection.

Furthermore, when you use cartopy and basemap plotting commands, “map projection” coordinates are the default coordinate system rather than longitude-latitude coordinates. This choice is confusing for many users, since the vast majority of geophysical data are stored with longitude-latitude (i.e., “Plate Carrée”) coordinates.

Solution

ProPlot lets you specify geographic projections by simply passing the PROJ name to subplots with e.g. fig, ax = pplt.subplots(proj='pcarree'). Alternatively, the Proj constructor function can be used to quickly generate cartopy.crs.Projection and Basemap instances.

ProPlot also gives you access to various cartopy and basemap features via the proplot.axes.GeoAxes.format method. This lets you quickly modify geographic plot settings like latitude and longitude gridlines, gridline labels, continents, coastlines, and political boundaries.

Finally, GeoAxes makes longitude-latitude coordinates the “default” coordinate system by passing transform=ccrs.PlateCarree() to CartopyAxes plotting methods and latlon=True to BasemapAxes plotting methods. And to enforce global coverage over the poles and across longitude seams, you can pass globe=True to any 2D plotting command, e.g. contourf or pcolormesh.

See the user guide for details.

Xarray and pandas integration

Limitation

Data intended for visualization are commonly stored in array-like containers that include metadata – namely xarray.DataArray, pandas.DataFrame, and pandas.Series. When matplotlib receives these objects, it simply ignores the associated metadata. To create plots that are labeled with the metadata, you must use the xarray.DataArray.plot, pandas.DataFrame.plot, and pandas.Series.plot tools instead.

This approach is fine for quick plots, but not ideal for complex ones. It requires learning a different syntax from matplotlib, and tends to encourage using the pyplot interface rather than the object-oriented interface. These tools also include features that would be useful additions to matplotlib in their own right, without requiring special containers and a separate interface.

Solution

ProPlot reproduces many of the xarray.DataArray.plot, pandas.DataFrame.plot, and pandas.Series.plot features on the Axes plotting methods themselves. Passing a DataArray, DataFrame, or Series through any plotting method automatically updates the axis tick labels, axis labels, subplot titles, and colorbar and legend labels from the metadata. This feature can be disabled by setting rc.autoformat to False.

Also, as desribed above, ProPlot implements features that were originally only available from the xarray.DataArray.plot, pandas.DataFrame.plot, and pandas.Series.plot commands – like grouped bar plots, layered area plots, heatmap plots, and on-the-fly colorbars and legends – directly within the Axes plotting commands.

Aesthetic colors and fonts

Limitation

A common problem with scientific visualizations is the use of “misleading” colormaps like 'jet'. These colormaps tend to have jarring jumps in hue, saturation, and luminance that can trick the human eye into seeing non-existing patterns. It is important to use “perceptually uniform” colormaps instead. Matplotlib comes packaged with some of its own, plus colormaps from the ColorBrewer project, but several external projects offer a larger variety of aesthetically pleasing “perceptually uniform” colormaps.

Matplotlib also “registers” the X11/CSS4 color names, but these are relatively limited. The more intuitive and more numerous XKCD color survey names can be accessed with the 'xkcd:' prefix, but this is cumbersome, and external projects like open color offer even more useful names.

Finally, matplotlib comes packaged with DejaVu Sans as the default font. This font is open source and include glyphs for a huge variety of characters, but unfortunately (in our opinion) it is not very aesthetically pleasing. It can also be difficult to change the default matplotlib font.

Solution

ProPlot adds new colormaps, new colors, and new fonts to help you make more aesthetically pleasing figures.

For details on adding new colormaps, colors, and fonts, see the .proplot folder section.

Manipulating colormaps and cycles

Limitation

In matplotlib, colormaps are implemented with the LinearSegmentedColormap class, representing “smooth” color gradations, and the ListedColormap class, representing “discrete” color sets. They are generally cumbersome to modify or create from scratch. Meanwhile, property cycles used for individual plot elements are implemented with the Cycler class. They are also cumbersome to modify and they cannot be “registered” by name like colormaps.

The seaborn package introduces “color palettes” to make working with colormaps and property cycles easier, but it would be nice to have similar features integrated more closely with the matplotlib interface.

Solution

In ProPlot, it is easy to manipulate colormaps and property cycles:

  • The Cycle constructor function can be used to make new property cycles and retrieve named property cycle colors. It can also make property cycles by splitting up the colors from registered or on-the-fly LinearSegmentedColormaps. Property cycle names are “registered” by adding them as ListedColormap instances. Arbitrary cycles can be displayed using show_cycles.

  • The Colormap constructor function can be used to slice and merge existing colormaps or generate brand new colormaps. It can also return the ListedColormaps containing property cycle colors for use with commands like pcolor (useful, e.g., for categorical data). It can also be used to create new PerceptuallyUniformColormaps. Arbitrary colormaps can be displayed using show_cmaps.

Importing ProPlot also makes all colormap and property cycle names case-insensitive, and colormaps can be reversed or cyclically shifted by 180 degrees simply by appending '_r' or '_s' to the colormap name. This is powered by the ColormapDatabase dictionary, which replaces matplotlib’s native colormap database.

Physical units engine

Limitation

Matplotlib uses figure-relative units for the margins left, right, bottom, and top, and axes-relative units for the column and row spacing wspace and hspace. Relative units tend to require “tinkering” with numbers until you find the right one. And since they are relative, if you decide to change your figure size or add a subplot, they will have to be readjusted.

Matplotlib also requires users to set the figure size figsize in inches. This may be confusing for users outside of the United States.

Solution

ProPlot introduces the physical units engine units for interpreting figsize, figwidth, figheight, refwidth, refheight, left, right, top, bottom, wspace, hspace, and arguments in a few other places. Acceptable units include inches, centimeters, millimeters, pixels, points, picas, and em-heights. Em-heights are particularly useful, as the labels already present can be useful “rulers” for figuring out the amount of space needed.

units is also used to convert settings passed to rc from arbitrary physical units to points – for example rc.ticklen, rc[‘title.size’], and rc[‘title.pad’]. See the user guide for details.

Flexible global settings

Limitation

In matplotlib, there are several rcParams that you often want to set all at once, like the tick lengths and spine colors. It is also often desirable to change these settings for individual subplots rather than globally.

Solution

In ProPlot, you can use the rc object to change lots of settings at once with convenient shorthands. This is meant to replace matplotlib’s rcParams dictionary. Settings can be changed with pplt.rc.key = value, pplt.rc[key] = value, pplt.rc.update(...), with the format method, or with the context method. ProPlot also adds a bunch of new settings for controlling proplot-specific features. See the user guide for details.

Loading saved settings

Limitation

Matplotlib rcParams settings can be changed using a matplotlibrc file in the same directory as your python script. But it can be difficult to design and store your own colormaps and color cycles for future use. It is also difficult to get matplotlib to use custom .ttf and .otf font files, which may be desirable when you are working on Linux servers with limited font selections.

Solution

ProPlot settings can be changed using a ~/.proplotrc file in the home directory or by adding files named .proplotrc or proplotrc to either 1) the current directory or 2) any parent directories. This can be useful when working in projects with lots of subfolders. See the user guide for details.

ProPlot also automatically loads colormaps, color cycles, colors, and font files saved in the ~/.proplot/cmaps, ~/.proplot/cycles, ~/.proplot/colors, and ~/.proplot/fonts, folders in your home directory. You can save colormaps and color cycles to these folders simply by passing save=True to Colormap and Cycle. To manually load from these folders, e.g. if you have added files to these folders but you do not want to restart your ipython session, simply call register_cmaps, register_cycles, register_colors, or register_fonts.