1.2 Figure 和 Axes 对象


文档摘要

1.2 Figure 和 Axes 对象 本节摘要:Figure 是 Matplotlib 的顶层画布,一张图上所有的内容都挂在它里面;Axes 是画布上的坐标系区域,是真正把数据画出来的地方;而 Artist 是所有可见元素(线、文本、刻度、图例)的共同基类。理解这三者的层级关系,是驾驭 Matplotlib 的第一道门。本节先把"为什么要分画布和坐标系两层"的直觉讲清楚,再逐个拆开这两个对象,最后对比 pyplot 快捷接口和面向对象接口的差异,帮你彻底摆脱"plt 和 ax 到底用哪个"的困惑。

1.2 Figure 和 Axes 对象

本节摘要:Figure 是 Matplotlib 的顶层画布,一张图上所有的内容都挂在它里面;Axes 是画布上的坐标系区域,是真正把数据画出来的地方;而 Artist 是所有可见元素(线、文本、刻度、图例)的共同基类。理解这三者的层级关系,是驾驭 Matplotlib 的第一道门。本节先把"为什么要分画布和坐标系两层"的直觉讲清楚,再逐个拆开这两个对象,最后对比 pyplot 快捷接口和面向对象接口的差异,帮你彻底摆脱"plt 和 ax 到底用哪个"的困惑。

本节目标

阅读完本节,你应当能够:

  1. 用自己的话解释 Figure 与 Axes 的分工,以及为什么要拆成两层
  2. 分得清 Axes(坐标系区域)和 Axis(横轴或纵轴)这两个极易混淆的词
  3. 说出 Artist 是什么,以及它和 Figure、Axes 的包含关系
  4. 区分 pyplot 快捷接口和面向对象接口,判断各自适用场景
  5. 读懂并复现"一个 Figure 里放多个 Axes"的布局代码

一、为什么非要分"画布"和"坐标系"两层

第一次看 Matplotlib 代码的人,几乎都会被 fig, ax = plt.subplots() 这句弄糊涂:为什么画个图要返回两个东西?figax 到底谁是图?

答案藏在一个很朴素的现实里:一张纸可以同时画好几幅互不相干的小图,但每幅小图又各有各的横轴纵轴。 如果 Matplotlib 只给你一个对象,你既没法描述"这张大纸多大",也没法描述"这幅小图在哪、它的轴范围是多少"。所以它干脆把职责拆成两层——纸是一层,纸上的图是另一层。

  • Figure 是那张纸,管的是全局:整张图多大、背景什么色、分辨率多高、纸上一共放几幅小图。
  • Axes 是纸上的一幅图,管的是局部:这幅图自己的两条轴、自己的标题、自己的数据。

这个拆法解决了一个很实际的场景——多子图。你想在一张报告图里并排放"销售额折线"和"各区域柱状图",就得先有一张能装下两个坐标系的画布,再往里面塞两个 Axes。如果只有一层对象,这个需求根本表达不了。

💡 关键直觉:别把 Figure 和 Axes 想成两个平级的"图",它们是从属关系——Axes 是"长在" Figure 里的。画布是容器,坐标系是容器里的内容。这个主次关系一旦立住,后面所有接口都好记了。

二、Figure:那张纸

Figure 就是整张画布。它自己不画数据,只负责"撑场面"——定尺寸、定底色、定分辨率,以及提供一个让 Axes 挂上去的地方。

创建一个 Figure,最直接的方式是 plt.figure(),它会返回一个 Figure 对象。但更常用的其实是 plt.subplots(),因为后者在创建画布的同时,顺手就帮你挂好了一个 Axes——对"画一幅图"这个最常见需求来说,它一步到位。

import matplotlib.pyplot as plt fig = plt.figure(figsize=(8, 6)) # 一张 8×6 英寸的画布 ax = fig.add_subplot(111) # 在画布上挂一个坐标系 ax.plot([1, 2, 3], [1, 4, 2])

上面这段和 plt.subplots() 做的事本质一样,只是拆得更细,把"建画布"和"挂坐标系"分成了两步。拆开看,你才能看清 Figure 的职责。

Figure 身上有几个最常用的属性值得现在记一下:figsize 定画布的长宽(单位是英寸),dpi 定每英寸的像素点数(影响清晰度),facecolor 定背景色。这些名字在第 1.5 节保存图像时还会反复出现。

⚠️ 常见坑figsize 的单位是英寸,不是像素,也不是厘米。很多人想"把图调成 800 像素宽",结果写了个 figsize=(800, 600),得到一张巨大的画布。真正的像素宽,是 figsize 的英寸数乘上 dpi 算出来的。

三、Axes:纸上那个坐标系

Axes 是真正干活的地方。你所有绘图函数——画线、画点、画柱——最终都作用在某个 Axes 上。一个 Axes 里包含了一整组元素:横轴、纵轴、标题、轴标签、刻度、图例、以及画出来的数据图形本身。

这里必须先拆一个让无数人栽跟头的词:Axes 和 Axis 不是一回事。

  • Axes(读音接近"艾克西兹",复数形式):指整个坐标系区域,含两条轴、标题、图例、绘图区——是一个"框"。
  • Axis(单数):只指横轴或纵轴那一条线——是"框"的一条边。

你可以这样记:一个 Axes 里面有两条 Axis。ax.set_title 是给整个 Axes 设标题,ax.set_xlabel 是给其中一条 Axis 设标签。前者是"框"的级别,后者是"边"的级别。

import matplotlib.pyplot as plt fig, ax = plt.subplots() ax.plot([1, 2, 3], [2, 1, 3]) ax.set_title("标题挂在 Axes 上") # 整个坐标系区域 ax.set_xlabel("这是 x 轴(Axis)") ax.set_ylabel("这是 y 轴(Axis)") ax.set_xlim(0, 4) # 控制一条轴的范围 ax.set_ylim(0, 4)

这段代码里,set_title 作用在 Axes 整体上,set_xlimset_ylim 则分别作用在两条 Axis 上。把这两类方法分清,你就不会再把"设标题"和"设轴"混在一起。

四、Artist:你看见的一切

Figure 和 Axes 之外,还有第三个词会反复出现——Artist。它是 Matplotlib 里所有可见元素的基类:一条折线是一个 Artist,一个标题文本是一个 Artist,一个刻度线也是一个 Artist。

它不像 Figure、Axes 那样是"一个具体的对象",而是一个"类别"——Figure 和 Axes 本身其实也是 Artist 的特例。这张层级图能帮你看清三者的关系:

这张图的信息量很大,但核心就一条:从 Figure 往下,一层层都是 Artist。 你现在不必记住每个子元素的名字,只需要建立一个直觉——在 Matplotlib 里,你调的任何"外观",本质上都是在改某个 Artist 的属性。1.4 节讲的颜色、线型、标记,全是在给这些 Artist 设属性。

💡 关键直觉:当你看到一张图想"这个字太小了想改大点",其实是在问"这个文本 Artist 的字号属性是哪个"。Matplotlib 里几乎所有的定制,都是"找到对应的 Artist,改它的属性"。这个思维方式能让你从"死记参数"升级到"按图索骥"。

五、两种接口:plt 快捷,还是 ax 精确

搞懂对象之后,还有一个绕不开的困惑:为什么有的教程写 plt.plot(...),有的写 ax.plot(...)?这两个都能画图,区别在哪?

答案是 Matplotlib 提供两套接口,一套快捷、一套精确:

  • pyplot 接口plt.xxx):背后维护一个"当前画布、当前坐标系"的隐式状态。你连续调 plt.plotplt.title,它都知道作用在最近那个坐标系上。写起来短,适合快速探索、画单张图。
  • 面向对象接口fig.xxx / ax.xxx):你手里明确握着 figax 这两个对象,所有操作都显式指定作用对象。写起来稍长,但不会含糊,适合画多子图、做精细控制。
维度 pyplot 快捷接口 面向对象接口
写法 plt.plot(x, y) ax.plot(x, y)
作用对象 隐式,靠"当前状态" 显式,直接指定 ax
代码长度 略长
画单张图 方便 稍繁琐但清晰
画多子图 容易搞混作用对象 清晰,各管各的 ax
推荐场景 快速探索、临时分析 正式产出、多图、可维护

我的建议很明确:哪怕画单张图,也优先用面向对象接口。 原因很实在——plt.plot 那套"当前状态"在只有一个图时看不出问题,一旦你要加第二张子图、或者代码要给别人维护,隐式状态就开始咬人:你以为是画到 A 图,结果画到了 B 图。ax.plot 从第一天就把"画在哪"写清楚,习惯成本极低,收益却很大。

import matplotlib.pyplot as plt # 面向对象接口:一次建 1 行 2 列,共两个坐标系 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) ax1.plot([1, 2, 3], [1, 4, 2]) # 明确画在左边这个 ax1 ax2.plot([1, 2, 3], [3, 1, 2]) # 明确画在右边这个 ax2 ax1.set_title("左图") ax2.set_title("右图")

这段代码里,"画在哪"这件事从对象名 ax1ax2 上一眼就能看出来。等你在第 2 章学多子图时,这套写法会直接复用。

图 一个 Figure 包含多个 Axes 的布局示意

图 一个 Figure 包含多个 Axes 的布局示意

一句话点题:Figure 是"一",Axes 是"多"——一张画布上可以并排放多个坐标系,每个坐标系各画各的图,这就是多子图的全部秘密。

六、三种建 Axes 的方式,各用在什么时候

创建 Axes 的入口不止 plt.subplots 一个,Matplotlib 给了好几种,各有各的用场。把它们分清,你就能在合适的地方用合适的工具。

  • plt.subplots:一次创建画布加一组规整网格的 Axes,最常用。它返回 (fig, axes),单个 Axes 时 axes 是一个对象,多个时是一个数组。绝大多数场景用这一个就够。
  • fig.add_subplot(rows, cols, index):在已有画布上按"行、列、序号"逐个加 Axes,适合已经握着画布、想慢慢往里填的情况。序号从 1 开始,按"从左到右、从上到下"数。
  • fig.add_axes([left, bottom, width, height]):按"相对画布的比例"精确定位一个 Axes。四个数分别是左下角的横坐标、纵坐标、宽、高,都是 0 到 1 之间的小数,适合画中画、非规整布局这类要把图摆到任意位置的场景。
import matplotlib.pyplot as plt fig = plt.figure(figsize=(10, 6)) ax1 = fig.add_subplot(2, 2, 1) # 2 行 2 列里的第 1 个 ax2 = fig.add_subplot(2, 2, 2) # 第 2 个 ax3 = fig.add_axes([0.6, 0.1, 0.3, 0.3]) # 在右下角自定义一个小图 ax1.plot([1, 2, 3], [1, 4, 2]) ax2.plot([1, 2, 3], [3, 1, 2]) ax3.plot([1, 2, 3], [2, 2, 2])

add_axes 平时用得不多,但它是理解"坐标是相对画布的比例"这套坐标系的好例子:leftbottom 定左下角位置,widthheight 定尺寸,全部用画布宽高的比例表达。这套"相对坐标"的思维,第 4 章讲坐标变换时还会更深入地出现,现在先混个眼熟就行。

七、Figure 级与 Axes 级:总标题和子标题

搞懂两层对象的分工后,有个细节会变得很自然——总标题和子标题是两个不同的方法。

  • ax.set_title 给某一个 Axes 设标题,属于"子标题"。
  • fig.suptitle 给整张 Figure 设总标题,显示在所有 Axes 之上,属于"总标题"。

当你画一张含多个子图的大图时,通常两者都要:suptitle 说明整张图在讲什么(比如"华东各城市销售对比"),每个 set_title 说明各自子图在讲什么(比如"上海""杭州")。

import matplotlib.pyplot as plt fig, axes = plt.subplots(1, 2, figsize=(10, 4)) fig.suptitle("华东销售对比(总标题)", fontsize=16) axes[0].plot([1, 2, 3], [1, 4, 2]) axes[0].set_title("上海") axes[1].plot([1, 2, 3], [3, 1, 2]) axes[1].set_title("杭州")

这个"总标题加子标题"的组合,正是 Figure 和 Axes 两层对象各自承担职责的直接体现:总标题挂在 Figure 上,子标题挂在各自的 Axes 上。能分清这两个方法分别改的是谁,就说明对象模型真正想明白了。

⚠️ 常见坑:把总标题误写成 fig.set_title。Figure 没有 set_title 这个常规方法,它的总标题方法叫 suptitle(super title 的缩写,即"总标题")。记不住时就想:总标题在子标题之上,所以带一个"super"。

八、两个关于对象的常见疑问

Axes 到底是单数还是复数?

它始终是复数形式(词尾带 s),即便你只画一个坐标系,这个对象在 Matplotlib 里也叫 Axes,而不是 Axe 或 Axis。你可以把它当成一个"永远带 s"的专有名词——它指代的是"坐标系区域"这个整体,而不是某一条轴。一个坐标、多个坐标,都叫 Axes;单数的 Axis 只用来指横轴或纵轴那一条线。

我不确定该用 plt 还是 ax 时,怎么选?

默认选 ax(面向对象接口)。只有一种情况 plt 更省事:你只想临时画一张图快速看一眼,不打算保留成代码,也不会出现多图需求。除此之外,都用 ax——它从一开始就写清楚"画在哪",代码越往后、图越复杂,越不容易出错。

一次建了好几个 Axes,怎么分别拿到它们?

plt.subplots 在子图多于一个时,返回的第二个值是一个数组(而不是单个对象),数组里的每个元素对应一个 Axes,按"从左到右、从上到下"的顺序排列。你可以解包一次拿全,也可以用下标单独取。

fig, axes = plt.subplots(2, 2) # 返回 2×2 的 Axes 数组 axes[0, 0].plot([1, 2, 3], [1, 2, 3]) # 左上 axes[1, 1].plot([1, 2, 3], [3, 2, 1]) # 右下

这里 axes[0, 0] 是左上角、axes[1, 1] 是右下角,下标就是"第几行、第几列"。这个"返回值是数组"的细节,等第 2 章专门讲多子图时会成为主力写法,现在先知道有这个形态即可。

一节小结

  • Figure 是画布,Axes 是坐标系,两者是从属关系,不是平级关系。
  • 分两层是为了支持"一张纸放多幅图",这是多子图能成立的前提。
  • Axes(复数,整个区域)≠ Axis(单数,一条轴),一个 Axes 里有两条 Axis。
  • Artist 是所有可见元素的基类,线、文本、刻度、图例都是它,Figure 和 Axes 本身也是它的特例。
  • pyplot 接口靠隐式状态,面向对象接口靠显式对象,画多图或正式产出时优先面向对象接口。
  • figsize 单位是英寸不是像素,真实像素由 figsizedpi 决定。

下一节我们正式动手,把数据真正画进这个坐标系里——plot、scatter、bar、hist 四个最基础的绘图函数,以及它们各自适合什么样的数据。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U