ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python Matplotlib 从入门到实战:核心概念、常用图表与问题排查

Python Matplotlib 从入门到实战:核心概念、常用图表与问题排查 1. 项目概述Matplotlib 到底能帮你解决什么问题做 Python 数据分析的人十有八九绕不过 Matplotlib 这个库。它不是什么炫酷的交互式可视化框架而是 Python 生态里最基础、最通用的绘图工具。你每天看到的折线图、柱状图、散点图、热力图很大一部分都是通过它画出来的。说白了Matplotlib 的目标就一句话把你手里的数据变成一张看得懂的图。我最早接触 Python 的时候学的是语法、列表、字典、循环但真正让我觉得“哇这玩意儿有用”的就是用 Matplotlib 画出的第一张曲线图。那会儿我用一个循环生成了一组随机数再调用几行代码把它们绘制成线看着图像窗口里那条起伏的曲线才意识到数据不再是一堆枯燥的数字而是可以直观感知的东西。如果你也正在学 Python、做数据分析或者打算写爬虫、做量化策略只要涉及“把结果展现出来”Matplotlib 就是绕不开的一环。这篇博文不是贴文档而是把我自己用 Matplotlib 的项目经验、踩过的坑、常用的函数和接口整理出来。内容涵盖了环境安装、figure/axes/axis 三个核心概念的关系、常用图形绘制、颜色与网格控制、图例插入、子图布局以及雷达图这类稍复杂图形的实现思路。从零基础入门到实际项目里能出图基本都覆盖了适合正在学习 Python 可视化的朋友、刚接触数据分析的初学者以及想系统梳理 Matplotlib 常用接口的开发者。2. 核心概念拆解figure、axes、axis 的关系与区别2.1 三个概念到底各管什么先说结论figure 是画板axes 是画布上的一个绘图区axis 是一条坐标轴。三者是包含关系不是平级关系。刚开始学 Matplotlib 的时候我被这三个词搞得晕头转向。因为很多教程随手就写 plt.plot也不解释内部到底做了什么。后来读代码、看文档慢慢才明白你调用的每次绘图本质上都是在 figure 这个顶层容器里往某个 axes 上画东西而这个 axes 的边界由一组 axisx 轴和 y 轴来约束和标定。用一个生活化的比喻figure 就像一张 A4 纸你可以在这张纸上划分出多个区域每个区域就是一个 axes图形实际画在区域内axis 则是区域边缘的标尺——x 轴、y 轴它们决定了数据在区域里的位置展示方式。一张 A4 纸可以只有一幅图也可以排布四幅小图这就是 figure 和 axes 的组合关系。顺手验证一下运行fig, ax plt.subplots()返回的 fig 就是 figure 对象ax 是默认建立的一个 axes 对象。如果你继续调用ax.plot()图会画在 ax 这个坐标区内而如果你直接用plt.plot()Matplotlib 会在幕后自动帮你创建一个 figure 和 axes所以你看不到这两个对象但底层逻辑没变。2.2 层级关系和常用代码验证为了更直观地理解我贴一段我平时用来验证这段关系的代码强烈建议你自己跑一遍import matplotlib.pyplot as plt fig plt.figure(figsize(8, 6)) # 创建一张画板 ax fig.add_subplot(111) # 在画板上添加一个绘图区 ax.plot([1, 2, 3, 4], [1, 4, 9, 16], o-) ax.set_xlabel(X 轴) ax.set_ylabel(Y 轴) ax.set_title(一个 axes 上的折线) print(fig.axes) # 查看画板里的绘图区列表 print(ax.get_xaxis()) # 获取 x 轴对象 plt.show()运行之后你会发现fig.axes返回了一个列表里面包含的就是我们刚创建的 ax。这说明 figure 持有 axes 的引用而每一个 axes 都包含 x 轴和 y 轴两个 axis 对象。理解了这层关系后面学习 subplot、多子图布局就轻松多了。2.3 什么时候该用 plt 接口什么时候该用 ax 接口很多新手纠结一个问题画图的时候到底该写plt.plot还是ax.plot我的经验是简单场景怎么方便怎么来plt.plot直接命中但凡涉及多个子图、需要精细控制坐标轴、或者要在同一个 axes 上反复添加内容就老老实实走面向对象接口也就是fig, ax plt.subplots()的思路。原因是plt是一个全局状态的接口它默认会把绘图内容丢到当前活跃的 axes 上。在只有一个 figure 一个 axes 的时候这个机制很省事可一旦你在同一个脚本里创建了多个 figure或者用 subplot 分了多个区域plt的“当前目标”就很容易混乱。用ax.plot()这种写法代码的意图更明确你清清楚楚地知道这张图要画在哪个绘图区、挂在哪个坐标轴下。3. 环境准备与工具配置从安装到出图3.1 Python 环境安装 Matplotlib 的三种方式先解决环境问题。很多人卡在第一步其实安装 Matplotlib 并不复杂。常规做法是直接用 pip 安装pip install matplotlib如果你用的是国内网络环境可以换用镜像源速度会快很多pip install matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple我个人的建议是如果是新手直接安装 Anaconda因为它自带 Python 和 300 多个常用库Matplotlib、NumPy、Pandas 都默认装好了省去了逐个安装的麻烦。如果你是老手用 virtualenv 或 conda 建独立环境避免不同项目之间的依赖冲突。装完以后验证一下在 Python 交互环境里执行import matplotlib print(matplotlib.__version__)能输出版本号说明安装成功。如果报错 ModuleNotFoundError大概率是环境没对——也就是你 pip 装的库和你当前运行的 Python 解释器不在同一个环境里。这个问题在 PyCharm 和 VSCode 中都很常见下面单独说。3.2 PyCharm 和 VSCode 配置的关键步骤PyCharm 里最常见的坑是你明明在终端里 pip install 成功了但点运行按钮还是提示找不到 matplotlib。原因很简单——PyCharm 默认使用项目解释器而你安装时用的却是系统 Python 或另一个解释器。解决方法打开 File - Settings - Project - Python Interpreter确认当前解释器路径。如果列表里没有 matplotlib点加号直接搜索安装或者把路径切换到你自己装过包的 Python 解释器。我平时更推荐直接在这个界面里点加号安装因为 PyCharm 会自动匹配到当前项目解释器不会出现环境错位的问题。VSCode 那边稍微不一样。VSCode 本身只是编辑器它运行的 Python 代码靠的是你选择的解释器。配置步骤概括为先确保 Python 扩展已安装。按 CtrlShiftPMac 是 CmdShiftP输入 Python: Select Interpreter选择你安装了 Matplotlib 的那个 Python 环境。运行脚本前确认终端里pip show matplotlib能显示详情。另外还有一个容易忽视的点在 VSCode 里运行图形界面程序如果用的是 Jupyter Notebook 或 Python 交互窗口plt.show()的行为和普通脚本不一样。Notebook 中通常需要加上%matplotlib inline才能在输出里直接显示图像而普通 .py 文件直接终端运行plt.show()会弹出独立窗口。如果你在 VSCode 里跑完没有显示任何东西优先排查是不是这两者混淆了。3.3 中文字体显示乱码的应急处理另外一个几乎人人都会遇到的问题图里的中文标题、标签显示成方块。原因是 Matplotlib 的默认字体不包含中文字符需要手动指定中文字体。最稳妥的方式是运行时在代码里指定import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, WenQuanYi Zen Hei] plt.rcParams[axes.unicode_minus] False # 解决负号显示为方块的问题第一行告诉 Matplotlib 优先使用黑体、微软雅黑等中文字体第二行专门处理负号。如果不加第二行坐标轴上的 -1、-2 这类数字会渲染成一个奇怪的方块非常影响观感。如果是 Linux 服务器上绘图系统可能根本没有这些字体还需要先安装字体文件再把字体注册进 Matplotlib。比较快的方式是下载一个 TTF 字体文件放到本地用户字体目录然后用matplotlib.font_manager注册import matplotlib.font_manager as fm fm.fontManager.addfont(/path/to/your/font.ttf)字体问题属于那种“不踩不知道踩了到处找教程”的坑建议一次性配置好。4. 常用函数详解接口体系与核心绘图逻辑4.1 Pyplot 接口与面向对象接口的对应关系Matplotlib 有两套用法一个是pyplot接口一个是面向对象接口。很多人初学时混着看教程一会儿 plt.plot一会儿 fig.add_subplot容易懵。其实两套接口做的事是一样的只是表达方式不同。pyplot接口的特点是简洁适合快速出图。你写plt.plot(x, y)它会自动创建 figure 和 axes然后把折线画上去再写plt.xlabel(time)它也会自动作用到当前 axes 上。整个过程不需要显式获取任何对象。面向对象接口更适合复杂的多子图场景。它的写法通常是fig, axes plt.subplots(2, 2) axes[0, 0].plot(...) axes[0, 1].scatter(...)每个坐标区都有自己独立的绘图方法不用纠结全局状态。两套接口的对应关系也很清晰plt.plot对应ax.plotplt.xlabel对应ax.set_xlabelplt.title对应ax.set_titleplt.legend对应ax.legend。理解了这层对应关系读别人的代码会非常快。4.2 最常用的几个绘图函数及参数plot是折线图的核心函数也是出现频率最高的。参数方面要记住几个关键点plt.plot(x, y, markero, linestyle--, colorcrimson, linewidth2, label销量趋势)marker 控制数据点标记常用值有 o 圆点、s 方点、^ 三角linestyle 控制线型- 实线、-- 虚线、-. 点划线color 可以用英文字母、十六进制色值或 RGB 元组linewidth 控制线的粗细label 配合 legend 使用为图例提供名称。linewidth 与 label 配合 legend 使用为图例提供名称。如果你画的是多个序列直接把数据交给同一个 plot 函数就行了。比如要对比两条曲线可以在同一个窗口里连续调用两次 plt.plotMatplotlib 会自动把它们叠加在当前 axes 上。scatter是散点图函数常用于展示两个变量之间的分布关系。它相比 plot 多了一个 s 参数控制点的大小一个 c 参数控制颜色还可以通过 alpha 参数让点呈现半透明适合点密度高的情况plt.scatter(x, y, s50, cy, cmapviridis, alpha0.6)这里 c 接受一个序列配合 cmap 色图可以根据数值大小自动映射颜色视觉信息量立刻提升。bar是柱状图函数类目数据常用。有一个参数需要特别留意bar的默认宽度是 0.8如果类别很多柱子会很挤建议把 width 调小。水平柱状图和横向对比场景我用barh在处理长类别名称时比垂直柱状图舒服得多。柱状图还有一个常见的需求是标注数值我通常用ax.text或循环遍历柱子的位置手动添加文本虽然麻烦一点但效果比默认的好。hist是直方图函数。它和柱状图长得像但作用完全不一样。直方图展示的是数据的分布特征横轴是数值区间纵轴是落在该区间的样本数量。参数bins控制分组数量这个值影响图表的解读精度我会针对不同数据分布多试几次。直方图是我用来看数值型特征分布的首选尤其在排查数据异常时一图就能看出来有没有极端值。pie是饼图函数占比展示场景常用。一个提醒如果类别太多饼图会变得非常拥挤建议超过 5 类就合并成“其他”或者改用柱状图。它有一个参数autopct%.1f%%可以在扇区内部显示百分比我一般都会加上。4.3 图像细节控制颜色、透明度、网格与图例这些都属于“画图之后的美化工作”但恰恰是它们决定了一张图是“能用”还是“好看”。颜色的选择上我强烈建议不要用 Python 默认的颜色序列默认的前两个颜色蓝和橙容易看腻。Matplotlib 提供了非常丰富的色图比如tab10、Set2、viridis。用cmap参数可以直接指定也可以手动传入颜色列表colors [#4C72B0, #DD8452, #55A868, #C44E52]网格线grid默认是关闭的。开启后读取数据点的时候体验会好很多plt.grid(True, linestyle--, alpha0.6)把线型设为虚线、透明度调到 0.5 左右既保留参考功能又不至于抢走主体数据的焦点。图例legend是另一个高频需求。插入图例前你得保证每个绘图函数里都写了 label 参数然后调用plt.legend(locupper right, fontsize10)loc 参数控制位置除了描述性关键字还可以直接用数字 0 到 10 对应代码。图例位置跟数据分布容易冲突我会在数据密集区域的对角方向放图例减少遮挡。透明度alpha参数是我非常喜欢的一个细节。散点图数据点重叠严重时把 alpha 调到 0.3 到 0.6重叠区域颜色会自然加深反而能看出点的密度分布。5. 完整实操一从数据到折线图、柱状图和散点图5.1 一个具体的业务场景假设你手头有一份电商平台三个月的销售数据需要做一张图汇报给业务方。数据大概是这样的月份订单量销售额万元广告投入万元1月120035.25.02月150041.86.23月180052.47.5需求很典型用折线图看订单量和销售额的变化趋势用柱状图对比各月销售额再用散点图展示广告投入和销售额之间的关系。5.2 折线图绘制与坐标轴细节import matplotlib.pyplot as plt months [1月, 2月, 3月] orders [1200, 1500, 1800] sales [35.2, 41.8, 52.4] ads [5.0, 6.2, 7.5] plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False plt.figure(figsize(8, 5)) plt.plot(months, orders, markero, label订单量) plt.plot(months, sales, markers, label销售额万元) plt.xlabel(月份) plt.ylabel(数量 / 万元) plt.title(第一季度销售趋势) plt.legend() plt.grid(True, linestyle--, alpha0.5) plt.show()这里有几个细节值得说明。第一折线图的 x 轴数据是字符串列表Matplotlib 会把它当作类别处理不会按数值排序如果你传入的是日期类型效果会略有不同。第二两个序列的数值范围差异很大订单量是上千数量级销售额是几十画在同一张图上时两者的刻度不在一个量级销售额的曲线看起来会贴在地板上。这时候更合理的做法是使用双 y 轴左侧显示订单量右侧显示销售额这样两条线的波动幅度都能看清。双 y 轴的实现也不复杂fig, ax1 plt.subplots() ax2 ax1.twinx() ax1.plot(months, orders, o-, color#4C72B0, label订单量) ax2.plot(months, sales, s-, color#DD8452, label销售额) ax1.set_ylabel(订单量) ax2.set_ylabel(销售额万元)5.3 柱状图与散点图的组合判断柱状图部分我想对比三个月的销售额plt.figure(figsize(8, 5)) plt.bar(months, sales, color[#4C72B0, #DD8452, #55A868], width0.5) plt.xlabel(月份) plt.ylabel(销售额万元) plt.title(各月份销售额对比) for i, v in enumerate(sales): plt.text(i, v 0.5, str(v), hacenter, fontsize11) plt.show()用 text 循环标注数值时v0.5 是为了让文字稍微高出柱顶避免和柱顶重叠。hacenter 让文字在柱子中心水平对齐。这一步看似小实际汇报时大家都喜欢直接看到数字省得盯着坐标轴估算。散点图用来探索广告投入和销售额的关系plt.figure(figsize(8, 5)) plt.scatter(ads, sales, s120, csales, cmapviridis, alpha0.7) plt.xlabel(广告投入万元) plt.ylabel(销售额万元) plt.colorbar() plt.show()如果你发现点大致成一条向上的趋势说明广告投入和销售额呈正相关。用 scatter 的关键是别忘记加 colorbar它会告诉你颜色的深浅代表什么含义否则读者只能看到一团颜色完全不知道怎么解读。6. 完整实操二多子图布局与雷达图的实战做法6.1 用 subplots 把多张图拼到一张画布上很多时候一张图不够需要在一张画布上展示多个角度。比如刚才那份电商数据我可以把折线图和柱状图放在同一张画布的不同位置fig, axes plt.subplots(2, 2, figsize(12, 8)) axes[0, 0].plot(months, orders, o-) axes[0, 0].set_title(订单量趋势) axes[0, 1].bar(months, sales, color#55A868) axes[0, 1].set_title(销售额对比) axes[1, 0].scatter(ads, sales, csales, cmapviridis) axes[1, 0].set_title(广告投入与销售额关系) axes[1, 1].plot(months, ads, s--) axes[1, 1].set_title(广告投入趋势) plt.tight_layout() plt.show()subplots(2, 2) 返回的 axes 是一个二维数组用 axes[0, 0]、axes[1, 1] 这样的索引定位每一个子图。这里有一个不能少的步骤plt.tight_layout()。如果忘记调用子图与子图之间会挤在一起标题互相重叠整体非常难看。tight_layout 会自动调整子图的边距和间距。另一个常用参数是sharex和sharey。如果多个子图共享 x 轴的含义比如共用同一组月份把它们设为 True各子图的坐标轴刻度会保持一致看上去更整齐画布空间的利用率也更高。6.2 雷达图的实现逻辑雷达图是我认为 Matplotlib 中看起来最“高级”的图形之一但其实实现并不复杂核心只是把直角坐标换算成角度坐标。比如给一个商品做多维度评分维度包括价格、质量、服务、物流、售后每个维度满分 10 分import numpy as np import matplotlib.pyplot as plt labels [价格, 质量, 服务, 物流, 售后] scores [8, 9, 7, 6, 8] angles np.linspace(0, 2 * np.pi, len(labels), endpointFalse).tolist() scores scores[:1] angles angles[:1] fig, ax plt.subplots(figsize(6, 6), subplot_kwdict(polarTrue)) ax.fill(angles, scores, colorskyblue, alpha0.4) ax.plot(angles, scores, color#4C72B0, linewidth2) ax.set_xticks(angles[:-1]) ax.set_xticklabels(labels) ax.set_ylim(0, 10) plt.show()代码里的关键点有两个。第一np.linspace(0, 2*np.pi, len(labels), endpointFalse)把 360 度平均分成 5 份得到每个维度对应的角度第二个是把首尾数据接起来因为多边形需要闭合——雷达图的数值序列首尾必须相同否则图形会在最后一维和第一维之间缺一条边。subplot_kw 里传 polarTrue 是 Matplotlib 创建极坐标子图的标准方式。很多人在这里卡壳忘记传这个参数结果画出来的是直角坐标系里的普通折线形态完全不对。雷达图适合展示多维度综合评价但如果维度超过 8 个图形会变得很拥挤可读性大打折扣。遇到维度很多的情况我更建议改用条形图或热力图。6.3 把图保存成文件出图之后保存也是刚需毕竟大多数场景下要的是 PNG 或 PDF 文件。保存不能用 plt.show() 截图而要使用 savefigplt.savefig(sales_report.png, dpi300, bbox_inchestight)dpi 控制分辨率报告打印的话建议 300bbox_inchestight 会裁剪掉图表周围的空白区域避免图上白边太多。唯一要注意的是savefig 要在 show 之前调用因为 show 之后 figure 会被清空保存出来可能是一张空白图。我这里说的“清空”是指 pyplot 状态机在窗口关闭后重置了当前 figure所以保存动作必须先于展示执行。如果你不放心可以用 fig.savefig 的面向对象写法会稳定很多。7. 高频问题排查从“图不显示”到“中文乱码”7.1 图窗口闪退或完全不显示用 PyCharm 运行带 plt.show() 的脚本弹出的窗口一闪而过或者根本看不到任何窗口这类问题出现频率极高。我在 PyCharm 里遇到过在 VSCode 里也遇到过原因各不一样。PyCharm 里一闪而过的主要原因是脚本运行结束后进程退出窗口被随之销毁。解决办法有两个方向一是在代码末尾加plt.show()之后不让程序直接结束比如加input()等待回车二是设置 PyCharm 的 Python 运行配置勾选“Show plots in tool window”。不过更根本的方案是改用 Jupyter 或交互式命令行它们天然支持内嵌图像。VSCode 里不显示图像常见原因是输出窗口选错了。运行的是 .py 文件时plt.show() 会弹出一个独立窗口如果你用的是“Python Interactive”输出方式需要明确配置渲染后端。还有一个我踩过的坑如果代码里设置了matplotlib.use(Agg)图像只保存文件不会显示窗口因为 Agg 是非交互式后端。排查时可以先检查代码里有没有这行配置。另外如果是在云服务器或者远程 Linux 环境上没有图形界面图像窗口无法显示只能保存文件或使用 headless 模式。7.2 坐标轴刻度过密或重叠绘制大量数据时x 轴刻度会自动变得密集文字挤成一团。我自己在画日粒度数据时遇到过这个现象年份月份标签全部堆在一起根本没法看。处理方式有两种。第一种是用plt.xticks(rotation45)旋转标签避免文字横向重叠第二种是手动设置刻度间隔只抽取一部分点位作为刻度显示。比如有一年的日数据x 轴只显示每个月的 1 号位置import pandas as pd import matplotlib.dates as mdates plt.gca().xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m-%d)) plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval1))这种方式比单纯旋转更专业坐标轴看起来也干净得多。7.3 图例和标签不生效图例不显示九成原因是绘图时漏写了 label 参数。很多人以为调用了 plt.legend() 就万事大吉实际上 legend 是从当前 axes 的绘图对象中收集 label 信息的如果 label 是空的自然就没有任何图例可显示。另一种情况是调用了两次 plt.legend()后一次覆盖了前一次。比如你先把折线图图例放在左上角后面又加了一个散点图并重新调用 legend图例只显示最后一次的内容。遇到这种需求建议用单一 axes 的接口把需要显示的序列统一放进图例。7.4 Matplotlib 和 NumPy、Pandas 的配合问题实际项目中很少直接给 plot 传裸的 Python 列表多半会配合 Pandas 的 DataFrame 使用。DataFrame 可以直接传给 plot 函数import pandas as pd df pd.DataFrame({月份: months, 订单量: orders, 销售额: sales}) df.plot(x月份, y[订单量, 销售额]) plt.show()这其实是经过 pandas 封装过的 Matplotlib 接口本质上还是在调用 Matplotlib 的绘图函数但省去了自己传列表的步骤。使用 DataFrame 绘图时有一个坑列名如果是中文可能在部分版本上出现编码问题另外索引若不是从 0 连续的整数绘图时 x 轴可能会用索引值而不是你想用的列。建议显式指定 x 参数不要依赖默认索引。7.5 大数据量绘制的性能问题当数据量达到几十万甚至上百万个点的时候Matplotlib 的默认 scatter 会很慢渲染出来也是一团墨点难以辨认。我处理大规模日志数据时遇到过这种问题常用的方案有几种。最简单的方案是采样只画一部分点比如每 10 个点取 1 个。对于趋势判断来说足够用了。其次是使用 hexbin——它是六边形分箱散点图会把二维平面划分成六边形网格统计每个网格内的点数再以颜色深浅呈现密度。这种图在百万级数据点上的渲染效率远高于普通散点图。另一个思路是结合 numpy 做聚合把数据按分位数或区间统计后再画柱状图或折线图。可视化的目的首先是“看懂趋势”没有必要让图承担所有的数据细节。8. 我自己的经验沉淀三个关于可视化的体会写到这里其实 Matplotlib 的基础函数、核心概念和常用场景聊得差不多了最后想分享几个我自己长期使用的习惯算是一个阶段性的经验总结。第一个体会画图前先用脑子想清楚要表达什么。数据可视化不是机械地把数字换成图形而是要回答一个问题——你用这张图想让读者看到什么。是趋势、是占比、是分布、还是相关性想清楚这一点选图就非常自然趋势用折线占比用饼图或堆叠柱分布用直方图或箱线图相关性用散点图。凭喜好选图出来的东西多半说不清楚问题。第二个体会时间是花在细节上的。一次成图很简单但要做出真正能用于汇报的图细节打磨是少不了的。坐标轴标签是否有单位图例位置是否遮挡数据颜色是否有语义字体大小是否能在投影后看清网格线是否过于干扰视线这些细节拼起来才是专业和非专业的区别。我现在拿到任何一张产出图都会先检查坐标轴标签和标题因为这是信息传递的底线。第三个建议把那些自己常用的绘图模板存成函数。我在经历过反复调整坐标轴、颜色、字体之后慢慢把自己的绘图习惯沉淀成了一套模板函数比如画折线图一定带网格、柱状图一定标注数值、散点图一定配 colorbar。时间长了你会发现代码复用比每次重新手写快得多而且出图风格统一。Matplotlib 的入门门槛其实很低几行代码就能出图但真正把它用好靠的是对数据、对图形语义、对细节的理解。希望这篇记录能帮你少走一些弯路把更多精力放在数据本身。
返回列表