备注
前往结尾 下载完整示例代码。
在图形中排列多个轴#
通常情况下,一个图形中需要多个 Axes,通常组织成一个规则的网格。Matplotlib 提供了多种工具来处理 Axes 的网格布局,这些工具随着库的发展而不断演进。在这里,我们将讨论我们认为用户最常使用的工具,这些工具是如何组织 Axes 的基础,并提及一些较旧的工具。
备注
Matplotlib 使用 Axes 来指代包含数据、x 轴和 y 轴、刻度、标签、标题等的绘图区域。更多详情请参见 图形的部分。另一个常用的术语是“子图”,它指的是与其他 Axes 对象在网格中的一个 Axes。
概述#
创建网格形状的 Axes 组合#
subplots
用于创建图形和轴网格的主要函数。它一次性创建并放置图形上的所有轴,并返回一个包含网格中轴句柄的对象数组。参见
Figure.subplots
。
或
subplot_mosaic
创建图形和轴网格的一种简单方法,具有额外的灵活性,即轴也可以跨越行或列。轴以带标签的字典形式返回,而不是数组。另请参见
Figure.subplot_mosaic
和 复杂且语义化的图形组合 (subplot_mosaic)。
有时,自然会有多个不同的坐标轴网格组,在这种情况下,Matplotlib 引入了 SubFigure
的概念:
SubFigure
图中的虚拟图形。
底层工具#
这些概念的基础是 GridSpec
和 SubplotSpec
:
GridSpec
指定子图将放置的网格的几何形状。需要设置网格的行数和列数。可选地,可以调整子图布局参数(例如,左、右等)。
SubplotSpec
指定子图在给定
GridSpec
中的位置。
一次添加单个坐标轴#
上述函数通过一次函数调用创建所有 Axes。也可以一次添加一个 Axes,这在 Matplotlib 的早期版本中是常用的方式。这样做通常不那么优雅和灵活,但在交互式工作或需要在自定义位置放置 Axes 时有时很有用:
add_axes
在指定位置
[left, bottom, height, width]
添加一个 Axes,位置以图形宽度和高度的分数表示。subplot
或Figure.add_subplot
在图形上添加一个子图,使用基于1的索引(继承自Matlab)。可以通过指定网格单元范围来跨越列和行。
subplot2grid
类似于
pyplot.subplot
,但使用基于0的索引和二维Python切片来选择单元格。
作为一个手动添加 Axes ax 的简单示例,让我们在一个 4 英寸 x 3 英寸的图形中添加一个 3 英寸 x 2 英寸的 Axes。注意,子图的位置是以图形归一化单位定义的 [左, 底, 宽, 高]:
import matplotlib.pyplot as plt
import numpy as np
w, h = 4, 3
margin = 0.5
fig = plt.figure(figsize=(w, h), facecolor='lightblue')
ax = fig.add_axes([margin / w, margin / h, (w - 2 * margin) / w,
(h - 2 * margin) / h])

用于制作网格的高级方法#
基本 2x2 网格#
我们可以使用 subplots
创建一个基本的 2×2 的 Axes 网格。它返回一个 Figure
实例和一个 Axes
对象的数组。Axes 对象可以用来访问在 Axes 上放置艺术家的方法;这里我们使用 annotate
,但其他例子可能是 plot
、pcolormesh
等。
fig, axs = plt.subplots(ncols=2, nrows=2, figsize=(5.5, 3.5),
layout="constrained")
# add an artist, in this case a nice label in the middle...
for row in range(2):
for col in range(2):
axs[row, col].annotate(f'axs[{row}, {col}]', (0.5, 0.5),
transform=axs[row, col].transAxes,
ha='center', va='center', fontsize=18,
color='darkgrey')
fig.suptitle('plt.subplots()')

我们将标注许多 Axes,因此让我们封装标注,而不是每次需要时都使用那一大段标注代码:
def annotate_axes(ax, text, fontsize=18):
ax.text(0.5, 0.5, text, transform=ax.transAxes,
ha="center", va="center", fontsize=fontsize, color="darkgrey")
同样的效果可以通过 subplot_mosaic
实现,但返回类型是一个字典而不是数组,用户可以为键赋予有意义的含义。这里我们提供了两个列表,每个列表代表一行,列表中的每个元素代表一列的键。
fig, axd = plt.subplot_mosaic([['upper left', 'upper right'],
['lower left', 'lower right']],
figsize=(5.5, 3.5), layout="constrained")
for k, ax in axd.items():
annotate_axes(ax, f'axd[{k!r}]', fontsize=14)
fig.suptitle('plt.subplot_mosaic()')

固定纵横比的轴网格#
固定纵横比的坐标轴在图像或地图中很常见。然而,它们在布局上提出了挑战,因为有两组约束作用于坐标轴的大小——既要适应图形,又要保持设定的纵横比。这通常会导致坐标轴之间出现较大的间隙:
fig, axs = plt.subplots(2, 2, layout="constrained",
figsize=(5.5, 3.5), facecolor='lightblue')
for ax in axs.flat:
ax.set_aspect(1)
fig.suptitle('Fixed aspect Axes')

解决这个问题的一种方法是改变图形的长宽比,使其接近Axes的长宽比,但这需要反复试验。Matplotlib还提供了``layout="compressed"``,它将适用于简单的网格,以减少Axes之间的间隙。(``mpl_toolkits``还提供了`~.mpl_toolkits.axes_grid1.axes_grid.ImageGrid`来实现类似的效果,但使用的是非标准的Axes类)。
fig, axs = plt.subplots(2, 2, layout="compressed", figsize=(5.5, 3.5),
facecolor='lightblue')
for ax in axs.flat:
ax.set_aspect(1)
fig.suptitle('Fixed aspect Axes: compressed')

跨越网格中行或列的轴#
有时我们希望坐标轴跨越网格的行或列。实际上有多种方法可以实现这一点,但最方便的方法可能是通过重复其中一个键来使用 subplot_mosaic
:
fig, axd = plt.subplot_mosaic([['upper left', 'right'],
['lower left', 'right']],
figsize=(5.5, 3.5), layout="constrained")
for k, ax in axd.items():
annotate_axes(ax, f'axd[{k!r}]', fontsize=14)
fig.suptitle('plt.subplot_mosaic()')

请参阅以下内容,了解如何使用 GridSpec
或 subplot2grid
实现相同的功能。
网格中的可变宽度或高度#
subplots
和 subplot_mosaic
允许网格中的行具有不同的高度,列具有不同的宽度,使用 gridspec_kw 关键字参数。GridSpec
接受的间距参数可以传递给 subplots
和 subplot_mosaic
:
gs_kw = dict(width_ratios=[1.4, 1], height_ratios=[1, 2])
fig, axd = plt.subplot_mosaic([['upper left', 'right'],
['lower left', 'right']],
gridspec_kw=gs_kw, figsize=(5.5, 3.5),
layout="constrained")
for k, ax in axd.items():
annotate_axes(ax, f'axd[{k!r}]', fontsize=14)
fig.suptitle('plt.subplot_mosaic()')

嵌套轴布局#
有时,拥有两个或多个可能不需要相互关联的 Axes 网格是有帮助的。实现这一点的最简单方法是使用 Figure.subfigures
。请注意,子图布局是独立的,因此每个子图中的 Axes 脊线不一定对齐。有关使用 GridSpecFromSubplotSpec
实现相同效果的更详细方法,请参见下文。
fig = plt.figure(layout="constrained")
subfigs = fig.subfigures(1, 2, wspace=0.07, width_ratios=[1.5, 1.])
axs0 = subfigs[0].subplots(2, 2)
subfigs[0].set_facecolor('lightblue')
subfigs[0].suptitle('subfigs[0]\nLeft side')
subfigs[0].supxlabel('xlabel for subfigs[0]')
axs1 = subfigs[1].subplots(3, 1)
subfigs[1].suptitle('subfigs[1]')
subfigs[1].supylabel('ylabel for subfigs[1]')

也可以使用嵌套列表通过 subplot_mosaic
来嵌套 Axes。这种方法不像上面那样使用子图,因此缺乏为每个子图添加 suptitle
和 supxlabel
等的能力。相反,它是一个围绕下面描述的 subgridspec
方法的便捷包装。
inner = [['innerA'],
['innerB']]
outer = [['upper left', inner],
['lower left', 'lower right']]
fig, axd = plt.subplot_mosaic(outer, layout="constrained")
for k, ax in axd.items():
annotate_axes(ax, f'axd[{k!r}]')

低级和高级网格方法#
在内部,Axes 网格的排列是通过创建 GridSpec
和 SubplotSpec
的实例来控制的。GridSpec 定义了一个(可能不均匀的)单元格网格。对 GridSpec 进行索引会返回一个覆盖一个或多个网格单元的 SubplotSpec,并可用于指定 Axes 的位置。
以下示例展示了如何使用低级方法通过 GridSpec 对象来排列 Axes。
基本 2x2 网格#
我们可以以与 plt.subplots(2, 2)
相同的方式实现一个 2x2 的网格:
fig = plt.figure(figsize=(5.5, 3.5), layout="constrained")
spec = fig.add_gridspec(ncols=2, nrows=2)
ax0 = fig.add_subplot(spec[0, 0])
annotate_axes(ax0, 'ax0')
ax1 = fig.add_subplot(spec[0, 1])
annotate_axes(ax1, 'ax1')
ax2 = fig.add_subplot(spec[1, 0])
annotate_axes(ax2, 'ax2')
ax3 = fig.add_subplot(spec[1, 1])
annotate_axes(ax3, 'ax3')
fig.suptitle('Manually added subplots using add_gridspec')

跨越行或网格的轴#
我们可以使用 NumPy 切片语法 来索引 spec 数组,新的 Axes 将跨越该切片。这等同于 fig, axd = plt.subplot_mosaic([['ax0', 'ax0'], ['ax1', 'ax2']], ...)
:
fig = plt.figure(figsize=(5.5, 3.5), layout="constrained")
spec = fig.add_gridspec(2, 2)
ax0 = fig.add_subplot(spec[0, :])
annotate_axes(ax0, 'ax0')
ax10 = fig.add_subplot(spec[1, 0])
annotate_axes(ax10, 'ax10')
ax11 = fig.add_subplot(spec[1, 1])
annotate_axes(ax11, 'ax11')
fig.suptitle('Manually added subplots, spanning a column')

对 GridSpec 布局的手动调整#
当显式使用 GridSpec 时,你可以调整从 GridSpec 创建的子图的布局参数。请注意,此选项与 constrained layout 或 Figure.tight_layout
不兼容,因为它们都会忽略 left 和 right,并调整子图大小以填充图形。通常,这种手动放置需要迭代以确保轴的刻度标签不会重叠。
这些间距参数也可以作为 gridspec_kw 参数传递给 subplots
和 subplot_mosaic
。
fig = plt.figure(layout=None, facecolor='lightblue')
gs = fig.add_gridspec(nrows=3, ncols=3, left=0.05, right=0.75,
hspace=0.1, wspace=0.05)
ax0 = fig.add_subplot(gs[:-1, :])
annotate_axes(ax0, 'ax0')
ax1 = fig.add_subplot(gs[-1, :-1])
annotate_axes(ax1, 'ax1')
ax2 = fig.add_subplot(gs[-1, -1])
annotate_axes(ax2, 'ax2')
fig.suptitle('Manual gridspec with right=0.75')

使用 SubplotSpec 的嵌套布局#
你可以使用 subgridspec
创建类似于 subfigures
的嵌套布局。这里,Axes 的脊线 是 对齐的。
注意,这也可以从更详细的 gridspec.GridSpecFromSubplotSpec
中获得。
fig = plt.figure(layout="constrained")
gs0 = fig.add_gridspec(1, 2)
gs00 = gs0[0].subgridspec(2, 2)
gs01 = gs0[1].subgridspec(3, 1)
for a in range(2):
for b in range(2):
ax = fig.add_subplot(gs00[a, b])
annotate_axes(ax, f'axLeft[{a}, {b}]', fontsize=10)
if a == 1 and b == 1:
ax.set_xlabel('xlabel')
for a in range(3):
ax = fig.add_subplot(gs01[a])
annotate_axes(ax, f'axRight[{a}, {b}]')
if a == 2:
ax.set_ylabel('ylabel')
fig.suptitle('nested gridspecs')

这是一个更复杂的嵌套 GridSpec 示例:我们创建一个外部的 4x4 网格,每个单元格包含一个内部的 3x3 Axes 网格。我们通过隐藏每个内部 3x3 网格中的适当脊线来勾勒出外部的 4x4 网格。
def squiggle_xy(a, b, c, d, i=np.arange(0.0, 2*np.pi, 0.05)):
return np.sin(i*a)*np.cos(i*b), np.sin(i*c)*np.cos(i*d)
fig = plt.figure(figsize=(8, 8), layout='constrained')
outer_grid = fig.add_gridspec(4, 4, wspace=0, hspace=0)
for a in range(4):
for b in range(4):
# gridspec inside gridspec
inner_grid = outer_grid[a, b].subgridspec(3, 3, wspace=0, hspace=0)
axs = inner_grid.subplots() # Create all subplots for the inner grid.
for (c, d), ax in np.ndenumerate(axs):
ax.plot(*squiggle_xy(a + 1, b + 1, c + 1, d + 1))
ax.set(xticks=[], yticks=[])
# show only the outside spines
for ax in fig.get_axes():
ss = ax.get_subplotspec()
ax.spines.top.set_visible(ss.is_first_row())
ax.spines.bottom.set_visible(ss.is_last_row())
ax.spines.left.set_visible(ss.is_first_col())
ax.spines.right.set_visible(ss.is_last_col())
plt.show()

更多阅读#
参考文献
以下函数、方法、类和模块的使用在本示例中展示:
脚本的总运行时间: (0 分钟 4.567 秒)