matplotlib 介绍

matplotlib 大致是类似 matlab 风格的 2D 绘图库(3D 的功能比较弱,还是基于 2D 引擎勉强实现的),同时提供了两套 api:

  • 一个是面向过程的,主要调用 matplotlib.pyplot 的函数;
  • 一个是面向对象的,主要调用 matplotlib 的两个子类:matplotlib.figure.Figurematplotlib.axes.Axes ,使用它们的方法进行细节操作。

面向过程的 api 适合简易使用的场景,但是不容易弄清原理;为了更复杂的绘图要求,这里主要采用面向对象的 api。关于绘图的呈现方式,在 Jupyter 中很可能会自动绘图而不需要 plt.show(),这与魔法指令 %matplotlib inline 有关。

在这一系列笔记中,需要使用如下模块:

1
2
3
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

由于 matplotlib 的内容太多太杂,这里只会介绍最容易理解的,最本质的面向对象的接口,然后作为辅助会介绍其它等价的,或者更方便的调用方式。但是 matplotlib 的面向对象其实有很多层,在每一层都可能为同一个功能提供了等价的接口,这里为了简化笔记,主要考虑 FigureAxes 层面提供的接口。

简单例子

面向过程风格的绘图例子如下,主要是 plt 的函数调用:

1
2
3
4
5
plt.plot(
np.linspace(0, 5, 100),
(lambda x: np.cos(2 * np.pi * x) * np.exp(-x))(np.linspace(0, 5, 100)),
)
plt.title("eg-1-1")

面向对象风格的绘图例子如下,主要是画布和子图的方法调用:

1
2
3
4
5
6
fig, ax = plt.subplots()
ax.plot(
np.linspace(0, 5, 100),
(lambda x: -np.cos(2 * np.pi * x) * np.exp(-x))(np.linspace(0, 5, 100)),
)
ax.set_title("eg-1-2")

基本概念

对于一个图像,matplotlib 将其分成了两层主要的概念:

  • Figure 层,可以理解为整个画布,控制整体的尺寸,标题等;
  • Axes 层,可以理解为一个子图或者子图的数组,这里的子图通常有两个坐标轴,以及具体数据图像和细节组成,因此名称为 Axes。一个 Figure 对象可以包括多个子图,达到多个子图并列放置的效果。

这两个概念非常难以翻译,因此下面主要使用英文。

一个 Figure 上主要有如下的元素:

  • Title 标题,这里存在 Figure 标题和 Axes 标题的区分
  • Legend 图例
  • x Axis, y Axis 坐标轴
  • xlabel, ylabel 坐标轴标签
  • Grid 网格
  • Line 线条
  • Markers 标记
  • Major tick, Minor tick 主刻度,副刻度
  • Major tick label 主刻度标签
  • Spine 边框线

各个元素如下图所示

创建 Figure 和 Axes

创建 Figure

创建 Figure 对象的函数原型如下

1
2
3
4
5
def figure(num=None, figsize=None, dpi=None, facecolor=None, edgecolor=None, frameon=True)
...

fig = plt.figure()
fig = plt.figure(figsize=(4,3))

常用参数的含义:

  • figsize 尺寸,依次为横向宽度和纵向高度,单位为英寸,例如 figsize=(5,2.7)
  • dpi 分辨率
  • facecolor 背景颜色
  • edgecolor 边框颜色
  • frameon 是否显示边框

添加 Axes

Figure 上必须有至少一个 Axes 对象,才能进行绘图,绘图的数据终归是要基于 Axes 的坐标轴体系来完成。

在Figure上添加一个 Axes 对象可以使用下面的语句

1
2
fig = plt.figure()
ax = fig.add_subplot(111)

多个 Axes 在同一个 Figure 上是按照矩阵格式排列布局的,例如 3 行 2 列一共 6 个位置,当然并不要求填满所有位置。 可以使用如下方式,依次添加 Axes 对象,需要给出整体的行列数以及自身的序号(从 1 开始)

1
2
3
4
5
6
7
8
fig = plt.figure()

ax1 = fig.add_subplot(3,2,1) # 3行2列,位置1
ax2 = fig.add_subplot(3,2,2)
ax3 = fig.add_subplot(3,2,3)
ax4 = fig.add_subplot(3,2,4)
ax5 = fig.add_subplot(3,2,5)
ax6 = fig.add_subplot(3,2,6)

得到的空给框架如下(这里的刻度尺都是动态的,会随着填入数据的范围而调整)

对于上面函数中小的个位数索引(即总位置不超过 9 个),可以直接拼成一个三位数参数,例如

1
2
3
4
ax1 = fig.add_subplot(3,2,1)

# i.e.
ax1 = fig.add_subplot(321)

注意:如果反复添加 Axes,它们可能会相互重叠显示,显得非常混乱。

一次性创建 Figure 并添加 Axes

对于多行多列的矩阵布局,先创建 Figure,然后一个个添加 Axes 非常繁琐,可以使用pyplot 提供的 plt.subplots 接口一次性完成。

例如,创建一个Figure并添加一个Axes

1
2
3
fig, ax = plt.subplots()

type(ax) # matplotlib.axes._axes.Axes

例如,创建一个Figure并添加多个Axes

1
2
3
4
fig, axes = plt.subplots(3,2)

type(axes) # numpy.ndarray
print(axes.shape) # (3,2)

通过访问这个数组的分量来实现对每一个 Axes 的修改。

需要注意的是,还有一个名称非常相似的接口 plt.subplot,它是面向过程绘图的接口,含义是为当前的 Figure 添加一个 Axes。