想象你拿到一套毛坯房。客厅、卧室、厨房都还没隔开,墙是灰的,灯是白的,家具一件没有。直接住进去,能住,但没人想住。画图也一样——Matplotlib 默认给你的只是一张空白画布和一个光秃秃的坐标系,能出图,但离“专业”差得很远。 装修一套房子要分几步:先砌墙隔出房间(子图),再按需求定制隔断(gridspec 布局),然后定一本装修手册统一全屋风格(rcParams 全局样式),挑选统一的家具色系(colormap),在门口挂门牌、墙上贴便利贴(图例与注释),最后请摄影师拍高清样板间(高清导出)。六个步骤走完,你的图就能从“毛坯房”变成“精装样板间”。 这篇文章,就是你的装修经理上岗指南。
Matplotlib 子图与样式是 AI 工程师的“出图基本功”:论文配图、实验对比、模型报告、仪表盘都靠它撑场面。学会它,你画的每一张图都专业、清晰、可复现。
目录
可视化演示
展示Matplotlib专业图表制作的核心技巧:多子图布局系统、rcParams全局样式配置、colormap类型选择与匹配、高清导出参数,交互切换四种模式。
本节配有交互动效(进入视口后自动加载,约 3KB)。
子图布局:subplots与gridspec系统 subplots创建规则行列网格,gridspec支持不等大小子图灵活排列 fig, axes = plt.subplots(nrows, ncols) 合理布局让多图对比清晰有序。
子图基础:subplots 一行隔出房间
装修第一步是砌墙。你站在毛坯房里,手里拿着户型图,心里盘算着:“客厅放数据、卧室放对比、厨房放误差。” 这时候最省力的做法是喊来工头 plt.subplots,他拿激光水平仪一照,刷刷几道墙,立刻给你隔出大小一样的房间。你只要告诉他:“给我 2 行 3 列的房间”,剩下的他全包。
工头干活很快,因为他的逻辑很朴素:行数 × 列数 = 房间总数。plt.subplots(2, 3) 会返回两个东西:一个是整栋房子的图纸(fig),另一个是所有房间的钥匙串(axes)。这个钥匙串不是普通列表,而是一个二维的“房号网格”——你想进第 1 行第 2 列的房间,就写 axes[0][1]。
最后给你自己定个规矩:以后画子图,一律用 ax.plot() 而不是 plt.plot()。前者是“进到指定房间去布置家具”,后者是“在整个毛坯房里随便画”,房间一多,后者就乱了套。另外,sharex=True 这个参数就像邻居两家共用一面承重墙——大家 x 轴刻度对齐,缩放联动,绝对不会出现上下两张图 x 轴范围对不上的尴尬。
隔完房间,毛坯房的骨架就出来了。记住工头的口头禅:先想清要几行几列,再动手画。房间隔对了,后面所有家具布置都是水到渠成的事。
- 行数 \(n_{\text{row}}\)户型图上横向要排几排房间,对应
subplots(2, 3)里的第一个数字 2。 - 列数 \(n_{\text{col}}\)每排并排几个房间,对应第二个数字 3。
- 子图总数 \(n_{\text{ax}}\)两行三列一共隔出 6 个房间,也是钥匙串
axes里子图对象的个数。
子图布局:GridSpec 设计师的定制隔断
工头隔的房间全是豆腐块,大小一样、千篇一律。可真住进去你就发现:客厅要放投影看数据,得大;卫生间只放个误差条,一小间就够。这时候你需要请设计师GridSpec出手。他先在地面画出精细的网格线(网格),再告诉你说:“这块地皮我想占两格,那块只要半格。”
GridSpec 是比 subplots 更底层的工具。你先用 fig.add_gridspec(2, 3) 画好一张 2 行 3 列的“隐形网格纸”,然后通过 fig.add_subplot(gs[0, :]) 把第一行整行都划给一个房间,或者用 gs[1, 0:2] 让第二行左边两格合成一个宽房间。这种“切片”语法就像在地图上圈地,想圈多大圈多大。
最常用的装饰是 width_ratios=[2, 1]——它直接规定左边两间房的宽度总和与右边一间房的比例是 2:1。看看下面这个真实场景:左侧大子图画两条模型的 loss 曲线,右侧小子图只画一个柱状对比。左边信息量大,右边点到为止,主次分明,这就是专业论文配图的排版感。
隔断墙砌好了,房子的骨架彻底定型。布局决定气质——同样的内容,排版主次分明,观感立刻翻倍。
- 比例系数 \(r_1\)、\(r_2\)设计师在图纸上标的两列“权重”,比如
width_ratios=[2, 1]里的 2 和 1,它不是真实像素,而是分配筹码。 - 实际宽度 \(w_1\)、\(w_2\)扣除边距后,两列子图真正分到的像素宽度。
- 比例等式宽度之比严格等于权重之比,所以
2:1的权重就得到“左宽右窄”的视觉。
全局样式:rcParams 一本装修手册管全家
墙砌好了,接下来要定风格。最怕的就是每个房间各装各的:客厅北欧风、卧室赛博朋克、厨房复古田园,客人进来以为走错了楼。聪明人会在开工前买一本装修手册(rcParams),把全屋的墙纸、地板、灯光色温一次性定好。Matplotlib 里的 rcParams 就是这本手册。
rcParams 说白了就是一个全局字典,里面存着几十个“全屋默认值”:'font.size' 是字体大小的默认档位,'axes.grid' 是每个房间要不要默认铺网格地板,'figure.dpi' 是整栋楼的出图分辨率。你只需要在画图前写一行 plt.rcParams['axes.grid'] = True,之后所有子图自动带上网格,不用每个房间单独嘱咐。
想偷懒换风格,还有更快的办法:plt.style.use('ggplot') 就像一键套用设计师样板间——墙纸、灯光、地砖全部换成 R 语言 ggplot 风格的米灰底加深色边框。最常见的坑是中文乱码:手册里默认字体是西文字体,遇到中文就显示成方块。解决方案是在手册里补一条“中文字体白名单”:plt.rcParams['font.sans-serif'] = ['SimHei'],顺便把负号显示修好。
手册定好了,全屋风格统一,客人进门不再精分。一次配置,全局生效,这就是 rcParams 的效率哲学。
- 键集合 \(\mathcal{K}\)手册里所有条目名的集合,比如
'font.size'、'axes.grid'、'figure.dpi'。 - 手册取值 \(\text{rcParams}[k]\)某一条目对应的默认值,比如
'font.size'对应 11 号字。 - 应用到画布画图时每个房间都会自动读取手册里的值,所以你在任何子图里都不用重复设置。
颜色管理:colormap 全屋统一色系
装修风格定了,接下来挑软装。最考验审美的环节来了:客厅窗帘是雾霾蓝,卧室床单却是荧光粉,这种配色会让客人连夜逃走。colormap(色图)就是你的软装设计师,它给你一整条经过精心调校的“色卡”,你只管按色卡取色,绝不会翻车。
Matplotlib 里最推荐的是 viridis 这条色卡。它从深紫渐变到明黄,全彩盲友好、灰度打印也能区分、相邻色阶变化均匀——专业术语叫“感知均匀”。而老牌 jet 彩虹色卡虽然鲜艳,其实像夜市霓虹灯,中间有大量视觉突变,会误导读者过曝数据细节。
取色时有一条流水线:先把数据的最小值 z_min 和最大值 z_max 当作标尺,把任意数据 z 归一化到 0 到 1 之间,然后再去色卡上“按刻度取色”。这就是 Normalize(vmin, vmax) 在做的事。散点图里写 c=数值数组, cmap='viridis',每个点就自动按自己的数值取到对应颜色,高大上的热力图立刻出炉。
色彩统一了,整张图立刻有了“高级感”。记住:选色卡像选窗帘,耐看比鲜艳更重要。
- 数据值 \(z\)某个样本的原始数值,比如模型准确率 0.87。
- 最小值与最大值 \(z_{\min}\)、\(z_{\max}\)当前数据集里所有数值的两端,决定标尺范围。
- 归一化值 \(z'\)把原始值映射到 0 到 1。0.87 在这个标尺上被压到约 0.82。
- 查色表 \(\text{cmap}(z')\)拿着 0.82 去 viridis 色卡上取一个对应的颜色,完成着色。
图例注释:门牌与便利贴
房间多了,客人难免迷路。你得在每扇门挂上门牌(legend),说明这间房展示的是什么;在特别重要的墙面上贴便利贴(annotate),画个箭头指向某个值得注意的细节。别小看这些"paperwork",它是读者理解你图的关键。
ax.legend() 是挂门牌的标准动作,它会自动根据每条曲线的 label 参数生成图例牌。你可以指定挂在房间的哪个角落:loc='upper right' 是右上角,loc='best' 是让 Matplotlib 自己找不挡数据的位置。如果嫌门牌背景难看,加个 frameon=False 去掉边框,瞬间清爽。
而 ax.annotate() 是便利贴的高级玩法,它管两件事:文本放哪(xytext),箭头指向哪(xy)。中间的偏移量 d 就是你贴便利贴时和重点之间错开的距离。配合 arrowprops=dict(arrowstyle='->'),一条优雅的箭头线就出现了——论文里标“最优值”、“拐点”,全靠这一招。
门牌和便利贴装好了,客人不再迷路,你的图也会说话。细节决定专业度——一幅图的功力,往往就藏在箭头和一行小字里。
- 目标点 \(\vec{p}_{\text{target}}\)箭头最终指向的数据位置,比如曲线上的峰值点。
- 偏移向量 \(\vec{d}_{\text{offset}}\)文本相对目标点错开的距离,避免文字遮挡数据。
- 文本位置 \(\vec{p}_{\text{text}}\)便利贴的实际落点,是目标点平移后的坐标。
导出应用:savefig 竣工验收的高清大片
装修全部完工,最后一步是请摄影师拍样板间。你用手机随手一拍,画质糊、构图歪、还带个阴影——这就像默认的 plt.show(),只适合临时预览。真正的交付必须用 fig.savefig() 拍“高清大片”。
摄影师最看重的参数是 dpi——解析力。dpi=300 是印刷级画质,右下角那个默认的 dpi=100 连论文初稿都糊。第二个救场参数是 bbox_inches='tight',相当于自动裁剪构图,把图周围多余的空白边全部去掉,保证图片是“所见即所得”的紧凑版。修完这两步,一张能进论文的图就出炉了。
别忘了选对交付格式:.png 是位图,适合插网页;.pdf 和 .svg 是矢量图,放大一万倍也不糊,投期刊、做报告选它们。还有一个小彩蛋 transparent=True——透明背景,把图叠到深色 PPT 上时不会有白色方块,像剪纸一样自然贴合。
竣工验收通过,精装样板间正式交付。好图是留给读者的面子,导出是留给自己的里子——里子不行,再漂亮的设计也会在交付时露馅。
---
- 物理宽度 \(w_{\text{inch}}\)图在纸面上的实际尺寸,由
figsize决定,单位是英寸。 - 分辨率 \(\text{dpi}\)每英寸排列多少个像素点,300 就是每英寸 300 个点。
- 像素宽度 \(w_{\text{px}}\)最终图片的像素数。8 英寸 × 300 dpi = 2400 像素,足够清晰打印。
AI 应用场景
科研论文多面板配图
阶段:结果可视化与论文写作
解决的问题:审稿人要求对比实验图信息密度高,单张图放不下基线与多个改进模型;手工用 PS 拼接又容易失真、对不齐坐标轴。用 gridspec 定制布局 + rcParams 统一风格,一套脚本生成论文级别的多面板对比图。
具体案例:某 CV 论文需要展示模型在 4 个数据集上的精度曲线。用 GridSpec(2, 2, width_ratios=[1,1]) 排成 2×2 布局,每格画一个数据集的曲线,viridis 色图区分不同方法,annotate 标出最佳 epoch,最后 savefig('fig.png', dpi=300, bbox_inches='tight') 导出。
量化交易日报仪表盘
阶段:数据监控与日报自动化
解决的问题:传统做法是每天手工打开 Excel 生成多张图再拼进邮件,字体不统一、颜色混乱、耗时 40 分钟。用 subplots(3, 3) 一次性铺出九宫格日报,配合 plt.style.use('ggplot') 统一风格,全自动跑批出图。
具体案例:每天 9 点定时任务生成 3×3 仪表盘:净值曲线、回撤曲线、日收益率分布、板块热力图(imshow + viridis)、相关性矩阵等。rcParams 里统一配置中文字体,领导打开邮件就是一份整整齐齐的专业日报。
机器学习超参数调优可视化
阶段:模型调参与实验记录
解决的问题:网格搜索跑完几百组参数,只看日志数字完全看不出规律。用 colormap 把超参数编码成颜色,散点图 + 注释标出最优区域,规律一目了然。
具体案例:在二维平面上画散点图,横轴是学习率,纵轴是 batch size,点的颜色用 c=验证准确率, cmap='viridis' 编码。colorbar 展示准确率色标,annotate 直接标出全局最优点的参数组合,调参方向瞬间清晰。
Python 演示
import matplotlib.pyplot as pltimport numpy as np # 0. 全局装修手册:一次定义,全图生效plt.rcParams["font.size"] = 11plt.rcParams["axes.grid"] = True # 1. 设计师定制隔断:左侧大图、右侧小图fig = plt.figure(figsize=(8, 4))gs = fig.add_gridspec(1, 2, width_ratios=[2, 1]) # 2. 入住两个"房间"ax1 = fig.add_subplot(gs[0])ax2 = fig.add_subplot(gs[1]) x = np.linspace(0, 4 * np.pi, 200)cmap = plt.cm.viridisfor i, f in enumerate([1.0, 0.5]): ax1.plot(x, np.sin(f * x), color=cmap(i / 2), label="sin({}x)".format(f))ax1.set_title("Main curve")ax1.legend() ax2.barh(["A", "B"], [3, 5], color=[cmap(0.3), cmap(0.7)])ax2.annotate("max", xy=(5, 1), xytext=(0.5, 4.6), arrowprops={"arrowstyle": "->"}) fig.suptitle("Model Comparison Dashboard")fig.tight_layout()fig.savefig("dashboard.png", dpi=300, bbox_inches="tight")
plt.rcParams["axes.grid"] = True全局打开网格,所有子图自动带网格线,不用每个 ax 单独设置。gs = fig.add_gridspec(1, 2, width_ratios=[2, 1])创建一列两行的网格,左边宽度是右边的两倍。ax1 = fig.add_subplot(gs[0])从网格里取第 0 个格子作为左图,“圈地”建房间。cmap = plt.cm.viridis选定感知均匀的 viridis 色图,作为全图的统一色卡。color=cmap(i / 2)将索引 i 归一化后去色卡取色,两条曲线颜色协调不刺眼。ax2.annotate("max", xy=(5, 1), xytext=(0.5, 4.6), arrowprops={"arrowstyle": "->"})从文本位置画箭头指向柱状图最右侧的峰值点。fig.savefig("dashboard.png", dpi=300, bbox_inches="tight")以 300 dpi 导出,并裁剪掉多余白边,得到交付级成品图。
常见误区
误区我以为 `plt.subplots(2, 3)` 返回的 `axes` 就是个普通的 list,所以直接写 `axes[0]` 想取第一个子图,结果取回来一长条东西。
正解axes 是 NumPy 的 ndarray,形状是 (2, 3) 的二维数组。想取第 1 行第 2 列的子图要写 axes[0][1];只有 subplots(1, 3) 这样单行时才是一维数组,axes[0] 才代表第一个子图。把钥匙串想象成一张“房号矩阵”,而不是一串钥匙。
为何常见:大多数教程都用 subplots(1, 2) 举例,一维数组用 axes[0] 恰好能跑通,初学者就误以为它永远是列表。
误区我用了 `subplots` 划分好子图,接着写 `plt.plot()` 画线,发现所有线都挤到了最后一张图上。
正解plt.plot() 操作的是“当前子图”(gca),在子图模式下容易乱的根源在这里。正确做法是拿子图对象调用方法:ax1.plot()、ax1.set_title()。plt 是项目经理,ax 是具体施工队,图纸分好了就要让施工队进房间干活,而不是让项目经理在毛坯房里乱画。
为何常见:Matplotlib 有 pyplot 和面向对象两套 API,教程里混着讲,初学者分不清哪句该配哪句。
误区我画图时标题写中文,出来的全是方框,第一反应是“Matplotlib 不支持中文”,差点换库。
正解Matplotlib 完全支持中文,只是默认字体里没有中文字形。你需要在装修手册里指定一个系统里存在的中文字体:plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS'],再加上 plt.rcParams['axes.unicode_minus'] = False 修复负号显示。这是字体配置问题,不是功能缺陷。
为何常见:默认配置跑英文一切正常,中文报错有很强的误导性,很容易被误判为“不支持”。
误区我导出论文图时把 `dpi` 调到了 600,结果文件 50MB,打开还特别卡,图也糊。
正解清晰度是 figsize × dpi 共同决定的,只调 dpi 相当于用同一张底片强行放大像素,反而又大又虚。正确组合是:figsize=(8, 4) 定物理尺寸,dpi=300 定解析力,再加 bbox_inches='tight' 裁掉空白。文件太大时考虑存矢量格式 .pdf 或 .svg,无限放大都不糊。
为何常见:大家直觉认为 dpi 是“清晰度开关”,忘了像素数 = 物理尺寸 × dpi 这个乘法关系。
误区我觉得 `colormap` 就是换皮肤,`jet` 彩虹色又好看又炫酷,直接用它画热力图。
正解jet 是“感知不均匀”色图,中间的青绿过渡区会形成视觉假边界,明明数据连续平滑,图上看却像多了几道坎。viridis 等感知均匀色图保证相邻数据取到的颜色差异一致,色盲友好、灰度打印可区分。选色图不是选衣服,是选“不撒谎的标尺”。
为何常见:jet 是 Matplotlib 老版本默认色图,大家在各种老帖子和软件里见得多,先入为主以为它“专业”。
面试题
Q1. 执行 fig, axes = plt.subplots(2, 3) 后,axes 的形状是什么?
- A.
(2, 3)← 正确答案 - B.
(6,) - C.
(3, 2) - D.
(2, 2)
答案:A。subplots(nrows, ncols) 返回的 axes 是 NumPy 二维数组,形状为 (nrows, ncols),即 (2, 3)。就像户型图是 2 行 3 列的房间矩阵,取子图要用 axes[i][j] 双下标。选项 B 是展平后的一维形状,C 是行列写反,D 是行列数都错。
Q2. 在 Matplotlib 中显示中文标题不乱码,正确做法是?
- A.设置
plt.rcParams['font.sans-serif'] = ['SimHei']← 正确答案 - B.将图片保存为高 dpi
- C.使用
viridis色图 - D.设置
figsize更大
答案:A。中文乱码是因为默认字体不含中文字形,需要在全局装修手册 rcParams 里指定中文字体,同时建议设置 axes.unicode_minus=False 修复负号。调 dpi、换色图、改尺寸都跟字体渲染无关,属于“头痛医脚”。
Q3. GridSpec 里设置 width_ratios=[3, 1] 表示什么?
- A.两列子图的宽度之比为 3:1← 正确答案
- B.两行子图的高度之比为 3:1
- C.子图数量为 3 比 1
- D.边距与绘图区之比为 3:1
答案:A。width_ratios 控制的是列的宽度比例,[3, 1] 表示左列实际宽度是右列的 3 倍。它对应“设计师定制隔断”:客厅分 3 份宽、卫生间分 1 份宽。高度比例要用 height_ratios,子图数量和边距都不由它控制。
Q4. 以下哪个 colormap 是感知均匀、色盲友好、适合科学数据可视化的?
- A.
viridis← 正确答案 - B.
jet - C.
hsv - D.
gist_rainbow
答案:A。viridis 从深紫渐变到明黄,感知均匀且对色盲友好,是 Matplotlib 的默认推荐色图。jet 彩虹色会在青绿处产生视觉假边界,hsv 和 gist_rainbow 都是高饱和彩色循环,不适合表达连续数值。选色图像选窗帘,耐看比鲜艳重要。
Q5. 下面哪两个参数是 savefig 导出高清论文图最常用的组合?
- A.
dpi=300, bbox_inches='tight'← 正确答案 - B.
figsize=(8, 4), cmap='viridis' - C.
loc='best', frameon=False - D.
transparent=True, sharex=True
答案:A。dpi=300 保证分辨率足够印刷,bbox_inches='tight' 自动裁剪掉多余白边,这两个是论文出图的黄金搭档。figsize 是创建画布时用的,cmap 是着色用的,loc/frameon 是图例参数,sharex 是创建子图时的参数,都不属于 savefig 的核心职责。
Q6. 关于 rcParams 与单个子图 ax.set_xxx 的关系,下列说法正确的是?
- A.局部设置可以覆盖全局默认值← 正确答案
- B.全局 rcParams 会强制覆盖所有局部设置
- C.两者互不影响
- D.设置了 rcParams 后就不能再用 ax 接口
答案:A。rcParams 是全局默认值,ax.set_title()、ax.set_xlabel() 等是局部覆盖,遵循“局部优先于全局”的原则。就像房子有统一装修手册,但你可以往某一个房间单独挂一幅画。选项 B 把优先级搞反了,C 忽略了覆盖关系,D 更是无中生有。
Q7. ax.annotate("text", xy=(5, 1), xytext=(0.5, 4.6), arrowprops={"arrowstyle": "->"}) 中 xytext 指定的是?
- A.注释文本的放置位置← 正确答案
- B.箭头指向的数据点位置
- C.箭头的颜色与粗细
- D.坐标轴的范围
答案:A。xytext 是“text 对应的 xy”,即注释文本所在位置;xy 才是箭头指向的目标数据点。从 xytext 到 xy 之间画一条带箭头的线,等价于便利贴贴在一个位置、箭头指向另一个位置。选 B 是把 xy 和 xytext 弄混了。
Q8. 关于 subplots(2, 2, sharex=True),下列说法正确的有?
- A.四个子图共享 x 轴数值范围← 正确答案
- B.缩放任意一个子图的 x 轴,其他子图会联动← 正确答案
- C.默认情况下非顶部的子图 x 轴刻度标签会被隐藏← 正确答案
- D.四个子图的标题会自动合并
答案:ABC。sharex=True 让所有子图共享 x 轴,数据范围对齐、缩放联动,并且为避免重复显示刻度,默认只保留最底部子图的刻度标签。这就像上下铺的邻居共用一面墙,墙动大家一起动。子图标题不会自动合并,D 错误。
Q9. 关于 colormap 与颜色的理解,正确的有?
- A.
viridis是感知均匀色图← 正确答案 - B.
Normalize(vmin, vmax)将数据映射到 0-1 区间← 正确答案 - C.用
c=数值数组, cmap='viridis'可以让散点按数值着色← 正确答案 - D.
jet最适合表达连续型数据
答案:ABC。归一化公式 z' = (z - z_min) / (z_max - z_min) 正是 Normalize 做的事,拿到 0-1 的值再去色图取色,散点图就能按数值着色。viridis 感知均匀、色盲友好。而 jet 会在青绿处产生视觉假边界,不适合连续数据,D 错误。
Q10. 为了给论文生成高清无白边的配图,以下做法正确的有?
- A.
fig.savefig("fig.png", dpi=300, bbox_inches="tight")← 正确答案 - B.
plt.rcParams["font.size"] = 11保证字体统一← 正确答案 - C.同时导出
.png和.pdf,位图与矢量图双保险← 正确答案 - D.只要把
dpi调到 1000,其他参数不用管
答案:ABC。高清出图是组合拳:dpi=300 给足解析力,bbox_inches='tight' 裁掉白边,全局字体配置保证各子图文字统一,双格式导出让投稿、演示都方便。只调 dpi 会导致文件巨大且裁剪不当,D 漏掉了最关键的因素。