在图中排列多个坐标轴#

通常,一个图形上需要多个坐标轴,通常组织成规则的网格。Matplotlib 有多种用于处理坐标轴网格的工具,这些工具随着库的历史而发展。在这里,我们将讨论我们认为用户最应该使用的工具、坐标轴组织方式的基础工具,并提及一些较旧的工具。

注意

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

概述#

创建网格形状的坐标轴组合#

subplots

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

subplot_mosaic

创建图形和坐标轴网格的简单方法,并且增加了坐标轴也可以跨越行或列的灵活性。坐标轴以带标签的字典而不是数组的形式返回。另请参阅 Figure.subplot_mosaic复杂且语义化的图形组合 (subplot_mosaic)

有时,自然会有多个不同的坐标轴网格组,在这种情况下,Matplotlib 具有 SubFigure 的概念

SubFigure

图形内的虚拟图形。

底层工具#

这些工具的底层是 GridSpecSubplotSpec 的概念

GridSpec

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

SubplotSpec

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

一次添加单个坐标轴#

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

add_axes

在由 [左, 下, 宽, 高] 指定的位置添加单个坐标轴,以图形宽度或高度的分数表示。

subplotFigure.add_subplot

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

subplot2grid

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

作为手动添加坐标轴 ax 的一个简单示例,让我们向一个 4 英寸 x 3 英寸的图形添加一个 3 英寸 x 2 英寸的坐标轴。请注意,子图的位置定义为 [左,下,宽,高],以图形归一化单位表示

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 坐标轴网格。它返回一个 Figure 实例和一个 Axes 对象数组。可以使用 Axes 对象访问在 Axes 上放置艺术家的各个方法;这里我们使用 annotate,但其他示例可以是 plotpcolormesh 等。

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()

我们将注释很多坐标轴,因此让我们封装注释,而不是每次需要时都使用大量注释代码

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()

固定纵横比坐标轴的网格#

固定纵横比坐标轴对于图像或地图很常见。但是,它们对布局提出了挑战,因为坐标轴的大小受到两组约束的限制 - 它们适合图形,并且具有设置的纵横比。这导致默认情况下坐标轴之间存在较大的间隙

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

解决此问题的一种方法是将图形的纵横比更改为接近坐标轴的纵横比,但这需要反复试验。Matplotlib 还提供了 layout="compressed",它将与简单的网格一起使用,以减少坐标轴之间的间隙。(mpl_toolkits 还提供了 ImageGrid 来实现类似的效果,但使用非标准的坐标轴类)。

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)跨越网格的行或列。实际上有多种方法可以实现这一点,但最方便的方法可能是通过重复键来使用 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()

嵌套的轴布局#

有时,拥有两个或多个可能不需要相互关联的轴网格会很有帮助。实现此目的最简单的方法是使用 Figure.subfigures。请注意,子图布局是独立的,因此每个子图中的轴脊线不一定对齐。请参阅下文,了解如何使用 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 来嵌套轴。此方法不像上面那样使用子图,因此无法为每个子图添加 suptitlesupxlabel 等。相反,它是对下面描述的 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}]')
arranging axes

底层和高级网格方法#

在内部,轴网格的排列是通过创建 GridSpecSubplotSpec 的实例来控制的。 *GridSpec* 定义一个(可能不均匀的)单元格网格。索引 *GridSpec* 会返回一个涵盖一个或多个网格单元的 SubplotSpec,可用于指定轴的位置。

以下示例展示了如何使用底层方法通过 *GridSpec* 对象排列轴。

基本的 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

网格中跨越行或网格的轴#

我们可以使用 NumPy 切片语法 索引 spec 数组,新轴将跨越该切片。这与 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* 创建的子图的布局参数。请注意,此选项与 constrained layoutFigure.tight_layout 不兼容,它们都会忽略 leftright 并调整子图的大小以填充图形。通常,这种手动放置需要多次迭代才能使轴刻度标签不与轴重叠。

这些间距参数也可以作为 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 的嵌套布局。这里的轴脊线是*对齐的*。

请注意,也可以从更详细的 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 的内部轴网格。我们通过隐藏每个 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

更多阅读#

  • 有关 子图镶嵌 的更多详细信息。

  • 有关 约束布局 的更多详细信息,该布局用于对齐这些示例中的大多数间距。

脚本总运行时间: (0 分钟 15.904 秒)

由 Sphinx-Gallery 生成的图库