Matplotlib进阶教程:布局讲解

466b95bffb45822634c8e15e2e0e5517.gif

在后台回复【阅读书籍】

即可获取python相关电子书~

Hi,我是山月。

今天来给大家介绍下Matplotlib的布局部分~

01

自定义图形布局

可以创建axes的网格状组合的方法:

1)subplots()

用于创建图形和axes的主要函数。它类似于 matplotlib.pyplot.subplot(),但会同时创建并放置图形上的所有axes。

2)GridSpec

指定将放置子图的网格几何形状。要设置网格的行数和列数,也可调整子图的布局参数(如left、right等)。

3)SubplotSpec

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

4)subplot2grid()

是与 subplot() 类似的辅助函数,但使用从 0 开始的索引并让 subplot 占据多个单元格。

1、入门指南

使用 subplots() 创建一个 2×2 网格非常简单,它会返回一个 Figure 实例和一个 Axes 对象数组。

import matplotlib.pyplot as plt

fig1, f1_axes = plt.subplots(ncols=2, nrows=2, constrained_layout=True)

plt.show()

效果:

582ef2d5ac3ddac75b682ef6a0fdb30d.png

使用gridspec 实现相同的效果会过于冗长:

先分别创建figure和GridSpec实例,然后将 gridspec 实例的元素传递给 add_subplot() 方法以创建轴对象。

gridspec元素的访问方式与 numpy 数组相同。

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

fig2 = plt.figure(constrained_layout=True)
spec2 = gridspec.GridSpec(ncols=2, nrows=2, figure=fig2)
f2_ax1 = fig2.add_subplot(spec2[0, 0])
f2_ax2 = fig2.add_subplot(spec2[0, 1])
f2_ax3 = fig2.add_subplot(spec2[1, 0])
f2_ax4 = fig2.add_subplot(spec2[1, 1])

plt.show()

gridspec 的强大之处在于能够创建跨行和列的子图。注意用于选择每个子图将占据的gridspec部分的 Numpy 切片语法。

我们也可以使用 Figure.add_gridspec方法创建不同宽度的子图:

import matplotlib.pyplot as plt

fig3 = plt.figure(constrained_layout=True)
gs = fig3.add_gridspec(3, 3)
f3_ax1 = fig3.add_subplot(gs[0, :])
f3_ax1.set_title('gs[0, :]')
f3_ax2 = fig3.add_subplot(gs[1, :-1])
f3_ax2.set_title('gs[1, :-1]')
f3_ax3 = fig3.add_subplot(gs[1:, -1])
f3_ax3.set_title('gs[1:, -1]')
f3_ax4 = fig3.add_subplot(gs[-1, 0])
f3_ax4.set_title('gs[-1, 0]')
f3_ax5 = fig3.add_subplot(gs[-1, -2])
f3_ax5.set_title('gs[-1, -2]')

plt.show()

效果:

30b8bf00ec626614a2f8ebbb0e524d5f.png

这个方法与上面的方法类似,先初始化一个统一的gridspec,然后使用 numpy 索引和切片为给定的子图分配多个“单元格”。

import matplotlib.pyplot as plt

fig4 = plt.figure(constrained_layout=True)
spec4 = fig4.add_gridspec(ncols=2, nrows=2)
anno_opts = dict(xy=(0.5, 0.5), xycoords='axes fraction',
                 va='center', ha='center')

f4_ax1 = fig4.add_subplot(spec4[0, 0])
f4_ax1.annotate('GridSpec[0, 0]', **anno_opts)
fig4.add_subplot(spec4[0, 1]).annotate('GridSpec[0, 1:]', **anno_opts)
fig4.add_subplot(spec4[1, 0]).annotate('GridSpec[1:, 0]', **anno_opts)
fig4.add_subplot(spec4[1, 1]).annotate('GridSpec[1:, 1:]', **anno_opts)

plt.show()

效果:

066be6c652f4f4623011d39170e85242.png

另一种方法是使用 width_ratios 和 height_ratios 参数,这些关键字参数是数字列表。

请注意,绝对值是没有意义的,只有它们的相对比率很重要。

这意味着在同样宽的图中width_ratios=[2, 4, 8] 等效于 width_ratios=[1, 2, 4]。

import matplotlib.pyplot as plt

fig5 = plt.figure(constrained_layout=True)
widths = [2, 3, 1.5]
heights = [1, 3, 2]
spec5 = fig5.add_gridspec(ncols=3, nrows=3, width_ratios=widths,
                          height_ratios=heights)
for row in range(3):
    for col in range(3):
        ax = fig5.add_subplot(spec5[row, col])
        label = 'Width: {}\nHeight: {}'.format(widths[col], heights[row])
        ax.annotate(label, (0.1, 0.5), xycoords='axes fraction', va='center')

plt.show()

效果:

70023c14288fecaae9d3ad9789101892.png

学习使用 width_ratios 和 height_ratios 特别有用,因为顶级函数 subplots() 在 gridspec_kw 参数中接受它们。

就此而言,GridSpec 接受的任何参数都可以通过 gridspec_kw 参数传递给 subplots()。

在不直接使用 gridspec 实例的情况下重新创建上一个图:

import matplotlib.pyplot as plt

widths = [2, 3, 1.5]
heights = [1, 3, 2]

gs_kw = dict(width_ratios=widths, height_ratios=heights)
fig6, f6_axes = plt.subplots(ncols=3, nrows=3, constrained_layout=True,
                             gridspec_kw=gs_kw)
for r, row in enumerate(f6_axes):
    for c, ax in enumerate(row):
        label = 'Width: {}\nHeight: {}'.format(widths[c], heights[r])
        ax.annotate(label, (0.1, 0.5), xycoords='axes fraction', va='center')

plt.show()

subplots 和 get_gridspec 方法可以组合使用,因为有时使用 subplots 制作子图更方便。

import matplotlib.pyplot as plt

fig7, f7_axs = plt.subplots(ncols=3, nrows=3)
gs = f7_axs[1, 2].get_gridspec()
# 移除下面的轴
for ax in f7_axs[1:, -1]:
    ax.remove()
axbig = fig7.add_subplot(gs[1:, -1])
axbig.annotate('Big Axes \nGridSpec[1:, -1]', (0.1, 0.5),
               xycoords='axes fraction', va='center')

fig7.tight_layout()

plt.show()

效果:

55d1c82d1a13a75712a5d62bd98c0c62.png

2、微调Gridspec 布局

当使用 GridSpec 时,你可以调整用 GridSpec 创建的子图的布局参数。

请注意,此选项与 constrained_layout 或 Figure.tight_layout 不兼容。

import matplotlib.pyplot as plt

fig8 = plt.figure(constrained_layout=False)
gs1 = fig8.add_gridspec(nrows=3, ncols=3, left=0.05, right=0.48, wspace=0.05)
f8_ax1 = fig8.add_subplot(gs1[:-1, :])
f8_ax2 = fig8.add_subplot(gs1[-1, :-1])
f8_ax3 = fig8.add_subplot(gs1[-1, -1])

plt.show()

效果:

96deb7b6f4fdd5d549720135a2baa58d.png

这类似于 subplots_adjust(),但它只影响从给定的 GridSpec 创建的子图。

import matplotlib.pyplot as plt

fig9 = plt.figure(constrained_layout=False)
gs1 = fig9.add_gridspec(nrows=3, ncols=3, left=0.05, right=0.48,
                        wspace=0.05)
f9_ax1 = fig9.add_subplot(gs1[:-1, :])
f9_ax2 = fig9.add_subplot(gs1[-1, :-1])
f9_ax3 = fig9.add_subplot(gs1[-1, -1])

gs2 = fig9.add_gridspec(nrows=3, ncols=3, left=0.55, right=0.98,
                        hspace=0.05)
f9_ax4 = fig9.add_subplot(gs2[:, :-1])
f9_ax5 = fig9.add_subplot(gs2[:-1, -1])
f9_ax6 = fig9.add_subplot(gs2[-1, -1])

plt.show()

效果:

f06020201f4a2fe6ba8eed2b837551b3.png

3、SubplotSpec

你可以从 SubplotSpec 创建 GridSpec,在这种情况下,其布局参数设置为给定的 SubplotSpec 的位置。

import matplotlib.pyplot as plt

fig10 = plt.figure(constrained_layout=True)
gs0 = fig10.add_gridspec(1, 2)

gs00 = gs0[0].subgridspec(2, 3)
gs01 = gs0[1].subgridspec(3, 2)

for a in range(2):
    for b in range(3):
        fig10.add_subplot(gs00[a, b])
        fig10.add_subplot(gs01[b, a])
        
plt.show()

效果:

22b5b495bde2f701344973e1d348a4c6.png

一个更复杂的嵌套 GridSpec 示例:

import matplotlib.pyplot as plt
import numpy as np
from itertools import product

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)

fig11 = plt.figure(figsize=(8, 8), constrained_layout=False)

# gridspec内部的gridspec
outer_grid = fig11.add_gridspec(4, 4, wspace=0.0, hspace=0.0)

for i in range(16):
    inner_grid = outer_grid[i].subgridspec(3, 3, wspace=0.0, hspace=0.0)
    a, b = int(i/4)+1, i % 4+1
    for j, (c, d) in enumerate(product(range(1, 4), repeat=2)):
        ax = fig11.add_subplot(inner_grid[j])
        ax.plot(*squiggle_xy(a, b, c, d))
        ax.set_xticks([])
        ax.set_yticks([])
        fig11.add_subplot(ax)

all_axes = fig11.get_axes()

# 只露出外面的边框线
for ax in all_axes:
    for sp in ax.spines.values():
        sp.set_visible(False)
    if ax.get_subplotspec().is_first_row():
        ax.spines['top'].set_visible(True)
    if ax.get_subplotspec().is_last_row():
        ax.spines['bottom'].set_visible(True)
    if ax.get_subplotspec().is_first_col():
        ax.spines['left'].set_visible(True)
    if ax.get_subplotspec().is_last_col():
        ax.spines['right'].set_visible(True)

plt.show()

效果:

177112b552d5ccbe2320961d683e788c.png

02

约束布局

constrained_layout 可以自动调整子图和装饰(如图例和颜色条),以便它们适合图形窗口,同时仍尽可能保留用户请求的逻辑布局。

在将axes添加到图形之前,需要激活 constrained_layout,有两种方法激活:

1)使用 subplots() 或 figure() 的相应参数:

plt.subplots(constrained_layout=True)

2)通过 rcParams 激活:

plt.rcParams['figure.constrained_layout.use'] = True

注意事项 :

  • constrained_layout 只考虑刻度标签、轴标签、标题和图例。因此,其他artists可能被剪裁,也可能重叠。

  • 它假定刻度标签、轴标签和标题所需的额外空间与轴的原始位置无关。这通常是正确的,但在极少数情况下并非如此。

  • 后端处理渲染字体的方式存在细微差别,因此结果不会是完全相同的。

  • artists添加超出axes 边界的坐标轴时会产生不一样的布局,这可以通过使用 add_artist() 将artists直接添加到图形中来避免。

1、简单示例

在 Matplotlib 中,axes(包括子图)的位置在标准化图形坐标中指定。

但是轴标签或标题(有时甚至是刻度标签)可能会超出图形区域,从而被剪裁。

import matplotlib.pyplot as plt

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.

def example_plot(ax, fontsize=12, nodec=False):
    ax.plot([1, 2])
    ax.locator_params(nbins=3)
    if not nodec:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)
    else:
        ax.set_xticklabels('')
        ax.set_yticklabels('')

fig, ax = plt.subplots(constrained_layout=False)
example_plot(ax, fontsize=24)

plt.show()

效果:

116bf2f6e8c6fa30d13d0d02f1a1f985.png

为了防止这种情况,可以使用 constrained_layout=True 来自动调整图形。

fig, ax = plt.subplots(constrained_layout=True)
example_plot(ax, fontsize=24)

效果:

d543da34a7554ebb1079861454794b6a.png

当你有多个子图时,你可能会看到不同轴的标签相互重叠。

import matplotlib.pyplot as plt

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.

def example_plot(ax, fontsize=12, nodec=False):
    ax.plot([1, 2])
    ax.locator_params(nbins=3)
    if not nodec:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)
    else:
        ax.set_xticklabels('')
        ax.set_yticklabels('')

fig, axs = plt.subplots(2, 2, constrained_layout=False)
for ax in axs.flat:
    example_plot(ax)

plt.show()

效果:

10db80362634e36a041558b0903fd749.png

在对plt.subplots的调用中指定 constrained_layout=True 会导致布局受到适当的约束。

fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
    example_plot(ax)

效果:

cdd7a300ae5cb9469f8fc45055204ed4.png

2、颜色条

如果你要使用 colorbar() 命令创建颜色条,则需要为其腾出空间,可以通过constrained_layout 自动执行此操作。

注意,如果你指定 use_gridspec=True,它将被忽略,因为此选项用于通过紧凑布局。

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

arr = np.arange(100).reshape((10, 10))
norm = mcolors.Normalize(vmin=0., vmax=100.)
# 如上所述:这使得所有pcolormesh调用一致:
pc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}
fig, ax = plt.subplots(figsize=(4, 4), constrained_layout=True)
im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=ax, shrink=0.6)

plt.show()

效果:

33eb69f65d40db9ab7b7ad515a569450.png

如果你为颜色条的 ax 参数指定axes列表(或其他可迭代容器),则 constrained_layout 将占用指定axes的空间。

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

arr = np.arange(100).reshape((10, 10))
norm = mcolors.Normalize(vmin=0., vmax=100.)
pc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}

fig, axs = plt.subplots(2, 2, figsize=(4, 4), constrained_layout=True)
for ax in axs.flat:
    im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs, shrink=0.6)

plt.show()

效果:

bce8b500fe785a2b2a3701b71b1b975d.png

如果你从axes网格中指定axes列表,颜色条将适当地占用空间并留下间隙,但所有子图仍将具有相同的大小。

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

arr = np.arange(100).reshape((10, 10))
norm = mcolors.Normalize(vmin=0., vmax=100.)
pc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}

fig, axs = plt.subplots(3, 3, figsize=(4, 4), constrained_layout=True)
for ax in axs.flat:
    im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs[1:, ][:, 1], shrink=0.8)
fig.colorbar(im, ax=axs[:, -1], shrink=0.6)

plt.show()

效果:

436253df2e478d42967f85ec282485bf.png

颜色条不会如理想中一样是对齐的,因为与底部轴配对的颜色条被绑定到轴的subplotspec 。

因此当gridspec级别的颜色条被添加时,颜色条会收缩。

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

arr = np.arange(100).reshape((10, 10))
norm = mcolors.Normalize(vmin=0., vmax=100.)
pc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}

fig, axs = plt.subplots(3, 1, figsize=(4, 4), constrained_layout=True)
for ax in axs[:2]:
    im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs[:2], shrink=0.6)
im = axs[2].pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs[2], shrink=0.6)

plt.show()

效果:

19d57767a9b42aea35e9583cb37bdfb0.png

3、标题

constrained_layout 也可以为标题腾出空间。

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

arr = np.arange(100).reshape((10, 10))
norm = mcolors.Normalize(vmin=0., vmax=100.)
pc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}

fig, axs = plt.subplots(2, 2, figsize=(4, 4), constrained_layout=True)
for ax in axs.flat:
    im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs, shrink=0.6)
fig.suptitle('Big Suptitle')

plt.show()

效果:

a14c810b26942a7f8d28bf21a0bc9e28.png

4、图例

图例可以放置在其父轴之外。

约束布局可以为 Axes.legend() 创建的图例处理此问题,但不处理通过 Figure.legend() 创建的图例。

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(constrained_layout=True)
ax.plot(np.arange(10), label='This is a plot')
ax.legend(loc='center left', bbox_to_anchor=(0.8, 0.5))

plt.show()

效果:

7a0bd73ace9fba474f6e1d9e124e5284.png

其实这将从子图布局中占取空间:

import matplotlib.pyplot as plt
import numpy as np

fig, axs = plt.subplots(1, 2, figsize=(4, 2), constrained_layout=True)
axs[0].plot(np.arange(10))
axs[1].plot(np.arange(10), label='This is a plot')
axs[1].legend(loc='center left', bbox_to_anchor=(0.8, 0.5))

plt.show()

效果:

9f8de39948d45bffb5595ef94ed8fa7e.png

为了不让图例或其他artist 从子图布局中占取空间,我们可以使用leg.set_in_layout(False)。

当然,这意味着图例可能会被裁剪,但可以通过调用 fig.savefig('outname.png', bbox_inches='tight') 来保存完整的图。

注意,必须再次切换图例的 get_in_layout 状态才能使保存的文件正常工作。

并且如果我们希望在打印之前调整axes的大小,必须手动触发constrained_layout。

import matplotlib.pyplot as plt
import numpy as np

fig, axs = plt.subplots(1, 2, figsize=(4, 2), constrained_layout=True)
axs[0].plot(np.arange(10))
axs[1].plot(np.arange(10), label='This is a plot')
leg = axs[1].legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
leg.set_in_layout(False)
fig.canvas.draw()
leg.set_in_layout(True)
fig.set_constrained_layout(False)
fig.savefig('CL01.png', bbox_inches='tight', dpi=100)

plt.show()

效果:

6368b38950a38b3d4c80ade29d6388d6.png

保存的文件如下所示:

dae6c8812a4c87ee6b460e64aa91802c.png

其实更好的方法是使用 Figure.legend 提供的 legend 方法:

import matplotlib.pyplot as plt
import numpy as np

fig, axs = plt.subplots(1, 2, figsize=(4, 2), constrained_layout=True)
axs[0].plot(np.arange(10))
lines = axs[1].plot(np.arange(10), label='This is a plot')
labels = [l.get_label() for l in lines]
leg = fig.legend(lines, labels, loc='center left',
                 bbox_to_anchor=(0.8, 0.5), bbox_transform=axs[1].transAxes)
fig.savefig('CL02.png', bbox_inches='tight', dpi=100)

plt.show()

5、填充和间距

对于 constrained_layout,我们在每个轴的边缘会设置一个填充。

此填充设置了距绘图边缘的距离,以及相邻绘图之间的最小距离。

它由函数 set_constrained_layout_pads 的关键字参数 w_pad 和 h_pad 以英寸为单位指定:

import matplotlib.pyplot as plt

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.

def example_plot(ax, fontsize=12, nodec=False):
    ax.plot([1, 2])
    ax.locator_params(nbins=3)
    if not nodec:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)
    else:
        ax.set_xticklabels('')
        ax.set_yticklabels('')

fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
    example_plot(ax, nodec=True)
    ax.set_xticklabels('')
    ax.set_yticklabels('')
fig.set_constrained_layout_pads(w_pad=4./72., h_pad=4./72.,
        hspace=0., wspace=0.)
fig.suptitle('4.72')

fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
    example_plot(ax, nodec=True)
    ax.set_xticklabels('')
    ax.set_yticklabels('')
fig.set_constrained_layout_pads(w_pad=2./72., h_pad=2./72.,
        hspace=0., wspace=0.)
fig.suptitle('2.72')

plt.show()

效果:

971707d8cde53ab3c2ec77d7b51f168f.png

子图之间的间距由 wspace 和 hspace 设置,指定为子图组整体大小的一部分。

如果图形的大小发生变化,那么这些空间也会按比例变化。

fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
    example_plot(ax, nodec=True)
    ax.set_xticklabels('')
    ax.set_yticklabels('')
fig.set_constrained_layout_pads(w_pad=2./72., h_pad=2./72.,
        hspace=0.2, wspace=0.2)

效果:

fba8af50fb2167504d2c5c62078ad7a9.png

颜色条和其他子图的wspace、hsapce是分开放置的。

颜色条和它所连接的轴之间的填充永远不会小于 w_pad (对于垂直颜色条)或 h_pad (对于水平颜色条)。

请注意在 colorbar 调用中参数pad 默认的大小为0.02

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

arr = np.arange(100).reshape((10, 10))
norm = mcolors.Normalize(vmin=0., vmax=100.)
pc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}

fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
    pc = ax.pcolormesh(arr, **pc_kwargs)
    fig.colorbar(pc, ax=ax, shrink=0.6, pad=0)
    ax.set_xticklabels('')
    ax.set_yticklabels('')
fig.set_constrained_layout_pads(w_pad=2./72., h_pad=2./72.,
        hspace=0.2, wspace=0.2)

plt.show()

效果:

ecfb49f20fefbce6b0ea8b9d57b7449f.png

在上面的示例中,颜色条与绘图的距离永远不会超过 2 pts,但如果我们希望它离图更远一点,我们可以将它的 pad 值指定为非零。

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

arr = np.arange(100).reshape((10, 10))
norm = mcolors.Normalize(vmin=0., vmax=100.)
pc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}

fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
    pc = ax.pcolormesh(arr, **pc_kwargs)
    fig.colorbar(pc, ax=ax, shrink=0.6, pad=0.05)
    ax.set_xticklabels('')
    ax.set_yticklabels('')
fig.set_constrained_layout_pads(w_pad=2./72., h_pad=2./72.,
        hspace=0.2, wspace=0.2)

plt.show()

效果:

6e629309aea3c8c0898d24d7bf4696e4.png

6、参数

可以在脚本或 matplotlibrc 文件中设置五个参数,它们都有前缀 figure.constrained_layout:

  • use:是否使用 constrained_layout,默认为False。

  • w_pad、h_pad:围绕axes对象填充。值为表示英寸的浮点数,默认值为 3./72. 英寸(3 pts)

  • wspace,hspace:子图之间的空间。是一个浮点数表示被分隔的子图宽度的一小部分,默认值为 0.02。

import matplotlib.pyplot as plt

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.

def example_plot(ax, fontsize=12, nodec=False):
    ax.plot([1, 2])

    ax.locator_params(nbins=3)
    if not nodec:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)
    else:
        ax.set_xticklabels('')
        ax.set_yticklabels('')

plt.rcParams['figure.constrained_layout.use'] = True
fig, axs = plt.subplots(2, 2, figsize=(3, 3))
for ax in axs.flat:
    example_plot(ax)
    
plt.show()

效果:

ffa2fb02c9e49a8d72005f9ad6049a8b.png

7、GridSpec方法

除subplots() 之外,constrained_layout还可适用于GridSpec() 、 add_subplot() 方法。

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.

def example_plot(ax, fontsize=12, nodec=False):
    ax.plot([1, 2])
    ax.locator_params(nbins=3)
    if not nodec:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)
    else:
        ax.set_xticklabels('')
        ax.set_yticklabels('')
plt.rcParams['figure.constrained_layout.use'] = True

fig = plt.figure()

gs1 = gridspec.GridSpec(2, 1, figure=fig)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])

example_plot(ax1)
example_plot(ax2)
    
plt.show()

效果:

4fd1f70293f19f019a2fd25dc190cb73.png

使用 add_gridspec 和 subgridspec函数可以完成更复杂的 gridspec 布局。

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.

def example_plot(ax, fontsize=12, nodec=False):
    ax.plot([1, 2])
    ax.locator_params(nbins=3)
    if not nodec:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)
    else:
        ax.set_xticklabels('')
        ax.set_yticklabels('')
plt.rcParams['figure.constrained_layout.use'] = True

fig = plt.figure()
gs0 = fig.add_gridspec(1, 2)
gs1 = gs0[0].subgridspec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])

example_plot(ax1)
example_plot(ax2)

gs2 = gs0[1].subgridspec(3, 1)
for ss in gs2:
    ax = fig.add_subplot(ss)
    example_plot(ax)
    ax.set_title("")
    ax.set_xlabel("")

ax.set_xlabel("x-label", fontsize=12)
    
plt.show()

效果:

d5f96293462f74be6a5012ff3523fc8b.png

如果我们希望两个网格的顶部和底部对齐,那么它们需要在同一个 gridspec 中:

import matplotlib.pyplot as plt

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.

def example_plot(ax, fontsize=12, nodec=False):
    ax.plot([1, 2])
    ax.locator_params(nbins=3)
    if not nodec:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)
    else:
        ax.set_xticklabels('')
        ax.set_yticklabels('')

fig = plt.figure(constrained_layout=True)
gs0 = fig.add_gridspec(6, 2)
ax1 = fig.add_subplot(gs0[:3, 0])
ax2 = fig.add_subplot(gs0[3:, 0])
example_plot(ax1)
example_plot(ax2)
ax = fig.add_subplot(gs0[0:2, 1])
example_plot(ax)
ax = fig.add_subplot(gs0[2:4, 1])
example_plot(ax)
ax = fig.add_subplot(gs0[4:, 1])
example_plot(ax)

plt.show()

效果:

cc4dc7ff4076e19262a8f6b4581e0986.png

此示例使用两个 gridspecs 使颜色条只和一组pcolors有关。

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.

def example_plot(ax, fontsize=12, nodec=False):
    ax.plot([1, 2])
    ax.locator_params(nbins=3)
    if not nodec:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)
    else:
        ax.set_xticklabels('')
        ax.set_yticklabels('')

arr = np.arange(100).reshape((10, 10))
norm = mcolors.Normalize(vmin=0., vmax=100.)
pc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}

def docomplicated(suptitle=None):
    fig = plt.figure(constrained_layout=True)
    gs0 = fig.add_gridspec(1, 2, figure=fig, width_ratios=[1., 2.])
    gsl = gs0[0].subgridspec(2, 1)
    gsr = gs0[1].subgridspec(2, 2)

    for gs in gsl:
        ax = fig.add_subplot(gs)
        example_plot(ax)
    axs = []
    for gs in gsr:
        ax = fig.add_subplot(gs)
        pcm = ax.pcolormesh(arr, **pc_kwargs)
        ax.set_xlabel('x-label')
        ax.set_ylabel('y-label')
        ax.set_title('title')
        axs += [ax]
    fig.colorbar(pcm, ax=axs)
    if suptitle is not None:
        fig.suptitle(suptitle)

docomplicated()
 
plt.show()

效果:

035bbe1c61840d706700c1b3550df72d.png

8、手动设置axes位置

手动调用set_position可以设置axes位置,因此 constrained_layout 不再对其产生影响。

import matplotlib.pyplot as plt

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.

def example_plot(ax, fontsize=12, nodec=False):
    ax.plot([1, 2])
    ax.locator_params(nbins=3)
    if not nodec:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)
    else:
        ax.set_xticklabels('')
        ax.set_yticklabels('')

fig, axs = plt.subplots(1, 2)
example_plot(axs[0], fontsize=12)
axs[1].set_position([0.2, 0.2, 0.4, 0.4])

plt.show()

效果:

6f8e4b294ad927b8a4647dcfa83feeab.png


如果你想在数据空间中插入axes,则需要使用 fig.execute_constrained_layout() 来手动布局,然后在正确的位置里插入图形。

但是,如果之后更改图形的大小,它将无法正确定位。

同样,如果将图形打印到另一个后端,由于后端渲染字体的方式存在微小差异,位置也可能会略有变化。

import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.

def example_plot(ax, fontsize=12, nodec=False):
    ax.plot([1, 2])
    ax.locator_params(nbins=3)
    if not nodec:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)
    else:
        ax.set_xticklabels('')
        ax.set_yticklabels('')

fig, axs = plt.subplots(1, 2)
example_plot(axs[0], fontsize=12)
fig.execute_constrained_layout()
bb_data_ax2 = Bbox.from_bounds(0.5, 1., 0.2, 0.4)
disp_coords = axs[0].transData.transform(bb_data_ax2)
fig_coords_ax2 = fig.transFigure.inverted().transform(disp_coords)
bb_ax2 = Bbox(fig_coords_ax2)
ax2 = fig.add_axes(bb_ax2)

plt.show()

效果:

a576a4104a4e9b6112fd228d636a8f6d.png

03

紧凑布局

tight_layout 会自动调整 subplot 参数,以便 subplot(s) 适合图形区域。

但它只检查刻度标签、轴标签和标题的范围。

1、简单示例

在 matplotlib 中,axes(包括子图)的位置在图形坐标中指定。

轴标签或标题(有时甚至是刻度标签)可能会超出图形区域,从而被剪裁。

import matplotlib.pyplot as plt

plt.rcParams['savefig.facecolor'] = "0.8"

def example_plot(ax, fontsize=12):
    ax.plot([1, 2])

    ax.locator_params(nbins=3)
    ax.set_xlabel('x-label', fontsize=fontsize)
    ax.set_ylabel('y-label', fontsize=fontsize)
    ax.set_title('Title', fontsize=fontsize)

plt.close('all')
fig, ax = plt.subplots()
example_plot(ax, fontsize=24)

plt.show()

效果:

caa9bcdfb9bca7a0c690528eae0f5366.png

为了防止这种情况,需要调整 axes的位置。

可以使用tight_layout(),它会自动为你执行此操作:

fig, ax = plt.subplots()
example_plot(ax, fontsize=24)
plt.tight_layout()

效果:

db8d767f95044ad45f8b5641b2289d75.png

注意, matplotlib.pyplot.tight_layout() 只会在调用时才调整子图参数。

为了在每次重绘图形时执行此调整,你可以调用 fig.set_tight_layout(True),或者等效地将 figure.autolayout 参数设置为 True。

当你有多个子图时,你通常会看到不同轴的标签相互重叠:

import matplotlib.pyplot as plt

plt.rcParams['savefig.facecolor'] = "0.8"

def example_plot(ax, fontsize=12):
    ax.plot([1, 2])

    ax.locator_params(nbins=3)
    ax.set_xlabel('x-label', fontsize=fontsize)
    ax.set_ylabel('y-label', fontsize=fontsize)
    ax.set_title('Title', fontsize=fontsize)

plt.close('all')

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)

plt.show()

效果:

0d10834d4680e79bcdba45bb0a466d95.png

tight_layout() 可以调整子图之间的间距以最小化重叠:

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
plt.tight_layout()

效果:

8c887c541367365b7d4d1b3a0154e6e7.png

tight_layout() 可以接受 pad、w_pad 和 h_pad 的关键字参数,以控制图形边界周围和子图之间的填充。

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)

效果:

d51dd5a7985ba7630dc8293603710acf.png

即使子图的大小不同,但只要它们的网格格式兼容,tight_layout() 也将起作用。

plt.close('all')
fig = plt.figure()

ax1 = plt.subplot(221)
ax2 = plt.subplot(223)
ax3 = plt.subplot(122)

example_plot(ax1)
example_plot(ax2)
example_plot(ax3)

plt.tight_layout()

效果:

5f15dff899ad41991bc09821fa9a3c0f.png

plt.tight_layout()也适用于使用 subplot2grid() 创建的子图:

plt.close('all')
fig = plt.figure()

ax1 = plt.subplot2grid((3, 3), (0, 0))
ax2 = plt.subplot2grid((3, 3), (0, 1), colspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=2)
ax4 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)

example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)

plt.tight_layout()

效果:

a38fe9ebfeb3d37901e52f8a2d6461d8.png

2、GridSpe方法

tight_layout()对于GridSpec方法也适用:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

plt.rcParams['savefig.facecolor'] = "0.8"

def example_plot(ax, fontsize=12):
    ax.plot([1, 2])

    ax.locator_params(nbins=3)
    ax.set_xlabel('x-label', fontsize=fontsize)
    ax.set_ylabel('y-label', fontsize=fontsize)
    ax.set_title('Title', fontsize=fontsize)

plt.close('all')

fig = plt.figure()
gs1 = gridspec.GridSpec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])
example_plot(ax1)
example_plot(ax2)
gs1.tight_layout(fig)

plt.show()

效果:

8779710139daa7fac9f7f8bc80820cdc.png

你可以提供一个可选的 rect 参数,它将指定子图的边界框。 rect 参数的值是标准图形坐标,默认为 (0, 0, 1, 1)。

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

plt.rcParams['savefig.facecolor'] = "0.8"

def example_plot(ax, fontsize=12):
    ax.plot([1, 2])

    ax.locator_params(nbins=3)
    ax.set_xlabel('x-label', fontsize=fontsize)
    ax.set_ylabel('y-label', fontsize=fontsize)
    ax.set_title('Title', fontsize=fontsize)

plt.close('all')

fig = plt.figure()
gs1 = gridspec.GridSpec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])
example_plot(ax1)
example_plot(ax2)
gs1.tight_layout(fig, rect=[0, 0, 0.5, 1])

plt.show()

效果:

fdcac479574e5d9a7770d193c1294ffe.png

tight_layout()也可用于具有多个 gridspecs 方法的图形:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

plt.rcParams['savefig.facecolor'] = "0.8"

def example_plot(ax, fontsize=12):
    ax.plot([1, 2])

    ax.locator_params(nbins=3)
    ax.set_xlabel('x-label', fontsize=fontsize)
    ax.set_ylabel('y-label', fontsize=fontsize)
    ax.set_title('Title', fontsize=fontsize)

plt.close('all')

fig = plt.figure()
gs1 = gridspec.GridSpec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])

example_plot(ax1)
example_plot(ax2)

gs1.tight_layout(fig, rect=[0, 0, 0.5, 1])
gs2 = gridspec.GridSpec(3, 1)

for ss in gs2:
    ax = fig.add_subplot(ss)
    example_plot(ax)
    ax.set_title("")
    ax.set_xlabel("")

ax.set_xlabel("x-label", fontsize=12)
gs2.tight_layout(fig, rect=[0.5, 0, 1, 1], h_pad=0.5)

# 匹配两个网格的顶部和底部
top = min(gs1.top, gs2.top)
bottom = max(gs1.bottom, gs2.bottom)

gs1.update(top=top, bottom=bottom)
gs2.update(top=top, bottom=bottom)

plt.show()

效果:

1f68b96b5bfc98a5dea0586ba2d6bb9d.png

3、图例

添加图例:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(4, 3))
lines = ax.plot(range(10), label='A simple plot')
ax.legend(bbox_to_anchor=(0.7, 0.5), loc='center left',)
fig.tight_layout()

plt.show()

效果:

d32257728bf978bef20e9ac479b6bbda.png

设置 leg.set_in_layout(False) 可以从边界框计算中删除图例:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(4, 3))
lines = ax.plot(range(10), label='B simple plot')
leg = ax.legend(bbox_to_anchor=(0.7, 0.5), loc='center left',)
leg.set_in_layout(False)
fig.tight_layout()

plt.show()

效果:

6f6f456c1f45b8072d8c30f03276fdcf.png

4、AxesGrid1方法

虽然作用有限,但tight_layout()也支持 mpl_toolkits.axes_grid1:

import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import Grid

plt.rcParams['savefig.facecolor'] = "0.8"

def example_plot(ax, fontsize=12):
    ax.plot([1, 2])

    ax.locator_params(nbins=3)
    ax.set_xlabel('x-label', fontsize=fontsize)
    ax.set_ylabel('y-label', fontsize=fontsize)
    ax.set_title('Title', fontsize=fontsize)

plt.close('all')

fig = plt.figure()
grid = Grid(fig, rect=111, nrows_ncols=(2, 2),
            axes_pad=0.25, label_mode='L',
            )

for ax in grid:
    example_plot(ax)
ax.title.set_visible(False)

plt.tight_layout()

plt.show()

效果:

cafb3fe271907d7c245821d8bb0e6408.png

5、颜色条

如果你使用 colorbar() 命令创建颜色条,则创建的颜色条是 Axes 的实例,而不是 Subplot,因此tight_layout 不起作用。

但你可以通过 gridspec 创建一个颜色条作为子图。

import matplotlib.pyplot as plt
import numpy as np

arr = np.arange(100).reshape((10, 10))
fig = plt.figure(figsize=(4, 4))
im = plt.imshow(arr, interpolation="none")
plt.colorbar(im, use_gridspec=True)
plt.tight_layout()

plt.show()

效果:

cea7d7e0afa6b475157ea859da1f088d.png

另一种选择是使用 AxesGrid1 工具包为颜色条显式创建 axes。

import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable

plt.close('all')
arr = np.arange(100).reshape((10, 10))
fig = plt.figure(figsize=(4, 4))
im = plt.imshow(arr, interpolation="none")

divider = make_axes_locatable(plt.gca())
cax = divider.append_axes("right", "5%", pad="3%")
plt.colorbar(im, cax=cax)
plt.tight_layout()

plt.show()

效果:

072f8adf5384c854471396342cf270f7.png

好啦,这个部分就介绍到这~

a89104ba2f4b28a738ae5d49daedea15.png

END

6f3869ec7153601f27b9edc3ba84aee2.gif

您的“点赞”、“在看”和 “分享”是我们产出的动力。

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值