6.4 子图与子区 难点突破

image0


在 matplotlib 中有两个非常重要,而且很容易混淆的概念,一个是 subplot,一个是axes。这两个概念将贯穿整个 matplotlib 学习历程。在往后进行深度研究之前呢,务必要先弄懂这两个概念,否则将后面的绘制代码,我相信你一定会一头雾水的。

在查阅了一些相关中文文档后,我暂且将 subplot 称为 子区,而将 axes 称之为 子图。这些只是为了方便后续的描述。

你会发现,即使翻译成中文后,还是无法帮助我们直观的理解这两个概念。因此我特地画了几张图,来解释这两者到底是啥区别。

6.4.1 子区

子区(subplot),是基于 网格(grid)来规划的。

比如,这个写法

plt.subplot(2, 2, 1)

是将当前图像(figure)按 2 行 2 列的布局进行分割,然后取索引为 1 的子图。注意 matlibplot 是完全借鉴了 MATLAB 的思想,所以的起始索引为 1,不像 Python 的起始索引为 0。 image1

他有好几种写法,这里写我在官网学到的几个方法。

这几种写法是等价的。

# 第一种写法
ax = plt.subplot(2, 2, 1)

# 第二种写法
ax = plt.subplot2grid((2, 2), (0, 0))

# 第三种写法: GridSpec
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(2, 2)
ax = plt.subplot(gs[0, 0])

这第二种写法呢,是将图像分成 2 行 2 列,再取 第 0 索引行(第一行),第 0 索引列(第一列)。

学完了以上内容,我们来使用最简单的方法(第一种)来实践一下。

import numpy as np
import matplotlib.pyplot as plt

def fig(t):
    return np.exp(-t) * np.cos(2*np.pi*t)

t1 = np.arange(0.0, 5.0, 0.1)
t2 = np.arange(0.0, 5.0, 0.02)

plt.figure(1)
# 等同于 plt.subplot(2, 1,1)
plt.subplot(211)
plt.plot(t1, fig(t1), 'bo', t2, fig(t2), 'k')

# 等同于 plt.subplot(2, 1,2)
plt.subplot(212)
plt.plot(t2, np.cos(2*np.pi*t2), 'r--')
plt.show()

image2

6.4.2 子图

子图(axes),和子区(subplot)非常相似,一个子图可能是由一个或多个子区域构成的。它比子区更加灵活。

它可以是这样 image3

要实现如上这个效果。常用的有两种方法。

第一种,使用 subplot2grid

axes1 = plt.subplot2grid((3, 3), (0, 0), colspan=3)
axes2 = plt.subplot2grid((3, 3), (1, 0), colspan=2)
axes3 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
axes4 = plt.subplot2grid((3, 3), (2, 0))
axes5 = plt.subplot2grid((3, 3), (2, 1))

第二种,使用 GridSpec (可以切片)

import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(3, 3)
ax1 = plt.subplot(gs[0, :])
ax2 = plt.subplot(gs[1, :-1])
ax3 = plt.subplot(gs[1:, -1])
ax4 = plt.subplot(gs[-1, 0])
ax5 = plt.subplot(gs[-1, -2])

这个比较规则的划分我们举个例子看看。

image4

代码如下:

import numpy as np
import matplotlib.pyplot as plt


def f(t):
    return np.exp(-t) * np.cos(2*np.pi*t)


t1 = np.arange(0.0, 3.0, 0.01)

ax1 = plt.subplot(212)
ax1.margins(0.05)           # Default margin is 0.05, value 0 means fit
ax1.plot(t1, f(t1), 'k')

ax2 = plt.subplot(221)
ax2.margins(2, 2)           # Values >0.0 zoom out
ax2.plot(t1, f(t1), 'r')
ax2.set_title('Zoomed out')

ax3 = plt.subplot(222)
ax3.margins(x=0, y=-0.25)   # Values in (-0.5, 0.0) zooms in to center
ax3.plot(t1, f(t1), 'g')
ax3.set_title('Zoomed in')

plt.show()

为什么说,子图的灵活性更高呢,因为它允许把图片放置到图像(figure)中的任何地方(如下图)。所以如果我们想要在一个大图片中嵌套一个小点的图片,我们通过子图(axes)来完成它。

image5

图中的 axes 是如何实现的,刚开始我也有点懵逼,在查阅了官方文档后,我才明白。 image6

left 是指,离左边界的距离。 bottom 是指,离底边的距离。 width 是指,子图的宽度。 height 是指,子图的高度。

以上四个参数,是一个(0, 1)的比例(相比于figure),而不是具体数值。

同样地,这个我们也来看一个例子。

这个图的亮点,在于中间,多了两个子图,就像往图中贴上了两个插画一样。 image7

那么这个如何实现呢?

import matplotlib.pyplot as plt
import numpy as np

# Fixing random state for reproducibility
np.random.seed(19680801)


# create some data to use for the plot
dt = 0.001
t = np.arange(0.0, 10.0, dt)
r = np.exp(-t[:1000] / 0.05)  # impulse response
x = np.random.randn(len(t))
s = np.convolve(x, r)[:len(x)] * dt  # colored noise

# the main axes is subplot(111) by default
plt.plot(t, s)
plt.axis([0, 1, 1.1 * np.min(s), 2 * np.max(s)])
plt.xlabel('time (s)')
plt.ylabel('current (nA)')
plt.title('Gaussian colored noise')

# this is an inset axes over the main axes
a = plt.axes([.65, .6, .2, .2], facecolor='k')
n, bins, patches = plt.hist(s, 400, density=True)
plt.title('Probability')
plt.xticks([])
plt.yticks([])

# this is another inset axes over the main axes
b = plt.axes([0.2, 0.6, .2, .2], facecolor='k')
plt.plot(t[:len(r)], r)
plt.title('Impulse response')
plt.xlim(0, 0.2)
plt.xticks([])
plt.yticks([])

plt.show()

image8