🔥
个人主页:
杨利杰YJlio
❄️
个人专栏:
《Sysinternals实战教程》
《Windows PowerShell 实战》
《WINDOWS教程》
《IOS教程》
《微信助手》
《锤子助手》
《Python》
《Kali Linux》
《那些年未解决的Windows疑难杂症》
🌟
让复杂的事情更简单,让重复的工作自动化

《超简单:用 Python 让 Excel 飞起来》读书笔记:第8章 案例05 制作雷达图对比多项指标
- 1. 第8章 案例05 制作雷达图对比多项指标
- 2. 适用场景:什么时候适合用雷达图
- 3. 核心原理:雷达图到底是怎么画出来的
- 4. 最小可用代码:先画出一张单对象雷达图
- 5. 多对象对比:方案A vs 方案B
- 6. 图表优化:让雷达图更像报告图
- 7. 企业可交付版:从 Excel 读取评分并导出雷达图
- 8. 常见问题与踩坑记录
-
- 8.1 中文显示乱码
- 8.2 雷达图形状没有闭合
- 8.3 不同指标量纲不一致
- 8.4 指标越多越好吗?
- 8.5 多对象叠加太乱
- 9. 总结:雷达图是多指标对比的“短板扫描器”
- 11. 发布版补充:我建议这样使用这篇笔记

1. 第8章 案例05 制作雷达图对比多项指标
V4修正版进度标记:本篇为 07月10日 第2篇,承接前文图表可视化章节继续更新。
读书笔记定位:本文是我阅读《超简单:用 Python 让 Excel 飞起来》时整理的学习笔记,重点不是照搬原书,而是把“雷达图”这个知识点重新整理成更适合博客阅读、办公自动化实战和后续查阅的版本。
这一篇主要讲的是:如何用 Python 制作雷达图,用来对比多个指标,并快速发现方案、人员、设备或服务质量中的优势和短板。
我对雷达图的理解很简单:它不是为了展示一个数,而是为了把多个指标变成一个“形状”。这个形状越饱满,整体表现越均衡;某个方向明显凹进去,通常就代表那里存在短板。
雷达图最适合解决的问题,不是“谁的某一项最高”,而是“谁的综合表现更均衡”。这也是它在方案评估、能力画像、服务质量评分、产品对比中经常出现的原因。
这张图展示了雷达图在多指标对比中的整体表达效果,核心是把响应速度、解决率、满意度、成本、稳定性等多个维度放在同一个可视化结构中。

从这张图中我们可以看出,雷达图最大的价值是**让读者不用逐行阅读表格,也能快速判断强项和短板**。这比单纯罗列 5 个、6 个评分数字更直观,尤其适合做汇报图、方案对比图和服务质量看板。
但这里有一个前提:雷达图不是万能图。如果指标口径不统一,或者不同指标的量纲差距很大,图形看起来很漂亮,结论却可能是错的。

2. 适用场景:什么时候适合用雷达图
雷达图适合“多个维度同时对比”的场景。它不是替代柱形图、折线图,而是补充它们解决不了的问题。
常见场景包括:
- 方案对比:性能、成本、稳定性、易维护性、安全性;
- 人员能力画像:沟通能力、执行力、技术能力、协作能力、复盘能力;
- 服务质量分析:响应速度、解决率、满意度、复发率控制、文档规范;
- 设备或产品评估:可靠性、功耗、扩展性、维护成本、用户体验;
- IT 运维报表:多个部门、多个系统、多个支持小组的综合表现对比。
我个人更建议把雷达图用于“5 到 8 个指标”的对比。指标太少,雷达图没有优势;指标太多,图形会像毛线团,读者反而看不清。
推荐使用雷达图的条件:
| 指标数量 | 5~8 个最合适 |
| 数据量纲 | 尽量统一到 0~100 |
| 对比对象 | 1~3 个对象较清晰 |
| 使用目的 | 找短板、看均衡度、做综合评分 |
| 不适合场景 | 时间趋势、精确排名、类别过多的数据 |
不要把雷达图当成“好看版表格”。如果你的目的只是展示精确数值排名,用柱形图或条形图更直接。

3. 核心原理:雷达图到底是怎么画出来的
从实现角度看,雷达图并不神秘。它本质上是一个极坐标图。普通图表用 x、y 坐标定位点;雷达图用的是角度 + 半径。
每一个指标对应一个角度,每一个分数对应从中心向外延伸的距离。把所有点连起来,再把最后一个点接回第一个点,就形成了一个闭合多边形。
#mermaid-svg-ooc5sv87YOLPJtOD{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-ooc5sv87YOLPJtOD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ooc5sv87YOLPJtOD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ooc5sv87YOLPJtOD .error-icon{fill:#552222;}#mermaid-svg-ooc5sv87YOLPJtOD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ooc5sv87YOLPJtOD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ooc5sv87YOLPJtOD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ooc5sv87YOLPJtOD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ooc5sv87YOLPJtOD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ooc5sv87YOLPJtOD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ooc5sv87YOLPJtOD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ooc5sv87YOLPJtOD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ooc5sv87YOLPJtOD .marker.cross{stroke:#333333;}#mermaid-svg-ooc5sv87YOLPJtOD svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ooc5sv87YOLPJtOD p{margin:0;}#mermaid-svg-ooc5sv87YOLPJtOD .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ooc5sv87YOLPJtOD .cluster-label text{fill:#333;}#mermaid-svg-ooc5sv87YOLPJtOD .cluster-label span{color:#333;}#mermaid-svg-ooc5sv87YOLPJtOD .cluster-label span p{background-color:transparent;}#mermaid-svg-ooc5sv87YOLPJtOD .label text,#mermaid-svg-ooc5sv87YOLPJtOD span{fill:#333;color:#333;}#mermaid-svg-ooc5sv87YOLPJtOD .node rect,#mermaid-svg-ooc5sv87YOLPJtOD .node circle,#mermaid-svg-ooc5sv87YOLPJtOD .node ellipse,#mermaid-svg-ooc5sv87YOLPJtOD .node polygon,#mermaid-svg-ooc5sv87YOLPJtOD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ooc5sv87YOLPJtOD .rough-node .label text,#mermaid-svg-ooc5sv87YOLPJtOD .node .label text,#mermaid-svg-ooc5sv87YOLPJtOD .image-shape .label,#mermaid-svg-ooc5sv87YOLPJtOD .icon-shape .label{text-anchor:middle;}#mermaid-svg-ooc5sv87YOLPJtOD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ooc5sv87YOLPJtOD .rough-node .label,#mermaid-svg-ooc5sv87YOLPJtOD .node .label,#mermaid-svg-ooc5sv87YOLPJtOD .image-shape .label,#mermaid-svg-ooc5sv87YOLPJtOD .icon-shape .label{text-align:center;}#mermaid-svg-ooc5sv87YOLPJtOD .node.clickable{cursor:pointer;}#mermaid-svg-ooc5sv87YOLPJtOD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ooc5sv87YOLPJtOD .arrowheadPath{fill:#333333;}#mermaid-svg-ooc5sv87YOLPJtOD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ooc5sv87YOLPJtOD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ooc5sv87YOLPJtOD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ooc5sv87YOLPJtOD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ooc5sv87YOLPJtOD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ooc5sv87YOLPJtOD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ooc5sv87YOLPJtOD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ooc5sv87YOLPJtOD .cluster text{fill:#333;}#mermaid-svg-ooc5sv87YOLPJtOD .cluster span{color:#333;}#mermaid-svg-ooc5sv87YOLPJtOD 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-ooc5sv87YOLPJtOD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ooc5sv87YOLPJtOD rect.text{fill:none;stroke-width:0;}#mermaid-svg-ooc5sv87YOLPJtOD .icon-shape,#mermaid-svg-ooc5sv87YOLPJtOD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ooc5sv87YOLPJtOD .icon-shape p,#mermaid-svg-ooc5sv87YOLPJtOD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ooc5sv87YOLPJtOD .icon-shape .label rect,#mermaid-svg-ooc5sv87YOLPJtOD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ooc5sv87YOLPJtOD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ooc5sv87YOLPJtOD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ooc5sv87YOLPJtOD :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
准备指标名称
把指标均匀分配到圆周角度
把每个指标分数映射为半径
首尾相连形成闭环
plot绘制边界线
fill填充区域
设置刻度/图例/标题
这张图展示了用极坐标绘制雷达图的底层逻辑,包括角度分配、首尾闭环和区域填充。

这一步的核心要点是:雷达图不是 Matplotlib 里一个单独的“魔法函数”,而是通过 polar=True 创建极坐标,再用 plot() 和 fill() 组合出来的。
核心逻辑可以拆成三步:

4. 最小可用代码:先画出一张单对象雷达图
先不要一上来就追求复杂效果。雷达图最重要的是先跑通基本结构:指标、分数、角度、闭环、极坐标。
import numpy as np
import matplotlib.pyplot as plt
# 中文显示
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 指标维度
labels = ["响应速度", "解决率", "满意度", "复发率控制", "文档规范"]
# 每个指标的分数,建议统一到 0~100
values = [80, 90, 85, 70, 88]
# 指标数量
N = len(labels)
# 把圆周均匀分成 N 份
angles = np.linspace(0, 2 * np.pi, N, endpoint=False)
# 首尾闭环
values = np.concatenate([values, [values[0]]])
angles = np.concatenate([angles, [angles[0]]])
# 创建极坐标图
fig = plt.figure(figsize=(7, 7))
ax = plt.subplot(111, polar=True)
# 绘制边界线和填充区域
ax.plot(angles, values, linewidth=2, label="服务质量")
ax.fill(angles, values, alpha=0.25)
# 设置指标标签
ax.set_thetagrids(angles[:–1] * 180 / np.pi, labels)
# 设置半径范围
ax.set_ylim(0, 100)
plt.title("服务质量雷达图")
plt.legend(loc="upper right")
plt.tight_layout()
plt.show()
这段代码是最基础但最重要的版本。只要你理解了它,后面做多对象对比、图表优化、Excel 自动读取数据,本质上都是在这个基础上扩展。
真正需要记住的不是代码本身,而是这条链路:指标名称决定角度,指标分数决定半径,首尾闭环决定雷达图形状。

5. 多对象对比:方案A vs 方案B
雷达图真正有价值的地方,是做多对象对比。比如两个方案、两个部门、两台设备、两套流程,同时放到一张图里,就能快速看出差异。
但这里也要克制。同一张雷达图里不建议放太多对象。超过 3 个对象后,线条和填充区域会互相遮挡,读者很难判断哪个对象更优。
这张图展示了方案A和方案B在多个指标上的雷达图对比,可以直接看出两套方案的优势区间和关键差距。

从这张图中我们可以看出,方案A在响应速度、解决率、满意度上更强,而方案B在复发率控制、文档规范、成本控制方面更有优势。雷达图不是告诉你“谁绝对赢”,而是告诉你谁在哪些维度更适合当前目标。
对应代码如下:
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
labels = ["响应速度", "解决率", "满意度", "复发率控制", "文档规范", "成本控制"]
plan_a = [90, 85, 80, 70, 75, 65]
plan_b = [65, 70, 65, 85, 85, 90]
N = len(labels)
angles = np.linspace(0, 2 * np.pi, N, endpoint=False)
angles = np.concatenate([angles, [angles[0]]])
plan_a = np.concatenate([plan_a, [plan_a[0]]])
plan_b = np.concatenate([plan_b, [plan_b[0]]])
fig = plt.figure(figsize=(7, 7))
ax = plt.subplot(111, polar=True)
ax.plot(angles, plan_a, linewidth=2, label="方案A")
ax.fill(angles, plan_a, alpha=0.18)
ax.plot(angles, plan_b, linewidth=2, linestyle="–", label="方案B")
ax.fill(angles, plan_b, alpha=0.18)
ax.set_thetagrids(angles[:–1] * 180 / np.pi, labels)
ax.set_ylim(0, 100)
plt.title("方案A vs 方案B 多指标雷达图")
plt.legend(loc="upper right", bbox_to_anchor=(1.25, 1.1))
plt.tight_layout()
plt.show()
我的建议:做方案对比时,不要只在图上写“方案A更好”或者“方案B更好”。更专业的表达应该是:方案A适合追求响应和满意度的场景,方案B适合追求成本和稳定控制的场景。

6. 图表优化:让雷达图更像报告图
雷达图如果不优化,很容易显得乱:标签挤在一起、网格太重、刻度不清楚、起始位置不符合阅读习惯。
我做报告图时,通常会做四个优化:
- 从顶部开始绘制;
- 顺时针排列指标;
- 设置清晰的半径刻度;
- 填充区域保持透明,不抢线条和文字。
这张图展示了雷达图优化后的效果,重点包括顶部起始、清晰网格、刻度分层和透明填充。

这一步的核心要点是:雷达图的美化不是为了炫技,而是为了降低读图成本。尤其是汇报场景里,图表必须让读者一眼知道哪里强、哪里弱、差距有多大。
可以加入下面这些优化代码:
# 从顶部开始
ax.set_theta_offset(np.pi / 2)
# 顺时针方向
ax.set_theta_direction(–1)
# 设置半径刻度
ax.set_yticks([20, 40, 60, 80, 100])
ax.set_yticklabels(["20", "40", "60", "80", "100"])
# 网格线变淡
ax.grid(True, linestyle="–", alpha=0.4)
注意:不要为了好看把填充透明度调得太高。透明度过高会遮住网格、标签和其他方案的区域,尤其是多对象对比时会造成误判。

7. 企业可交付版:从 Excel 读取评分并导出雷达图
真正办公自动化场景里,数据通常不是直接写在 Python 代码里的,而是在 Excel 里。更合理的流程应该是:Excel 维护评分数据,Python 读取数据,自动生成雷达图,再导出 PNG。
这才是可以交付给同事复用的版本。同事只需要改 Excel 评分表,不需要改 Python 代码。
这张图展示了从 Excel 评分数据读取,到自动生成雷达图,再导出 PNG 报告图片的完整流程。

从这张图中我们可以看出,办公自动化的关键不是“画出一张图”,而是把流程做成可重复执行的链路:读取数据、生成图表、导出文件、用于报告。
假设 Excel 表结构如下:
| 响应速度 | 90 | 65 |
| 解决率 | 85 | 70 |
| 满意度 | 80 | 65 |
| 复发率控制 | 70 | 85 |
| 文档规范 | 75 | 85 |
| 成本控制 | 65 | 90 |
完整代码如下:
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 radar_from_excel(
xlsx_path: str,
sheet_name: str = "评分",
label_col: str = "指标",
series_cols: list[str] | None = None,
out_png: str = "out/radar.png",
r_min: int = 0,
r_max: int = 100
):
"""
从 Excel 读取多指标评分数据,自动生成雷达图并保存为 PNG。
"""
if series_cols is None:
series_cols = ["方案A", "方案B"]
df = pd.read_excel(xlsx_path, sheet_name=sheet_name)
if label_col not in df.columns:
raise ValueError(f"Excel 中找不到指标列:{label_col}")
for col in series_cols:
if col not in df.columns:
raise ValueError(f"Excel 中找不到评分列:{col}")
labels = df[label_col].astype(str).tolist()
N = len(labels)
if N < 3:
raise ValueError("雷达图至少需要 3 个指标。")
angles = np.linspace(0, 2 * np.pi, N, endpoint=False)
angles = np.concatenate([angles, [angles[0]]])
fig = plt.figure(figsize=(8, 8))
ax = plt.subplot(111, polar=True)
# 从顶部开始,顺时针显示
ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(–1)
for col in series_cols:
values = pd.to_numeric(df[col], errors="coerce").fillna(0).to_numpy()
# 限制分数范围,避免异常值破坏图表
values = np.clip(values, r_min, r_max)
values = np.concatenate([values, [values[0]]])
ax.plot(angles, values, linewidth=2, label=col)
ax.fill(angles, values, alpha=0.18)
ax.set_thetagrids(angles[:–1] * 180 / np.pi, labels)
ax.set_ylim(r_min, r_max)
ax.set_yticks([20, 40, 60, 80, 100])
ax.set_yticklabels(["20", "40", "60", "80", "100"])
ax.grid(True, linestyle="–", alpha=0.4)
plt.title("雷达图:多指标对比", fontsize=16)
plt.legend(loc="upper right", bbox_to_anchor=(1.25, 1.12))
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_path = radar_from_excel(
xlsx_path="score.xlsx",
sheet_name="评分",
label_col="指标",
series_cols=["方案A", "方案B"],
out_png="out/方案对比雷达图.png"
)
print("✅ 雷达图已导出:", png_path)
这个版本的代码比最小示例更适合真实工作,因为它增加了字段校验、异常值限制、输出目录自动创建和 PNG 导出。

8. 常见问题与踩坑记录
8.1 中文显示乱码
Matplotlib 默认字体不一定支持中文。如果标题、图例、指标名称变成方块,通常是字体问题。
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
如果电脑没有 SimHei,可以改成:
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei"]
8.2 雷达图形状没有闭合
这是新手最常见的问题。原因是没有把第一个值追加到末尾。
values = np.concatenate([values, [values[0]]])
angles = np.concatenate([angles, [angles[0]]])
没有闭环,雷达图就不是完整的能力画像。
8.3 不同指标量纲不一致
比如一个指标是 0~100,另一个指标是金额,另一个指标是次数。如果直接画到同一个雷达图里,结论基本不可信。
推荐做法:先把所有指标转换成统一评分,比如 0~100 分,再画图。
8.4 指标越多越好吗?
不是。指标越多,雷达图越难读。超过 10 个指标后,图表的表达能力会明显下降。
我建议控制在 5~8 个指标,超过这个范围可以先做指标分组,再拆成多张图。
8.5 多对象叠加太乱
一张雷达图放 4 个、5 个对象,看起来很丰富,实际很难读。
更稳妥的做法:一张图只放 2~3 个对象。如果要比较更多对象,可以拆成多个小图,或者改用条形图。

9. 总结:雷达图是多指标对比的“短板扫描器”
这一节的核心,不是记住某一段 Matplotlib 代码,而是理解雷达图在数据表达里的位置。
雷达图真正适合表达的是“综合画像”,不是单项排名。它通过图形轮廓把多个指标同时展示出来,让强项和短板变得可见。
我建议你记住这几个关键点:
如果只会画图,但不能解释图里的短板和业务含义,这张图就只是装饰。
真正有价值的雷达图,应该帮助读者做判断:哪个方案更均衡,哪个指标拖后腿,下一步应该优先优化哪里。

11. 发布版补充:我建议这样使用这篇笔记
这一节在书中的位置是 第8章 案例05,核心主题是 制作雷达图对比多项指标。我不建议只把它当成图表绘制代码,而应该把它放到办公自动化和数据汇报场景里理解。
实际工作中,雷达图最适合做三件事:方案评估、能力画像、服务质量对比。如果你要做的是趋势变化,应该优先考虑折线图;如果你要做精确排名,应该优先考虑柱形图或条形图。
我的建议是:后续做类似图表时,不要只追求“图好看”,而要把数据口径、指标含义、评分规则和结论解释一起写清楚。这样图表才不是装饰,而是可以支撑判断的证据。
返回顶部
🔝 返回顶部
点击回到顶部


