1d plotting

ProPlot adds new features to various Axes plotting methods thanks to a set of wrapper functions. These features are a strict superset of the existing matplotlib API – if you want, you can use plotting commands exactly as you always have.

Standardized input

The standardize_1d wrapper is used to standardize the positional arguments for several different plotting functions. standardize_1d allows you to optionally omit x coordinates (in which case they are inferred from the y coordinates) or pass 2D y coordinate arrays (in which case the plotting method is called with each column of the array).

Pandas and xarray

The standardize_1d wrapper also supports integration 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.

[1]:
import xarray as xr
import numpy as np
import pandas as pd

# DataArray
state = np.random.RandomState(51423)
data = np.sin(np.linspace(0, 2*np.pi, 20))[:, None] \
    + state.rand(20, 8).cumsum(axis=1)
da = xr.DataArray(data, dims=('x', 'cat'), coords={
    'x': xr.DataArray(np.linspace(0, 1, 20), dims=('x',), attrs={'long_name': 'distance', 'units': 'km'}),
    'cat': xr.DataArray(np.arange(0, 80, 10), dims=('cat',), attrs={'long_name': 'parameter', 'units': 'K'})
}, name='position series')

# DataFrame
ts = pd.date_range('1/1/2000', periods=20)
data = (np.cos(np.linspace(0, 2*np.pi, 20))**4)[:, None] + state.rand(20, 5)**2
df = pd.DataFrame(data, index=ts, columns=['foo', 'bar', 'baz', 'zap', 'baf'])
df.name = 'time series'
df.index.name = 'time (s)'
df.columns.name = 'columns'
[2]:
import proplot as plot
f, axs = plot.subplots(ncols=2, axwidth=2.2, share=0)
axs.format(suptitle='Automatic subplot formatting')

# Plot DataArray
color = plot.shade('light blue', 0.4)
cycle = plot.Cycle(color, fade=90, space='hpl')
axs[0].plot(da, cycle=cycle, lw=3, colorbar='ul', colorbar_kw={'locator': 20})

# Plot Dataframe
color = plot.shade('jade', 0.4)
cycle = plot.Cycle(color, fade=90, space='hpl')
axs[1].plot(df, cycle=cycle, lw=3, legend='uc')
/home/docs/checkouts/readthedocs.org/user_builds/proplot/conda/v0.4.1/lib/python3.7/site-packages/proplot/utils.py:103: ProPlotWarning: Rebuilding font manager.
/home/docs/checkouts/readthedocs.org/user_builds/proplot/conda/v0.4.1/lib/python3.7/site-packages/pandas/plotting/_matplotlib/converter.py:103: FutureWarning: Using an implicitly registered datetime converter for a matplotlib plotting method. The converter was registered by pandas on import. Future versions of pandas will require you to explicitly register matplotlib converters.

To register the converters:
        >>> from pandas.plotting import register_matplotlib_converters
        >>> register_matplotlib_converters()
  warnings.warn(msg, FutureWarning)
[2]:
(<matplotlib.lines.Line2D at 0x7f720f252150>,
 <matplotlib.lines.Line2D at 0x7f720f6f7dd0>,
 <matplotlib.lines.Line2D at 0x7f720f1d2b10>,
 <matplotlib.lines.Line2D at 0x7f720f17fd10>,
 <matplotlib.lines.Line2D at 0x7f720f17f910>)
_images/1dplots_7_2.svg

On-the-fly error bars

Thanks to the add_errorbars wrapper, you can add error bars on-the-fly by passing certain keyword args to plot, scatter, bar, barh, or violinplot. If you pass 2D arrays to these commands with means=True or medians=True, the means or medians of each column are drawn as points, lines, or bars, and error bars are drawn to represent the spread in each column. You can draw both thin “bars” with optional whiskers, and thick “boxes” overlayed on top of these bars. You can also pass error bar coordinates manually with the bardata and boxdata keyword args. See add_errorbars for details.

[3]:
import proplot as plot
import numpy as np
import pandas as pd
plot.rc['title.loc'] = 'uc'
plot.rc['axes.ymargin'] = plot.rc['axes.xmargin'] = 0.05
state = np.random.RandomState(51423)
data = state.rand(20, 8).cumsum(axis=0).cumsum(axis=1)[:, ::-1] \
    + 20*state.normal(size=(20, 8)) + 30
f, axs = plot.subplots(
    nrows=3, aspect=1.5, axwidth=3,
    share=0, hratios=(2, 1, 1)
)
axs.format(suptitle='Error bars with various plotting commands')
axs[1:].format(xlabel='column number', xticks=1, xgrid=False)

# Asking add_errorbars to calculate bars
ax = axs[0]
obj = ax.barh(data, color='red orange', means=True)
ax.format(title='Column statistics')
ax.format(ylabel='column number', title='Bar plot', ygrid=False)

# Showing a standard deviation range instead of percentile range
ax = axs[1]
ax.scatter(
    data, color='k', marker='x', markersize=50, barcolor='gray5',
    medians=True, barstd=True, barrange=(-1, 1), barzorder=0, boxes=False, capsize=2
)
ax.format(title='Scatter plot')

# Supplying error bar data manually
ax = axs[2]
boxdata = np.percentile(data, (25, 75), axis=0)
bardata = np.percentile(data, (5, 95), axis=0)
ax.plot(
    data.mean(axis=0), boxes=False, marker='o', markersize=5,
    edgecolor='k', color='cerulean', boxdata=boxdata, bardata=bardata
)
ax.format(title='Line plot')
plot.rc.reset()
_images/1dplots_10_0.svg

Bar plots

bar_wrapper and cycle_changer make it easier to generate useful bar plots. You can now pass 2D arrays to bar or barh, and columns of data will be grouped or stacked together. Also, bar and barh now use default x and y coordinates if you failed to provide them explicitly, just like plot. See bar_wrapper for details.

[4]:
import proplot as plot
import numpy as np
import pandas as pd
plot.rc.titleloc = 'uc'
plot.rc.margin = 0.05
f, axs = plot.subplots(nrows=2, aspect=2, axwidth=3.5, share=0, hratios=(3, 2))
state = np.random.RandomState(51423)
data = state.rand(5, 5).cumsum(axis=0).cumsum(axis=1)[:, ::-1]
data = pd.DataFrame(
    data, columns=pd.Index(np.arange(1, 6), name='column'),
    index=pd.Index(['a', 'b', 'c', 'd', 'e'], name='row idx')
)

# Side-by-side bars
ax = axs[0]
obj = ax.bar(
    data, cycle='Reds', colorbar='ul',
    edgecolor='red9', colorbar_kw={'frameon': False}
)
ax.format(
    xlocator=1, xminorlocator=0.5, ytickminor=False,
    title='Side-by-side', suptitle='Bar plot wrapper demo'
)

# Stacked bars
ax = axs[1]
obj = ax.barh(
    data.iloc[::-1, :], cycle='Blues',
    legend='ur', edgecolor='blue9', stacked=True
)
ax.format(title='Stacked')
axs.format(grid=False)
plot.rc.reset()
_images/1dplots_13_0.svg

Area plots

To make area plots, use the convenient fill_between aliases area and areax. These are wrapped with fill_between_wrapper and fill_betweenx_wrapper.

The fill_between wrappers enable “stacking” successive columns of a 2D input array like in pandas. They also add a new negpos keyword for creating area plots that change color when the fill boundaries cross each other. The most common use case for this is highlighting the negative and positive areas underneath a line, as shown below.

[5]:
import proplot as plot
import numpy as np
plot.rc.margin = 0
f, axs = plot.subplots(array=[[1, 2], [3, 3]], hratios=(1, 0.8), share=0)
axs.format(xlabel='xlabel', ylabel='ylabel', suptitle='Area plot demo')
state = np.random.RandomState(51423)
data = state.rand(5, 3).cumsum(axis=0)
cycle = ('gray3', 'gray5', 'gray7')

# Overlaid and stacked area patches
ax = axs[0]
ax.area(
    np.arange(5), data, data + state.rand(5)[:, None], cycle=cycle, alpha=0.5,
    legend='uc', legend_kw={'center': True, 'ncols': 2, 'labels': ['z', 'y', 'qqqq']},
)
ax.format(title='Fill between columns')
ax = axs[1]
ax.area(
    np.arange(5), data, stacked=True, cycle=cycle, alpha=0.8,
    legend='ul', legend_kw={'center': True, 'ncols': 2, 'labels': ['z', 'y', 'qqqq']},
)
ax.format(title='Stack between columns')

# Positive and negative color area patches
ax = axs[2]
data = 5*(state.rand(20)-0.5)
ax.area(data, negpos=True, negcolor='blue7', poscolor='red7')
ax.format(title='Negative and positive data', xlabel='xlabel', ylabel='ylabel')
axs.format(grid=False)
plot.rc.reset()
_images/1dplots_16_0.svg

Box plots and violin plots

boxplot and violinplot are now wrapped with boxplot_wrapper, violinplot_wrapper, and cycle_changer, making it much easier to plot distributions of data with aesthetically pleasing default settings and automatic axis labeling.

[6]:
import proplot as plot
import numpy as np
import pandas as pd
N = 500
state = np.random.RandomState(51423)
f, axs = plot.subplots(ncols=2)
data = state.normal(size=(N, 5)) + 2*(state.rand(N, 5)-0.5)*np.arange(5)
data = pd.DataFrame(
    data,
    columns=pd.Index(['a', 'b', 'c', 'd', 'e'], name='xlabel')
)
axs.format(
    ymargin=0.1, xmargin=0.1, grid=False,
    suptitle='Boxes and violins demo'
)

# Box plots
ax = axs[0]
obj1 = ax.boxplot(
    data, lw=0.7, marker='x', fillcolor='gray5',
    medianlw=1, mediancolor='k'
)
ax.format(title='Box plots', titleloc='uc')

# Violin plots
ax = axs[1]
obj2 = ax.violinplot(
    data, lw=0.7, fillcolor='gray7',
    points=500, bw_method=0.3, means=True
)
ax.format(title='Violin plots', titleloc='uc')
/home/docs/checkouts/readthedocs.org/user_builds/proplot/conda/v0.4.1/lib/python3.7/site-packages/proplot/utils.py:133: UserWarning: Got conflicting or duplicate keyword args: {'lw': 0.7, 'linewidth': 0.7}. Using the first one.
  f'Got conflicting or duplicate keyword args: {kwargs}. '
/home/docs/checkouts/readthedocs.org/user_builds/proplot/conda/v0.4.1/lib/python3.7/site-packages/proplot/utils.py:133: UserWarning: Got conflicting or duplicate keyword args: {'lw': 0.7, 'linewidth': 0.7}. Using the first one.
  f'Got conflicting or duplicate keyword args: {kwargs}. '
_images/1dplots_19_1.svg

Parametric plots

You can draw “parametric” plots in ProPlot using the parametric method or by passing the cmap keyword argument to plot. “Parametric” plots are line collections that map individual line segments to individual colors, where each color represents a parametric coordinate (e.g. time). Parametric coordinates must be passed with the values keyword argument. See parametric for details.

[7]:
import proplot as plot
import numpy as np
N = 50
cmap = 'IceFire'
values = np.linspace(-N/2, N/2, N)
f, axs = plot.subplots(
    share=0, ncols=2, wratios=(2, 1),
    axwidth='7cm', aspect=(2, 1)
)
axs.format(suptitle='Parametric plots demo')

# Parametric line with smooth gradations
ax = axs[0]
state = np.random.RandomState(51423)
m = ax.plot((state.rand(N) - 0.5).cumsum(), state.rand(N),
            cmap=cmap, values=values, lw=7, extend='both')
ax.format(
    xlabel='xlabel', ylabel='ylabel',
    title='Line with smooth gradations'
)
ax.format(xlim=(-1, 5), ylim=(-0.2, 1.2))
ax.colorbar(m, loc='b', label='parametric coordinate', locator=5)

# Parametric line with stepped gradations
N = 12
ax = axs[1]
values = np.linspace(-N/2, N/2, N + 1)
radii = np.linspace(1, 0.2, N + 1)
angles = np.linspace(0, 4*np.pi, N + 1)
x = radii*np.cos(1.4*angles)
y = radii*np.sin(1.4*angles)
m = ax.plot(x, y, values=values, linewidth=15, interp=False, cmap=cmap)
ax.format(
    xlim=(-1, 1), ylim=(-1, 1), title='Step gradations',
    xlabel='cosine angle', ylabel='sine angle'
)
ax.colorbar(m, loc='b', maxn=10, label=f'parametric coordinate')
[7]:
<matplotlib.colorbar.Colorbar at 0x7f720b8b8b10>
_images/1dplots_22_1.svg

Scatter plots

Thanks to scatter_wrapper and cycle_changer, scatter now accepts 2D arrays, just like plot. Also, successive calls to scatter now use the property cycler properties (e.g. color, marker, and markersize), and to reduce confusion, scatter optionally accepts keywords that look like plot keywords (e.g. color instead of c and markersize instead of s). You can also pass colormaps to scatter just as with matplotlib.

We are also considering supporting 2D array input and property cycle iteration for more obscure matplotlib plotting commands like stem, step, vlines, and hlines. Stay tuned!

[8]:
import proplot as plot
import numpy as np
import pandas as pd
f, axs = plot.subplots(ncols=2, share=1)
state = np.random.RandomState(51423)
x = (state.rand(20)-0).cumsum()
data = (state.rand(20, 4)-0.5).cumsum(axis=0)
data = pd.DataFrame(data, columns=pd.Index(['a', 'b', 'c', 'd'], name='label'))

# Scatter plot with property cycler
ax = axs[0]
ax.format(title='Extra prop cycle properties', suptitle='Scatter plot demo')
obj = ax.scatter(
    x, data, legend='ul', cycle='warm', legend_kw={'ncols': 2},
    cycle_kw={'marker': ['x', 'o', 'x', 'o'], 'markersize': [5, 10, 20, 30]}
)

# Scatter plot with colormap
ax = axs[1]
ax.format(title='Scatter plot with cmap')
data = state.rand(2, 100)
obj = ax.scatter(
    *data, color=data.sum(axis=0), size=state.rand(100), smin=3, smax=30,
    marker='o', cmap='plum', colorbar='lr', vmin=0, vmax=2,
    colorbar_kw={'label': 'label', 'locator':0.5}
)
axs.format(xlabel='xlabel', ylabel='ylabel')
_images/1dplots_25_0.svg