Skip to content

第 2 章 figure 与 axes

学习目标

  • 理解 Figure 与 Axes 的对象关系
  • 会用 plt.subplots() 创建画布与坐标系
  • 区分 pyplot 状态机写法与面向对象写法
  • 会用 plt.subplots(1, 2)plt.subplots(2, 2) 创建多子图并索引
  • 掌握 figsize 控制图形大小

2.1 Figure 与 Axes:两张对象

第 1 章的图能画出来,是因为 pyplot 替我们创建了对象。Matplotlib 的核心模型只有两类对象:

Figure(画布)是整张图的外壳:一张图有且仅有一个 Figure。它负责背景、整体大小,以及容纳所有坐标系。

Axes(坐标系)是真正画数据的区域,包含两个坐标轴、刻度、网格。一个 Figure 里可以有一个或多个 Axes。

用一句话记:Figure 是白纸,Axes 是白纸上画数据的那块区域;一张白纸可以裁成多块区域。

2.2 subplots:一手拿画布,一手拿坐标系

plt.subplots() 一次性创建 Figure 和它的 Axes,并同时返回两者:

python
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
print(type(fig))
print(type(ax))

真实输出:

<class 'matplotlib.figure.Figure'>
<class 'matplotlib.axes._axes.Axes'>

fig 是 Figure 对象,ax 是 Axes 对象。subplots 的完整拼写是「sub-plot」:一张画布里的若干绘图区,每个叫一个 subplot(子图)。

2.3 两种写法:pyplot 状态机 vs 面向对象

同一张图有两种画法。pyplot 状态机写法把「当前图」「当前坐标系」记在一个全局状态里,每个 plt.xxx 都作用在「当前」对象上:

python
import matplotlib.pyplot as plt
import numpy as np

days = np.arange(1, 8)
high = np.array([28, 31, 33, 30, 29, 32, 34])

plt.figure(figsize=(7, 4.2))
plt.plot(days, high, marker="o", label="最高气温")
plt.title("一周气温(pyplot 写法)")
plt.xlabel("日期")
plt.ylabel("气温(°C)")
plt.legend()
plt.show()

面向对象写法直接用拿到的 figax 对象,在对象上调用方法:

python
import matplotlib.pyplot as plt
import numpy as np

days = np.arange(1, 8)
high = np.array([28, 31, 33, 30, 29, 32, 34])

fig, ax = plt.subplots(figsize=(7, 4.2))
ax.plot(days, high, marker="o", label="最高气温")
ax.set_title("一周气温(面向对象写法)")
ax.set_xlabel("日期")
ax.set_ylabel("气温(°C)")
ax.legend()
plt.show()

两段代码输出同一张图,区别只在接口:pyplot 用 plt.plotplt.title;面向对象用 ax.plotax.set_title。图 2-1 是面向对象写法的真实输出:

图 2-1 面向对象写法:fig, ax = plt.subplots()

本书从本章起统一用面向对象写法。原因:一旦一个 Figure 里有多个 Axes,pyplot 的「当前坐标系」概念会让人混淆,而面向对象写法每个对象清清楚楚。pyplot 写法见 2.5 的对比图 2-2:

图 2-2 同一张图的 pyplot 状态机写法

2.4 figure 的参数:大小与分辨率

plt.subplots(figsize=(宽, 高))figsize 控制 Figure 的物理大小,单位是英寸。横轴宽、纵轴高的图适合趋势;近正方形适合分布。例:

python
fig, ax = plt.subplots(figsize=(7, 4.2))   # 宽 7 英寸,高 4.2 英寸

dpi(dots per inch,每英寸点数)控制清晰度,通常 100 左右即可;要放大打印时提高 dpi。图的大小直接决定图形在报告里的观感,第 9 章讲布局时会进一步用到。

2.5 一个 Figure 里多个 Axes

plt.subplots(行数, 列数) 把画布切成网格,返回的 axes 是一个数组。一行两列:

python
import matplotlib.pyplot as plt
import numpy as np

days = np.arange(1, 8)
high = np.array([28, 31, 33, 30, 29, 32, 34])
low  = np.array([19, 21, 22, 20, 19, 21, 23])

fig, axes = plt.subplots(1, 2, figsize=(10, 4.2))
axes[0].plot(days, high, marker="o")
axes[0].set_title("最高气温")
axes[0].set_xlabel("日期")
axes[0].set_ylabel("气温(°C)")
axes[1].plot(days, low, marker="s")
axes[1].set_title("最低气温")
axes[1].set_xlabel("日期")
axes[1].set_ylabel("气温(°C)")
plt.show()

图 2-3 真实输出:

图 2-3 subplots(1, 2):一行两列两个坐标系

axes[0] 是左图、axes[1] 是右图。两行两列时,axes 是二维数组,用 axes[行, 列] 索引:

python
fig, axes = plt.subplots(2, 2, figsize=(9, 7))
axes[0, 0].plot(...)   # 左上
axes[0, 1].plot(...)   # 右上
axes[1, 0].plot(...)   # 左下
axes[1, 1].plot(...)   # 右下

axes.flat 可以把二维数组拍平,方便循环设置共同属性。图 2-4 是 2×2 网格的真实输出:

图 2-4 subplots(2, 2):2×2 网格

2.6 读懂报错:找错对象

面向对象写法最常见的报错是在错误的对象上调用了方法

错误一:把 subplots 返回的 axes 数组当成单个 Axes 用:

>>> fig, axes = plt.subplots(1, 2)
>>> axes.plot([1, 2, 3])
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    axes.plot([1, 2, 3])
AttributeError: 'numpy.ndarray' object has no attribute 'plot'

解读:subplots(1, 2) 返回的是由两个 Axes 组成的数组(numpy.ndarray),数组本身没有 plot 方法。要画数据,得用 axes[0].plot(...)axes[1].plot(...)

错误二:在 Figure 上调用绘图方法:

>>> fig = plt.figure()
>>> fig.plot([1, 2, 3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    fig.plot([1, 2, 3])
AttributeError: 'Figure' object has no attribute 'plot'

解读:plotAxes 的方法,不是 Figure 的方法。Figure 只负责当容器。想画图:要么用 plt.plot(pyplot 自动取当前 Axes),要么用 fig.add_subplot() 先创建 Axes 再画。

动手实践

  1. 运行 fig, ax = plt.subplots(),分别打印 type(fig)type(ax),确认对象类型。
  2. 用面向对象写法画出第 1 章的一周气温图(复现图 2-1)。
  3. plt.subplots(1, 2) 画两张并排的图:左边最高气温、右边最低气温,复现图 2-3。
  4. plt.subplots(1, 2) 改成 plt.subplots(2, 2) 并各画一张图,复现图 2-4。
  5. 故意执行 axes.plot([1, 2, 3])(其中 fig, axes = plt.subplots(1, 2)),阅读报错,说明问题出在哪。

常见错误

错误写法现象原因
axes.plot(...),其中 axes 来自 subplots(1, 2)AttributeError: 'numpy.ndarray' object has no attribute 'plot'subplots 返回的是 Axes 数组,要用 axes[0]axes[1] 索引
fig.plot(...)AttributeError: 'Figure' object has no attribute 'plot'plot 是 Axes 的方法,不是 Figure 的方法
忘了 fig, ax = plt.subplots() 直接用 axNameError: name 'ax' is not defined先创建再使用
混用 plt.titleax.plot 后布局混乱标题作用在「当前坐标系」上pyplot 函数作用于全局当前对象;建议全用面向对象写法
plt.subplots(2, 2) 后用 axes[0] 索引axes[0] 返回一整行(仍是数组),再 .plot()AttributeError: 'numpy.ndarray' object has no attribute 'plot'二维数组要用 axes[0, 0] 两个下标

章末练习

基础

  1. 写出 plt.subplots() 的返回值分别是什么对象,并各用一个例子说明它们的职责。
  2. 用面向对象写法画一张 5 个点的折线图,标题为「练习图」,保存为 p2.png
  3. plt.subplots(1, 2) 画两张并排折线图,左图 [1, 2, 3]、右图 [3, 2, 1]

提高

  1. 分别用 pyplot 写法和面向对象写法画同一张图,并把两份代码贴在一起对比,指出各自「当前状态」如何管理。
  2. plt.subplots(2, 2, figsize=(8, 6)) 画 2×2 网格,四个子图分别画正弦、余弦、正切、常数的曲线(用 np.linspace 生成 x)。

挑战

  1. plt.subplots(1, 2) 返回的 axesfor 循环遍历,给每个子图画一条不同的线,并统一设置网格,体会 axes.flat 的用法。
  2. 思考并实验:fig, ax = plt.subplots() 之后再调用 fig.add_subplot() 会发生什么?plt.gca() 返回的是什么?用代码验证你的猜想。

章末自测

每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。

  1. plt.subplots() 的返回值是?
    • A. 只有一个 Figure
    • B. (fig, ax) 两个对象
    • C. 只有一个 Axes
    • D. 一个列表
  2. 谁负责容纳多个坐标系?
    • A. Axes
    • B. Figure
    • C. Grid
    • D. Ticks
  3. 真正画数据线的是哪个对象的方法?
    • A. Figure.plot
    • B. Axes.plot
    • C. plt.figure
    • D. Canvas.plot
  4. fig, axes = plt.subplots(1, 2) 后,type(axes) 是?
    • A. Axes
    • B. Figure
    • C. numpy.ndarray
    • D. list
  5. 2×2 网格里右下角的子图用哪个下标访问?
    • A. axes[2]
    • B. axes[1, 1]
    • C. axes[2, 2]
    • D. axes[0, 1]
  6. figsize=(8, 6) 的单位是?
    • A. 像素
    • B. 英寸
    • C. 厘米
    • D. 点(pt)
  7. 面向对象写法里,设置标题应调用?
    • A. plt.title()
    • B. ax.set_title()
    • C. fig.title()
    • D. ax.title()
  8. axes.plot([1, 2, 3])axes 来自 subplots(1, 2) 时,结果是?
    • A. 正常画出折线
    • B. 只画第一个子图
    • C. AttributeError: 'numpy.ndarray' object has no attribute 'plot'
    • D. 画出两条线
  9. pyplot 状态机写法中,plt.plot 作用在哪个对象上?
    • A. 所有 Figure
    • B. 「当前」Figure 里的「当前」Axes
    • C. 新创建的 Axes
    • D. 无对象
  10. 关于两种写法,正确的说法是?
    • A. 面向对象写法不支持多子图
    • B. pyplot 写法不支持 savefig
    • C. 两者都能画出同样的图,面向对象更适合复杂布局
    • D. 必须二选一,不能混用