在一些情况中,如果能将不同的数据图表并列展示,对于我们进行数据分析和比较会很有帮助。Matplotlib 提供了子图表的概念来实现这一点:单个图表中可以包括一组小的 axes 用来展示多个子图表。这些子图表可以是插图,网格状分布或其他更复杂的布局。在本节中我们会介绍 Matplotlib 中用来构建子图表的四个函数。
import matplotlib.pyplot as plt plt.style.use('seaborn-white') import numpy as np
plt.axes
:手动构建子图表
构建 axes 作为子图表的最基础方法就是使用plt.axes
函数。正如我们前面已经看到,默认情况下,这个函数够创建一个标准的 axes 对象填满整个图表区域。plt.axes
函数也可以接收一个可选的列表参数用来指定在 axes 在整个图表中的坐标点位置。列表中有四个数值分别为[left, bottom, width, height]
(取值都是 0-1),代表着子图表的左边、底部、宽度、高度在整个图表中左边、底部、宽度、高度所占的比例值。
例如,我们可以在距离左边和底部 65%的位置,以插图的形式放置一个宽度和高度都是 20%子图表,上述数值应该为[0.65, 0.65, 0.2, 0.2]
:
ax1 = plt.axes() # 标准图表 ax2 = plt.axes([0.65, 0.65, 0.2, 0.2]) #子图表 plt.show()
与上述等价的面向对象接口的语法是fig.add_axes()
。我们使用这个方法来创建两个垂直堆叠的子图表:
fig = plt.figure() # 获得figure对象 ax1 = fig.add_axes([0.1, 0.5, 0.8, 0.4], xticklabels=[], ylim=(-1.2, 1.2)) # 左边10% 底部50% 宽80% 高40% ax2 = fig.add_axes([0.1, 0.1, 0.8, 0.4], ylim=(-1.2, 1.2)) # 左边10% 底部10% 宽80% 高40% x = np.linspace(0, 10) ax1.plot(np.sin(x)) ax2.plot(np.cos(x));
这样我们就有两个子图表(上面的子图表没有 x 轴刻度),这两个子图表正好吻合:上面图表的底部是整个图表高度 50%位置,而下面图表的顶部也是整个图表的 50%位置(0.1+0.4)。
plt.subplot
:简单网格的子图表
将子图表的行与列对齐是一个很常见的需求,因此 Matplotlib 提供了一些简单的函数来实现它们。这些函数当中最底层的是plt.subplot()
,它会在网格中创建一个子图表。函数接受三个整数参数,网格行数,网格列数以及该网格子图表的序号(从左上角向右下角递增):
for i in range(1, 7): plt.subplot(2, 3, i) plt.text(0.5, 0.5, str((2, 3, i)), fontsize=18, ha='center')
plt.subplots_adjust
函数用来调整这些子图表之间的距离。下面的代码使用了与plt.subplot()
等价的面向对象接口方法fig.add_subplot()
:
fig = plt.figure() fig.subplots_adjust(hspace=0.4, wspace=0.4) for i in range(1, 7): ax = fig.add_subplot(2, 3, i) ax.text(0.5, 0.5, str((2, 3, i)), fontsize=18, ha='center')
上例中我们指定了plt.subplots_adjust
函数的hspace
和wspace
参数,它们代表这沿着高度和宽度方向子图表之间的距离,单位是子图表的大小(在本例中,距离是子图表宽度和高度的 40%)。
plt.subplots
:一句代码设置所有网格子图表
上面的方法当我们需要创建大量的子图表网格时会变得非常冗长乏味,特别是如果我们需要将内部图表 x 轴和 y 轴标签隐藏的情况下。因此,plt.subplots
在这种情况下是一个合适的工具(注意末尾有个 s)。这个函数会一次性创建所有的网格子图表,而不是单个网格,并将它们在一个 NumPy 数组中返回。参数是行数和列数,还有两个可选的关键字参数sharex
和sharey
,可以让你指定不同子图表之间的关联。
下面我们来创建一个 网格的子图表,其中每一行的子图表共享它们的 y 轴,而每一列的子图表共享它们的 x 轴:
fig, ax = plt.subplots(2, 3, sharex='col', sharey='row')
注意上面我们设置了sharex
和sharey
之后,内部子图表的 x 轴和 y 轴的标签就自动被去掉了。返回值中 ax 是一个 NumPy 数组,里面含有每一个子图表的实例,你可以使用 NumPy 索引的语法很简单的获得它们:
# axes是一个2×3的数组,可以通过[row, col]进行索引访问 for i in range(2): for j in range(3): ax[i, j].text(0.5, 0.5, str((i, j)), fontsize=18, ha='center') fig
并且相对于plt.subplot
,plt.subplots()
更复合 Python 从 0 开始进行索引的习惯。
plt.GridSpec
:更复杂的排列
当你需要子图表在网格中占据多行或多列时,plt.GridSpec()
正是你所需要的。plt.GridSpec()
对象并不自己创建图表;它只是一个可以被传递给plt.subplot()
的参数。例如,一个两行三列并带有指定的宽度高度间隔的 gridspec 可以如下创建:
grid = plt.GridSpec(2, 3, wspace=0.4, hspace=0.3)
使用这个对象我们可以指定子图表的位置和占据的网格,仅需要使用熟悉的 Python 切片语法即可:
plt.subplot(grid[0, 0]) plt.subplot(grid[0, 1:]) plt.subplot(grid[1, :2]) plt.subplot(grid[1, 2]);
这种灵活的网格对齐控制方式有着广泛的应用。作者经常在需要创建多个直方图的联合图表中使用这种方法,如下例:
# 构建二维正态分布数据 mean = [0, 0] cov = [[1, 1], [1, 2]] x, y = np.random.multivariate_normal(mean, cov, 3000).T # 使用GridSpec创建网格并加入子图表 fig = plt.figure(figsize=(6, 6)) grid = plt.GridSpec(4, 4, hspace=0.2, wspace=0.2) main_ax = fig.add_subplot(grid[:-1, 1:]) y_hist = fig.add_subplot(grid[:-1, 0], xticklabels=[], sharey=main_ax) x_hist = fig.add_subplot(grid[-1, 1:], yticklabels=[], sharex=main_ax) # 在主图表中绘制散点图 main_ax.plot(x, y, 'ok', markersize=3, alpha=0.2) # 分别在x轴和y轴方向绘制直方图 x_hist.hist(x, 40, histtype='stepfilled', orientation='vertical', color='gray') x_hist.invert_yaxis() # x轴方向(右下)直方图倒转y轴方向 y_hist.hist(y, 40, histtype='stepfilled', orientation='horizontal', color='gray') y_hist.invert_xaxis() # y轴方向(左上)直方图倒转x轴方向 plt.show()
这种沿着数据各自方向分布并绘制相应图表的需求是很通用的,因此在 Seaborn 包中它们有专门的 API 来实现