[TOC]

Plotting 2D data

ProPlot adds new features to various Axes plotting methods using a set of wrapper functions. When a plotting method like contourf is “wrapped” by one of these functions, it accepts the same parameters as the wrapper. These features are a strict superset of the matplotlib API. This section documents the features added by wrapper functions to 2D plotting commands like contour, contourf, pcolor, and pcolormesh.

Colormaps and normalizers

It is often desirable to create ProPlot colormaps on-the-fly, without explicitly using the Colormap constructor function. To enable this, the cmap_changer wrapper adds the cmap and cmap_kw arguments to every 2D plotting method. These arguments are passed to the Colormap constructor function, and the resulting colormap is used for the input data. For example, to create and apply a monochromatic colormap, you can simply use cmap=’color name’.

The cmap_changer wrapper also adds the norm and norm_kw arguments. They are passed to the Norm constructor function, and the resulting normalizer is used for the input data. For more information on colormaps and normalizers, see the colormaps section and this matplotlib tutorial.

[1]:
import proplot as plotimport numpy as npN = 20state = np.random.RandomState(51423)data = 10 ** (0.25 * np.cumsum(state.rand(N, N), axis=0))fig, axs = plot.subplots(ncols=2, span=False)axs.format(    xlabel='xlabel', ylabel='ylabel', grid=True,    suptitle='On-the-fly colormaps and normalizers')# On-the-fly colormaps and normalizersaxs[0].pcolormesh(data, cmap=('orange0', 'blood'), colorbar='b')axs[1].pcolormesh(data, norm='log', cmap=('orange0', 'blood'), colorbar='b')axs[0].format(title='Linear normalizer')axs[1].format(title='Logarithmic normalizer')

img

Discrete colormap levels

The cmap_changer wrapper also applies the DiscreteNorm normalizer to every colormap plot. DiscreteNorm converts data values to colormap colors by (1) transforming data using an arbitrary continuous normalizer (e.g. LogNorm), then (2) mapping the normalized data to discrete colormap levels (just like BoundaryNorm).

By applying DiscreteNorm to every plot, ProPlot permits distinct “levels” even for commands like pcolor and pcolormesh. Distinct levels can help the reader discern exact numeric values and tends to reveal qualitative structure in the figure. They are also critical for users that would prefer contours, but have complex 2D coordinate matrices that trip up the contouring algorithm. DiscreteNorm also fixes the colormap end-colors by ensuring the following conditions are met (this may seem nitpicky, but it is crucial for plots with very few levels):

  1. All colormaps always span the entire color range, independent of the extend setting.
  2. Cyclic colormaps always have distinct color levels on either end of the colorbar.
[2]:
import proplot as plotimport numpy as np# Pcolor plot with and without distinct levelsfig, axs = plot.subplots(ncols=2, axwidth=2)state = np.random.RandomState(51423)data = (state.normal(0, 1, size=(33, 33))).cumsum(axis=0).cumsum(axis=1)axs.format(suptitle='Pcolor plot with levels')for ax, n, mode, side in zip(axs, (200, 10), ('Ambiguous', 'Discernible'), 'lr'):    ax.pcolor(data, cmap='spectral_r', N=n, symmetric=True, colorbar=side)    ax.format(title=f'{mode} level boundaries', yformatter='null')

img

[3]:
import proplot as plotimport numpy as npfig, axs = plot.subplots(    [[0, 0, 1, 1, 0, 0], [2, 3, 3, 4, 4, 5]],    wratios=(1.5, 0.5, 1, 1, 0.5, 1.5), axwidth=1.7, ref=1, right='2em')axs.format(suptitle='DiscreteNorm color-range standardization')levels = plot.arange(0, 360, 45)state = np.random.RandomState(51423)data = (20 * (state.rand(20, 20) - 0.4).cumsum(axis=0).cumsum(axis=1)) % 360# Cyclic colorbar with distinct end colorsax = axs[0]ax.pcolormesh(    data, levels=levels, cmap='phase', extend='neither',    colorbar='b', colorbar_kw={'locator': 90})ax.format(title='cyclic colormap\nwith distinct end colors')# Colorbars with different extend valuesfor ax, extend in zip(axs[1:], ('min', 'max', 'neither', 'both')):    ax.pcolormesh(        data[:, :10], levels=levels, cmap='oxy',        extend=extend, colorbar='b', colorbar_kw={'locator': 90}    )    ax.format(title=f'extend={extend!r}')

img

Special normalizers

The LinearSegmentedNorm colormap normalizer provides even color gradations with respect to index for an arbitrary monotonically increasing list of levels. This is automatically applied if you pass unevenly spaced levels to a plotting command, or it can be manually applied using e.g. norm=’segmented’.

The DivergingNorm normalizer ensures the colormap midpoint lies on some central data value (usually 0), even if vmin, vmax, or levels are asymmetric with respect to the central value. This can be applied using e.g. norm=’diverging’ and be configured to scale colors “fairly” or “unfairly”:

  • With fair scaling (the default), the gradations on either side of the midpoint have equal intensity. If vmin and vmax are not symmetric about zero, the most intense colormap colors on one side of the midpoint will be truncated.
  • With unfair scaling, the gradations on either side of the midpoint are warped so that the full range of colormap colors is traversed. This configuration should be used with care, as it may lead you to misinterpret your data!

The below example demonstrates how these normalizers can be used for datasets with unusual statistical distributions.

[4]:
import proplot as plotimport numpy as np# Linear segmented normstate = np.random.RandomState(51423)data = 10**(2 * state.rand(20, 20).cumsum(axis=0) / 7)fig, axs = plot.subplots(ncols=2, axwidth=2.4)ticks = [5, 10, 20, 50, 100, 200, 500, 1000]for i, (norm, title) in enumerate(zip(    ('linear', 'segmented'),    ('Linear normalizer', 'LinearSegmentedNorm'))):    m = axs[i].contourf(        data, levels=ticks, extend='both',        cmap='Mako', norm=norm,        colorbar='b', colorbar_kw={'ticks': ticks},    )    axs[i].format(title=title)axs.format(suptitle='Linear segmented normalizer demo')

img

[5]:
import proplot as plotimport numpy as np# Diverging normstate = np.random.RandomState(51423)data1 = (state.rand(20, 20) - 0.43).cumsum(axis=0)data2 = (state.rand(20, 20) - 0.57).cumsum(axis=0)fig, axs = plot.subplots(nrows=2, ncols=2, axwidth=2.4, order='F')cmap = plot.Colormap('DryWet', cut=0.1)axs.format(suptitle='Diverging normalizer demo')i = 0for data, mode, fair in zip(    (data1, data2),    ('positive', 'negative'),    ('fair', 'unfair')):    for fair in ('fair', 'unfair'):        norm = plot.Norm('diverging', fair=(fair == 'fair'))        ax = axs[i]        m = ax.contourf(data, cmap=cmap, norm=norm)        ax.colorbar(m, loc='b', locator=1)        ax.format(title=f'Skewed {mode} data, {fair!r} scaling')        i += 1

img

Standardized arguments

The standardize_2d wrapper is used to standardize positional arguments across all 2D plotting methods. Among other things, it guesses coordinate edges for pcolor and pcolormesh plots when you supply coordinate centers, and calculates coordinate centers for contourf and contour plots when you supply coordinate edges. Notice the locations of the rectangle edges in the pcolor plots shown below.

[6]:
import proplot as plotimport numpy as np# Figure and sample datastate = np.random.RandomState(51423)x = y = np.array([-10, -5, 0, 5, 10])xedges = plot.edges(x)yedges = plot.edges(y)data = state.rand(y.size, x.size)  # "center" coordinateslim = (np.min(xedges), np.max(xedges))with plot.rc.context({'image.cmap': 'Grays', 'image.levels': 21}):    fig, axs = plot.subplots(ncols=2, nrows=2, share=False)    axs.format(        xlabel='xlabel', ylabel='ylabel',        xlim=lim, ylim=lim, xlocator=5, ylocator=5,        suptitle='Standardized input demonstration'    )    axs[0].format(title='Supplying coordinate centers')    axs[1].format(title='Supplying coordinate edges')    # Plot using both centers and edges as coordinates    axs[0].pcolormesh(x, y, data)    axs[1].pcolormesh(xedges, yedges, data)    axs[2].contourf(x, y, data)    axs[3].contourf(xedges, yedges, data)

img

Pandas and xarray integration

The standardize_2d wrapper also integrates 2D plotting methods with pandas DataFrames and xarray DataArrays. When you pass a DataFrame or DataArray to any plotting command, the x-axis label, y-axis label, legend label, colorbar label, and/or title are configured from the metadata. This restores some of the convenience you get with the builtin pandas and xarray plotting functions. This feature is optional; installation of pandas and xarray are not required.

[7]:
import xarray as xrimport numpy as npimport pandas as pd# DataArraystate = np.random.RandomState(51423)linspace = np.linspace(0, np.pi, 20)data = 50 * state.normal(1, 0.2, size=(20, 20)) * (    np.sin(linspace * 2) ** 2    * np.cos(linspace + np.pi / 2)[:, None] ** 2)lat = xr.DataArray(    np.linspace(-90, 90, 20),    dims=('lat',),    attrs={'units': 'deg_north'})plev = xr.DataArray(    np.linspace(1000, 0, 20),    dims=('plev',),    attrs={'long_name': 'pressure', 'units': 'mb'})da = xr.DataArray(    data,    name='u',    dims=('plev', 'lat'),    coords={'plev': plev, 'lat': lat},    attrs={'long_name': 'zonal wind', 'units': 'm/s'})# DataFramedata = state.rand(12, 20)df = pd.DataFrame(    (data - 0.4).cumsum(axis=0).cumsum(axis=1),    index=list('JFMAMJJASOND'),)df.name = 'temporal data'df.index.name = 'month'df.columns.name = 'variable (units)'
[8]:
import proplot as plotfig, axs = plot.subplots(nrows=2, axwidth=2.5, share=0)axs.format(collabels=['Automatic subplot formatting'])# Plot DataArraycmap = plot.Colormap('RdPu', left=0.05)axs[0].contourf(da, cmap=cmap, colorbar='l', linewidth=0.7, color='k')axs[0].format(yreverse=True)# Plot DataFrameaxs[1].contourf(df, cmap='YlOrRd', colorbar='r', linewidth=0.7, color='k')axs[1].format(xtickminor=False)

img

Contour and gridbox labels

The cmap_changer wrapper also allows you to quickly add labels to heatmap, pcolor, pcolormesh, contour, and contourf plots by simply using labels=True. The label text is colored black or white depending on the luminance of the underlying grid box or filled contour.

cmap_changer draws contour labels with clabel and grid box labels with text. You can pass keyword arguments to these functions using the labels_kw dictionary keyword argument, and change the label precision with the precision keyword argument. See cmap_changer for details.

[9]:
import proplot as plotimport pandas as pdimport numpy as npfig, axs = plot.subplots(    [[1, 1, 2, 2], [0, 3, 3, 0]],    axwidth=2.2, share=1, span=False, hratios=(1, 0.9))state = np.random.RandomState(51423)data = state.rand(6, 6)data = pd.DataFrame(data, index=pd.Index(['a', 'b', 'c', 'd', 'e', 'f']))axs.format(xlabel='xlabel', ylabel='ylabel', suptitle='Labels demo')# Heatmap with labeled boxesax = axs[0]m = ax.heatmap(    data, cmap='rocket', labels=True,    precision=2, labels_kw={'weight': 'bold'})ax.format(title='Heatmap plot with labels')# Filled contours with labelsax = axs[1]m = ax.contourf(    data.cumsum(axis=0), labels=True,    cmap='rocket', labels_kw={'weight': 'bold'})ax.format(title='Filled contour plot with labels')# Line contours with labelsax = axs[2]ax.contour(    data.cumsum(axis=1) - 2, color='gray8',    labels=True, lw=2, labels_kw={'weight': 'bold'})ax.format(title='Line contour plot with labels')

img

Heatmap plots

The new heatmap command calls pcolormesh and configures the axes with settings that are suitable for heatmaps – fixed aspect ratio, no gridlines, no minor ticks, and major ticks at the center of each box. Among other things, this is useful for displaying covariance and correlation matrices, as shown below.

[10]:
import proplot as plotimport numpy as npimport pandas as pd# Covariance datastate = np.random.RandomState(51423)data = state.normal(size=(10, 10)).cumsum(axis=0)data = (data - data.mean(axis=0)) / data.std(axis=0)data = (data.T @ data) / data.shape[0]data[np.tril_indices(data.shape[0], -1)] = np.nan  # fill half with empty boxesdata = pd.DataFrame(data, columns=list('abcdefghij'), index=list('abcdefghij'))# Covariance matrix plotfig, ax = plot.subplots(axwidth=4.5)m = ax.heatmap(    data, cmap='ColdHot', vmin=-1, vmax=1, N=100,    lw=0.5, edgecolor='k', labels=True, labels_kw={'weight': 'bold'},    clip_on=False,  # turn off clipping so box edges are not cut in half)ax.format(    suptitle='Heatmap demo', title='Table of correlation coefficients', alpha=0,    xloc='top', yloc='right', yreverse=True, ticklabelweight='bold', linewidth=0,    ytickmajorpad=4,  # the ytick.major.pad rc setting; adds extra space)

img