在 Figure 中排列多个 Axes#

通常,希望在一个图中同时显示多个 Axes,通常组织成规则的网格.Matplotlib 有各种各样的工具用于处理 Axes 网格,这些工具在库的历史中不断发展.在这里,我们将讨论用户应该最常使用的工具,这些工具支持 Axes 的组织方式,并提及一些旧的工具.

备注

Matplotlib 使用 Axes 来指代包含数据,x 轴和 y 轴,刻度,标签,标题等的绘图区域.有关更多详细信息,请参阅 Figure 的组成部分 .另一个经常使用的术语是"子图 (subplot)",它指的是与其他 Axes 对象位于网格中的 Axes.

概述#

创建网格形状的 Axes 组合#

subplots

用于创建图形和 Axes 网格的主要函数.它一次性创建并将所有 Axes 放置在图形上,并返回一个对象数组,其中包含网格中 Axes 的句柄.请参阅 Figure.subplots .

subplot_mosaic

一种创建图形和 Axes 网格的简单方法,增加的灵活性是 Axes 也可以跨行或跨列.Axes 在标记的字典中返回,而不是数组.另请参阅 Figure.subplot_mosaic复杂和语义图形合成(subplot_mosaic) .

有时很自然地拥有不止一组不同的 Axes 网格,在这种情况下,Matplotlib 具有 SubFigure 的概念:

SubFigure

图形中的虚拟图形.

底层工具#

支持这些的是 GridSpecSubplotSpec 的概念:

GridSpec

指定放置子图的网格的几何形状.需要设置网格的行数和列数.可以选择调整子图的布局参数(例如,左,右等).

SubplotSpec

指定子图在给定 GridSpec 中的位置.

一次添加单个Axes#

上述函数在一个函数调用中创建所有Axes.也可以一次添加一个Axes,这最初是Matplotlib的工作方式.这样做通常不太优雅和灵活,但有时对于交互式工作或将Axes放置在自定义位置很有用:

add_axes

在图形宽度或高度的分数中,在由 [left, bottom, width, height] 指定的位置添加单个Axes.

subplotFigure.add_subplot

在图形上添加单个子图,使用从1开始的索引(继承自Matlab).可以通过指定网格单元格范围来跨越列和行.

subplot2grid

类似于 pyplot.subplot ,但使用从0开始的索引和二维python切片来选择单元格.

作为一个手动添加Axes ax的简单示例,让我们向一个4英寸x 3英寸的图形添加一个3英寸x 2英寸的Axes.请注意,子图的位置被定义为图形归一化单位中的[left, bottom, width, height]:

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])
arranging axes

用于创建网格的高级方法#

基本2x2网格#

我们可以使用 subplots 创建一个基本的2x2 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()')
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()')
plt.subplot_mosaic()

固定纵横比的Axes网格#

固定纵横比的Axes对于图像或地图来说很常见.然而,它们给布局带来了一个挑战,因为有两组约束被施加在Axes的大小上--它们适合在图形中,并且它们具有设定的纵横比.这导致默认情况下Axes之间存在很大的间隙:

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')
Fixed aspect Axes

解决这个问题的一个方法是改变图形的纵横比,使其接近Axes的纵横比,然而这需要反复试验.Matplotlib还提供了 layout="compressed" ,它将与简单的网格一起工作,以减少Axes之间的间隙.( mpl_toolkits 还提供了 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')
Fixed aspect Axes: compressed

Axes跨越网格中的行或列#

有时我们希望Axes跨越网格的行或列.实际上有很多方法可以做到这一点,但最方便的可能是通过重复其中一个键来使用 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()')
plt.subplot_mosaic()

有关如何使用 GridSpecsubplot2grid 完成相同操作的说明,请参见下文.

网格中的可变宽度或高度#

subplotssubplot_mosaic 都允许网格中的行具有不同的高度,列具有不同的宽度,这可以通过gridspec_kw关键字参数来实现. GridSpec 接受的间距参数可以传递给 subplotssubplot_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()')
plt.subplot_mosaic()

嵌套的Axes布局#

有时拥有两个或多个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]')
arranging axes

也可以使用嵌套列表,使用 subplot_mosaic 嵌套Axes.这种方法不使用子图,就像上面一样,因此无法添加每个子图的 suptitlesupxlabel 等.相反,它是 subgridspec 方法周围的便捷包装器(wrapper),如下所述.

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}]')
arranging axes

底层和高级网格方法#

在内部,Axes 网格的排列是通过创建 GridSpecSubplotSpec 的实例来控制的.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')
Manually added subplots using add_gridspec

跨行或跨列的网格中的 Axes#

我们可以使用 NumPy slice syntax 来索引 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')
Manually added subplots, spanning a column

手动调整 GridSpec 布局#

当显式使用 GridSpec 时,您可以调整从 GridSpec 创建的子图的布局参数.请注意,此选项与约束布局或 Figure.tight_layout 不兼容,因为它们都会忽略左右边距,并调整子图大小以填充图形.通常,这种手动放置需要迭代才能使 Axes 的刻度标签不与 Axes 重叠.

这些间距参数也可以作为 gridspec_kw 参数传递给 subplotssubplot_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')
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')
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()
arranging axes

更多阅读#

参考文献

此示例显示了以下函数,方法,类和模块的用法:

  • matplotlib.pyplot.subplots

  • matplotlib.pyplot.subplot_mosaic

  • matplotlib.figure.Figure.add_gridspec

  • matplotlib.figure.Figure.add_subplot

  • matplotlib.gridspec.GridSpec

  • matplotlib.gridspec.SubplotSpec.subgridspec

  • matplotlib.gridspec.GridSpecFromSubplotSpec

脚本的总运行时间:(0 分 15.625 秒)

Gallery generated by Sphinx-Gallery