Plotting 1D data¶
ProPlot adds new features to various Axes
plotting methods using a set of “wrapper” functions. When a plotting method like
plot
is “wrapped” by one of these functions, it accepts
the same parameters as the wrapper. These additions are a strict superset of
matplotlib – if you are not interested, you can use matplotlib’s plotting methods
just like you always have. This section documents the features added by wrapper
functions to 1D plotting commands like plot
,
scatter
, bar
, and
barh
.
Standardized arguments¶
The standardize_1d
wrapper standardizes
positional arguments across all 1D plotting methods.
standardize_1d
lets you optionally omit the x
coordinates, in which case they are inferred from the data. It also permits
passing 2D y coordinate arrays to any plotting method, in which case the
plotting method is called for each column of the array.
[1]:
import proplot as pplt
import numpy as np
N = 5
state = np.random.RandomState(51423)
with pplt.rc.context({'axes.prop_cycle': pplt.Cycle('Grays', N=N, left=0.3)}):
# Sample data
x = np.linspace(-5, 5, N)
y = state.rand(N, 5)
# Figure
fig, axs = pplt.subplots(ncols=2, share=False)
axs.format(xlabel='xlabel', ylabel='ylabel')
axs.format(suptitle='Standardized arguments demonstration')
# Plot by passing both x and y coordinates
ax = axs[0]
ax.area(x, -1 * y / N, stack=True)
ax.bar(x, y, linewidth=0, alpha=1, width=0.8)
ax.plot(x, y + 1, linewidth=2)
ax.scatter(x, y + 2, marker='s', markersize=5**2)
ax.format(title='Manual x coordinates')
# Plot by passing just y coordinates
# Default x coordinates are inferred from DataFrame,
# inferred from DataArray, or set to np.arange(0, y.shape[0])
ax = axs[1]
ax.area(-1 * y / N, stack=True)
ax.bar(y, linewidth=0, alpha=1)
ax.plot(y + 1, linewidth=2)
ax.scatter(y + 2, marker='s', markersize=5**2)
ax.format(title='Auto x coordinates')
/home/docs/checkouts/readthedocs.org/user_builds/proplot/conda/v0.7.0/lib/python3.8/site-packages/proplot/__init__.py:9: ProPlotWarning: Rebuilding font cache.
from .config import * # noqa: F401 F403

Pandas and xarray integration¶
The standardize_1d
wrapper integrates 1D plotting
methods with pandas DataFrame
s and xarray DataArray
s.
If you omitted x coordinates, standardize_1d
tries to
retrieve them from the DataFrame or DataArray. If the coordinates are string
labels, standardize_1d
converts them into indices and tick labels
using FixedLocator
and IndexFormatter
.
If you did not explicitly set the x-axis label, y-axis label, title, or
on-the-fly legend or colorbar label,
standardize_1d
also tries to retrieve them from the DataFrame or
DataArray.
You can also pass a Dataset, DataFrame, or dictionary to any plotting
command using the data
keyword, then pass dataset keys as positional
arguments instead of arrays. For example, ax.plot('y', data=dataset)
is translated to ax.plot(dataset['y'])
, and the x coordinates are
inferred thereafter.
These features restore some of the convenience you get
with the builtin pandas and xarray plotting functions. They are also
optional – installation of pandas and xarray are not required. All of
these features can be disabled by setting rc.autoformat
to False
or by passing autoformat=False
to any plotting command.
[2]:
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)
)
coords = {
'x': xr.DataArray(
np.linspace(0, 1, 20),
dims=('x',),
attrs={'long_name': 'distance', 'units': 'km'}
),
'num': xr.DataArray(
np.arange(0, 80, 10),
dims=('num',),
attrs={'long_name': 'parameter'}
)
}
da = xr.DataArray(
data, dims=('x', 'num'), coords=coords, name='energy', attrs={'units': 'kJ'}
)
# DataFrame
data = (
(np.cos(np.linspace(0, 2 * np.pi, 20))**4)[:, None] + state.rand(20, 5)**2
)
ts = pd.date_range('1/1/2000', periods=20)
df = pd.DataFrame(data, index=ts, columns=['foo', 'bar', 'baz', 'zap', 'baf'])
df.name = 'data'
df.index.name = 'date'
df.columns.name = 'category'
[3]:
import proplot as pplt
fig, axs = pplt.subplots(ncols=2, refwidth=2.2, share=0)
axs.format(suptitle='Automatic subplot formatting')
# Plot DataArray
cycle = pplt.Cycle('dark blue', space='hpl', N=da.shape[1])
axs[0].scatter(da, cycle=cycle, lw=3, colorbar='ul', colorbar_kw={'locator': 20})
# Plot Dataframe
cycle = pplt.Cycle('dark green', space='hpl', N=df.shape[1])
axs[1].plot(df, cycle=cycle, lw=3, legend='uc')
[3]:
[<matplotlib.lines.Line2D at 0x7f3b2174d9a0>,
<matplotlib.lines.Line2D at 0x7f3b2174da90>,
<matplotlib.lines.Line2D at 0x7f3b216d40a0>,
<matplotlib.lines.Line2D at 0x7f3b216d4400>,
<matplotlib.lines.Line2D at 0x7f3b2174a850>]

Property cycles¶
It is often useful to create on-the-fly property cycles
and use different property cycles for different plot elements. You can create and
apply property cycles on-the-fly using the cycle
and cycle_kw
arguments, available
with any plotting method wrapped by apply_cycle
. cycle
and
cycle_kw
are passed to the Cycle
constructor function, and the resulting property cycle
is used for the plot. You can specify cycle
once with 2D input data (in which case
each column is plotted in succession according to the property cycle) or call a
plotting command multiple times with the same cycle
argument each time (the
property cycle is not reset). For more information on property cycles, see the
color cycles section and this matplotlib tutorial.
[4]:
import proplot as pplt
import numpy as np
# Sample data
M, N = 9, 4
state = np.random.RandomState(51423)
data1 = state.rand(M, N)
data2 = state.rand(M, N) * 1.5
with pplt.rc.context({'lines.linewidth': 3}):
# Figure
fig, axs = pplt.subplots(ncols=2, refwidth=2.2, span=False)
axs.format(xlabel='xlabel', ylabel='ylabel', suptitle='Local property cycles demo')
# Use property cycle for columns of 2D input data
axs[0].plot(
data1 * data2,
cycle='black',
cycle_kw={'ls': ('-', '--', '-.', ':')}
)
# Use property cycle with successive plot() calls
for i in range(data1.shape[1]):
axs[1].plot(data1[:, i], cycle='Reds', cycle_kw={'N': N, 'left': 0.3})
for i in range(data1.shape[1]):
axs[1].plot(data2[:, i], cycle='Blues', cycle_kw={'N': N, 'left': 0.3})

Line plots¶
The plot
command is wrapped by
apply_cycle
and standardize_1d
.
The new plotx
command can be used
just like plot
, except a single argument is interpreted
as x coordinates (with y coordinates inferred from the data),
and multiple arguments are interpreted as (y, x) pairs. This is
analogous to barh
and fill_betweenx
.
Also, the x extent of lines drawn with plot
and
the y extent of lines drawn with plotx
are now
“sticky”, i.e. there is no padding between the lines and axes edges by default.
As with the other 1D plotting commands, step
,
hlines
, vlines
, and
stem
are wrapped by standardize_1d
.
step
now use the property cycle, just like
plot
. vlines
and
hlines
are also wrapped by vlines_extras
and hlines_extras
, which permit applying
different colors for “negative” and “positive” lines using negpos=True
(the default colors are rc.negcolor
= 'blue7'
and rc.poscolor
= 'red7'
).
[5]:
import proplot as pplt
import numpy as np
state = np.random.RandomState(51423)
fig, axs = pplt.subplots(ncols=2, nrows=3, refwidth=2.2, share=1, span=False)
axs.format(suptitle='Line plots demo', xlabel='xlabel', ylabel='ylabel')
# Vertical vs. horizontal
data = (state.rand(10, 5) - 0.5).cumsum(axis=0)
ax = axs[0]
ax.format(title='Dependent x-axis')
ax.plot(data, lw=2.5, cycle='seaborn')
ax = axs[1]
ax.format(title='Dependent y-axis')
ax.plotx(data, lw=2.5, cycle='seaborn')
# Vertical lines
gray = 'gray7'
data = state.rand(20) - 0.5
ax = axs[2]
ax.area(data, color=gray, alpha=0.2)
ax.vlines(data, negpos=True, lw=2)
ax.format(title='Vertical lines')
# Horizontal lines
ax = axs[3]
ax.areax(data, color=gray, alpha=0.2)
ax.hlines(data, negpos=True, lw=2)
ax.format(title='Horizontal lines')
# Step
ax = axs[4]
data = state.rand(20, 4).cumsum(axis=1).cumsum(axis=0)
cycle = ('gray6', 'blue7', 'red7', 'gray4')
ax.step(data, cycle=cycle, labels=list('ABCD'), legend='ul', legend_kw={'ncol': 2})
ax.format(title='Step plot')
# Stems
ax = axs[5]
data = state.rand(20)
ax.stem(data)
ax.format(title='Stem plot')

Scatter plots¶
The scatter
command is wrapped by
scatter_extras
, apply_cycle
, and
standardize_1d
. This means that
scatter
now permits omitting x coordinates
and accepts 2D y coordinates, just like plot
.
As with plotx
, the new scatterx
command is used just like scatter
, except a
single argument is interpreted as x coordinates (with default y
coordinates inferred from the data), and multiple arguments are interpreted
as (y, x) pairs. scatter
also now accepts keywords
that look like plot
keywords (e.g., color
instead of c
and
markersize
instead of s
). This way, scatter
can
be used simply to “plot markers, not lines” without changing the
input arguments relative to plot
.
scatter
now uses the property cycler by default, just like
plot
. It can be changed using the cycle
keyword argument,
and it can include properties like marker
and markersize
. The colormap cmap
and normalizer norm
used with the optional c
color array are now passed through
the Colormap
and Norm
constructor
functions, and the the s
marker size array can now be conveniently scaled using
the keywords smin
and smax
(analogous to vmin
and vmax
used for colors).
[6]:
import proplot as pplt
import numpy as np
import pandas as pd
# Sample data
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'))
# Figure
fig, axs = pplt.subplots(ncols=2, nrows=2, refwidth=2.2, share=1, span=False)
axs.format(suptitle='Scatter plot demo')
# Vertical vs. horizontal
ax = axs[0]
ax.set_title('Dependent x-axis')
ax.scatter(data, cycle='538')
ax = axs[1]
ax.set_title('Dependent y-axis')
ax.scatterx(data, cycle='538')
# Scatter plot with property cycler
ax = axs[2]
ax.set_title('With property cycle')
obj = ax.scatter(
x, data, legend='ul', legend_kw={'ncols': 2},
cycle='Set2', cycle_kw={'m': ['x', 'o', 'x', 'o'], 'ms': [5, 10, 20, 30]}
)
# Scatter plot with colormap
ax = axs[3]
ax.set_title('With colormap')
data = state.rand(2, 100)
obj = ax.scatter(
*data,
s=state.rand(100), smin=3, smax=60, marker='o',
c=data.sum(axis=0), cmap='dark red',
colorbar='lr', colorbar_kw={'label': 'label'},
)
axs.format(xlabel='xlabel', ylabel='ylabel')

Bar plots and area plots¶
The bar
and barh
methods
are wrapped by bar_extras
,
apply_cycle
, and standardize_1d
.
This means that bar
and barh
employ
default x or y coordinates if you failed to provide them explicitly.
You can now group or stack columns of data by passing 2D arrays to
bar
or barh
, just like in
pandas. You can also use different colors for “negative” and “positive”
bars by passing negpos=True
(the default colors are rc.negcolor
= 'blue7'
and rc.poscolor
= 'red7'
).
The fill_between
and fill_betweenx
commands are wrapped by fill_between_extras
and
fill_betweenx_extras
. They also have the optional shorthands
area
and areax
. You can now stack
or overlay columns of data by passing 2D arrays to to these commands, just like in
pandas. You can also draw area plots that change color when the fill boundaries
cross each other by passing negpos=True
(the default colors are rc.negcolor
= 'blue7'
and rc.poscolor
= 'red7'
). The most common use case for this is highlighting negative
and positive areas with different colors. Also, the x extent of shading drawn
with fill_between
and the y extent of shading drawn with
fill_betweenx
is now “sticky”, i.e. there is no padding between
the shading and axes edges by default.
[7]:
import proplot as pplt
import numpy as np
import pandas as pd
# Sample data
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')
)
# Figure
pplt.rc.abc = True
pplt.rc.titleloc = 'l'
pplt.rc.abcstyle = 'a.'
fig, axs = pplt.subplots(nrows=2, refaspect=2, refwidth=4.8, share=0, hratios=(3, 2))
# Side-by-side bars
ax = axs[0]
obj = ax.bar(
data, cycle='Reds', edgecolor='red9',
colorbar='ul', colorbar_kw={'frameon': False}
)
ax.format(
xlocator=1, xminorlocator=0.5, ytickminor=False,
title='Side-by-side', suptitle='Bar plot demo'
)
# Stacked bars
ax = axs[1]
obj = ax.barh(
data.iloc[::-1, :], cycle='Blues', edgecolor='blue9',
legend='lr', stack=True,
)
ax.format(title='Stacked')
axs.format(grid=False)

[8]:
import proplot as pplt
import numpy as np
# Sample data
state = np.random.RandomState(51423)
data = state.rand(5, 3).cumsum(axis=0)
cycle = ('gray3', 'gray5', 'gray7')
# Figure
fig, axs = pplt.subplots(ncols=2, refwidth=2.3, share=0)
axs.format(grid=False, xlabel='xlabel', ylabel='ylabel', suptitle='Area plot demo')
# Overlaid area patches
ax = axs[0]
ax.area(
np.arange(5), data, data + state.rand(5)[:, None], cycle=cycle, alpha=0.7,
legend='uc', legend_kw={'center': True, 'ncols': 2, 'labels': ['z', 'y', 'qqqq']},
)
ax.format(title='Fill between columns')
# Stacked area patches
ax = axs[1]
ax.area(
np.arange(5), data, stack=True, cycle=cycle, alpha=0.8,
legend='ul', legend_kw={'center': True, 'ncols': 2, 'labels': ['z', 'y', 'qqqq']},
)
ax.format(title='Stack between columns')

[9]:
import proplot as pplt
import numpy as np
# Sample data
state = np.random.RandomState(51423)
data = 4 * (state.rand(40) - 0.5)
# Figure
fig, axs = pplt.subplots(nrows=2, refaspect=2, figwidth=5)
axs.format(
xmargin=0, xlabel='xlabel', ylabel='ylabel', grid=True,
suptitle='Positive and negative colors demo',
)
axs.axhline(0, color='k', linewidth=1) # zero line
# Bar plot
axs[0].bar(data, width=1, negpos=True)
axs[0].format(title='Bar plot')
# Area plot
axs[1].area(data, negpos=True, lw=0.5, edgecolor='k')
axs[1].format(title='Area plot')
# Reset title styles changed above
pplt.rc.reset()

Shading and error bars¶
The indicate_error
wrapper lets you draw error bars
and error shading on-the-fly by passing certain keyword arguments to
plot
, scatter
,
bar
, plot
,
scatterx
, or barh
.
If you pass 2D arrays to these methods with mean=True
or median=True
,
the means or medians of each column are drawn as points, lines, or bars, and
error bars or shading is drawn to represent the spread of the distribution
for each column. You can also specify the error bounds manually with the
bardata
, boxdata
, shadedata
, and fadedata
keywords.
indicate_error
can draw thin error bars with optional whiskers,
thick “boxes” overlayed on top of these bars (think of these as miniature boxplots),
and up to 2 layers of shading. See indicate_error
for details.
[10]:
import numpy as np
import pandas as pd
# Sample data
# Each column represents a distribution
state = np.random.RandomState(51423)
data = state.rand(20, 8).cumsum(axis=0).cumsum(axis=1)[:, ::-1]
data = data + 20 * state.normal(size=(20, 8)) + 30
data = pd.DataFrame(data, columns=np.arange(0, 16, 2))
data.columns.name = 'column number'
data.name = 'variable'
# Calculate error data
# Passed to 'errdata' in the 3rd subplot example
means = data.mean(axis=0)
means.name = data.name # copy name for formatting
fadedata = np.percentile(data, (5, 95), axis=0) # light shading
shadedata = np.percentile(data, (25, 75), axis=0) # dark shading
[11]:
import proplot as pplt
import numpy as np
# Loop through "vertical" and "horizontal" versions
varray = [[1], [2], [3]]
harray = [[1, 1], [2, 3], [2, 3]]
for orientation, array in zip(('horizontal', 'vertical'), (harray, varray)):
# Figure
fig, axs = pplt.subplots(
array, refaspect=1.5, refwidth=4,
share=0, hratios=(2, 1, 1)
)
axs.format(
abc=True, abcstyle='A.', suptitle=f'Indicating {orientation} error bounds'
)
# Medians and percentile ranges
ax = axs[0]
kw = dict(
color='light red', legend=True,
median=True, barpctile=90, boxpctile=True,
# median=True, barpctile=(5, 95), boxpctile=(25, 75) # equivalent
)
if orientation == 'horizontal':
ax.barh(data, **kw)
else:
ax.bar(data, **kw)
ax.set_title('Bar plot')
# Means and standard deviation range
ax = axs[1]
kw = dict(
color='denim', marker='x', markersize=8**2, linewidth=0.8,
label='mean', shadelabel=True,
mean=True, shadestd=1,
# mean=True, shadestd=(-1, 1) # equivalent
)
if orientation == 'horizontal':
ax.scatterx(data, legend='b', legend_kw={'ncol': 1}, **kw)
else:
ax.scatter(data, legend='ll', **kw)
ax.set_title('Marker plot')
# User-defined error bars
ax = axs[2]
kw = dict(
shadedata=shadedata, fadedata=fadedata,
label='mean', shadelabel='50% CI', fadelabel='90% CI',
color='ocean blue', barzorder=0, boxmarker=False,
)
if orientation == 'horizontal':
ax.plotx(means, legend='b', legend_kw={'ncol': 1}, **kw)
else:
ax.plot(means, legend='ll', **kw)
ax.set_title('Line plot')


Histogram plots¶
ProPlot wraps the hist
command with
standardize_1d
and apply_cycle
(see the 1d plotting section).
It also wraps the hist2d
and
hexbin
commands with
standardize_2d
and apply_cmap
.
In the future, ProPlot may introduce a kdeplot
command analogous to
seaborn.kdeplot
for drawing “smooth” histograms with optional
panels showing the marginal distributions. For now, marginal distributions
for hist2d
plots can be easily plotted
using panel axes.
[12]:
import proplot as pplt
import numpy as np
# Sample data
M, N = 300, 3
state = np.random.RandomState(51423)
x = state.normal(size=(M, N)) + state.rand(M)[:, None] * np.arange(N) + 2 * np.arange(N)
# Sample overlayed histograms
fig, ax = pplt.subplots(refwidth=4, refaspect=(3, 2))
ax.format(suptitle='Overlaid histograms', xlabel='distribution', ylabel='count')
ax.hist(
x, pplt.arange(-3, 8, 0.2), alpha=0.7,
cycle=('blue9', 'gray9', 'orange9'), labels=list('abc'), legend='ul',
)
# Sample data
N = 500
x = state.normal(size=(N,))
y = state.normal(size=(N,))
bins = pplt.arange(-3, 3, 0.25)
# Histogram with marginal distributions
fig, axs = pplt.subplots(ncols=2, refwidth=2.3)
axs.format(
abc=True, abcstyle='A.', titleabove=True, title='Test',
ylabel='y axis', suptitle='Histograms with marginal distributionss'
)
for ax, which, color in zip(axs, 'lr', ('blue9', 'orange9')):
ax.hist2d(
x, y, bins, vmin=0, vmax=10, levels=50,
cmap=color, colorbar='b', colorbar_kw={'label': 'count'}
)
color = pplt.scale_luminance(color, 1.5) # histogram colors
side = ax.panel(which, space=0)
side.hist(y, bins, color=color, vert=False) # or orientation='horizontal'
side.format(grid=False, xlocator=[], xreverse=(which == 'l'))
top = ax.panel('t', space=0)
top.hist(x, bins, color=color)
top.format(grid=False, ylocator=[])


Box plots and violin plots¶
The boxplot
and violinplot
commands are wrapped by boxplot_extras
,
violinplot_extras
, apply_cycle
,
and standardize_1d
. They also now have the optional shorthands
boxes
and violins
. The wrappers
apply aesthetically pleasing default settings and permit configuration using
keyword arguments like color
, boxcolor
, and fillcolor
. They also
automatically apply axis labels based on the DataFrame
or
DataArray
column labels or the input x coordinate labels.
[13]:
import proplot as pplt
import numpy as np
import pandas as pd
# Sample data
N = 500
state = np.random.RandomState(51423)
data1 = state.normal(size=(N, 5)) + 2 * (state.rand(N, 5) - 0.5) * np.arange(5)
data1 = pd.DataFrame(data1, columns=pd.Index(list('abcde'), name='label'))
data2 = state.rand(100, 7)
data2 = pd.DataFrame(data2, columns=pd.Index(list('abcdefg'), name='label'))
# Figure
fig, axs = pplt.subplots([[1, 1, 2, 2], [0, 3, 3, 0]], span=False)
axs.format(
titleloc='l', abc=True, abcstyle='A.', grid=False,
suptitle='Boxes and violins demo')
# Box plots
ax = axs[0]
obj1 = ax.boxplot(
data1, means=True, meancolor='red', marker='x', fillcolor='gray5',
)
ax.format(title='Box plots')
# Violin plots
ax = axs[1]
obj2 = ax.violinplot(
data1, fillcolor='gray7', means=True, points=100,
)
ax.format(title='Violin plots')
# Boxes with different colors
ax = axs[2]
colors = pplt.Colors('pastel2') # list of colors from the cycle
ax.boxplot(data2, fillcolor=colors, orientation='horizontal')
ax.format(title='Multiple colors', ymargin=0.15)

Parametric plots¶
To make “parametric” plots, use the new parametric
command. Parametric plots are LineCollection
s that
map individual line segments to individual colors, where each segment represents a
“parametric” coordinate (e.g., time). The parametric coordinates are specified with
the values
keyword argument. See parametric
for details. As
shown below, it is also easy to build colorbars from the
LineCollection
returned by parametric
.
[14]:
import proplot as pplt
import numpy as np
import pandas as pd
fig, axs = pplt.subplots(
share=0, ncols=2, wratios=(2, 1),
figwidth='16cm', refaspect=(2, 1)
)
axs.format(suptitle='Parametric plots demo')
cmap = 'IceFire'
# Sample data
state = np.random.RandomState(51423)
N = 50
x = (state.rand(N) - 0.52).cumsum()
y = state.rand(N)
c = np.linspace(-N / 2, N / 2, N) # color values
c = pd.Series(c, name='parametric coordinate')
# Parametric line with smooth gradations
ax = axs[0]
m = ax.parametric(
x, y, c, interp=5, capstyle='round', joinstyle='round',
lw=7, cmap=cmap, colorbar='b', colorbar_kw={'locator': 5}
)
ax.format(xlabel='xlabel', ylabel='ylabel', title='Line with smooth gradations')
# Sample data
N = 12
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)
c = np.linspace(-N / 2, N / 2, N + 1)
# Parametric line with stepped gradations
ax = axs[1]
m = ax.parametric(x, y, c, cmap=cmap, lw=15)
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='parametric coordinate')
[14]:
<matplotlib.colorbar.Colorbar at 0x7f3b1fc0b5b0>
