Python Matplotlib subplot函数详解:创建子图

使用 Matplotlib 除可以生成包含多条折线的复式折线图之外,它还允许在一张数据图上包含多个子图。

调用 subplot() 函数可以创建一个子图,然后程序就可以在子图上进行绘制。subplot(nrows, ncols, index, **kwargs) 函数的 nrows 参数指定将数据图区域分成多少行;ncols 参数指定将数据图区域分成多少列;index 参数指定获取第几个区域。

subplot() 函数也支持直接传入一个三位数的参数,其中第一位数将作为 nrows 参数;第二位数将作为 ncols 参数;第三位数将作为 index 参数。

下面程序示范了生成多个子图:
import matplotlib.pyplot as plt
import numpy as np

plt.figure()
# 定义从-pi到pi之间的数据,平均取64个数据点
x_data = np.linspace(-np.pi, np.pi, 64, endpoint=True)  # ①
# 将整个figure分成两行两列,第三个参数表示该图形放在第1个网格
plt.subplot(2, 2, 1)
# 绘制正弦曲线
plt.plot(x_data, np.sin(x_data))
plt.gca().spines['right'].set_color('none')
plt.gca().spines['top'].set_color('none')
plt.gca().spines['bottom'].set_position(('data', 0))
plt.gca().spines['left'].set_position(('data', 0))
plt.title('正弦曲线')

# 将整个figure分成两行两列,并将该图形放在第2个网格
plt.subplot(222)
# 绘制余弦曲线
plt.plot(x_data, np.cos(x_data))
plt.gca().spines['right'].set_color('none')
plt.gca().spines['top'].set_color('none')
plt.gca().spines['bottom'].set_position(('data', 0))
plt.gca().spines['left'].set_position(('data', 0))
plt.title('余弦曲线')

# 将整个figure分成两行两列,并该图形放在第3个网格
plt.subplot(223)
# 绘制正切曲线
plt.plot(x_data, np.tan(x_data))
plt.gca().spines['right'].set_color('none')
plt.gca().spines['top'].set_color('none')
plt.gca().spines['bottom'].set_position(('data', 0))
plt.gca().spines['left'].set_position(('data', 0))
plt.title('正切曲线')

plt.show()
上面程序多次调用 subplot() 函数来生成子图,每次调用 subplot() 函数之后的代码表示在该子图区域绘图。上面程序将整个数据图区域分成 2×2 的网格,程序分别在第 1 个网格中绘制正弦曲线,在第 2 个网格中绘制余弦曲线,在第 3 个网格中绘制正切曲线。

可能有读者感到疑问,plot() 函数不是用于绘制折线图的吗?怎么此处还可用于绘制正弦曲线、余弦曲线呢?

其实此处绘制的依然是折线图。看程序中的 ① 号代码调用 numpy 的 linspace() 函数生成了一个包含多个数值的列表,该数值列表的范围是从 -pi 到 pi,平均分成 64 个数据点,程序中用到的 numpy.sin()、numpy.cos()、numpy.tan() 等函数也返回一个列表:传入这些函数的列表包含多少个值,这些函数返问的列表也包含多少个值。

这意味着上面程序所给制的折线图会包含 64 个转折点,由于这些转折点非常密集,看上去显得比较光滑,因此就变成了曲线。

如果读者将程序中x_data=np.linespace(-np.pi,np.pi,64,endpoint = True)代码的 64 改为4、6等较小的数,将会看到程序绘制的依然是折线图。

运行上面程序,可以看到如图 1 所示的效果。

生成多个子图
图 1 生成多个子图

如图 1 所示的显示效果比较差,程序明明只要显示 3 个子图,但第 4 个位置被空出来了,能不能让某个子图占多个网格昵?答案是肯定的,程序做好控制即可。例如,将上面程序改为如下形式:
import matplotlib.pyplot as plt
import numpy as np

plt.figure()
# 定义从-pi到pi之间的数据,平均取64个数据点
x_data = np.linspace(-np.pi, np.pi, 64, endpoint=True)  # ①
# 将整个figure分成两行一列,第三个参数表示该图形放在第1个网格
plt.subplot(2, 1, 1)
# 省略绘制正弦曲线
...

# 将整个figure分成两行两列,并将该图形放在第4个网格
plt.subplot(223)
# 省略绘制余弦曲线
...

# 将整个figure分成两行两列,并该图形放在第4个网格
plt.subplot(224)
#省略绘制正切曲线
...

plt.show()
上面程序中第 8 行代码将整个区域分成两行一列,并指定子图占用第 1 个网格,也就是整个区域的第一行:第 13 行代码将整个区域分成两行两列,并指定子图占用第 3 个网格(注意不是第 2 个网络,因为第一个子图已经占用了第一行,对于两行两列的网格来说,第一个子图已经占用了两个网格,因此此处指定子图占用第 3 个网格,这意味着该子图在第二行第一格);第 18 行代码将整个区域分成两行两列,并指定子图占用第 4 个网格,这意味着该子图会在第二行第二格。

运行上面程序,可以看到如图 2 所示的效果。

控制多个子图的分布
图 2 控制多个子图的分布

如果读者不想这么费劲来计算行、列,将上面程序改为如下形式:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.gridspec as gridspec

plt.figure()
# 定义从-pi到pi之间的数据,平均取64个数据点
x_data = np.linspace(-np.pi, np.pi, 64, endpoint=True)  # ①

# 将绘图区域分成2行3列
gs = gridspec.GridSpec(2, 3)
# 指定ax1占用第一行(0)整行
ax1 = plt.subplot(gs[0, :])
# 指定ax1占用第二行(1)的第一格(第二个参数0代表)
ax2 = plt.subplot(gs[1, 0])
# 指定ax1占用第二行(1)的第二、三格(第二个参数0代表)
ax3 = plt.subplot(gs[1, 1:3])

# 绘制正弦曲线
ax1.plot(x_data, np.sin(x_data))
ax1.spines['right'].set_color('none')
ax1.spines['top'].set_color('none')
ax1.spines['top'].set_color('none')
ax1.spines['bottom'].set_position(('data', 0))
ax1.spines['left'].set_position(('data', 0))
ax1.set_title('正弦曲线')

# 绘制余弦曲线
ax2.plot(x_data, np.cos(x_data))
ax2.spines['right'].set_color('none')
ax2.spines['top'].set_color('none')
ax2.spines['bottom'].set_position(('data', 0))
ax2.spines['left'].set_position(('data', 0))
ax2.set_title('余弦曲线')

# 绘制正切曲线
ax3.plot(x_data, np.tan(x_data))
ax3.spines['right'].set_color('none')
ax3.spines['top'].set_color('none')
ax3.spines['bottom'].set_position(('data', 0))
ax3.spines['left'].set_position(('data', 0))
ax3.set_title('正切曲线')

plt.show()
上面程序中的第 10 行代码将绘图区域分成两行三列;第 12 行代码调用 subplot(gs[0,:]),指定 ax1子图区域占用第一行整行,其中第一个参数 0 代表行号,没有指定列范围,因此该子图在整个第一行;第 14 行代码调用 subplot(gs[1, 0]),指定 ax2 子图区域占用第二行的第一格,其中第一个参数 1 代表第二行,第二个参数 0 代表第一格,因此该子图在第二行的第一格;第 16 行代码调用 subplot(gs [1 , 1 : 3]),指定 ax3 子图区域占用第二行的第二格到第三格,其中第一个参数 1 代表第二行,第二个参数 1:3 代表第二格到第三格,因此该子图在第二行的第二格到第三格。

定义完 ax1、ax2、ax3 这 3 个子图所占用的区域之后,接下来程序就可以通过 ax1、ax2、ax3 的方法在各自的子图区域绘图了。运行上面程序,可以看到如图 3 所示的效果。

使用 GridSpec 管理子图的分布
图 3 使用 GridSpec 管理子图的分布