Matplotlib 是 Python 中的数据可视化软件包,支持跨平台。Jonh D. Hunter 于 2002 年开始编写,2003 年发布第一版,并加入 BSD 开源软件组织。Matplotlib 提供了面向绘图对象的 API,能够实现常用图像的绘制,并能配合 Python GUI 工具(如 PyQt, Tkinter 等)把图像嵌入到应用程序中,同时也支持以脚本形式嵌入 IPython Shell, Jupyter notebook, Web 应用服务器中使用.

Matplotlib 架构和图像构成

Matplotlib 架构由 3 个层次构成,分别是:

  1. 后端层。作为最底层,它包含 3 个基类,分别是 FigureCanvas(图层画布,提供绘图的画布)、Renderer(绘图操作,提供画布上绘图的方法)、Event(事件处理,处理鼠标、键盘事件);
  2. 美工层。提供绘图的标题、坐标刻度、轴标签;
  3. 脚本层。负责生成图像和坐标系。

Matplotlib 图像由 4 个层次构成,分别是:

  1. Figure:画布,包含所有元素,如标题、轴线等;
  2. Axes:绘图区,或轴域区;
  3. Axis:坐标轴,包含轴长、轴标签和刻度等;
  4. Artist:文本对象(title, label)等能看到的所有对象。

Matplotlib 安装与使用

安装

1
2
3
4
5
6
# 安装最新版
pip install matplotlib
# 安装指定的版本
pip install matplotlib==3.2.2
# 卸载
pip uninstall matplotlib

面向对象绘图

Python 是一门面向对象的语言,Matplotlib 也提供了对象类,如画布类、绘图区类等,下面根据类实例化方法画一个图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import matplotlib.pyplot as plt  # 常用画图类 matplotlib.pyplot
import numpy as np # numpy 是 Python 中科学计算的基础包。提供多维数组对象,各种派生对象(如掩码数组和矩阵)等等

x = np.linspace(-5, 5, 101)
y = np.cos(x)

fig = plt.figure(figsize=(3, 3), dpi=100) # 创建画布实例,并设置画布大小和显示DPI
ax = fig.add_axes(
rect=[0, 0, 1, 1]
) # 创建绘图区类或轴域类,并指定绘图区域 rect,从左向右分别表示距离画布左、下的距离,绘图区域的宽和高,都是相对画布的比例值,取值都在 [0, 1]
ax.set_title("余弦曲线") # 设置标题
ax.set_xlabel("弧度") # 设置横轴标签
ax.set_ylabel("余弦值") # 设置纵轴标签
ax.plot(
x, y, color="cyan", marker="o", linestyle="dashed", linewidth=1, markersize=6
) # 画曲线
plt.show() # 显示图像

png

简单绘图

上面的面向对象绘图根据常规绘图的步骤进行,先创建画布,然后创建绘图区,最后绘图。有时,为了快速绘图,我们可以直接一条命令绘图,它会自动创建画布和绘图区。

1
2
3
4
5
6
7
8
9
10
11
12
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5, 5, 101)
y = np.cos(x)
plt.plot(
x, y, color="purple", marker="^", linestyle="dashed", linewidth=1, markersize=6
) # 自动创建合适大小的画布和绘图区进行绘图
plt.title("余弦曲线")
plt.xlabel("弧度")
plt.ylabel("余弦值")
plt.show()

png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5, 5, 101)
plt.plot(
x,
np.cos(x),
color="purple",
marker="^",
linestyle="dashed",
linewidth=1,
markersize=6,
label="余弦",
)
plt.plot(x, np.sin(x), "go", label="正弦") # color 和 marker 简写。绘制第二条曲线
plt.title("正余弦曲线")
plt.xlabel("弧度")
plt.ylabel("正余弦值")
plt.legend()
plt.show()

png

Matplotlib subplot 绘多幅图-遮挡

有时我们想在一个画布上绘制多幅图,此时,可以使用 subplot 方法。但注意,subplot 会自动创建子图的画布,会覆盖、遮挡低层画布绘制的图像。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5, 5, 101)

plt.subplot(1, 1, 1, facecolor="red") # 最低层画布
plt.plot(np.sin(x))

plt.subplot(
2, 2, 1, facecolor="cyan"
) # 中间层画布。注意该 2x2画布会覆盖上面整个画布,即使只画这一个图,其他区域也会被覆盖。注意与下面2x1的区别
plt.plot(np.sin(x))
plt.subplot(2, 2, 2, facecolor="red") # 中间层画布,
plt.plot(np.sin(x))
plt.subplot(2, 2, 3, facecolor="blue") # 中间层画布,
plt.plot(np.sin(x))
plt.subplot(2, 2, 4, facecolor="yellow") # 中间层画布,
plt.plot(np.sin(x))

# plt.subplot(2, 1, 1, facecolor='yellow')
# plt.plot(np.tan(x))
plt.subplot(2, 1, 2, facecolor="orange") # 最顶层画布。注意该画布只有下面一半的区域,因此不会覆盖上半区域
plt.plot(np.tan(x))

plt.show()

png

Matplotlib add_subplot 绘多幅图-堆叠

subplot 方法会用高层画布完全遮挡低层画布,包括低层画布的坐标刻度等信息。如果我们不想遮挡低层画布的未被高层画布覆盖的区域,可以使用 add_subplot 方法,它只遮挡绘图区,甚至低层画布的坐标轴刻度仍能保留下来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5, 5, 101)

fig = plt.figure()
ax1 = fig.add_subplot(111, facecolor="y") # 最低层画布颜色是黄色
ax1.plot(np.sin(x))
ax2 = fig.add_subplot(221, facecolor="c") # add_subplot 直接增加的是一个自带画布的绘图区(坐标轴)
ax2.plot(np.sin(x))
ax3 = fig.add_subplot(222, facecolor="b")
ax3.plot(np.cos(x))
ax4 = fig.add_subplot(212, facecolor="orange") # 最高层画布颜色是橙色
ax4.plot(np.tan(x))
plt.show()

png

并排画多张图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5, 5, 101)

fig = plt.figure()
ax1 = fig.add_subplot(221, facecolor="y")
ax1.plot(np.sin(x))
ax2 = fig.add_subplot(222, facecolor="c")
ax2.plot(np.sin(x))
ax3 = fig.add_subplot(223, facecolor="b")
ax3.plot(np.cos(x))
ax4 = fig.add_subplot(224, facecolor="orange")
ax4.plot(np.tan(x))
plt.show()

png

Matplotlib add_axes 绘多幅图-堆叠在任意区域

add_axes 绘图类似于 add_subplot,但能画在低层画布的任何区域,灵活性更好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5, 5, 101)

fig = plt.figure(facecolor="g")
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.8], facecolor="c")
ax2 = fig.add_axes([0.4, 0.35, 0.3, 0.3], facecolor="y")
ax1.plot(x, np.sin(x), "b")
ax2.plot(x, np.cos(x), "r")
ax1.set_title("正弦曲线")
ax2.set_title("余弦曲线")
ax2.text(0.5, 0.5, "我是余弦曲线的标识", transform=ax2.transAxes) # 绘制文本
plt.show()

png

取消坐标轴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5, 5, 101)

fig = plt.figure(facecolor="g")
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.8], facecolor="c")
ax2 = fig.add_axes([0.4, 0.35, 0.3, 0.3], facecolor="y")
ax1.plot(x, np.sin(x), "b")
ax2.plot(x, np.cos(x), "r")
ax1.set_title("正弦曲线")
ax2.set_title("余弦曲线")
ax2.text(0.5, 0.5, "我是余弦曲线的标识", transform=ax2.transAxes)
ax2.axis("off") # 取消坐标轴
plt.show()

png

Matplotlib subplots 绘多幅图-均匀分布

使用 add_subplot 能够画多幅图,但如果索引指定不好,可能会造成绘图区覆盖情况。使用 subplots 能够很好地进行并排图像绘画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5, 5, 101)

fig, ax = plt.subplots(2, 2)
ax[0][0].plot(x, np.sin(x))
ax[0][0].set_title("正弦曲线")
ax[0][1].plot(x, np.cos(x))
ax[0][1].set_title("余弦曲线")
ax[1][0].plot(x, np.tan(x))
ax[1][0].set_title("正切曲线")
ax[1][1].plot(x, np.tanh(x))
ax[1][1].set_title("双曲正切曲线")
plt.show()

png

可以看到上图像的横坐标刻度和下图像的标题遮挡,可以使用如下的两种方法处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5, 5, 101)

fig, ax = plt.subplots(2, 2)
ax[0][0].plot(x, np.sin(x))
ax[0][0].set_title("正弦曲线")
ax[0][1].plot(x, np.cos(x))
ax[0][1].set_title("余弦曲线")
ax[1][0].plot(x, np.tan(x))
ax[1][0].set_title("正切曲线")
ax[1][1].plot(x, np.tanh(x))
ax[1][1].set_title("双曲正切曲线")
fig.tight_layout() # 自动调整
plt.show()

png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5, 5, 101)

fig, ax = plt.subplots(2, 2)
ax[0][0].plot(x, np.sin(x))
ax[0][0].set_title("正弦曲线")
ax[0][1].plot(x, np.cos(x))
ax[0][1].set_title("余弦曲线")
ax[1][0].plot(x, np.tan(x))
ax[1][0].set_title("正切曲线")
ax[1][1].plot(x, np.tanh(x))
ax[1][1].set_title("双曲正切曲线")
plt.subplots_adjust(wspace=0.2, hspace=0.5) # 手动调整
plt.show()

png

Matplotlib subplot2grid 绘多幅图-不均匀划分

subplots 绘多幅图是均匀划分画布,如果想要不均匀划分,想把某些图占用更多的画布,可以采用 subplot2grid 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import matplotlib.pyplot as plt
import numpy as np

ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=2) # 画布不均匀划分
ax2 = plt.subplot2grid((3, 3), (0, 2), rowspan=3)
ax3 = plt.subplot2grid((3, 3), (1, 0), rowspan=2, colspan=2)

x = np.linspace(-5, 5, 101)

ax1.plot(x, np.sin(x))
ax1.set_title(r"$\sin$") # latex 数学公式
ax2.plot(x, np.cos(x))
ax2.set_title(r"$\cos$")
ax2.grid(color="r", ls="-.", lw=0.25) # 设置绘图区网格线
ax3.plot(x, np.tan(x))
ax3.set_title(r"$\tan$")
plt.tight_layout() # 自动调整子图间距
plt.show()

png

3D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits import mplot3d

fig = plt.figure()
# 创建绘图区域
ax = plt.axes(projection="3d")
# 构建xyz
z = np.linspace(0, 1, 100)
x = z * np.sin(20 * z)
y = z * np.cos(20 * z)
c = x + y
ax.scatter3D(x, y, z, c=c)
ax.set_title("3d Scatter plot")
plt.show()

png

字体大小调整

所有字体统一调整:

1
2
3
4
5
6
7
8
9
10
11
12
import matplotlib

font = {'weight' : 'bold', 'size' : 22}

matplotlib.rc('font', **font)

# 或者
matplotlib.rcParams.update({'font.size': 22})

# 或者
import matplotlib.pyplot as plt
plt.rcParams.update({'font.size': 22})

所有可调整的属性见 Customizing matplotlib page

单个对象字体设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import matplotlib.pyplot as plt

SMALL_SIZE = 8
MEDIUM_SIZE = 10
BIGGER_SIZE = 12

plt.rc('font', size=SMALL_SIZE) # controls default text sizes
plt.rc('axes', titlesize=SMALL_SIZE) # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE) # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE) # fontsize of the figure title

# 或者
import matplotlib

SMALL_SIZE = 8
matplotlib.rc('font', size=SMALL_SIZE)
matplotlib.rc('axes', titlesize=SMALL_SIZE)

参考文献

  1. Matplotlib教程(非常详细)