Pyleoclim figures with multiple panels#

Authors#

Julien Emile-Geay, USC Earth Sciences

Author1 = {“name”: “Julien Emile-Geay”, “affiliation”: “Department of Earth Sciences, University of Southern California”, “email”: “julieneg@usc.edu”, “orcid”: “0000-0001-5920-4751”}

Preamble#

Goal(s)#

Most plot() commands in pyleoclim are designed to be on their own within a notebook cell for exploratory purposes. However, it may sometimes be desirable to combine such plots into a larger figure. This notebook shows how to do that.

Reading Time: 5 min

Keywords#

Visualization, Customization

Pre-requisites#

This tutorial assumes basic knowledge of Python, particularly Matplotlib. If you are not familiar with it, check out this tutorial.

Relevant Packages#

pyleoclim, matplotlib

Demonstration#

We start by loading a few of our favorite packages and creating 2 incompletely sampled series:

import numpy as np
import pyleoclim as pyleo
import matplotlib.pyplot as plt
ns = 2 ; nt = 500; n_del = int(0.1*nt)
serieslist = []

for j in range(ns):
    t, v = pyleo.utils.gen_ts(nt= nt, model = 'colored_noise',alpha = 1) # generate fractal noise
    deleted_idx = np.random.choice(range(np.size(t)), n_del, replace=False)
    tu =  np.delete(t, deleted_idx)
    vu =  np.delete(v, deleted_idx)
    ts = pyleo.Series(time = tu, value = vu, label = 'series ' + str(j+1))
    serieslist.append(ts)

We then place them into a MultipleSeries object and plot them:

ms = pyleo.MultipleSeries(serieslist)
ms.stackplot()
(<Figure size 640x480 with 3 Axes>,
 {0: <Axes: ylabel='value'>,
  1: <Axes: ylabel='value'>,
  2: <Axes: xlabel='time'>})

Now, we might want to align those two series to perform some comparison (e.g. correlation). The function common_time() is very useful for this purpose, and it can invoke 3 different methods: interpolation, binning, and a Gaussian kernel. Say we want to compare how these three methods perform. An obvious way is to apply the functions and plot the result one by one:

ms.common_time().plot(title='linear interpolation (default)')
ms.common_time(method='bin').plot(title='binning')
ms.common_time(method='gkernel').plot(title=r'Gaussian kernel ($h=3$)')
(<Figure size 1000x400 with 1 Axes>,
 <AxesSubplot: title={'center': 'Gaussian kernel ($h=3$)'}, xlabel='time', ylabel='value'>)
../_images/7ebf51c79f62b4c23ffa0a1541c7e0b1e317e5d8413c568908117bc8b771bf94.png ../_images/a5542158547f54288a6a2589544721426bac028da8a936bc5ae8cb5be051e6dd.png ../_images/3e18a21aee7c24821227080bcd3bf208dd27da5622315e521109b296525b9629.png

These are really 3 different figures stacked on top of each other. The result is relatively pleasant because they have the same default size, but say we wanted it to pack them into a 2 x 2 grid and add a fourth example where we tweaked the bandwidth of the Gaussian kernel.

To do this, we can leverage Matplotlib’s subplots and create a figure and axes object (here called axs):

fig, axs = plt.subplots(2,2,sharex=True,sharey=True, figsize=(12,8))
axs = axs.flatten() # this command turns the 2x2 array of axes into a 1D array, which is easier for indexing
../_images/59b54f33766e11adbb853c504244db6172981c25dfbf66e5b1d6cd8607377aab.png

Now all we have to do is assign each of the panels (0, 1 ,2 ,3) with a plot, using the ax parameter of the plot() function. By default, a new Axes object is created when the function is invoked, but if this parameter is specified, the figure is drawn into the specified Axes object. So all you have to do is pass ax=axs[i-1] for the \(i^{th}\) plot:

from IPython.display import display
# apply common_time with default parameters
msc = ms.common_time()
msc.plot(title='linear interpolation',ax=axs[0], legend=False)

# apply common_time with binning
msc = ms.common_time(method='bin')
msc.plot(title='Binning',ax=axs[1], legend=False)

# apply common_time with gkernel
msc = ms.common_time(method='gkernel')
msc.plot(title=r'Gaussian kernel ($h=3$)',ax=axs[2],legend=False)

# apply common_time with gkernel and a large bandwidth
msc = ms.common_time(method='gkernel', h=.5)
msc.plot(title=r'Gaussian kernel ($h=.5$)',ax=axs[3],legend=False)
fig.tight_layout()
display(fig)
../_images/5d5a151f0e6daf56f0001453244f0db9f5390a5e908210021df4b5316bcada9c.png

Note also that we deactivated the legend with legend=False so as not to overburden the figure with redundant information.

Of course, it would have been easier to do this all in one fellswoop:

fig, axs = plt.subplots(2,2,sharex=True,sharey=True, figsize=(12,8))
axs = axs.flatten()
msc = ms.common_time()
msc.plot(title='linear interpolation',ax=axs[0], legend=False)
msc = ms.common_time(method='bin')
msc.plot(title='Binning',ax=axs[1], legend=False)
msc = ms.common_time(method='gkernel')
msc.plot(title=r'Gaussian kernel ($h=3$)',ax=axs[2],legend=False)
msc = ms.common_time(method='gkernel', h=.5)
msc.plot(title=r'Gaussian kernel ($h=.5$)',ax=axs[3],legend=False)
fig.tight_layout()
../_images/5d5a151f0e6daf56f0001453244f0db9f5390a5e908210021df4b5316bcada9c.png

Finally, note that we set sharex = True and sharey = True, which tells subplots() that your plots will be sharing x and y axes, so only the left-most and bottom axes bear tick labels. This is a nice way to remove chart junk. We could go further and silence some of the labels, too:

fig, axs = plt.subplots(2,2,sharex=True,sharey=True, figsize=(12,8))
axs = axs.flatten()
msc = ms.common_time()
msc.plot(title='linear interpolation',ax=axs[0], legend=False, xlabel='')
msc = ms.common_time(method='bin')
msc.plot(title='Binning',ax=axs[1], legend=False, xlabel='', ylabel='')
msc = ms.common_time(method='gkernel')
msc.plot(title=r'Gaussian kernel ($h=3$)',ax=axs[2],legend=False)
msc = ms.common_time(method='gkernel', h=.5)
msc.plot(title=r'Gaussian kernel ($h=.5$)',ax=axs[3],legend=False, ylabel='')
fig.tight_layout()
../_images/d0f8ba8c7d7234448dd6252ccfe9d7699debcbfb6839f801937a1658b7cc08c8.png

If you wanted uneven panel sizes, then you would have to learn about GridSpec, which is very powerful but has a steeper learning curve. In fact, if you’ve played with summary_plot(), or a dashboard, you have seen GridSpec in action already.

Finally, let’s redo this plot with a different plot style:

with plt.style.context('fivethirtyeight'):
    fig, axs = plt.subplots(2,2,sharex=True,sharey=True, figsize=(12,8))
    axs = axs.flatten()
    msc = ms.common_time()
    msc.plot(title='linear interpolation',ax=axs[0], legend=False, xlabel='')
    msc = ms.common_time(method='bin')
    msc.plot(title='Binning',ax=axs[1], legend=False, xlabel='', ylabel='')
    msc = ms.common_time(method='gkernel')
    msc.plot(title=r'Gaussian kernel ($h=3$)',ax=axs[2],legend=False)
    msc = ms.common_time(method='gkernel', h=.5)
    msc.plot(title=r'Gaussian kernel ($h=.5$)',ax=axs[3],legend=False, ylabel='')
    fig.tight_layout()
../_images/67054ae46d22a40aa05de22596bdfb34c57f46155f735a7012bc5b3b5bf8e854.png

Actually, pyleoclim has its own predefined styles as well, to tailor figures to web view (like this notebook) or journal articles, for instance. Styles are a very handy way to customize many features of a plot at once, without having to manually tinker with them. You can define your own, too!

Takeways#

  • pyleoclim’s plotting engine is built atop Matplotlib. If you want to customize plots, you need to learn a bit about Matplotlib

  • many of pyleoclim’s plots can be placed into existing Matplotlib Axes objects. This enables complex figures with multiple panels.

  • pyleoclim’s plots can be easily customized with style sheets.