数据可视化这个方向,Matplotlib 和 Seaborn 称得上 Python 生态里最常用的两张脸。我做了几年数据项目,从农产品价格分析到网约车运营大屏,几乎每个项目都会用到这两个库。可说实话,很多人学完基础教程,依然画不出能真正讲清楚问题的图——原因往往不在绘图函数不会调,而是没想明白这两个库的分工,以及一张图到底要回答什么问题。
这篇内容不是 API 手册,而是从选型到落地的完整链路。我会先讲清楚 Matplotlib 和 Seaborn 各自该在什么场景出场,然后分别走一遍核心绘图流程,再讲保存导出、GIF 动画、中文字体这些"书里不写但项目里天天遇到"的事,最后把这几年的踩坑经验整理成清单。无论你是刚装好环境准备画第一张图,还是正在做数据看板项目,都可以照着操作。
1. 两个库的分工:Matplotlib 是"底层画布",Seaborn 是"统计快捷方式"
很多人第一个问题就是:既然 Seaborn 好用,是不是只学 Seaborn 就够了?我的回答是:不够,而且会让你在真正需要精细控制的时候寸步难行。
1.1 先搞清楚什么时候该用哪一个
我用一个真实场景说明。上个月做某地农产品价格分析项目,客户要两张图:一张是不同品类价格的柱状对比,要求配色严格贴合品牌色;另一张要观察"当日上市量"和"批发价格"之间有没有规律,顺带看不同产地的差异。第一张我直接用 Matplotlib 画,因为要逐项控制颜色、边框、标签位置;第二张我用了 Seaborn,因为它自带回归拟合和分组统计,hue参数一行就能把产地区分开。
这两张图代表了两个库最典型的应用差异:
| 维度 | Matplotlib | Seaborn |
|---|---|---|
| 定位 | 底层全能绘图库,像素级控制 | 基于 Matplotlib 的高层统计绘图库 |
| 数据输入 | 多为数组、列表、pandas 列 | 直接接受 DataFrame,按列名映射 |
| 绘图理念 | 一块画布上逐个元素搭建 | 一句函数出完整统计图 |
| 默认风格 | 偏学术、朴素,需手动装饰 | 内置更现代的主题和配色 |
| 学习曲线 | 陡,理解 Figure/Axes 需要时间 | 缓,基础图几行代码搞定 |
| 适用场景 | 论文插图、复杂排版、定制化报表 | 数据探索、统计图表、快速建模前分析 |
但注意,Seaborn 所有图最终都是画在 Matplotlib 的 Figure 和 Axes 上的。也就是说,你用sns.boxplot()画完图,想调整图例位置、改坐标轴刻度、加一条参考线,还是得回来调 Matplotlib 的接口。这两个库不是二选一,而是"画布 + 预制件"的关系。
1.2 安装与环境配置:别再让 seaborn 下载卡住你
搜"seaborn 库下载"的人,大部分是卡在了安装这一步或者版本兼容上。直接说结论,最稳妥的组合是matplotlib + seaborn + pandas一起装:
pip install matplotlib seaborn pandas如果你在用 Anaconda,也可以:
conda install matplotlib seaborn pandas我建议在虚拟环境里装,不要一股脑塞进系统 Python。特别是机器上同时有 Python 3.8、3.10 多个版本时,环境隔离能省掉大量"为什么 import 报错"的排查时间。装完之后验证一下:
import matplotlib.pyplot as plt import seaborn as sns print(plt.__version__) print(sns.__version__)我实测下来,Seaborn 0.12 和 0.13 系列搭配 Matplotlib 3.7/3.8 是当前最稳定的组合。如果你用的是很新的 Python 版本,优先考虑升级到这两个库的最新小版本,不要死守旧版。还有个小细节:seaborn 在导入时会默认覆盖一部分 Matplotlib 的样式参数,所以如果你在同一个项目里既想用 Matplotlib 的默认风格,又 import 了 seaborn,图会"自动变好看",这不是 bug,是 seaborn 的设计。想要回到默认样式,用sns.reset_orig()。
2. Matplotlib 核心工作流:从数据到一张能用的图表
Matplotlib 之所以劝退一批初学者,是因为它不像 Seaborn 那样"给个数据就能画"。它要求你先理解两个核心对象:Figure 和 Axes。
2.1 理解 Figure 与 Axes:画框与画布的关系
我用一个生活类比:Figure 是整个画框,它决定了整张图最终显示多大、保存成多大;Axes 是画框里真正用来画图的那块画布,坐标轴、刻度、线条、标题,全都画在这块画布上。一个画框可以装一块画布,也可以装多块,这就是"一张图里放多个子图"的实现方式。
最常见的写法是:
import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(9, 5))figsize控制画框的宽高,单位是英寸,fig是整个 Figure 对象,ax是 Axes 对象。新手最容易犯的错是直接调plt.plot()画图,像用全局画布一样。这在简单场景没问题,但一旦你要画多子图、或者把图嵌入到 GUI 界面里,全局画布就会失控。我强烈建议从一开始就习惯显式拿到fig和ax,后面所有操作都通过ax来调。这样代码的可移植性和可维护性都会好很多,说白了就是"企业级项目"里可复现、可维护的基础。
2.2 五步完成一张基础折线图:从数据到结论
拿一个最常见的场景举例:一周销量数据,我想看走势,同时标出最高点。完整代码如下:
import pandas as pd import matplotlib.pyplot as plt sales = pd.DataFrame({ "day": ["周一", "周二", "周三", "周四", "周五", "周六", "周日"], "value": [320, 410, 385, 502, 610, 720, 690] }) fig, ax = plt.subplots(figsize=(9, 5)) # 第一步:绘制数据 ax.plot(sales["day"], sales["value"], marker="o", color="#2E86AB", linewidth=2) # 第二步:标题和坐标轴 ax.set_title("一周销量走势", fontsize=14) ax.set_xlabel("日期") ax.set_ylabel("销量") # 第三步:网格线辅助阅读 ax.grid(axis="y", linestyle="--", alpha=0.6) # 第四步:标注数据结论 peak = sales.loc[sales["value"].idxmax()] ax.annotate(f"峰值 {peak['value']}", xy=(peak["day"], peak["value"]), xytext=(peak["day"], peak["value"] + 30), ha="center", color="#A23B72") # 第五步:紧凑布局并展示 fig.tight_layout() plt.show()这五步每一步都有它的目的。绘制数据是基础,标题和坐标轴决定读者能不能看懂,网格线降低读图误差,标注则是"画结论"的关键——一张图不是把数据画完就结束,而是要把你想让读者注意到的信息直接指出来。idxmax()找到销量最高那天的索引,annotate在图上加文字注释和位置标记,这一步是区分"画图的人"和"用图表达观点的人"的分水岭。
df.plot()这种 pandas 自带绘图接口我也常用,它底层就是调 Matplotlib 的ax,适合快速看数据形态时用。但要出正式图表,我还是会回到显式ax的写法。这不是固执,而是 pandas 的绘图接口封装的设定项太多,一旦涉及复杂的图例、双坐标轴、子图布局,直接写 Matplotlib 反而更快。
2.3 定制细节:颜色、线型、标注与多子图布局
Matplotlib 的定制能力很深,但对你日常最有价值的就三块:颜色、线型、多子图布局。
先说话颜色的坑。默认的循环颜色是#1f77b4、#ff7f0e那一组,看多了会有股"默认感"。在真实项目中,我一般会提前定义一组合适的品牌色板:
BRAND_COLORS = ["#2E86AB", "#A23B72", "#F18F01", "#3B1F2B", "#73C1C9"]传色方式有三种最常用:十六进制字符串"#2E86AB"、RGB 元组(0.18, 0.53, 0.67)、颜色名称"steelblue"。十六进制最直观,也最容易和设计同事对齐。连续色系(比如热力图用的渐变色)用cmap参数指定,例如cmap="viridis"、cmap="RdBu_r",这一步在画热力图时非常关键。
线型方面,Matplotlib 支持几种基础样式:
| 线型 | 写法 | 典型用途 |
|---|---|---|
| 实线 | '-' | 实际数据趋势 |
| 虚线 | '--' | 预测值或拟合结果 |
| 点划线 | '-.' | 阈值或参考线 |
| 点线 | ':' | 次要对比数据 |
| 数据点标记 | marker='o' | 离散观测值强调 |
多子图布局是另一个高频需求。最简单的plt.subplots(1, 2)表示一行两列,返回的ax是一个数组,按索引取用:
fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].plot(...) # 左图 axes[1].plot(...) # 右图更自由的布局用GridSpec,比如左侧一张大图、右侧上下两张小图,这在做仪表盘式汇总图时非常好用:
import matplotlib.gridspec as gridspec fig = plt.figure(figsize=(12, 6)) gs = gridspec.GridSpec(2, 3, height_ratios=[1, 1]) ax_left = fig.add_subplot(gs[:, 0]) # 左侧纵向大图 ax_right_top = fig.add_subplot(gs[0, 1:]) # 右上 ax_right_bottom = fig.add_subplot(gs[1, 1:]) # 右下子图之间注意用fig.tight_layout()或plt.subplots_adjust调整间距,不然标题和坐标轴标签会互相挤压。这一步属于"做出来的图能不能直接交出去"的分水岭。
3. Seaborn 的真正价值:一行代码画出统计级图表
如果说 Matplotlib 是"给你一套画笔,画成什么样看你本事",那 Seaborn 就是"自带构图模板和统计功能的相机"。
3.1 为什么 Seaborn 更适合统计探索
Seaborn 从设计之初就面向数据分析场景。它默认内置了计算能力:画直方图时能附带核密度估计曲线;画散点图时能自动拟合回归线;画箱线图时四分位数、中位数、离群点全部自动算好。这些功能如果用 Matplotlib 手工实现,光是统计量计算就要写十几行代码,而 Seaborn 通常一个参数就能搞定。
另一个大优势是它直接吃 pandas 的 DataFrame,不用你手动把数据拆成几组再传给绘图函数。比如我想看不同渠道的销量分布,Matplotlib 的思路是先按渠道分组算统计量再画;Seaborn 的思路是直接把整个 DataFrame 丢给它,告诉它"x 是什么、y 是什么、按什么分组"就完了。
3.2 三大绘图函数族:分布、关系、分类
Seaborn 的函数看着多,其实可以归成三大族。搞清楚每个函数族解决什么问题,比记函数签名重要得多。
| 函数族 | 核心函数 | 解决什么问题 |
|---|---|---|
| 分布类 | displot、histplot、kdeplot | 单个或多个变量的数值分布 |
| 关系类 | relplot、scatterplot、lineplot | 两个变量之间的变化关系 |
| 分类类 | catplot、boxplot、violinplot | 分类变量下的取值对比 |
一个必须提醒的点:sns.distplot这个老函数在 Seaborn 0.14 版本已经被彻底移除了。网上大量老教程还在教distplot,你照着敲会直接报错。现在统一切换到sns.displot,它同时支持hist和kde两种呈现方式:
import seaborn as sns # 单变量分布:直方图 + 核密度曲线 sns.displot(df, x="price", kde=True, height=5, aspect=1.4) # 关系图:带回归拟合线的散点图 sns.relplot(df, x="supply", y="price", hue="region", kind="scatter") # 分类图:分组的箱线图 sns.catplot(df, x="category", y="price", kind="box", height=5)这三行代码基本覆盖了日常数据探索 80% 的需求。height和aspect控制图的尺寸比例,hue指定颜色分组的列名,kind切换同族函数的具体图表类型。刚开始用的时候,我建议直接把kind可能取的值翻一遍:box、violin、bar、point、strip、swarm,每种都跑一遍,你自然会在不同场景下选对图。
3.3 hue、col 与 row:用一行代码展示多维信息
Matplotlib 画多维数据时,需要手动分组、循环、合并图例,代码一长就容易出错。Seaborn 把这套流程压缩成了几个参数。hue用颜色区分第三个维度,col按列分面成多张子图,row按行分面。比如看不同充值档位用户的消费频次分布:
sns.displot(df, x="spend", hue="tier", kind="kde", fill=True, height=5)这个图里,x 轴是消费金额,hue把不同档位分成三条曲线,fill=True让曲线下方填充半透明色便于区分。如果档位太多,再加一个col="tier"就会拆成三张独立小图,每张只画一个档位。这比手动for循环要优雅得多。
但这里有个度的问题。一张图承载的维度是有限的:x 和 y 两个空间位置,hue一个颜色维度,size一个大小维度,col和row两个分面维度。理论上你可以叠很多层,但实际上一张图超过四个维度,读者就消化不了了。我在项目里见过有人把hue和size同时叠上去,结果图例比图还长,信息密度过高反而失去了可读性。数据可视化不是把所有信息塞进一张图,而是让一张图讲清一件事。
4. 让图表真正落地:保存、GIF 动画、中文字体与 Web 集成
画图只是开始,把图用出去才是终点。这一章讲的全是实际项目里绕不开的环节。
4.1 保存图片的参数细节:dpi、bbox_inches、facecolor
保存图片最常用的代码是:
fig.savefig("report.png", dpi=300, bbox_inches="tight", facecolor="white")三个参数各有讲究。dpi=300是印刷级清晰度,基本满足绝大多数报告和 PPT 需求,如果只是屏幕展示,dpi=150就够,文件体积更小。bbox_inches="tight"会自动裁掉图表周围多余的空白,我强烈建议每次都加上,不然保存出来的图四周往往有一圈不协调的白边。facecolor="white"解决透明背景问题,特别是图里有渐变填充色时,透明背景保存出来的 PNG 放到深色 PPT 里会一片混乱。
格式选择也看场景:
| 格式 | 适用场景 | 备注 |
|---|---|---|
| PNG | 报告、PPT、网页 | 位图,放大可能失真 |
| 论文、印刷 | 矢量图,文字清晰 | |
| SVG | 网页、矢量编辑 | 可无损缩放 |
| GIF | 动态演示 | 动画导出用 |
| WebP | 网页展示 | 体积更小,注意兼容性 |
4.2 从"做图"到"做动画":Matplotlib 保存 GIF 的实践
"Matplotlib 保存动画为 gif"是很多人搜过的需求。常见场景是做时间序列的动态演示,比如逐月展示销量变化。核心思路是:用FuncAnimation定义每一帧画什么,再用PillowWriter导出为 GIF。完整示例:
import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation, PillowWriter import numpy as np x = np.linspace(0, 2 * np.pi, 100) frames = 30 fig, ax = plt.subplots(figsize=(8, 5)) def update(frame): ax.clear() ax.plot(x[:frame * 3], np.sin(x[:frame * 3]), color="#2E86AB", lw=2) ax.set_xlim(0, 2 * np.pi) ax.set_ylim(-1.5, 1.5) ax.set_title(f"Frame {frame}") anim = FuncAnimation(fig, update, frames=frames, interval=100) anim.save("wave.gif", writer=PillowWriter(fps=10)) plt.close(fig)这里有几个关键点:update函数每帧被调用一次,ax.clear()必须放在开头,否则上一帧的内容会叠加在下一帧上;set_xlim和set_ylim要固定,不然坐标轴会随着数据范围抖动;interval是动画帧间毫秒数,fps是 GIF 帧率,两者不要同时设得太大。保存 GIF 需要安装 Pillow 库,pip install pillow即可。
实测建议:帧数控制在 30-50 帧,fps设在 10 左右,生成的 GIF 体积和流畅度比较均衡。如果你要导出的是高清动图或长视频,GIF 就力不从心了,这时候优先考虑 MP4。另外,动画类图表在正式报告里要慎用——如果没有播放条件,GIF 反而会分散读者注意力。
4.3 中文字体三件套:不折腾这个,项目就卡在中文乱码
这是几乎每个用 Matplotlib 的人都会踩的坑。默认字体不含中文字符,画出来的图全是方块。解决方案是设置字体和负号显示,两行代码:
plt.rcParams["font.sans-serif"] = ["PingFang SC", "Microsoft YaHei", "SimHei", "Noto Sans CJK SC"] plt.rcParams["axes.unicode_minus"] = Falseunicode_minus这个参数很多教程不会提。它的作用是解决负号显示问题:默认字体下,坐标轴的负号会被渲染成一个小方块,把unicode_minus设为False才能正常显示。不同系统的字体名不一样:macOS 上是PingFang SC,Windows 上是Microsoft YaHei或SimHei,Linux 服务器上一般要装Noto Sans CJK SC。如果字体没生效,可以用plt.rcParams["font.family"]指定一个大类,或者直接检查系统里有没有对应字体。
我在 Linux 服务器上跑自动化报表时吃过一次亏:代码在本地 Mac 上运行正常,部署到服务器后中文全变方块。后来找到的原因就是服务器上没有中文字体。解决方案是在服务器上安装字体,或者把字体文件放到项目目录里用font_manager指定路径。一句话:用 Matplotlib 出中文图,环境里必须有中文字体,这一步要在部署时就检查,不要等到报表发出去了才发现。
4.4 可视化项目的框架边界:Flask、ECharts 与 Matplotlib 如何配合
热搜里经常出现"农产品价格数据可视化-flask""网约车大数据综合项目-flask+echarts"这类关键词,背后其实是同一个问题:做一个数据看板,到底用 Python 绘图库还是 Web 图表库?
我的经验是,先分清需求类型:
- 静态周报、邮件附件、PPT 插图:直接用 Matplotlib/Seaborn 出 PNG/PDF 图。
- 需要筛选、钻取、实时交互的在线看板:用 Flask 提供接口,前端用 ECharts 渲染图表。
- 两者交叉的场景:后端用 Matplotlib 生成结论性图表,以图片形式嵌入页面;前端用 ECharts 做交互探索。
举例来说,做网约车运营大屏时,今日整体订单量、完单率这种汇报型指标,我倾向用 Matplotlib 生成一张精美的总览图放到页面顶部;而可点击筛选"按区域""按时段"查看流量趋势的部分,就必须用 ECharts,因为交互式下钻是静态图片给不了的。如果非要在 Web 里展示 Matplotlib 动态图,把生成的 GIF 或 PNG 存成静态文件,再用 Flask 静态路由映射出去,或者转 base64 嵌入 HTML,都是可行的做法,但不适合数据量大的场景。
这一章也是我对"企业级数据可视化"最直接的理解:不是画面多花哨,而是有一套规范的数据口径、可复现的出图脚本、自动化的报表流程。图本身只是链条的最后一环。
5. 踩过的坑与自查清单
最后这部分,是我这几年被各种图表问题折磨后总结出来的检查项,也是你最容易在网上教程里看不到的内容。
5.1 高频报错与对应解决思路
我按年份统计下来,Matplotlib 和 Seaborn 用户遇到最多的报错就这么几类:
| 报错或现象 | 原因 | 解决思路 |
|---|---|---|
sns.distplot不存在 | 旧 API 在新版本被移除 | 改用sns.displot或sns.histplot |
UserWarning: findfont: Font family ... not found | 系统中没有对应字体 | 安装中文字体或指定存在的字体路径 |
| 保存的图片内容被截断 | 坐标轴标签超出画布范围 | 保存时加bbox_inches="tight" |
| 多张图内容叠加在一起 | Figure 未关闭或重复使用 | 每次显式新建fig, ax,或plt.close(fig) |
| GIF 保存时报找不到 writer | 缺少 Pillow 库 | pip install pillow |
| 颜色和预期不一致 | 手动颜色被 seaborn 主题覆盖 | 显式传palette=None或color=参数 |
第一行那个distplot问题最典型。我见过很多初学者照着两三年前的教程敲代码,一运行就报错,然后怀疑是自己环境坏了。其实不是环境问题,是库升级了。2024 年之后的 Seaborn 版本,distplot已经不存在了,所有分布图都用displot系函数。学习时一定要留意教程的时效性。
5.2 容易被忽略的绘图习惯:坐标轴范围、图例位置、对比度
数据画对了,但图不好看、不好读,通常不是美学问题,而是这几个细节没注意。
坐标轴范围。Matplotlib 默认会自动缩放,但自动缩放有时会放大噪声。比如销量数据有一个异常高的峰值,默认坐标系会把其他所有数据压缩到图下方,趋势完全看不出来。这时候要手动ax.set_ylim()设定合理范围,或者用ax.set_yscale("log")换成对数坐标,让大跨度的数据也能展现结构。
图例位置。ax.legend(loc="best")虽然省事,但有时候"best"会把图例放到遮挡数据的位置。特别是折线图的末端,图例盖在最关键的趋势线上是常有的事。我习惯把图例放到图外或固定的四个角落,比如loc="upper left"或loc="lower right",再配合frameon=False去掉图例边框,整体观感干净很多。
颜色对比度。做汇报材料时要考虑色盲和灰度打印场景。红绿色盲人群看红绿对比图会直接一片模糊。安全做法是选用色觉友好型的配色方案,Seaborn 内置的palette="colorblind"是个好选择。深色背景上用浅色文字,浅色背景上用深色文字,这个基础原则在图表里一样适用。
5.3 我出图前的八项自查
画图容易,出能交付的图难。我每次提交图表前都会快速过一遍这八项:
- 数据口径是否正确,是否经过缺失值和重复值清洗。
- 图表类型是否匹配数据关系:趋势看折线、对比看柱状、分布看直方图、结构看箱线图。
- 标题是否直接写清了结论,而不是仅仅描述内容。
- 坐标轴是否完整显示,单位是否标注清楚。
- 图例是否清晰且不在数据区域附近遮挡。
- 颜色是否有足够对比度,是否考虑色盲人群和灰度打印。
- 保存格式是否匹配使用场景,dpi 是否满足输出要求。
- 是否做了中文字体和负号的处理,在目标运行环境验证过。
第八项最容易在本地开发时被忽略。我吃过一次亏:代码在本机出图一切正常,部署到客户的 Windows 服务器上,字体没了,图全变方块。从那时候起,我出图前一定会单独检查环境字体列表,而不是默认"能运行就等于能出图"。
最后分享一个屡试不爽的小技巧:废图别急着删,把它们和修改后的版本放在一起对比,记下每次改动的原因。时间一长,这套"改图记录"就变成了你自己的可视化避坑手册。下次遇到类似需求,不用从零开始调参,翻一翻记录就能直接命中正确方案。