首页 / 资讯中心 / 文章详情

Matplotlib数据可视化实战:核心框架与五种图表完全指南

Matplotlib数据可视化实战:核心框架与五种图表完全指南 ★ FEATURED ARTICLE
拿着一张只有数字的表格却讲不清楚业绩涨了还是跌了这种尴尬我见过太多次。直到你肯花半小时把数据画成图事情才会变得明朗。Matplotlib 就是 Python 生态里最普及的可视化工具库它能把枯燥的列表、数组、DataFrame 转成折线图、柱状图、散点图而且完全免费、离线可用。对于数据分析师、科研人员、学生以及任何需要用图说话的岗位Matplotlib 几乎是绕不开的起点技能。这篇文章我按自己实际用下来的经验从画图框架、五个最常用的图表类型、图表美化、多子图排版到高频报错排查按一条能直接照做的路径讲清楚。读完之后你至少能独立完成一张带标题、坐标轴、图例的成品图并且知道接下来遇到问题时该去改哪些参数。1. 理解 Matplotlib 的核心框架比背接口更重要我见过很多初学者拿着教程抄代码抄完折线图抄柱状图抄完之后换个数据就不会改了。原因很简单没弄懂 Matplotlib 的底层组织结构。其实这个库的设计思路一点也不复杂弄懂了之后你会发现所有绘图接口都长得差不多。1.1 先搞清楚 Figure、Axes、Artist 这三层结构很多教程直接教你plt.plot(x, y)然后图就出来了。但plt.plot只是一个快捷入口它背后的逻辑是这样的Figure整张画布。你可以把它理解成一张白纸所有的图表元素都画在这张纸上。一张 Figure 里可以有一张图也可以有多张图子图。Axes实际绘图区域。注意这里的 Axes 不是坐标轴而是一个子图本身。它包含坐标轴、刻度、边框以及你画的所有线条和标记。一个 Figure 里可以包含多个 Axes。Artist画在 Axes 上的每一个元素。标题、坐标轴标签、线条、数据点、图例、网格线……全都是 Artist。这个概念必须建立起来否则你后面会遇到一个很经典的坑用plt.title()设置了标题但子图模式下明明有多个子图标题却只出现在最后一个图上或者干脆跑到意料之外的地方。原因就是plt.title()只作用于当前活跃的 Axes如果你没有清晰地管理 Figure 和 Axes就很容易搞混。建议从一开始就养成写fig, ax plt.subplots()的习惯后续所有绘图操作都用ax.plot()、ax.set_title()这种面向对象的方式虽然代码长了几个字符但逻辑清晰切换子图、修改元素时绝对不会乱。1.2 pyplot 与面向对象接口什么时候用哪种Matplotlib 提供两套风格不同的接口新手经常被搞晕pyplot 接口plt.plot()、plt.xlabel()、plt.show()这种像是一个全局遥控器平时画一两张图很爽代码短交互式环境比如 Jupyter Notebook里用起来特别顺手。面向对象接口fig, ax plt.subplots()创建好 Figure 和 Axes 对象然后调用ax.plot()、ax.set_xlabel()等方法。适合复杂布局、多维数据对比、需要反复修改图表的场景。我的建议是如果是快速探索数据在 Jupyter 里用 pyplot 接口没问题如果是正经写脚本、做数据报告、或者画多子图一开始就用面向对象接口。哪怕你现在觉得写fig, ax plt.subplots()多打了几个字后面省下的力气是成倍的。import matplotlib.pyplot as plt import numpy as np # 不推荐隐式状态机 plt.plot([1, 2, 3], [4, 5, 6]) plt.title(demo) plt.show() # 推荐显式面向对象 fig, ax plt.subplots() ax.plot([1, 2, 3], [4, 5, 6]) ax.set_title(demo) plt.show()这两种写法在单图场景下几乎没有区别但多子图、多坐标轴共享、图表元素精细控制时面向对象接口的优势非常明显。1.3 快速上手的路径规划用最小闭环建立信心很多新手从过度复杂的教程开始看了半小时线型颜色参数还没看到图直接劝退。我的建议是用最小闭环建立信心准备一份简单的数据比如一个月每天的销售额。用plt.plot()画出来。加上标题、横纵轴标签。保存或者显示。就这么四步。等你亲眼看到一张图从数据里长出来后面学什么都有底气。然后再逐步增加图例、网格、颜色调整、子图布局。学习绘图库不是背 API而是用一次记一次用到什么查什么慢慢就熟了。2. 五个最常用的图表类型从 0 到 1 的实操全流程Matplotlib 能画的图表类型非常多但实际上日常使用频率最高的也就五种折线图、柱状图、散点图、饼图、直方图。把这五种吃透至少能覆盖 80% 以上的数据可视化需求。下面我逐个讲清楚套路、关键参数和最容易踩的坑。2.1 折线图从一条线开始理解绘图逻辑折线图主要用于展示数据随时间或其他连续变量的变化趋势。它是最基础、也最应该熟练掌握的图表类型。import matplotlib.pyplot as plt import numpy as np # 模拟 30 天的销售额数据 days np.arange(1, 31) sales [120, 132, 118, 145, 158, 142, 139, 165, 172, 168, 175, 190, 185, 178, 192, 205, 210, 198, 215, 224, 230, 228, 245, 250, 238, 260, 255, 268, 275, 290] fig, ax plt.subplots(figsize(10, 5)) # 核心绘图代码 ax.plot(days, sales, markero, linewidth2, markersize4) # 装饰元素 ax.set_title(某门店30天销售额趋势, fontsize14) ax.set_xlabel(日期天) ax.set_ylabel(销售额元) ax.grid(True, linestyle--, alpha0.6) plt.tight_layout() plt.show()这里有几个值得关注的细节markero表示在数据点上画小圆点当数据点不多时这个参数特别有用能清晰地看到每个数据点的实际位置数据点多时比如几百个就不用加了否则会显得非常拥挤。linewidth控制线宽一般 2 左右比较美观markersize控制标记大小。plt.tight_layout()这个函数我几乎每次都加它会自动调整子图之间的间距和边界的留白避免标题或坐标轴标签被截断。不加的话保存图片时经常发现边界被切掉一部分。如果要画多条线进行对比只需要在同一个 Axes 上多次调用ax.plot()然后配合label参数和ax.legend()添加图例。x np.linspace(0, 2 * np.pi, 100) y1 np.sin(x) y2 np.cos(x) fig, ax plt.subplots() ax.plot(x, y1, labelsin, color#4C72B0) ax.plot(x, y2, labelcos, color#DD8452) ax.legend(locupper right) plt.show()legend()的loc参数是新手最容易忽略的。图例放在图内会挡住数据关键区域放在外侧或者指定合适的位置能大幅提升可读性。2.2 柱状图与条形图分类数据对比的正确姿势柱状图纵向和条形图横向用于展示分类数据的数值大小对比。区别只在于方向但有一个很容易被忽视的细节柱状图的横轴通常是类别而不是连续数值。# 各品类季度销售对比 categories [食品, 饮料, 日用品, 服饰, 数码] values [15, 22, 18, 30, 25] fig, ax plt.subplots() ax.bar(categories, values) ax.set_title(各大类销售额万元) ax.set_ylabel(销售额万元) # 在每个柱子上方显示数值 for i, v in enumerate(values): ax.text(i, v 0.5, str(v), hacenter, fontsize10) plt.show()柱状图最常见的坑有两个第一当类别名称很长或者很多时纵向柱状图会互相挤压标签重叠。我遇到这种情况的解决办法是直接改成横向条形图用ax.barh替代ax.bar阅读起来舒服得多。第二柱状图里的数值标签问题。直接把数字标在柱子上确实直观但柱子的高度较小时标签容易和别的元素重叠所以我会调整text()的va垂直对齐和ha水平对齐参数让标签在柱子顶端上方偏移一点比如上面的v 0.5。分组柱状图是数据分析中的高频需求用来比较多个组别 × 多个指标。做法稍微复杂一些关键是控制好每一组柱子的 x 坐标偏移import numpy as np categories [Q1, Q2, Q3, Q4] A_product [20, 35, 30, 42] B_product [25, 32, 34, 38] x np.arange(len(categories)) width 0.35 fig, ax plt.subplots() rects1 ax.bar(x - width/2, A_product, width, label产品A) rects2 ax.bar(x width/2, B_product, width, label产品B) ax.set_xticks(x) ax.set_xticklabels(categories) ax.xlabel(季度) ax.ylabel(销量万台) ax.legend() plt.show()注意width 0.35是柱宽两组柱子分别在x - width/2和x width/2的位置这样正好相邻又不会重叠。类别的刻度用ax.set_xticks(x)固定在每个分组的正中间。2.3 散点图揭示两个变量的关系散点图适合展示两个连续变量之间的关系例如“广告投入”与“销售额”、“身高”与“体重”。散点图的核心价值在于帮你看清数据分布形态正相关、负相关、无关联、或者有聚集区间。# 模拟广告费用和销售额数据 np.random.seed(42) ad_spend np.random.uniform(5, 50, 100) sales 3 1.2 * ad_spend np.random.normal(0, 8, 100) fig, ax plt.subplots() ax.scatter(ad_spend, sales, alpha0.6, edgecolorsblack, linewidths0.5) ax.set_title(广告费用 vs 销售额) ax.set_xlabel(广告费用万元) ax.set_ylabel(销售额万元) plt.show()散点图有一个我建议新手一上来就掌握的参数alpha透明度。数据点多了以后点与点之间会互相遮挡你根本看不出来哪些区域密度更高。把alpha调到 0.5 或 0.6重叠区域的颜色会加深密度分布一目了然。加edgecolorsblack和很细的linewidths是让点在深色背景下也保持清晰的小技巧。还有一个衍生技巧散点图的点大小可以映射到第三个维度比如团队成员的工龄越长点越大这样可以在一张图里同时展示三个变量的信息。这种用法在气泡图中非常普遍核心就是给scatter()传s参数单位是点面积不是直径。2.4 直方图快速了解数据分布很多人容易把直方图和柱状图搞混。直方图用于展示数值变量的分布情况横轴是连续数值的分箱bin纵轴是落在每个分箱内的样本数量或频率。它的用途在于回答“数据主要集中在哪里”“是否呈正态分布”“有没有异常值”这类问题。# 模拟某班级学生的考试成绩 np.random.seed(0) scores np.random.normal(75, 12, 200) fig, ax plt.subplots() ax.hist(scores, bins15, edgecolorwhite, colorsteelblue, alpha0.7) ax.set_title(学生成绩分布) ax.set_xlabel(分数) ax.set_ylabel(人数) plt.show()bins是直方图最重要的参数。它控制分箱数量bins 少了图会显得太粗分布的形状被掩盖bins 多了又会太碎噪声明显。我的经验是先用系统默认值然后根据“是否还能看出整体趋势”来判断手动调整到 10~30 之间通常是不错的选择。使用ax.hist()时尽量加上edgecolorwhite给每个箱子描个白边。不加的话相邻箱子颜色连成一片界限不清晰在黑白打印场景下更是灾难。这是我做数据分析报告时踩过坑之后养成的习惯。2.5 饼图用的场景没有想象中多但始终要会饼图展示占比但说实话实际业务中我很少用它。原因是人类对角度的感知远不如对长度的感知准确柱状图在大多数场景下是更好的占比呈现方式。不过在某些语境里比如份额汇报饼图依然有它的位置。如果你要用注意下面几个原则labels [产品A, 产品B, 产品C, 产品D] sizes [35, 25, 20, 20] explode (0.05, 0.03, 0.03, 0.03) # 将第一块略微分离出来 fig, ax plt.subplots() ax.pie(sizes, explodeexplode, labelslabels, autopct%1.1f%%, shadowFalse, startangle90) ax.axis(equal) # 保证饼图是圆形 plt.show()饼图的关键细节autopct%1.1f%%会自动在扇区中央显示百分比startangle控制起始角度通常设置为 90 度让第一块从正上方开始ax.axis(equal)保证饼图是正圆形不给图形会被拉伸成椭圆。如果类别超过五六个或者百分比之间差距很接近我强烈建议换柱状图。3. 图表细节定制从中文字体到配色风格的完整方案很多初学者画出的图能看但不敢发原因通常不在绘图逻辑而在细节粗糙字体不对、颜色辣眼、标签重叠、缺少图例。这一节讲清楚如何把图做精致。3.1 中文字体乱码问题的标准解法在默认设置下直接用 Matplotlib 绘制中文标题十有八九会显示成方框。这是因为 Matplotlib 的默认字体是英文字体对中文字符支持不好。我在首次处理这个问题时也查了很久这里直接给标准解法import matplotlib.pyplot as plt # 常见中文字体任选其一 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] False # 解决负号显示异常font.sans-serif设置全局中文字体。SimHei是 Windows 系统常见的黑体Microsoft YaHei是微软雅黑macOS 一般用PingFang SC。axes.unicode_minus False这个设置同样重要它规定坐标轴上的负号使用 ASCII 短横线不然负号会显示成方块。如果你使用的是 Linux 服务器系统里可能没有中文字体那就需要先安装字体再使用比如通过包管理器安装中文字体包。还有一个更灵活的方法手动指定字体文件路径。from matplotlib import font_manager # 注册指定路径下的字体文件 font_path /path/to/your/font.ttf font_prop font_manager.FontProperties(fnamefont_path) plt.rcParams[font.family] font_prop.get_name()这个方案的好处是你完全可以自备字体文件不受系统限制在服务器部署场景下非常实用。你在拿到别人的代码时看到字体相关的设置照抄即可理解了原理就不慌。3.2 标题、标签、图例、网格精细调整的一揽子方案图表信息传达的核心是标题、坐标轴标签、图例这三件套。我整理了一套我自己几乎每次绘图都会用到的标准模板fig, ax plt.subplots(figsize(8, 5)) ax.plot(...) ax.set_title(主标题, fontsize16, pad12) ax.set_xlabel(横轴名称, fontsize12) ax.set_ylabel(纵轴名称, fontsize12) ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) ax.tick_params(axisboth, labelsize10) ax.legend(frameonFalse, locbest) ax.grid(axisy, linestyle--, alpha0.4)几个可以抄的经验pad12让标题和图表之间保持适当距离标题不会贴着图框。默认情况下标题离图太近显得局促。set_xlim()和set_ylim()手动控制 XY 轴范围。自动范围有时会在数据两侧留下过多空白适当裁剪能让曲线占据画面的主体。ax.legend(frameonFalse)去掉图例边框图面更清爽。图例边框在论文和幻灯片的场景里通常不是好看的选择。ax.grid(axisy)只显示水平网格线只对纵轴数据做参考线避免画面被交叉网格填满。新手经常默认开启全部网格结果图的视觉噪声很大。tick_params统一调整刻度文字的尺寸。很多人画完图发现刻度字太小、看不清就改这个参数。3.3 配色与内置风格不要自己配颜色除非你懂设计Matplotlib 的默认配色在十几年前显得还过得去但现在看起来确实有点过时。好消息是它内置了几套精心设计的风格主题更换只需一行代码plt.style.use(ggplot) plt.style.use(seaborn-v0_8) plt.style.use(fivethirtyeight) plt.style.use(tableau-colorblind10)我个人最常用seaborn-v0_8和tableau-colorblind10。前者整体配色柔和背景发灰线条对比度适中后者是色盲友好的配色方案适合做需要广泛传播的图表。如果你发现某套风格里坐标轴的背景不是你想要的可以通过plt.style.use和plt.rcParams再叠加调整。用颜色参数时如果你对设计没有把握不要自己随便写红红绿绿。Matplotlib 支持直接使用十六进制颜色码比如#4C72B0、#DD8452、#55A868这类经过设计的颜色组合比默认的red、blue看起来高级不少。从成套的配色方案里直接取色比自己瞎调更安全。4. 多子图布局一页纸讲清楚多图对比的正确做法实际工作中你很少只画一张图。常见场景是横轴相同要对比多个指标或者一次分析需要同时看趋势、分布、相关性和占比。这时候你需要把多张图布局在同一个画布上。4.1 用plt.subplots()快速创建规则网格plt.subplots()是创建子图最标准的方式第一个参数是行数第二个参数是列数fig, axes plt.subplots(2, 2, figsize(12, 8)) # axes 是一个 2x2 的数组axes[0][0] 代表左上角axes[0][1] 代表右上角 axes[0][0].plot(x1, y1) axes[0][0].set_title(左上) axes[0][1].plot(x2, y2) axes[1][0].hist(data) axes[1][1].scatter(x, y)我建议拿到axes数组后用axs axes.flatten()把它拍平再按顺序axs[0]、axs[1]……逐个使用不用去记二维下标。sharexTrue和shareyTrue参数也很常用。当多个子图横轴含义相同阈值一致时共享坐标轴可以让对比更加直接还可以避免刻度重复显示。如果子图共享横轴通常布局选择上让多张图垂直堆叠共用一条底轴分析起来非常舒服。4.2 用plt.subplots_adjust控制间距与尺寸比例画完多子图之后最常见的现象是子图之间挤在一起、标题重叠、轴标签被截断。解决方式除了plt.tight_layout()还有更精细的控制手段plt.subplots_adjust(wspace0.3, hspace0.4)wspace控制子图之间的水平间距hspace控制垂直间距。数值越大间距越宽。默认值有时候不够用尤其当子图的标题比较长时你会看到标题和上图底部几乎挨着。我常用的经验是先加plt.tight_layout()如果间距还不满意再加subplots_adjust微调。4.3 实战案例用四联图看一份销售数据假设手头有一份门店销售数据包含日期、销售额、客流量和折扣力度。我想一次性获得四个视角销售额随日期的变化折线销售额的分布直方图客流量和销售额的关系散点季度销售额占比饼图# 模拟数据 np.random.seed(10) days np.arange(1, 61) sales 30 0.3 * days np.random.normal(0, 5, 60) traffic 200 2 * days np.random.normal(0, 30, 60) fig, axs plt.subplots(2, 2, figsize(12, 8)) # 1. 趋势图 axs[0, 0].plot(days, sales, color#4C72B0) axs[0, 0].set_title(60天销售额趋势) axs[0, 0].set_ylabel(销售额千元) axs[0, 0].grid(axisy, linestyle--, alpha0.3) # 2. 直方图 axs[0, 1].hist(sales, bins12, color#55A868, edgecolorwhite) axs[0, 1].set_title(销售额分布) axs[0, 1].set_xlabel(销售额千元) # 3. 散点图 axs[1, 0].scatter(traffic, sales, alpha0.6, color#C44E52) axs[1, 0].set_title(客流量与销售额关系) axs[1, 0].set_xlabel(客流量人) axs[1, 0].set_ylabel(销售额千元) # 4. 饼图 quarters [Q1, Q2] q_sales [sales[:30].sum(), sales[30:].sum()] axs[1, 1].pie(q_sales, labelsquarters, autopct%1.1f%%, startangle90) axs[1, 1].set_title(上半年季度销售额占比) axs[1, 1].axis(equal) plt.tight_layout() plt.show()这样一个图表矩阵就把数据的主要特征全部覆盖了。你也可以把它扩展成仪表盘思路每周固定跑一次脚本自动生成同一套图用来追踪业务变化。很多所谓的“数据报表自动化”其实就是这么起步的。5. 图片保存、英文论文图表配置与高频报错排查画图完成之后保存和导出同样是关键一环。分辨率不够、字体缺失、中文乱码、图片边界截断这些都是我在实践中反复遇到的。下面直接给出可用的配置和排错方法。5.1savefig常见参数解析分辨率、边距、透明背景用plt.savefig()保存图片时我通常固定加上这几个参数fig.savefig(输出文件名.png, dpi300, bbox_inchestight, facecolorwhite)dpi300每英寸 300 个像素点这是论文投稿和打印的最低标准。默认值是 100放到 PPT 或者 Word 里会变得模糊。bbox_inchestight自动裁剪空白边界让图片内容紧凑地充满边框。如果不用这个参数保存出来的图四周往往有大量留白放到文档里显得很空。facecolorwhite设置底色为白色。如果你用了深色背景风格保存时忘了设置出来的图是一个大黑块打印时特别难看。需要注意的是如果是用plt.show()弹出的交互窗口查看图再点击窗口里的保存按钮保存的清晰度默认可能不高。所以我一直推荐在代码里直接用savefig保存成品图而不是依赖手动操作。savefig支持的格式很多PNG、JPG、PDF、SVG。在文档里需要高保真矢量图时用 PDF 或 SVG网页展示一般用 PNG直接放进 Word 报告用 PNG 加 dpi 300 就够了。5.2 英文论文图表配置不换代码只换样式的rcParams做学术出图的话有一套在论文场景下比较通用的设置。这里给出一份可以直接复制的常用配置专门解决“英文论文不接受默认风格”的痛点plt.rcParams.update({ font.size: 10, font.family: serif, axes.labelsize: 10, axes.titlesize: 10, xtick.labelsize: 9, ytick.labelsize: 9, legend.fontsize: 9, lines.linewidth: 1.5, axes.linewidth: 0.8, axes.spines.top: False, axes.spines.right: False, })这份配置把字体统一调节为清晰的衬线体serif标题和各轴标签的尺寸都控制在小字号图表线宽适中隐藏顶部和右侧边框线整体观感简洁克制。很多期刊图表的共同特点就是“细节克制”。我自己出图前会先套用这一套然后按目标期刊的要求微调字号省去大量手动调整的时间。5.3 高频报错与排查当你画不出图时的应急手册只讲成功路径不讲失败经验是耍流氓。我遇到的典型报错和解决方案整理成一张速查表现象常见原因处理方法中文显示为方框系统缺少匹配的中文字体设置plt.rcParams[font.sans-serif] [SimHei]负号显示为方框未关闭 Unicode 负号设置plt.rcParams[axes.unicode_minus] False保存图片后空白plt.show()之后才调savefig()确保先savefig()再plt.show()图例出现两次多次调用legend()或重复绘图检查循环中是否遗漏label避免重复添加坐标轴刻度过密数据量过大用plt.xticks()/plt.yticks()指定间隔或者ax.xaxis.set_major_locator()控制间距多子图标题重叠子图间距不够加plt.tight_layout()或调整hspace图片模糊分辨率不足dpi300bbox_inchestight内存堆积、脚本变慢循环中反复创建 Figure 没有关闭循环内用plt.close(fig)释放资源其中“先savefig再show”这条我反复强调因为我真的见过很多人在窗口里把图缩放了一下再截图结果出来的图又小又糊。另外如果你在循环里画了大量的图却不closeJupyter 内核的内存会被占满卡到怀疑人生。封装成函数时最好在函数内部画完图就plt.close(fig)只保留文件输出。5.4 解决“画布空白”和“只显示最后一个子图”的典型问题还有一种现象非常诡异明明代码看起来都正确但运行时画布一片空白或者多个子图只显示最后一个。这种问题基本都出在混淆了 pyplot 接口和面向对象接口。例子先用fig, ax plt.subplots()创建子图然后用plt.plot()画线。此时plt.plot()作用于“当前活跃的 Axes”如果当前的 Axes 不是你以为的那个就会画到别处去。解决办法是坚持用ax.plot()这种对象方法不使用plt.plot()在子图模式下绘图。另一个原因可能是你调用了plt.show()之后又创建了新的图形旧图自然就看不见了。需要一次展示多张独立图片时请把它们放进同一个 Figure 的子图里或者把fig, ax plt.subplots()改成fig plt.figure()后用fig.add_subplot(1, 1, 1)手动画一个坐标轴。另外Jupyter Notebook 环境里如果连续运行多个单元格绘制多个图有些配置会让输出被内联显示多次。你可以在代码开头用plt.close(all)清场或者用plt.gcf()获取当前图对象避免张冠李戴。写在最后的几个实在建议练习时建议按“画图—加装饰—保存”这个顺序逐步迭代。错了不要紧Matplotlib 是报错了立刻能看见反馈的工具你只要记住用面向对象接口、设置好中文字体、保存时带 dpi 和 bbox_inches就避开了绝大多数天坑。我最开始画图时的第一张成品图也很简陋没有网格、没有标题、坐标轴标签还是重叠的。后来每个参数都去查文档试一遍不知不觉就能一次性画出一张结构干净、信息完整的图了。你可以把第一张成品图收藏起来过三个月重新画一次同样数据的图对比一下差别这种“肉眼可见的进步”比看多少教程都更能建立正向反馈。画图的最终目的不是炫技而是把数据里的信息准确、高效地传达给看的人在这个前提下一切技巧都为清晰表达服务。
阅读完成 · 觉得有帮助?
咨询建站