In [1]:
name = '2017-05-05-matplotlib-subplots'
title = 'More on subplots in matplotlib'
tags = 'matplotlib'
author = 'Denis Sergeev'
In [2]:
from nb_tools import connect_notebook_to_post
from IPython.core.display import HTML, Image
html = connect_notebook_to_post(name, title, tags, author)
Today we covered a very important part of matplotlib
- how to create multiple subplots in one figure.
We touched upon the following topics:
OK, let's import the essentials:
In [3]:
%matplotlib inline
In [4]:
import numpy as np
import matplotlib.pyplot as plt
Make the figure bigger by default and use non-white facecolor to see the edges:
In [5]:
plt.rcParams['figure.facecolor'] = '0.9'
plt.rcParams['figure.figsize'] = (9, 6)
Axes are very similar to subplots but allow placement of plots at any location in the figure. So if we want to put a smaller plot inside a bigger one we do so with axes.
As an argument, plt.axes()
takes a sequence [Left, Bottom, Width, Height]
In [6]:
txt_kw = dict(ha='left', va='center', size=16, alpha=.5)
In [7]:
plt.axes()
plt.xticks([]), plt.yticks([])
plt.text(0.1, 0.1, 'default axes', **txt_kw)
plt.axes([0.2, 0.7, 0.35, 0.15])
plt.xticks([]), plt.yticks([])
plt.text(0.1, 0.5, 'axes([0.2, 0.7, 0.35, 0.15])', **txt_kw)
plt.axes([0.7, 0.1, 0.4, 0.5])
plt.xticks([]), plt.yticks([])
plt.text(0.1, 0.5, 'axes([0.7, 0.1, 0.4, 0.5])',**txt_kw)
Out[7]:
The simplest way to create a subplot(s) is this:
In [8]:
ax = plt.subplot()
To get a figure explicitly, you need a slightly different function:
In [9]:
fig, ax = plt.subplots()
which is equivalent to the following two lines of code:
In [10]:
fig = plt.figure()
ax = fig.add_subplot(111)
Of course, this function wouldn't be named subplots
if it wasn't used for creating multiple subplots. You can create a grid of axes by using the special keyword arguments:
In [11]:
plt.subplots(ncols=3, nrows=2) # , sharex=True
Out[11]:
Note that it returns a tuple containing a figure
object and a 2x3 numpy
array of axes objects.
In [12]:
from matplotlib.gridspec import GridSpec
For a more complex grid of plots, you can use GridSpec
. A GridSpec
instance provides array-like (2d or 1d) indexing that returns the SubplotSpec
instance.
For a SubplotSpec
that spans multiple cells, use slice.
In [13]:
fig = plt.figure()
gs = GridSpec(3, 3)
print('Type of gs is {}'.format(type(gs)))
ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1,:-1])
ax3 = fig.add_subplot(gs[1:, -1])
ax4 = fig.add_subplot(gs[-1,0])
ax5 = fig.add_subplot(gs[-1,-2])
for ax in fig.axes:
ax.set_xticks([])
ax.set_yticks([])
When a GridSpec
is explicitly used, you can adjust the layout parameters of subplots.
In [14]:
gs1 = GridSpec(3, 3)
gs1.update(left=0.05, right=0.48, wspace=0.05)
ax1 = plt.subplot(gs1[:-1, :])
ax2 = plt.subplot(gs1[-1, :-1])
ax3 = plt.subplot(gs1[-1, -1])
GridSpec
creates cells of equal sizes.
In [15]:
gs = GridSpec(2, 2,
width_ratios=[1, 2],
height_ratios=[4, 1])
ax1 = plt.subplot(gs[0])
ax2 = plt.subplot(gs[1])
ax3 = plt.subplot(gs[2])
ax4 = plt.subplot(gs[3])
Without relying on subplots()
or GridSpec
, you can use axes' get_position
and set_position
methods to get, modify and reset coordinates of the axes manually.
Another useful matplotlib toolbox that allows you to create grids of subplots is AxesGrid
. It is especially convenient if you need colorbars to be placed neatly beside main axes.
In [16]:
from mpl_toolkits.axes_grid1 import AxesGrid
In [17]:
fig = plt.figure()
axgr = AxesGrid(fig, 111,
cbar_mode="edge",
cbar_location="right",
cbar_pad=0.05,
nrows_ncols=(2, 3), axes_pad=0.4)
for ax in axgr.axes_all:
h = ax.pcolormesh(np.random.rand(100,200))
fig.colorbar(h, cax=axgr.cbar_axes[0])
# fig.colorbar(h, cax=axgr.cbar_axes[1])
Out[17]:
By default, AxesGrid
creates a grid of standard matplotlib axes. However, it has an axes_class
keyword that allows you to change the type of axes (all at the same time).
An example of exploiting option can be found on cartopy's website: AxesGrid example.
Yet another way of attaching extra subplots to existing axes is provided by makes_axes_locatable()
function.
Note: does not work with cartopy (?)
In [18]:
from mpl_toolkits.axes_grid1 import make_axes_locatable
In [19]:
fig, ax = plt.subplots()
im = ax.imshow(np.arange(100).reshape((10,10)))
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
plt.colorbar(im, cax=cax)
Out[19]:
An even better use case of this function is provided on the matplotlib website.
AxesGrid toolbox offers you a collection of anchored artists. It’s a collection of matplotlib artists whose location is anchored to the (axes) bounding box, like the legend.
In [20]:
from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
For example, to create a fixed annotation in a corner of the plot we can use the following code:
In [21]:
fig, ax = plt.subplots()
at = AnchoredText('Figure 1a',
loc=2, prop=dict(size=18), frameon=True)
ax.add_artist(at)
Out[21]:
If you are still struggling with subplots, try this little package: https://github.com/ajdawson/panel-plots
A simple to use and understand system for making panel plots with
matplotlib
. Thepanel-plots
package is designed to make it easy to create panel plots with specific sizes, either panel size or overall figure size, making it particularly suitable for generating figures for journal articles where you need to prepare an image of a particular size and ensure consistent and correct font sizing.
There was an extra question from the group, on how to deal with time axis in matplotlib: how to change the tick frequency, the label format, etc.
In [22]:
import datetime
import matplotlib.dates as mdates
In [23]:
x = [datetime.datetime.now() + datetime.timedelta(hours=i)
for i in range(5)]
y = np.random.rand(len(x))
In [24]:
x
Out[24]:
In [25]:
fig, ax = plt.subplots()
ax.plot(x, y)
min_loc = mdates.MinuteLocator(interval=15)
ax.xaxis.set_major_locator(min_loc)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%B %d %H'))
labels = ax.get_xticklabels()
_ = plt.setp(labels, rotation=90, fontsize=8)
In [26]:
HTML(html)
Out[26]: