跳转到内容

Figure/Axes 与基础图形

计算机基础 · Matplotlib

前置知识:ndarray、shape 与 dtype

本章目录

本章学习目标

  • 区分 Figure 与 Axes 并使用对象式接口
  • 按分析问题选择折线、柱图或散点
  • 设置清楚的坐标、单位和离线导出

一、Figure 是整张画布,Axes 是绘图区

Matplotlib 的 Figure 表示整张图,Axes 表示其中一个含坐标系统的绘图区,Axis 则表示具体坐标轴对象。常用入口 fig,ax=plt.subplots() 同时创建二者,再通过 ax.plot、ax.set_xlabel 等明确操作目标。这比依赖 pyplot 当前状态更容易阅读,多图时也不容易画到错误位置。课程示例按 Matplotlib 3.10.6 的稳定 API 验证,使用 Agg 后端生成图片,无需图形界面或网络。

Artist 是图中可绘制对象的总称,线条、文字、图例都属于这一模型。绘图调用返回对象,可在后续调整颜色、线宽或可见性,但初学不必背下整个对象树。先理解数据输入到 Axes,Figure 负责布局和保存,就能完成大多数分析图。命名 ax 比命名 plt1 清晰;创建多个 Axes 时,要明确每个绘图区回答什么问题而不是仅为填满画布。

二、图形选择要跟着问题走

折线图适合有自然顺序的连续变化,例如按日期排列的每日净额;柱图适合类别之间比较,例如不同渠道的付费人数;散点图适合观察两个数值变量关系,例如访问次数与购买额。不要把无序渠道用线连接,让读者误以为相邻渠道之间存在连续趋势;也不要只因为折线更好看就对原始乱序日期直接连线。

小数据日期一、二、三对应净额十、十五、十二:折线从十上升到十五,再下降到十二;渠道 A、B、C 付费人数五、八、三:柱高分别五、八、三;散点输入访问 [1,2,3] 与金额 [10,15,12] 只有三个观测,不能据此认定因果或稳定相关。作图前检查两个坐标长度一致,缺失是否应保留断点,类别是否已经聚合成每类一行。

三、标题、坐标与单位都是证据

图题说明指标和范围,横轴注明时间或类别,纵轴注明单位,图例说明不同系列代表什么。不要只写 value、date 等程序字段名,让读者猜测。柱图的长度编码依赖基线,通常从零开始,截断基线会夸大差异;折线图可以按任务缩放,但需要清楚显示刻度和范围。对数轴适合跨数量级比较,却不能容纳非正值,且其距离含义与线性轴不同。

每条线用不同颜色和线型,必要时加标记点,避免只靠颜色区分。图例的位置不要遮住关键数据。网格线宜弱于数据线,数字格式根据单位设置,不把百分比又乘一次一百。真正可靠的图不靠装饰,而靠准确编码和明确口径;例如“净额”必须说明已经扣退款,不能把累计额与每日额放在同一轴却使用含糊图例。

四、字体与后端的可移植性

中文能否显示取决于本地字体,并不是安装 Matplotlib 就自动拥有中文字体。示例依次查找 Microsoft YaHei、SimHei、Noto Sans CJK SC 等,若均不存在则打印提示并使用英文标签,避免生成满图缺字方框。字体选择是环境检查,不能把字体名称写进配置就认为一定安装成功;课程不会联网下载字体。

后端决定如何渲染输出。在自动脚本中必须在 import pyplot 前调用 matplotlib.use('Agg'),以便在无桌面环境运行。网页交互由浏览器 JavaScript 模拟固定数据作图,不执行 Matplotlib,也不反映本地字体和后端。学习时先运行脚本确认实际 PNG,再看交互里修改坐标范围如何改变视觉印象;两者共同帮助理解绘图规则,但需要明确不同执行环境。

五、保存、关闭与图像检查

使用 fig.savefig 指定课程 outputs 文件名、dpi 与 bbox_inches='tight',然后 plt.close(fig) 释放图对象;循环生成很多图却不关闭会持续占用资源。保存成功不等于图正确,至少检查文件存在且非空,再打开核对文字、坐标、图例、裁切和颜色。dpi 影响位图像素,不会自动纠正错误的数据口径。

配套代码生成三个基础图,数据量和手算一致。新增乱序日期时先排序并保持 x、y 对齐;新增空数据时先决定是否展示“无观测”,而不是画一个空白坐标框假装完成报告。面试中说明为什么选择这种图、如何编码指标和有哪些误读风险,比熟记十个绘图函数更重要。完成任务应能独立从数值数据得到一张别人可以理解并复查的图。

交互演示:观察机制

完整示例与实际输出

from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import font_manager
ROOT=Path(__file__).resolve().parents[1]
out=ROOT/'outputs'; out.mkdir(exist_ok=True)
candidates=['Microsoft YaHei','SimHei','Noto Sans CJK SC','WenQuanYi Zen Hei']
available={f.name for f in font_manager.fontManager.ttflist}
chosen=next((f for f in candidates if f in available),None)
if chosen:
    plt.rcParams['font.sans-serif']=[chosen]
    plt.rcParams['axes.unicode_minus']=False
else:
    print('No CJK font: using English labels; install a local CJK font for Chinese figures.')
days=[1,2,3]; net=[10,15,12]
assert len(days)==len(net)
fig,axes=plt.subplots(1,3,figsize=(11,3.4),layout='constrained')
axes[0].plot(days,net,marker='o',label='daily net')
axes[0].set(title='Daily net amount',xlabel='Day',ylabel='Amount (CNY)')
axes[0].legend()
axes[1].bar(['A','B','C'],[5,8,3])
axes[1].set(title='Paid users by channel',xlabel='Channel',ylabel='Users',ylim=(0,10))
axes[2].scatter([1,2,3],[10,15,12])
axes[2].set(title='Visits and spend',xlabel='Visits',ylabel='Spend (CNY)')
for ax in axes:
    ax.grid(axis='y',alpha=.2)
path=out/'mpl-basics.png'; fig.savefig(path,dpi=150,bbox_inches='tight'); plt.close(fig)
assert path.is_file() and path.stat().st_size>1000
print('saved',path.name,'daily total',sum(net),'font',chosen or 'English fallback')

实际运行通过CPU Python execution.

saved mpl-basics.png daily total 37 font Microsoft YaHei
Figure/Axes 与基础图形:配套脚本实际生成的图形
配套示例实际生成 · 合成教学数据

五道自测:先回答,再核对

01 · Figure 与 Axes 的区别是什么?

查看提示

画布和绘图区。

查看参考答案

Figure 是整张图;Axes 是其中一个坐标绘图区。

02 · 无序渠道之间该默认用折线连接吗?

查看提示

连接是否有顺序含义?

查看参考答案

通常不应,类别比较优先柱图。

03 · 柱图为何通常从零开始?

查看提示

柱长编码数值。

查看参考答案

截断基线会夸大长度差异,使视觉比例失真。

04 · 字体名称写入 rcParams 就保证中文可见吗?

查看提示

字体需实际存在。

查看参考答案

不能,需查本地字体并检查生成图,缺失时有明确 fallback。

05 · Agg 后端应该在何时设置?

查看提示

pyplot 导入前。

查看参考答案

在导入 matplotlib.pyplot 之前设置。

两项动手任务

任务 1 · 画 A、B 两渠道收入 [100,120] 的柱图,基线为零并标明元。

提示

使用 Axes 接口且保存在 outputs。

完整参考解答与检查方式

英文标签避免无中文字体环境缺字;保留数据对应顺序。

from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
ROOT=Path(__file__).resolve().parents[1]
out=ROOT/'outputs'; out.mkdir(exist_ok=True)
fig,ax=plt.subplots(figsize=(5,3),layout='constrained')
ax.bar(['A','B'],[100,120]); ax.set(title='Revenue by channel',ylabel='Revenue (CNY)',ylim=(0,140))
p=out/'task-channel.png'; fig.savefig(p,dpi=150); plt.close(fig)
assert p.stat().st_size>1000
print(p.name)

任务 2 · 将日期 [3,1,2]、净额 [12,10,15] 正确连成时间折线,说明为何不能只排序日期。

提示

先把记录成对排序。

完整参考解答与检查方式

仅排序日期会破坏对应关系;排序后的净额为十、十五、十二。

from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
pairs=sorted(zip([3,1,2],[12,10,15])); x,y=zip(*pairs)
assert list(y)==[10,15,12]
ROOT=Path(__file__).resolve().parents[1]
out=ROOT/'outputs'; out.mkdir(exist_ok=True)
fig,ax=plt.subplots(); ax.plot(x,y,marker='o'); ax.set(xlabel='Day',ylabel='Net (CNY)')
p=out/'task-sorted-line.png'; fig.savefig(p,dpi=150,bbox_inches='tight'); plt.close(fig)
assert p.is_file()
print(pairs)

官方资料与掌握状态

完成两项任务后,按独立完成程度记录。状态会尽量保存在当前浏览器,可导出备份。

动手任务记录

掌握状态与复习

关掉资料独立完成后记 A;提示后完成记 B;需要重学记 C。

记录尽量保存在当前浏览器,建议定期导出备份。