尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Matplotlib核心概念:理解figure、axes、axis关系及axes API绘图实践

Matplotlib核心概念:理解figure、axes、axis关系及axes API绘图实践 1. 先搞清楚figure、axes、axis的关系1.1 拿一张白纸来理解figure很多人在接触Matplotlib之后第一个绕不过去的坎就是figure、axes、axis这三组概念。我在刚入行的时候也是这样明明只是画个折线图却被这三个名字折腾得够呛。等到后来靠axes API做批量报表、多子图排版才真正意识到理解三者的分工就是掌握Matplotlib的一把钥匙。用生活里的场景来类比。figure是一张完整的白纸决定纸张大小、整幅图的面貌、全局标题、四周留白axes是你在白纸上规划出的绘图区域可以是一整块也可以切成几块每块画各自的内容。一张白纸上可以画好几幅小图每个小图就是一个独立的axes。而axis则是axes内部的两根标尺——x轴和y轴。所以figure、axes、axis的关系可以概括为Figure是顶层容器管理全局画布Axes是实际绘图区承载曲线、散点、柱状图这些具体图形元素Axis是坐标轴负责刻度、标签、坐标取值范围。三层各管各的职责不重叠。理解了这句话很多看似神秘的操作就有了抓手。1.2 axis只是axes的一部分axis和axes这两个词实在长得太像了英文里axes本来就是axis的复数所以在Matplotlib里混用这两个词的初学者特别多但它们的含义截然不同。在Matplotlib的世界里axes指的是一个绘图区对象这个对象整合了坐标轴、刻度和所有数据图形。你常用的ax.plot()、ax.scatter()操作对象都是这个axes。axis则单独指一根坐标轴比如二维图里的XAxis和YAxis它们主要负责坐标刻度线的位置、刻度标签的格式以及坐标轴的标题。你可以这样记忆axes是一块画板axis是画板上的尺子一块画板至少配两把尺子二维图有两把三维图有三把。我写代码时经常看到有人把set_xlabel挂在plt上或者用plt.xticks()调整坐标轴这在简单的例子里能跑通但一旦涉及多子图就很难控制。正确的方式应该是ax.set_xlabel()让axes对象去协调它内部的那根XAxis。这个习惯越早建立后面操作越顺手。1.3 搞清层级之后代码该怎么想当我们明白了三层结构写代码的思路也应该跟着调整。用pyplot快速画图时你其实是在向Matplotlib的全局状态发指令它自动找到当前figure和当前axes然后在上面操作。这种隐式逻辑在简单场景下很方便但一旦场景复杂全局状态就会变成一团迷雾。我在处理大量科学数据时几乎不会依赖pyplot的隐式机制而是显式地创建对象。比如plt.subplots()一返回我手里就同时攥住了fig和axs两个对象。后续的每个操作我都清楚它在哪块画布、哪个绘图区生效。这种显式编程让代码可读性大大提升甚至隔了几个月回来再看也完全能看明白当初每一行在干什么。2. 从pyplot到axes API2.1 两套接口的取舍Matplotlib最特别的地方是它同时提供两套完全不同的API接口。一套是pyplot模仿MATLAB的绘图方式plt.plot()、plt.xlabel()一路写到底另一套是面向对象接口通过figure和axes方法完成绘图例如ax.plot()、ax.set_xlabel()。两种方式能画出完全一样的效果但背后的设计哲学差别很大。pyplot的方式像在画布上甩命令每一步都是对全局当前状态的修改。它对即时交互探索很友好随便敲几行就能看到图形变化。但它最大的问题在于全局状态会悄悄地污染你的代码。比如一个函数内部调用了plt.plot()它往哪个axes里画如果外部环境已经存在多个axes那结果就是不可预期的。可维护性在这个点上是硬伤。而axes API的思路是把绘图对象显式握在手里ax就是axfig就是fig没有任何隐式的全局查找。虽然写起来比pyplot多敲了几个字母但换来的是逻辑透明、结构清晰。对于工程化项目、批量画图、团队协作而言这套接口是唯一正确的选择。2.2 一个函数解放复杂布局入门阶段可能觉得plt.plot()已经够用了但当需求变成“一页纸上放6张小图”时pyplot就显出疲态。你需要不断用plt.subplot(2, 3, 1)、plt.subplot(2, 3, 2)这样一行行地切换当前axes整个脚本又长又脆。axes API的解法则简洁得惊人fig, axs plt.subplots(2, 3, figsize(12, 8))这一行之后axs就是一个包含6个axes对象的二维数组。你可以通过axs[0][0]、axs[1][2]这样的下标访问任意一个绘图区然后分别往里面填充不同的图形。而且因为每个axes都是独立对象设置标题、坐标范围、背景色都不会互相干扰。我曾经做过一份月度绩效数据看板需要在同一个画布上呈现销售额、客户数、转化率、渠道对比等多张图。用axes API之后代码整体结构变成了“创建画布、规划布局、逐块绘制、统一定稿”四步。每一步都清晰且可复用后续只需要替换数据源就能生成新的图表。这就是接口设计带来的生产力差异。2.3 何时必须迁移到axes API如果你只是临时画一张图发给同事看看用pyplot没毛病。但如果你满足下面任意一条我建议立刻切换到axes API需要绘制多个子图需要在一个循环里批量生成图表需要精细控制坐标轴范围、刻度位置、图例位置需要把绘图逻辑封装成函数或类需要保存多种尺寸、多种格式的文件。尤其是封装绘图函数的时候axes API的价值特别明显。你可以写一个函数参数里接收一个ax对象函数内只负责在这个ax上画图def draw_metrics(ax, data, title): ax.plot(data[date], data[value]) ax.set_title(title) ax.grid(True, alpha0.3)这个函数无所谓外界有多少个axes只要调用方把合适的ax传进来它就能正常工作。你在主流程里通过subplots生成多个axes然后用循环对每个ax调用一次draw_metrics。这种组合方式让代码的复用度直接拉满删改任何一部分都不会波及全局。3. axes API驱动的常用图形实战3.1 散点图透明度、网格与颜色映射散点图是最常用的图形之一靠的就是它能把两个变量的分布关系直观地展示出来。在axes API中一行代码就能画基础散点ax.scatter(x, y, s50, csteelblue, alpha0.6)参数里s是点的大小c是颜色alpha是透明度。透明度平时不太被重视但数据点一旦成百上千重叠不可避免这时没有alpha的散点图就是一团黑什么信息都看不出来。把alpha调低到0.3到0.6之间点的重叠区域会自然显示出更深颜色相当于免费得到了一张密度图。我在画上万条用户行为数据的时候这个技巧几乎是必须的。网格线对散点图来说也很有用。加上ax.grid(True, linestyle--, alpha0.5)就能让读者快速估算数据点在坐标轴上的数值位置尤其适合分析报告的场景。需要注意网格线别太粗太实否则会抢数据的视觉权重虚线加低透明度是稳妥的选择。颜色映射是散点图的进阶玩法。当c参数接收的是一个数据数组而不是固定颜色字符串时点颜色会随数值变化例如sc ax.scatter(x, y, cz, cmapviridis, s50, alpha0.7) fig.colorbar(sc, axax)z数组里的数值越高点的颜色越亮。再配一个colorbar图例条就出来了。这种图在展示地理温度分布、城市房价、实验测量值时非常直观一张图同时呈现三个维度的信息。个人经验是优先选择viridis、plasma这类色彩感知均匀的colormap尽量避免使用老式的jet因为jet的黄色区域会给人造成数值上错误的层次感。3.2 雷达图的思路和实现雷达图在Matplotlib里没有现成的函数第一次画的人容易卡住。但理解原理之后就非常简单雷达图本质上是在极坐标系里画一条闭合的折线。所谓polar projection就是把普通的直角坐标网换成角度和半径角度代表指标维度半径代表数值大小。创建极坐标axes的语法是ax fig.add_subplot(projectionpolar)随后把各维度指标按顺序摆放角度再连线闭合。实际操作里有个麻烦点雷达图要在数据末尾重复第一个维度值否则多边形无法闭合另外默认起始角度指向右侧如果想让第一个指标出现在正上方需要调整theta_offset参数。画雷达图我总结了一条固定流程定义好指标顺序、把数值归一化到合理范围、计算角度、连线、填充一个半透明底色。这样可以避免很多新手才会遇到的锯齿形或图形错位问题。雷达图适合展示多维度的横向对比比如产品功能的用户满意度、球员能力面板、罕见病多指标评估。但坦白讲雷达图不适合承载太多维度超过八个维度时图形会变得密集而难以阅读。如果指标真的很多不妨拆分成两组雷达图或者改用热力图。3.3 图例的插入、定位与最佳实践图例是图表的眼睛少了它读者根本分不清哪条线是谁。在axes API里图例的机制很清晰绘图时为每个元素指定label最后统一调用一次ax.legend()即可ax.plot(x, y1, labelA组) ax.plot(x, y2, labelB组) ax.legend(locupper right)label不是画图后单独设置的它必须跟着绘图命令一起出现。如果漏了labellegend()就找不到对应的名称。图例的摆放有很多种方式loc参数可以传位置字符串比如upper left、lower right、center也可以直接传坐标元组。但默认位置在实际中经常和曲线重叠这时我最常用的办法是给loc传一个具体的(x, y)坐标精确控制图例的位置ax.legend(loc(0.02, 0.98))多子图的场景下还可以把图例统一放在整张figure的外部。此时用fig.legend()配合bbox_to_anchor把图例锚定到画布边框处避免图例覆盖子图内容。这个方法在输出宽幅报表时非常实用能让图例成为整图的公共说明。3.4 颜色体系与常用图形速览Matplotlib的颜色系统并不复杂但很多人经常混淆。常见的颜色用法有三种直接使用颜色名字符串例如r、blue、#FF5733使用颜色数组或列表同时指定多个点的颜色使用colormap颜色映射例如cmapviridis。要求颜色随数值变化时一定要用colormap否则没法实现连续的色彩梯度。在axes API中几乎所有图形都是ax对象的方法。折线图用ax.plot()、散点图用ax.scatter()、柱状图用ax.bar()、横向柱状图用ax.barh()、直方图用ax.hist()、箱线图用ax.boxplot()、饼图用ax.pie()。掌握这些方法的名字剩下的就是查参数。我在教新人时总说不需要背全所有参数只需要记住“所有绘图操作都挂在axes下”然后借助IDE的代码补全在ax后面按Tab键候选列表会告诉你一切。4. 环境准备安装、PyCharm、numpy配合4.1 安装和基础验证无论是Windows还是macOS安装Matplotlib和numpy的路径都相对简单只要有Python环境用pip就能完成。安装时要特别注意版本匹配问题如果Python版本较新建议直接用最新版的pip去装避免手动下载老版本安装包带来的依赖冲突。装完后我习惯用一个三行脚本验证安装结果import matplotlib.pyplot as plt import numpy as np fig, ax plt.subplots() ax.plot(np.arange(10), np.random.rand(10)) plt.show()这个验证脚本能同时确认numpy导入正常、matplotlib绘图函数正常、图形窗口可以弹出。如果在无显示器环境下比如远程开发机或者某些云平台则需要提前设置后端plt.switch_backend(Agg)。这样matplotlib会跳过交互式窗口直接支持savefig保存文件。4.2 PyCharm中运行matplotlib的正确姿势在PyCharm里跑matplotlib最常见的抱怨是“图不弹出来”或者“图弹出来一闪就没了”。图不弹出来多半是IDE把图形输出定向到了SciView工具窗口有时候会嵌入在侧边栏里而你没有发现。图一闪而过则通常是因为脚本运行结束后进程退出窗口没有机会保持。解决办法也比较直接确保代码最后调用plt.show()并且不要在交互式窗口打开前额外加input()之类的阻塞语句。更稳妥的方案是用savefig把图画到文件里fig.savefig(output.png, dpi200)然后直接用系统图片查看器查看。这个习惯不仅绕开了PyCharm的窗口问题还让绘图脚本变成了真正可复用的工具——每天早上跑一遍脚本自动生成最新图表直接用于邮件或报告。我自己的很多自动报表脚本都是这个套路写一个Python脚本让它在后台运行生成图表文件然后由调度系统自动发送。4.3 高频报错与排查方法Matplotlib的报错信息整体不算吓人但总有那么几个高频错误让新人心态爆炸。第一个是找不到字体尤其是显示中文时出现的findfont警告或者中文变成方框第二个是坐标轴数据类型错误明明想画时间序列却传了字符串数组报错信息里往往带着“invalid Matplotlib date value”字样第三个是数据里存在NaN或无穷值绘图时报“Input contains NaN”第四个是把numpy数组的某个维度误当成axes对象来调用plot导致报“numpy.ndarray object has no attribute plot”。排查这类问题我的经验是抛弃猜测直接看异常堆栈的最后几行。它会明确告诉你错误类型和触发地点然后你回到那一行检查数据类型。matplotlib的报错虽然总是英文但措辞其实很直接耐心读一遍基本能定位大方向。真正要留意的往往是隐性的“ warnings”比如Alphabets不清、字体缺失这些它们不会让程序崩溃但会让图片质量大打折扣不可忽视。5. 一个综合案例多子图报表从0到15.1 需求与布局设计理论聊了这么多如果不来个完整案例总感觉知识是散的。我用一个常见的数据分析场景来串一遍假设现在有一份城市空气质量监测数据包含PM2.5浓度、AQI数值、采集时间、站点名称等字段。需求是生成一张2x2的报表左上角画PM2.5与AQI的散点图右上角画PM2.5浓度随时间变化的折线图左下角画各站点平均浓度的柱状图右下角画某个重点站点的多指标雷达图。用axes API实现这个需求既能展示接口又能带出实际布局技巧。5.2 画布的创建与初判第一步无脑创建画布和子图网格fig, axs plt.subplots(2, 2, figsize(12, 9))axs是一个2x2的axes数组axs[0][0]、axs[0][1]、axs[1][0]、axs[1][1]分别对应从左到右、从上到下的四个绘图区。figsize决定了整张画布的尺寸这个数值在生成报表时尤其重要太大的画布在屏幕上滚动不便太小的画布印刷出来后字迹会糊。对于常规报表12x9英寸基本上是个稳妥开局。拿到axs之后我先规划每个子图里要放什么数据元素需要哪些图表类型以及要不要调整坐标范围。这个阶段不需要写绘图语句但我会把所有子图的标题先列出来避免画着画着忘记布局的初衷。5.3 四张子图实战左上角的散点图非常简单直接把PM2.5和AQI两列数据传给ax.scatter即可。因为数据量很大透明度必须加上否则点会全部叠成黑色。再开个网格让数值分布更清楚加一个颜色说明是AQI等级色彩带清晰传递信息。右上角的折线图要处理时间轴。Matplotlib对datetime对象有原生支持直接ax.plot(dates, pm25)就能自动生成时间刻度。如果觉得刻度太密或太疏可以用matplotlib.dates模块里的HourLocator和DateFormatter来控制刻度间隔和显示格式。时间轴最见功力多花十分钟调好刻度整张图的气质直接不同。左下角的柱状图处理起来也不复杂。ax.bar(sites, avg_concentration)就能画出站点平均浓度柱状图。我通常会再处理一下最大的柱子给最高的柱子换一个醒目的颜色其他柱子保持低调灰度。这样读者一眼就能看到重点关注对象。用循环判断数值是否为最大值然后区分color参数即可。右下角雷达图需要单独添加一个极坐标axes这里就有意思了因为add_subplot和subplots返回的axs不是同一个来源ax_radar fig.add_subplot(2, 2, 4, projectionpolar)雷达图的绘制逻辑是把指标名称转成角度把数值作为半径然后连线闭合。指标顺序要按业务逻辑排好最好顺时针排布同时加入半透明填充让图形更有可读性。所有指标的刻度范围要统一否则面积大小会失真。5.4 全局标题、间距与保存四张子图内容到位之后还需要全局收尾。用fig.suptitle()给整张figure加统一主标题用ax.set_title()给每个子图加小标题。接着调用fig.tight_layout()自动调整子图间距避免标题和坐标轴标签互相挤压。如果自动布局不满意还可以用fig.subplots_adjust(left0.1, right0.9, top0.9, bottom0.1)手动微调。最后保存输出时用fig.savefig(air_report.png, dpi200)把整张图保留下来。dpi这个参数决定输出清晰度普通屏幕预览150就够要出版或打印建议300。保存后还可以顺手用PIL或者matplotlib自己检查一下文件的大小和尺寸确保输出符合要求。到这一步一张结构清晰、信息完整的四联图报表就算做完了。整个过程完全基于axes API没有一处依赖隐式的当前状态每个图各自在自己所属的axes里独立工作。6. 避坑与学习建议6.1 性能、字体与全局状态用Matplotlib画图多了总会遇到几个独特的坑。性能问题是最常被忽视的。画几千上万个点的散点图毫无压力但数据量暴涨到几十万时交互式拖动就开始卡顿。我实测多次后遇到大数据量时的处理策略是能抽样就抽样或者用ax.plot()替代ax.scatter()它能用更快的路径渲染线条和点还要避免在循环里反复调用ax.set_xlim()这类方法因为每次调用都会触发重绘最好在绘图循环结束后统一设置坐标轴范围。字体问题则是中文用户的长期痛点。Matplotlib默认的字体通常不包含中文字符直接写中文会出现方块乱码。我的标准配置如下plt.rcParams[font.sans-serif] [SimHei, WenQuanYi Micro Hei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False第一行指定可用中文字体第二行解决负号显示异常。这两行配置建议写在所有绘图脚本开头。虽然网上有很多“中文字体包”一类的下载方式但配置rcParams才是从根上解决问题的办法。全局状态的问题我在开头就提过。这里再说一个更具体的例子很多人写脚本时会多次调用plt.figure()结果最后保存的图片里出现了意想不到的空白画布或重复图。这是因为图没关闭当前figure切换导致的。用axes API的最大好处之一就是彻底告别这类困扰fig和ax都显式握在手里不需要Matplotlib替你记着“现在正在画哪个图”。6.2 个人学习路径建议如果让我重新学一遍Matplotlib我不会从一堆教程开始而是直接打开官方的快速入门文档把figure和axes两个类的方法列表浏览一遍。我也不建议死记硬背API比起背诵更重要的是理解套路创建figure——添加axes——在axes上绘制——设置坐标轴和图例——保存输出。这个套路覆盖了90%以上的日常绘图场景。学习导航方面国内很多平台都提供了系统的Matplotlib练习项目比如“头歌”上就有关于Matplotlib接口和常用图形的分级实训。这种阶梯式练习的节奏比较适合打基础每一关只解决一个具体问题练到后面不会觉得学完就忘。我在指导新人时也常让他们把常见图全部用axes API重写一遍包括折线图、散点图、柱状图、直方图、箱线图、雷达图。全都写完一遍之后你的Matplotlib水平就从“会用”变成了“能控制”。我最后想说的一点是Matplotlib不是那种“学完就忘”的工具它是需要常练常新的东西。每次画图都会遇到新需求也都可能踩到新坑。与其背题库式地记忆参数不如建立自己的查文档习惯和样板代码库把常用图表模板沉淀下来后续真的能省下大量时间。
返回列表