资讯动态

Matplotlib堆积图实战:从数据准备到自动化出图的完整指南

发布时间:2026/10/9 22:39:19 来源:尧图企业网站定制
1. 从一张“糊成一团”的图说起堆积图到底解决什么问题很多人第一次接触 Matplotlib 的堆积图都是被一个很具体的需求逼出来的手头有几组数据它们共享同一个横轴比如一周七天、十二个月、几个渠道你想同时看到“总量”和“构成”。如果直接画多条折线线一多就互相穿插谁压着谁根本看不清如果画并排柱状图总量又得靠眼睛去加。堆积图Stacked Chart就是为这个场景生的——它把同一根柱子按类别往上叠柱子的总高度代表合计每一段的厚度代表该类别在当期的贡献。我第一次真正用上它是做一个渠道来源分析。当时有自然流量、活动流量、推荐流量三条线老板要看每天的总访问量同时想知道活动到底拉动了多少。折线图画出来三条线缠在一起汇报时被问“这天总量多少”我当场心算还差点算错。换成堆积柱状图之后总高度一眼就是总量活动那一段的颜色深浅直接反映拉动效果整个汇报顺畅了很多。这件事让我意识到堆积图的核心价值不是“好看”而是把“合计”和“构成”这两个信息压缩进同一个视觉通道。Matplotlib 里画堆积图底层其实就一句话plt.bar()的bottom参数。但真正把它用顺手需要理解的东西远不止这一句。你得知道数据要怎么组织成“可叠加”的形状颜色怎么选才不会让相邻段糊在一起标签怎么放才不重叠横向堆积和纵向堆积分别适合什么场景以及当类别多到七八个时该怎么救场。这些细节官方文档不会一条条告诉你但实际项目里每一个都会绊你一下。这篇内容适合三类人刚学完 Matplotlib 基础、想进阶到“能画出能汇报的图”的新手做过一些图表但堆积图总是画得别扭、颜色乱、标签挤的中级使用者以及需要把堆积图嵌进自动化报表、批量出图的工程实践者。我会从数据准备讲到样式调优再到几个真实踩过的坑尽量把每个“为什么这么写”都交代清楚。你不需要背 API跟着思路走一遍下次遇到类似需求就能直接改参数复用。2. 堆积图的数据形状为什么你的第一次尝试总是叠错2.1 从“宽表”到“可叠加数组”的转换逻辑堆积图最容易出错的地方不在画图代码而在数据准备。很多人手里拿到的是一张宽表长这样日期自然流量活动流量推荐流量周一1200800400周二1350950420周三11001500380你想画堆积柱状图于是直接plt.bar(日期, 自然流量)然后想再加一层却发现第二根柱子是从零开始画的把第一根盖住了。这就是没理解bottom参数的作用。bottom的意思是“这根柱子的底部从哪个高度开始画”默认是 0。要让活动流量叠在自然流量上面第二根柱子的bottom必须等于自然流量的值。所以正确的做法是先画最底层记下它的高度数组画第二层时把bottom设成第一层的高度画第三层时把bottom设成前两层之和。这个“累加”的过程就是堆积图的本质。用代码表达import matplotlib.pyplot as plt import numpy as np days [周一, 周二, 周三, 周四, 周五] natural np.array([1200, 1350, 1100, 1400, 1250]) activity np.array([800, 950, 1500, 1100, 900]) recommend np.array([400, 420, 380, 450, 500]) plt.bar(days, natural, label自然流量) plt.bar(days, activity, bottomnatural, label活动流量) plt.bar(days, recommend, bottomnatural activity, label推荐流量) plt.legend() plt.show()注意第三行的bottomnatural activity这里用的是 NumPy 数组的逐元素相加得到的就是前两层在每个位置上的累计高度。如果你用的是 Python 列表natural activity会变成列表拼接而不是逐元素相加结果完全错误。这是我见过新手最常踩的坑之一所以强烈建议在画图前先把数据转成np.array。2.2 横向堆积与纵向堆积的选择依据plt.bar()画的是纵向柱状图plt.barh()画的是横向条形图。堆积逻辑完全一样只是bottom变成了left参数。那什么时候该用横向我的经验是看类别标签的长度。如果横轴标签是“周一”“周二”这种短词纵向没问题但如果标签是“华东大区直营渠道”“华南大区经销商渠道”这种长文本纵向排列会挤成一团旋转 45 度也难看。这时候换成横向堆积标签在左侧自然横排可读性提升非常明显。另一个判断维度是类别数量。纵向堆积适合 3 到 5 个类别横向堆积在类别稍多时更从容因为纵向空间比横向空间更容易扩展。不过横向堆积也有代价它占的垂直空间大如果类别多、又要放进一页 PPT可能得压缩高度反而显得局促。所以我的习惯是先看标签长度再看类别数量两者综合决定方向。还有一个细节横向堆积时left参数的累加逻辑和纵向的bottom一模一样但很多人会忘记把plt.barh的height参数调一下。默认高度是 0.8如果柱子太粗段与段之间的边界会不明显适当调到 0.6 到 0.7 会让图更透气。2.3 数据归一化百分比堆积图的适用边界有时候你关心的不是绝对量而是构成比例。比如三个渠道每天各占多少百分比总量本身波动不重要。这时候要做百分比堆积图思路是先把每一列归一化成 100%再按普通堆积图画。total natural activity recommend natural_pct natural / total * 100 activity_pct activity / total * 100 recommend_pct recommend / total * 100 plt.bar(days, natural_pct, label自然流量) plt.bar(days, activity_pct, bottomnatural_pct, label活动流量) plt.bar(days, recommend_pct, bottomnatural_pct activity_pct, label推荐流量) plt.ylabel(占比 (%)) plt.legend() plt.show()百分比堆积图的好处是构成对比极其清晰缺点是丢失了总量信息。如果总量本身也是关键指标那百分比堆积就不合适得回到绝对量堆积或者在旁边加一条总量折线做辅助。我一般会在汇报时明确告诉听众“这张图只看结构不看规模”避免误读。提示归一化时务必处理分母为零的情况。如果某天总量为 0除法会产生nan或inf画图时柱子会消失甚至报错。稳妥做法是先过滤掉总量为 0 的日期或者用np.where把无效位置置零。3. 颜色与堆叠顺序让相邻色块不再“糊”在一起3.1 配色方案的取舍为什么默认色板常常不够用Matplotlib 默认的tab10色板在折线图里表现不错但用在堆积图上经常出问题。原因是堆积图的色块是紧挨着的相邻两段如果明度接近边界就会模糊。比如tab10里的蓝色和橙色放在一起还行但蓝色和紫色相邻时在投影仪上几乎分不清。我的做法是优先选择明度差异明显的配色。一个简单有效的策略是用同一色系的不同深浅比如深蓝、中蓝、浅蓝这样既保持了类别间的关联感又靠明度拉开层次。Matplotlib 里可以用plt.cm.Blues这类 colormap 采样import matplotlib.pyplot as plt import numpy as np colors plt.cm.Blues(np.linspace(0.4, 0.9, 3))np.linspace(0.4, 0.9, 3)生成三个从 0.4 到 0.9 的等距值对应从较浅到较深的蓝色。这样画出来的三段柱子有自然的层次感不会糊。如果类别之间没有天然关联那就用对比色但要注意避开红绿搭配色觉障碍人群很难区分。另一个实用技巧是给每段柱子加白色描边plt.bar(days, natural, label自然流量, edgecolorwhite, linewidth1.5)这条白边就像给每个色块画了条分界线即使颜色接近也能看清边界。我在做需要打印的黑白报表时甚至会直接用不同灰度加描边效果比彩色还稳。3.2 堆叠顺序的心理学谁该在最下面堆叠顺序不是随便排的。人眼对柱子底部的感知最稳定因为底部有横轴作为参照越往上判断某一段的绝对高度就越依赖上下文。所以最重要的、或者最稳定的类别应该放在最下面。举个例子如果自然流量是基本盘每天都有且波动不大那它放底层最合适读者一眼就能看出基本盘的规模。活动流量波动大放在中间或上层它的变化会通过柱子总高度的起伏体现出来。反过来如果把波动大的类别放底层整个柱子的起点就在跳读者很难判断其他类别。还有一个约定俗成的习惯如果类别有逻辑顺序比如“新客、老客、流失召回”就按这个顺序从下往上排不要为了颜色好看打乱。读者会下意识地按顺序理解顺序乱了会增加认知负担。3.3 图例位置与色块标注的配合图例默认在右上角但堆积图的右上角往往被柱子占据图例压上去会遮挡数据。我一般会把图例移到图外plt.legend(locupper left, bbox_to_anchor(1.02, 1), borderaxespad0)bbox_to_anchor把图例锚定在坐标轴右侧(1.02, 1)表示横坐标略超出右边界、纵坐标对齐顶部。这样图例不遮挡任何柱子适合放进报告。如果类别不多直接在色块上标数值比图例更直观。但堆积图的中间段标注很麻烦因为每段的高度不一文字容易溢出。我的做法是只标总量在柱子顶部中间段用图例区分。如果非要标每段可以用plt.text配合累计高度计算位置但类别超过四个就别折腾了视觉上会很乱。注意bbox_to_anchor的坐标是相对于坐标轴的比例不是数据坐标。很多人第一次用会填数据值结果图例飞到图外老远。记住它是 0 到 1 的比例值超过 1 就是往右/往上超出。4. 标签、网格与坐标轴把图从“能看”做到“能汇报”4.1 总量标签的自动计算与放置堆积图最有价值的信息之一是柱子顶部的总量。手动算好再写死数字数据一更新就全错了。正确做法是用累计高度自动计算totals natural activity recommend for i, total in enumerate(totals): plt.text(i, total 30, str(total), hacenter, vabottom, fontsize10)total 30是给文字留一点上方空间避免贴着柱子顶。hacenter让文字水平居中于柱子vabottom让文字的底部对齐给定坐标。这个偏移量 30 需要根据数据量级调整如果总量是几万30 就太小了文字会压在柱子上。一个更稳的做法是用坐标轴范围的百分比来定偏移offset max(totals) * 0.02这样无论数据量级如何偏移都是总量的 2%视觉上比较协调。4.2 网格线的取舍横向网格为什么更适合堆积图堆积图的读者需要比较不同柱子的同一段高度横向网格线能提供参照。但默认的网格线是双向的纵向网格线在堆积图里几乎没用还会和柱子边界打架。所以我会关掉纵向网格plt.grid(axisy, linestyle--, alpha0.4)axisy只保留横向网格alpha0.4让网格线变淡不抢柱子的视觉焦点。linestyle--虚线比实线更轻适合做背景参照。如果柱子本身颜色较深网格线要更淡否则会显得脏。我一般把alpha控制在 0.3 到 0.5 之间具体看柱子颜色的明度。深色柱子配 0.3浅色柱子配 0.5。4.3 坐标轴范围与留白别让柱子顶到天花板Matplotlib 默认会自动留一点边距但堆积图的柱子往往比较高自动边距可能不够总量标签会顶到图的上边缘。手动设置 y 轴上限更稳妥plt.ylim(0, max(totals) * 1.15)乘以 1.15 是给标签留出 15% 的顶部空间。这个系数不是固定的如果标签字号大可能要 1.2如果标签在柱子内部1.05 就够。关键是别让任何元素贴边贴边会显得局促汇报时也不好看。x 轴这边如果柱子数量少可以适当加宽plt.xlim(-0.5, len(days) - 0.5)这样第一根和最后一根柱子不会贴着左右边界视觉上更平衡。4.4 字体与字号中文显示与可读性底线Matplotlib 默认字体不支持中文直接画会显示成方框。这是新手必踩的坑。解决办法是设置支持中文的字体plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] FalseSimHei是黑体Windows 系统一般都有。Mac 系统可以用Arial Unicode MS或PingFang SC。axes.unicode_minus False是为了让负号正常显示否则负号也会变成方框。字号方面标题 14 到 16坐标轴标签 11 到 12刻度标签 10 左右图例 10。这是投影和打印都还能看清的下限。如果图要放进论文或正式报告可以整体放大 1 到 2 号。别用默认的 10 号字交差在会议室后排根本看不清。5. 多子图与批量出图把堆积图塞进自动化流程5.1 subplots 布局下的堆积图复用实际项目里很少只画一张图。更常见的是把几个维度的堆积图拼在一起比如按地区分四个子图每个子图展示该地区的渠道构成。这时候用plt.subplots创建网格然后在每个ax上画堆积图fig, axes plt.subplots(2, 2, figsize(12, 8)) for ax, region in zip(axes.flat, [华东, 华南, 华北, 西南]): ax.bar(days, natural, label自然) ax.bar(days, activity, bottomnatural, label活动) ax.bar(days, recommend, bottomnatural activity, label推荐) ax.set_title(region) ax.grid(axisy, linestyle--, alpha0.4) axes[0, 0].legend() fig.tight_layout()这里有几个关键点。第一axes.flat把二维数组拉平成一维方便用zip遍历。第二图例只在第一个子图上加一次避免重复。第三fig.tight_layout()自动调整子图间距防止标签重叠。如果不加这一句子图标题和上方子图的 x 轴标签经常打架。5.2 把堆积图封装成可复用函数每次画图都重写一遍bar调用太累而且容易漏参数。封装成函数是更工程化的做法def plot_stacked(ax, x, data_dict, colorsNone, show_totalTrue): bottom np.zeros(len(x)) for i, (label, values) in enumerate(data_dict.items()): color colors[i] if colors else None ax.bar(x, values, bottombottom, labellabel, colorcolor, edgecolorwhite, linewidth1.2) bottom np.array(values) if show_total: for i, total in enumerate(bottom): ax.text(i, total * 1.02, f{total:.0f}, hacenter, vabottom, fontsize9) ax.legend() ax.grid(axisy, linestyle--, alpha0.4) return ax这个函数接收一个ax、横轴标签、一个字典键是类别名值是数值列表自动处理累加、描边、总量标签。bottom初始化为全零数组每画一层就累加一次逻辑清晰。show_total开关控制是否显示总量方便在不同场景复用。封装之后画一张图就变成一行调用fig, ax plt.subplots(figsize(8, 5)) plot_stacked(ax, days, {自然流量: natural, 活动流量: activity, 推荐流量: recommend}) plt.show()数据变了只改字典样式调整只改函数维护成本大幅降低。5.3 保存与导出分辨率、格式与透明背景画完图要保存plt.savefig的参数直接影响输出质量plt.savefig(stacked.png, dpi300, bbox_inchestight, transparentFalse)dpi300是印刷级分辨率屏幕展示 150 就够但报告和论文建议 300。bbox_inchestight自动裁掉多余白边避免图周围一圈空白。transparentFalse保持白底如果要做 PPT 叠加可以设成True导出透明背景的 PNG。格式选择上PNG 适合屏幕和 PPTPDF 和 SVG 是矢量格式放大不失真适合论文和印刷。如果图里元素多、文件大SVG 可能比 PDF 更小。我一般同时导出 PNG 和 PDFPNG 用于快速预览PDF 用于正式场合。提示bbox_inchestight有时会把图例裁掉一部分尤其是图例放在图外时。如果发现图例不完整可以手动指定pad_inches增加边距或者干脆不用 tight手动调subplots_adjust。6. 那些年我踩过的堆积图坑从数据错位到标签重叠6.1 列表相加导致的“隐形错误”前面提过列表相加的问题这里展开说。Python 里[1, 2] [3, 4]得到[1, 2, 3, 4]而不是[4, 6]。如果你用列表存数据bottomnatural activity会得到一个长度翻倍的列表plt.bar拿到这个bottom后柱子位置和高度全乱但程序不一定报错图可能只是“看起来怪怪的”。这种错误最危险因为它不崩溃只是悄悄画错。我的习惯是数据一进来就转np.array从源头杜绝。如果数据来自 pandas.values或.to_numpy()也能拿到数组。总之画图前确认数据类型是数组不是列表。6.2 类别过多时的“彩虹条”灾难有一次我拿到八个渠道的数据想全堆在一张图上。画出来之后每根柱子被切成八段每段细得像纸片颜色倒是很丰富但根本看不出哪段是哪段。这就是类别过多的典型症状。堆积图的可读性上限大概是五到六个类别。超过这个数我的处理方式是把占比最小的几个类别合并成“其他”只保留前四到五个主要类别。合并之后柱子清爽很多主要类别的对比也更突出。如果“其他”本身也重要可以单独画一张小图或者用表格补充。另一个思路是换图型。类别多的时候分组柱状图或者热力图可能比堆积图更合适。堆积图不是万能的它擅长的是“少量类别 总量与构成并重”的场景。6.3 负值数据的处理堆积图遇到负数会怎样如果某个类别出现负值堆积图会变得很尴尬。因为bottom累加的逻辑假设所有值都是正的一旦有负数柱子会往下长和下面的柱子重叠视觉上完全乱套。我遇到负值时的做法是如果负值代表“减少”或“亏损”把它单独拿出来用另一种图型表达比如在堆积图下方加一条负值柱状图或者用折线标注。硬塞进堆积图只会让读者困惑。如果负值只是数据错误那当然先修数据。6.4 标签重叠的三种解法总量标签重叠通常发生在柱子数量多、数值接近的时候。三种解法按推荐顺序第一种旋转标签。plt.xticks(rotation45)让横轴标签倾斜腾出空间。但旋转后标签可能超出画布配合bbox_inchestight保存能自动裁切。第二种隔一个标一个。如果柱子太密没必要每个都标隔一个标一次信息量减半但可读性大增。第三种缩小字号。这是下策因为字号太小就失去意义了。如果前两种都不行说明柱子数量已经超出这张图的承载能力该考虑分图或者换图型了。6.5 图例遮挡数据的动态调整图例位置没有万能解。我的经验是先画图看图例默认位置有没有压到柱子压到了就往外移。往外移之后如果画布不够宽就调整figsize加宽。如果图例本身太长类别名很长可以改成横向排列plt.legend(ncol3, locupper center, bbox_to_anchor(0.5, -0.15))ncol3让图例分三列bbox_to_anchor(0.5, -0.15)把图例放到图的下方居中。这样图例不占图内空间适合类别名长的场景。代价是图的高度要留够否则图例会被裁掉。7. 从堆积图延伸出去几个值得一试的变体7.1 堆积面积图时间序列的连续堆积如果横轴是连续时间柱子之间没有间隙堆积柱状图就变成了堆积面积图。Matplotlib 里用plt.stackplot实现plt.stackplot(days, natural, activity, recommend, labels[自然, 活动, 推荐], colorsplt.cm.Blues(np.linspace(0.4, 0.9, 3)), alpha0.85) plt.legend(locupper left)stackplot自动处理累加不用手动算bottom比bar省事。它适合展示长时间跨度的构成变化趋势比如一年的月度数据。缺点是面积图的边界不如柱状图清晰精确比较某一天的具体数值比较困难更适合看整体趋势。7.2 百分比堆积与绝对量堆积的组合有时候既要看构成比例又要看总量规模。一个折中方案是画两张图上面一张绝对量堆积下面一张百分比堆积共享 x 轴。这样读者既能看总量起伏又能看结构变化。用plt.subplots(2, 1, sharexTrue)实现两张图对齐信息互补。这种组合图在业务汇报里特别实用。上面那张回答“量有多大”下面那张回答“结构怎么变”两个问题一次讲清。7.3 给堆积图加趋势线如果总量本身有趋势可以在堆积图上方叠加一条总量折线plt.plot(days, totals, colorblack, markero, linewidth2, label总量趋势)折线用黑色或深灰和彩色柱子区分开。markero在每天的位置加个圆点强调具体数值。这样一张图里既有构成又有总量趋势信息密度高但不乱。注意折线要画在柱子之后否则会被柱子盖住。7.4 动画堆积图让变化过程动起来如果数据有时间维度做成动画比静态图更有冲击力。Matplotlib 的FuncAnimation可以逐帧更新柱子高度from matplotlib.animation import FuncAnimation fig, ax plt.subplots() def update(frame): ax.clear() ax.bar(days, natural[:frame1], label自然) ax.bar(days, activity[:frame1], bottomnatural[:frame1], label活动) ax.legend() ani FuncAnimation(fig, update, frameslen(days), interval500) ani.save(stacked.gif, writerpillow)frameslen(days)表示帧数等于数据点数量interval500是每帧间隔 500 毫秒。writerpillow导出 GIF。动画适合展示数据逐步累积的过程但要注意文件大小帧数多、分辨率高时 GIF 会很大可以适当降低dpi或减少帧数。注意动画里的ax.clear()会清掉所有元素包括图例和网格所以每帧都要重新设置。如果图例位置固定可以在update外面设置一次但clear之后需要重新添加。这是动画代码里常见的坑。8. 一些让堆积图更“专业”的细节习惯画了这么多堆积图我慢慢养成了一些习惯这些习惯不会让图“能跑”但会让图“能看、能讲、能复用”。第一个习惯是先定数据再定样式。很多人一上来就调颜色、调字体结果数据一改所有样式白调。我的顺序是先把数据组织成正确的数组形状跑通最朴素的图确认数据没错位、没漏项再开始调样式。数据错了图再好看也是错的。第二个习惯是给每张图写一句“结论标题”。不要用“各渠道流量堆积图”这种描述性标题而是用“活动流量在周三达到峰值占总量的 45%”这种带结论的标题。读者不看图也能抓住重点看了图能验证结论。这个习惯是从做汇报被追问“所以呢”之后逼出来的。第三个习惯是颜色不超过五种字体不超过三种。颜色多了显乱字体多了显杂。主色调用同一色系深浅强调色用一个对比色足够了。字体上标题一种、正文一种最多加个数字字体别搞太花。第四个习惯是导出前必看缩略图。把图缩到 20% 大小看一眼如果这时候还能看清主要信息说明图的层次是清楚的如果缩略图里只剩一团颜色说明细节太多、对比不够得回去简化。这个检查方法特别有效因为汇报时投影出来的效果往往就接近缩略图。最后一个习惯是把常用配置写成模板。中文字体、网格样式、图例位置、保存参数这些每次都要设的东西写成一个setup_style()函数或者一个matplotlibrc文件每次画图前调用一次。省下来的时间可以用来打磨数据而不是重复调样式。堆积图本身不复杂复杂的是让它准确传达信息。数据形状对了颜色分得开标签放得下这张图就及格了。再往上就是靠这些细节习惯一点点磨出来的。我到现在也不敢说每张堆积图都画得完美但至少每次踩坑之后下一次会少踩一个。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑