<di
🔥
个人主页:
杨利杰YJlio
❄️
个人专栏:
《Sysinternals实战教程》
《Windows PowerShell 实战》
《WINDOWS教程》
《IOS教程》
《微信助手》
《锤子助手》
《Python》
《Kali Linux》
《那些年未解决的Windows疑难杂症》
🌟
让复杂的事情更简单,让重复的工作自动化
《超简单:用 Python 让 Excel 飞起来》读书笔记:第8章 扩展 制作堆积图观察结构变化
- 1. 第8章扩展:制作堆积图观察结构变化
- 2. 堆积图适合解决什么问题
- 3. 堆积柱形图核心原理:bottom 参数怎么理解
- 4. 多层堆积:结构变化 + 总量标签
- 5. 堆积面积图 stackplot:观察结构漂移
- 6. 企业可交付版:从 Excel 读取多列分类数据生成堆积图
- 7. 效果验证:不要只看“有没有生成图片”
-
- 7.1 验证输出文件是否生成
- 7.2 验证每月总量是否正确
- 7.3 验证分类颜色与图例是否对应
- 7.4 验证缺失值是否被合理处理
- 8. 常见问题与踩坑记录
-
- 8.1 类别太多,图表看不清
- 8.2 误把结构变化当成总量变化
- 8.3 堆积面积图不适合精确读数
- 8.4 中文乱码
- 9. 我的总结提升

1. 第8章扩展:制作堆积图观察结构变化
V4修正版进度标记:本篇为 07月11日 第2篇,承接 06月07日第2篇“2.7.2 自定义函数”之后继续更新。
读书笔记定位:本文是我阅读《超简单:用 Python 让 Excel 飞起来》时整理的学习笔记。本篇不只是记录 Matplotlib 的函数用法,而是重点讲清楚:什么时候该用堆积图、堆积图到底表达了什么、以及如何把它改造成可复用的 Excel 自动化报表脚本。
柱形图和折线图通常能回答一个问题:**总量是涨了还是跌了**。但是在真实工作里,只看总量往往不够。比如工单数量从 135 单涨到 182 单,我真正关心的不是“涨了”,而是:到底是哪一类工单涨了?软件类、硬件类、网络类分别贡献了多少?结构有没有发生变化?
这就是堆积图的价值:它不是单纯展示总量,而是在同一张图里同时展示“总量变化”和“构成变化”。如果用在运维报表、销售数据、成本结构、项目投入分类、故障类型统计里,堆积图比普通柱形图更容易看出问题。
这张图展示了本文的核心主题:通过堆积图同时观察总量趋势和构成变化。

从这张图中我们可以看出,堆积图的重点不是“图表好看”,而是把不同类别的数据叠加到同一个时间轴上,让读者一眼看到:总量在变化,结构也在变化。如果一张图只能告诉你总量变了,却看不出是谁贡献了变化,那它在分析价值上是不完整的。
本篇文章我会围绕 4 个问题展开:
-
堆积图适合解决什么问题?
-
堆积柱形图的 bottom 参数到底怎么理解?
-
多层堆积图如何加总量标签,让图表更适合汇报?
-
如何从 Excel 读取多列分类数据,一键生成可交付的堆积图?
#mermaid-svg-WM0PzubpPXhxVcXh{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WM0PzubpPXhxVcXh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WM0PzubpPXhxVcXh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WM0PzubpPXhxVcXh .error-icon{fill:#552222;}#mermaid-svg-WM0PzubpPXhxVcXh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WM0PzubpPXhxVcXh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WM0PzubpPXhxVcXh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WM0PzubpPXhxVcXh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WM0PzubpPXhxVcXh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WM0PzubpPXhxVcXh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WM0PzubpPXhxVcXh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WM0PzubpPXhxVcXh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WM0PzubpPXhxVcXh .marker.cross{stroke:#333333;}#mermaid-svg-WM0PzubpPXhxVcXh svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WM0PzubpPXhxVcXh p{margin:0;}#mermaid-svg-WM0PzubpPXhxVcXh .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-WM0PzubpPXhxVcXh .cluster-label text{fill:#333;}#mermaid-svg-WM0PzubpPXhxVcXh .cluster-label span{color:#333;}#mermaid-svg-WM0PzubpPXhxVcXh .cluster-label span p{background-color:transparent;}#mermaid-svg-WM0PzubpPXhxVcXh .label text,#mermaid-svg-WM0PzubpPXhxVcXh span{fill:#333;color:#333;}#mermaid-svg-WM0PzubpPXhxVcXh .node rect,#mermaid-svg-WM0PzubpPXhxVcXh .node circle,#mermaid-svg-WM0PzubpPXhxVcXh .node ellipse,#mermaid-svg-WM0PzubpPXhxVcXh .node polygon,#mermaid-svg-WM0PzubpPXhxVcXh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WM0PzubpPXhxVcXh .rough-node .label text,#mermaid-svg-WM0PzubpPXhxVcXh .node .label text,#mermaid-svg-WM0PzubpPXhxVcXh .image-shape .label,#mermaid-svg-WM0PzubpPXhxVcXh .icon-shape .label{text-anchor:middle;}#mermaid-svg-WM0PzubpPXhxVcXh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WM0PzubpPXhxVcXh .rough-node .label,#mermaid-svg-WM0PzubpPXhxVcXh .node .label,#mermaid-svg-WM0PzubpPXhxVcXh .image-shape .label,#mermaid-svg-WM0PzubpPXhxVcXh .icon-shape .label{text-align:center;}#mermaid-svg-WM0PzubpPXhxVcXh .node.clickable{cursor:pointer;}#mermaid-svg-WM0PzubpPXhxVcXh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WM0PzubpPXhxVcXh .arrowheadPath{fill:#333333;}#mermaid-svg-WM0PzubpPXhxVcXh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WM0PzubpPXhxVcXh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WM0PzubpPXhxVcXh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WM0PzubpPXhxVcXh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WM0PzubpPXhxVcXh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WM0PzubpPXhxVcXh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WM0PzubpPXhxVcXh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WM0PzubpPXhxVcXh .cluster text{fill:#333;}#mermaid-svg-WM0PzubpPXhxVcXh .cluster span{color:#333;}#mermaid-svg-WM0PzubpPXhxVcXh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WM0PzubpPXhxVcXh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WM0PzubpPXhxVcXh rect.text{fill:none;stroke-width:0;}#mermaid-svg-WM0PzubpPXhxVcXh .icon-shape,#mermaid-svg-WM0PzubpPXhxVcXh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WM0PzubpPXhxVcXh .icon-shape p,#mermaid-svg-WM0PzubpPXhxVcXh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WM0PzubpPXhxVcXh .icon-shape .label rect,#mermaid-svg-WM0PzubpPXhxVcXh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WM0PzubpPXhxVcXh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WM0PzubpPXhxVcXh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WM0PzubpPXhxVcXh :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Excel 分类数据
读取月份与分类字段
计算每一类数值
使用 bottom 或 stackplot 叠加
生成堆积图
观察总量趋势与结构变化
输出 PNG 或插入报告

2. 堆积图适合解决什么问题
我对堆积图的理解很简单:**它适合展示“总量 + 构成”的关系**。如果你只关心某一个指标随时间变化,用折线图就够了;如果你只想比较几个类别的大小,用普通柱形图也够了。但如果你想同时看清“总量变化”和“类别贡献”,堆积图就很合适。
比如下面这些场景,都适合用堆积图:
-
每月工单总量中,软件、硬件、网络分别占多少;
-
每月销售额中,不同产品线分别贡献多少;
-
每月成本中,人力、设备、差旅、外包分别占多少;
-
每周故障中,系统类、权限类、网络类、应用类分别变化如何。
推荐做法:当你需要回答“总量为什么变了”这个问题时,优先考虑堆积图。因为它能让你从“总量变化”进一步拆到“结构变化”,这比单独放一张总量柱形图更有解释力。
但是要注意,堆积图不是万能图。如果类别太多,堆积图会变成一块彩色蛋糕,读者反而看不清。我的经验是,类别最好控制在 3~5 类。如果超过 6 类,建议把小类合并为“其他”。

3. 堆积柱形图核心原理:bottom 参数怎么理解
在 Matplotlib 里做堆积柱形图,最关键的参数是 `bottom`。它的作用可以理解成:**下一层柱子从哪里开始画**。
普通柱形图是从 0 开始画。堆积柱形图不是这样。第一类从 0 开始,第二类要从第一类的顶部开始,第三类要从“第一类 + 第二类”的顶部开始。这个“顶部位置”,就是 `bottom`。
这张图展示了堆积柱形图中 `bottom` 参数的叠加逻辑。

从这张图中我们可以看出,堆积柱形图并不是简单把几组柱子画在一起,而是通过 `bottom` 参数把第二层数据放到第一层数据的顶部。这就是堆积图能同时表达“总量”和“构成”的底层原因。
下面是两类工单的最小可用版示例:软件类在底部,硬件类叠加在软件类上方。
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
months = ["1月", "2月", "3月", "4月", "5月", "6月"]
soft = [40, 35, 50, 45, 60, 55] # 软件类工单
hard = [80, 63, 85, 65, 90, 105] # 硬件类工单
x = np.arange(len(months))
plt.figure(figsize=(9, 6))
# 第一层:从 0 开始
plt.bar(x, soft, width=0.6, label="软件类", alpha=0.85)
# 第二层:从 soft 的顶部开始
plt.bar(x, hard, width=0.6, bottom=soft, label="硬件类", alpha=0.85)
plt.xticks(x, months)
plt.title("堆积柱形图:工单总量与构成结构")
plt.xlabel("月份")
plt.ylabel("工单数量(单)")
plt.grid(axis="y", linestyle="–", alpha=0.35)
plt.legend()
plt.tight_layout()
plt.show()
这段代码里最关键的一行是:
plt.bar(x, hard, width=0.6, bottom=soft, label="硬件类", alpha=0.85)
它的意思是:硬件类柱子不要从 0 开始,而是从软件类柱子的顶部开始。只要理解了 bottom,堆积柱形图就不难。

4. 多层堆积:结构变化 + 总量标签
真实报表里通常不止两类数据。比如工单统计可能包括:软件类、硬件类、网络类;销售统计可能包括:产品A、产品B、产品C;成本统计可能包括:人力、设备、外包、差旅。
多层堆积图的思路是一样的:每画一层,就把当前层的数值加到 `bottom` 里,下一层继续从新的顶部开始画。
这张图展示了多层堆积图,并在每根柱子顶部标注了总量。

从这张图中我们可以看出,顶部总量标签非常重要。它能让读者不用心算,也能直接知道每个月的总量。如果这张图用于汇报、周报、复盘报告,我建议一定加总量标签。
下面是三类工单的多层堆积代码:
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
months = ["1月", "2月", "3月", "4月", "5月", "6月"]
soft = np.array([40, 35, 50, 45, 60, 55])
hard = np.array([80, 63, 85, 65, 90, 105])
net = np.array([15, 10, 18, 12, 20, 22])
x = np.arange(len(months))
plt.figure(figsize=(10, 6))
plt.bar(x, soft, width=0.6, label="软件类", alpha=0.85)
plt.bar(x, hard, width=0.6, bottom=soft, label="硬件类", alpha=0.85)
plt.bar(x, net, width=0.6, bottom=soft + hard, label="网络类", alpha=0.85)
# 计算总量,并标在柱子顶部
total = soft + hard + net
for i, v in enumerate(total):
plt.text(i, v + 2, str(int(v)), ha="center", va="bottom")
plt.xticks(x, months)
plt.title("堆积柱形图:多分类结构 + 总量标注")
plt.xlabel("月份")
plt.ylabel("工单数量(单)")
plt.grid(axis="y", linestyle="–", alpha=0.35)
plt.legend()
plt.tight_layout()
plt.show()
这里要特别注意一件事:堆积图的颜色块表示“构成”,柱子总高度表示“总量”,不要把某一层的视觉高度误读成独立趋势。如果你要精确比较某一类本身的变化,最好再补一张普通折线图或分组柱形图。

5. 堆积面积图 stackplot:观察结构漂移
很多人会说“堆积折线图”,但在 Matplotlib 里更常用的是 `stackplot()`。它本质上是堆积面积图,适合观察多个分类随时间变化时,整体结构是如何漂移的。
堆积柱形图更适合按月份、按季度、按批次做离散对比;堆积面积图更适合看连续趋势,尤其是你想观察结构是否逐渐偏移时。
这张图展示了使用 `stackplot()` 生成的堆积面积图,用于观察不同类别的结构漂移。

从这张图中我们可以看出,最上方的边界线代表总量走势,每一层的厚度代表某一类数据的贡献大小。如果某一层在一段时间内越来越厚,就说明它对总量的贡献正在增加。
下面是 `stackplot()` 的最小可用版:
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
months = ["1月", "2月", "3月", "4月", "5月", "6月"]
x = np.arange(len(months))
soft = [40, 35, 50, 45, 60, 55]
hard = [80, 63, 85, 65, 90, 105]
net = [15, 10, 18, 12, 20, 22]
plt.figure(figsize=(10, 6))
plt.stackplot(
x,
soft,
hard,
net,
labels=["软件类", "硬件类", "网络类"],
alpha=0.75
)
plt.xticks(x, months)
plt.title("堆积面积图:结构贡献随时间变化")
plt.xlabel("月份")
plt.ylabel("工单数量(单)")
plt.legend(loc="upper left")
plt.grid(axis="y", linestyle="–", alpha=0.35)
plt.tight_layout()
plt.show()
我自己的判断是:如果你要做“趋势 + 结构”的整体观察,stackplot 很合适;如果你要做“每个月总量 + 每类构成”的汇报展示,堆积柱形图更稳。

6. 企业可交付版:从 Excel 读取多列分类数据生成堆积图
前面的代码是直接写死数据,适合学习。但真实办公自动化里,数据一般来自 Excel。所以更实用的版本应该是:读取 Excel → 选择分类列 → 自动生成堆积柱形图 → 保存 PNG。
假设 Excel 表结构如下:
| 月份 | 软件类 | 硬件类 | 网络类 |
|——|——–|——–|——–|
| 1月 | 40 | 80 | 15 |
| 2月 | 35 | 63 | 10 |
| 3月 | 50 | 85 | 18 |
| 4月 | 45 | 65 | 12 |
| 5月 | 60 | 90 | 20 |
| 6月 | 55 | 105 | 22 |
这张图展示了从 Excel 读取数据,再通过 Python 一键生成堆积图 PNG 的自动化流程。

从这张图中我们可以看出,真正可落地的办公自动化不是“会画一张图”,而是把 Excel 数据、Python 脚本、图表输出串成一条稳定链路。这样后续只要 Excel 数据更新,图表就可以重复生成。
下面是可复用的企业版脚本:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
def excel_to_stacked_bar(
xlsx_path: str,
sheet: str,
x_col: str,
category_cols: list[str],
out_png: str = "out/stacked_bar.png",
):
"""
从 Excel 读取多列分类数据,生成堆积柱形图。
参数说明:
xlsx_path : Excel 文件路径
sheet : 工作表名称
x_col : x轴字段,比如“月份”
category_cols : 需要堆积展示的分类字段列表
out_png : 输出图片路径
"""
df = pd.read_excel(xlsx_path, sheet_name=sheet)
x_labels = df[x_col].astype(str).tolist()
x = np.arange(len(x_labels))
plt.figure(figsize=(11, 6))
bottom = np.zeros(len(df))
for col in category_cols:
values = pd.to_numeric(df[col], errors="coerce").fillna(0).to_numpy()
plt.bar(x, values, width=0.6, bottom=bottom, label=col, alpha=0.85)
bottom += values
# 顶端总量标签
for i, v in enumerate(bottom):
plt.text(i, v + 2, str(int(v)), ha="center", va="bottom")
plt.xticks(x, x_labels)
plt.title("堆积柱形图:总量 + 构成结构")
plt.xlabel(x_col)
plt.ylabel("数量")
plt.grid(axis="y", linestyle="–", alpha=0.35)
plt.legend()
plt.tight_layout()
out_png = Path(out_png)
out_png.parent.mkdir(parents=True, exist_ok=True)
plt.savefig(out_png, dpi=200)
plt.close()
return str(out_png)
if __name__ == "__main__":
png = excel_to_stacked_bar(
xlsx_path="report.xlsx",
sheet="数据",
x_col="月份",
category_cols=["软件类", "硬件类", "网络类"],
out_png="out/堆积柱形图_工单结构.png",
)
print("✅ 已输出:", png)
这段代码真正需要你改的地方只有 4 个:
xlsx_path="report.xlsx"
sheet="数据"
x_col="月份"
category_cols=["软件类", "硬件类", "网络类"]
这里的设计思路是:把字段写成参数,而不是写死在代码逻辑里。这样后续从工单数据切换到销售数据、成本数据、资产数据时,只需要改字段名,不需要重写整段脚本。

7. 效果验证:不要只看“有没有生成图片”
脚本运行后,如果只看到 PNG 文件生成,并不能说明结果完全正确。图表类自动化最容易出的问题不是脚本报错,而是图生成了,但数据逻辑错了。
我建议至少做 4 个验证动作:
7.1 验证输出文件是否生成
首先确认 `out/堆积柱形图_工单结构.png` 是否存在。如果文件不存在,优先检查输出目录权限、路径是否正确,以及脚本是否执行到了 `plt.savefig()`。
7.2 验证每月总量是否正确
手动抽查一个月份,例如 1 月:
软件类 40 + 硬件类 80 + 网络类 15 = 总量 135
然后对照图表顶部的总量标签。如果顶部显示不是 135,就说明数据读取或累加逻辑有问题。
7.3 验证分类颜色与图例是否对应
堆积图依赖颜色区分分类,所以图例必须清楚。如果颜色和类别对应不上,读者就会误解图表。
7.4 验证缺失值是否被合理处理
代码中使用了:
pd.to_numeric(df[col], errors="coerce").fillna(0)
它会把无法转换成数字的内容变成 `NaN`,再填充为 0。这个做法适合普通统计场景,但如果空值代表“数据未录入”,而不是“真实为 0”,就要谨慎。空值直接填 0 可能会掩盖数据质量问题。

8. 常见问题与踩坑记录
8.1 类别太多,图表看不清
堆积图最怕类别太多。类别一多,颜色会很乱,图例也会变长,读者很难快速理解。
建议:保留主要类别,把低频类别合并成“其他”。比如工单类型里,如果“账号类”“权限类”“其他杂项”占比都很小,可以合并为“其他类”。
8.2 误把结构变化当成总量变化
堆积图中某一层变厚,说明该类贡献增加;但如果总量也在变化,就要结合总高度一起看。
不要只盯着某个颜色块看,要同时看总高度和颜色块厚度。
8.3 堆积面积图不适合精确读数
`stackplot()` 更适合观察趋势和结构漂移,不适合做精确数值对比。因为除最底层外,中间层的上下边界都会变化,读者很难精确判断某一类的绝对值。
如果要精确对比某一类,建议补充普通折线图或分组柱形图。
8.4 中文乱码
如果图表标题、坐标轴、图例中文显示成方块,通常是字体问题。建议在脚本开头加入:
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
如果当前电脑没有 SimHei 字体,可以换成系统里已有的中文字体。

9. 我的总结提升
这一篇的核心,不是简单记住 `plt.bar()`、`bottom`、`stackplot()` 这些函数名,而是要真正理解堆积图解决的分析问题:**不只看总量,更要看结构变化**。
从办公自动化角度看,堆积图不是一个孤立的图表技巧,而是一种数据表达方式。它可以帮助我们把 Excel 里的多列分类数据,转换成更容易阅读、更适合汇报、更容易发现问题的图形证据。
我建议你记住下面 4 个结论:
堆积柱形图适合看“每个时间点的总量与构成”。
bottom 参数是堆积柱形图的核心,它决定下一层从哪里开始画。
stackplot() 适合看“结构随时间如何漂移”。
企业场景下要优先做成 Excel 读取版,而不是把数据写死在代码里。
如果只会复制代码,但不能解释图表到底表达了什么,遇到真实业务数据时还是会卡住。所以我建议把每一篇图表类笔记都当成一个“小型数据表达模块”:先跑通,再理解,再封装,最后沉淀成自己的自动化报表模板。
下一篇可以继续往“分布分析”方向推进,比如箱线图(Box Plot):用分布说话,快速发现异常值和离散程度。这类图在运维、质量分析、数据清洗里都非常实用。
返回顶部



