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
cycle = plot.Cycle(plot.shade('light blue', 0.4), fade=90, space='hpl')
axs[0].plot(da, cycle=cycle, lw=3, colorbar='ul', colorbar_kw={'locator': 20})
# Plot Dataframe
cycle = plot.Cycle(plot.shade('jade', 0.4), fade=90, space='hpl')
axs[1].plot(df, cycle=cycle, lw=3, legend='uc')
/home/docs/checkouts/readthedocs.org/user_builds/proplot/conda/v0.3.0/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 0x7fb4cbb97400>,
 <matplotlib.lines.Line2D at 0x7fb4cbb52d68>,
 <matplotlib.lines.Line2D at 0x7fb4cbb52d30>,
 <matplotlib.lines.Line2D at 0x7fb4cbb16588>,
 <matplotlib.lines.Line2D at 0x7fb4cbb16828>)
_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')
# Asking add_errorbars to calculate bars
ax = axs[0]
obj = ax.barh(data, color='red orange', means=True)
ax.format(title='Column statistics')
# 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)
# 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)
# Formatting
axs[0].format(ylabel='column number', title='Bar plot', ygrid=False)
axs[1].format(title='Scatter plot')
axs[2].format(title='Line plot')
axs[1:].format(xlabel='column number', xticks=1, xgrid=False)
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'))
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')
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')
# 2D arrays
ax = axs[0]
ax.areax(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 colors
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'))
ax = axs[0]
# , boxprops={'color':'C0'})#, labels=data.columns)
obj1 = ax.boxplot(data, lw=0.7, marker='x', fillcolor='gray5',
                  medianlw=1, mediancolor='k')
ax.format(title='Box plots', titleloc='uc')
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')
axs.format(ymargin=0.1, xmargin=0.1, grid=False,
           suptitle='Boxes and violins demo')
/home/docs/checkouts/readthedocs.org/user_builds/proplot/conda/v0.3.0/lib/python3.7/site-packages/proplot/utils.py:102: ProPlotWarning: Got conflicting or duplicate keyword args, using the first one: {'lw': 0.7, 'linewidth': 0.7}
_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='6cm', aspect=(2, 1))
axs.format(suptitle='Parametric plots demo')
# 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)
# Step 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 0x7fb4c8930390>
_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 demo
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]})
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