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 features are a strict superset of the matplotlib API. This section documents the features added by wrapper functions to 1D plotting commands like plot, scatter, bar, and barh.

Property cycles

It is often desirable to use different property cycles for different axes or different plot elements. To enable this, the cycle_changer adds the cycle and cycle_kw to the 1D plotting methods. These arguments are passed to the Cycle constructor function, and the resulting property cycle is used to style the input data. ProPlot iterates through property cycle properties when (1) making multiple calls to a plotting command, or (2) plotting successive columns of 2-dimensional input data. For more information on property cycles, see the color cycles section and this matplotlib tutorial.

[1]:
import proplot as plot
import numpy as np
state = np.random.RandomState(51423)
data1 = state.rand(6, 4)
data2 = state.rand(6, 4) * 1.5
with plot.rc.context({'lines.linewidth': 3}):
    fig, axs = plot.subplots(ncols=2)
    axs.format(xlabel='xlabel', ylabel='ylabel', suptitle='Local property cycles demo')

    # Property cycles specific to datasets
    axs[0].plot(data1, cycle='Reds', cycle_kw={'left': 0.3})
    axs[0].plot(data2, cycle='Blues', cycle_kw={'left': 0.3})
    axs[1].plot(
        data1 * data2,
        cycle='black',
        cycle_kw={'linestyle': ('-', '--', '-.', ':')}
    )
/home/docs/checkouts/readthedocs.org/user_builds/proplot/conda/v0.6.2/lib/python3.8/site-packages/proplot/config.py:1454: ProPlotWarning: Rebuilding font cache.
_images/1dplots_2_1.svg

Standardized arguments

The standardize_1d wrapper is used to standardize positional arguments across all 1D plotting methods. standardize_1d allows you to optionally omit 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.

[2]:
import proplot as plot
import numpy as np

# Figure and sample data
N = 5
state = np.random.RandomState(51423)
with plot.rc.context({'axes.prop_cycle': plot.Cycle('Grays', N=N, left=0.3)}):
    fig, axs = plot.subplots(ncols=2, share=False)
    x = np.linspace(-5, 5, N)
    y = state.rand(N, 5)
    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, stacked=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, stacked=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')
_images/1dplots_4_0.svg

Pandas and xarray integration

The standardize_1d wrapper integrates 1D 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.

[3]:
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
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 = 'time series'
df.index.name = 'time (s)'
df.columns.name = 'columns'
[4]:
import proplot as plot
fig, axs = plot.subplots(ncols=2, axwidth=2.2, share=0)
axs.format(suptitle='Automatic subplot formatting')

# Plot DataArray
cycle = plot.Cycle('dark blue', fade=90, space='hpl', N=da.shape[1])
axs[0].scatter(da, cycle=cycle, lw=3, colorbar='ul', colorbar_kw={'locator': 20})

# Plot Dataframe
cycle = plot.Cycle('dark green', fade=90, space='hpl', N=df.shape[1])
axs[1].plot(df, cycle=cycle, lw=3, legend='uc')
[4]:
(<matplotlib.lines.Line2D at 0x7f95c2aa5f10>,
 <matplotlib.lines.Line2D at 0x7f95c2a58340>,
 <matplotlib.lines.Line2D at 0x7f95c2a586d0>,
 <matplotlib.lines.Line2D at 0x7f95c2a58a60>,
 <matplotlib.lines.Line2D at 0x7f95c2a9d5e0>)
_images/1dplots_7_1.svg

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, or barh.

If you pass 2D arrays to these methods with means=True or medians=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 this as a miniature boxplot), and up to 2 regions of shading. See indicate_error for details.

[5]:
import proplot as plot
import numpy as np
import pandas as pd
plot.rc['title.loc'] = 'uc'

# Generate sample data
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.name = 'variable'

# Generate figure
fig, axs = plot.subplots(
    nrows=3, aspect=1.5, axwidth=4,
    share=0, hratios=(2, 1, 1)
)
axs.format(suptitle='Indicating error bounds with various plotting commands')
axs[1:].format(xlabel='column number', xticks=1, xgrid=False)

# Automatically calculate medians and display default percentile range
ax = axs[0]
obj = ax.barh(
    data,
    color='light red', legend=True,
    medians=True, boxpctiles=True, barpctiles=(5, 95),
)
ax.format(title='Column statistics')
ax.format(ylabel='column number', title='Bar plot', ygrid=False)

# Automatically calculate means and display requested standard deviation range
ax = axs[1]
ax.scatter(
    data,
    color='denim', marker='x', markersize=8**2, linewidth=0.8,
    means=True, shadestds=(-1, 1), legend='ll',
)
ax.format(title='Scatter plot')

# Manually supply error bar data and legend labels
ax = axs[2]
means = data.mean(axis=0)
means.name = data.name
shadedata = np.percentile(data, (25, 75), axis=0)  # dark shading
fadedata = np.percentile(data, (5, 95), axis=0)  # light shading
ax.plot(
    means,
    shadedata=shadedata, fadedata=fadedata,
    shadelabel='50% CI', fadelabel='90% CI',
    color='ocean blue', barzorder=0, boxmarker=False, legend='ll',
)
ax.format(title='Line plot')
plot.rc.reset()
_images/1dplots_9_0.svg

Bar plots and area plots

The bar and barh methods are wrapped by bar_wrapper, cycle_changer, and standardize_1d. You can now group or stack columns of data by passing 2D arrays to bar or barh, just like in pandas, or use different colors for negative and positive bars by passing negpos=True. Also, bar and barh now employ “default” x coordinates if you failed to provide them explicitly.

To make filled “area” plots, use the new area and areax methods. These are alises for fill_between and fill_betweenx, which are wrapped by fill_between_wrapper and fill_betweenx_wrapper. You can now stack or overlay columns of data by passing 2D arrays to area and areax, just like in pandas. You can also now draw area plots that change color when the fill boundaries cross each other by passing negpos=True to fill_between. The most common use case for this is highlighting negative and positive areas with different colors, as shown below.

[6]:
import proplot as plot
import numpy as np
import pandas as pd
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')
)

# Generate figure
plot.rc.titleloc = 'uc'
fig, axs = plot.subplots(nrows=2, aspect=2, axwidth=4.8, share=0, hratios=(3, 2))

# 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 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_11_0.svg
[7]:
import proplot as plot
import numpy as np
state = np.random.RandomState(51423)
data = state.rand(5, 3).cumsum(axis=0)
cycle = ('gray3', 'gray5', 'gray7')

# Generate figure
fig, axs = plot.subplots(ncols=2, axwidth=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, 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')
_images/1dplots_12_0.svg
[8]:
import proplot as plot
import numpy as np
state = np.random.RandomState(51423)
data = 4 * (state.rand(50) - 0.5)

# Generate figure
fig, axs = plot.subplots(nrows=2, width=5, aspect=2)
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, edgecolor='none', negpos=True)
axs[0].format(title='Bar plot')

# Area plot
axs[1].area(data, negpos=True)
axs[1].format(title='Area plot')
_images/1dplots_13_0.svg

Box plots and violin plots

The boxplot and violinplot methods are now wrapped with boxplot_wrapper, violinplot_wrapper, cycle_changer, and standardize_1d. These wrappers add some useful options and apply aesthetically pleasing default settings. They also automatically apply axis labels based on the DataFrame column labels or the input x coordinate labels.

[9]:
import proplot as plot
import numpy as np
import pandas as pd

# Generate sample data
N = 500
state = np.random.RandomState(51423)
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')
)

# Generate figure
fig, axs = plot.subplots(ncols=2, axwidth=2.5)
axs.format(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')
_images/1dplots_15_0.svg

Parametric plots

To make “parametric” plots, use the new parametric method. 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.

[10]:
import proplot as plot
import numpy as np
fig, axs = plot.subplots(
    share=0, ncols=2, wratios=(2, 1),
    width='16cm', aspect=(2, 1)
)
axs.format(suptitle='Parametric plots demo')
cmap = 'IceFire'

# Parametric line with smooth gradations
ax = axs[0]
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
m = ax.parametric(
    x, y, c, cmap=cmap, lw=7, interp=5, capstyle='round', joinstyle='round'
)
ax.format(xlabel='xlabel', ylabel='ylabel', title='Line with smooth gradations')
ax.colorbar(m, loc='b', label='parametric coordinate', locator=5)

# Parametric line with stepped gradations
N = 12
ax = axs[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)
c = np.linspace(-N / 2, N / 2, N + 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')
[10]:
<matplotlib.colorbar.Colorbar at 0x7f95c27065b0>
_images/1dplots_17_1.svg

Other plotting methods

The scatter method is now wrapped by scatter_wrapper, cycle_changer, and standardize_1d. This means that 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 scatter now optionally accepts keywords that look like plot keywords (e.g. color instead of c and markersize instead of s).

ProPlot also supports property cycling for step plots and wraps the vlines and hlines methods with vlines_wrapper and hlines_wrapper, which adds the ability to use different colors for “negative” and “positive” lines.

[11]:
import proplot as plot
import numpy as np
import pandas as pd
fig, 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(suptitle='Scatter plot demo', title='Extra prop cycle properties')
obj = ax.scatter(
    x, data, legend='ul', cycle='Set2', 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='dark red', colorbar='lr', vmin=0, vmax=2,
    colorbar_kw={'label': 'label', 'locator': 0.5}
)
axs.format(xlabel='xlabel', ylabel='ylabel')
_images/1dplots_19_0.svg
[12]:
import proplot as plot
import numpy as np
state = np.random.RandomState(51423)
fig, axs = plot.subplots(ncols=2, nrows=2, share=0)
axs.format(suptitle='Line plots demo', xlabel='xlabel', ylabel='ylabel')

# Step
ax = axs[0]
data = state.rand(20, 4).cumsum(axis=1).cumsum(axis=0)
cycle = ('blue7', 'gray5', 'red7', 'gray5')
ax.step(data, cycle=cycle, labels=list('ABCD'), legend='ul', legend_kw={'ncol': 2})
ax.format(title='Step plot')

# Stems
ax = axs[1]
data = state.rand(20)
ax.stem(data, linefmt='k-')
ax.format(title='Stem plot')

# 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, linewidth=2)
ax.format(title='Vertical lines')

# Horizontal lines
ax = axs[3]
ax.areax(data, color=gray, alpha=0.2)
ax.hlines(data, negpos=True, linewidth=2)
ax.format(title='Horizontal lines')
_images/1dplots_20_0.svg