# 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 DataFrames and xarray DataArrays. 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()


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

[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,
# 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(
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 LineCollections 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>