用 Python 画世界杯球队能力雷达图:以阿根廷 vs 奥地利为例
世界杯期间,很多赛前分析都会写“谁更强”“谁更有机会赢”。
但如果放到 CSDN,单纯写球评就有点不合适。
技术文章更适合换一种方式:
用 Python 把球队能力做成可视化图表。
今天我们以阿根廷 vs 奥地利为例,画一张球队能力雷达图。
雷达图适合展示多维指标对比,比如:
- 进攻能力
- 防守稳定
- 控球能力
- 压迫强度
- 反击速度
- 近期状态
这种图不负责预测比分,也不做确定结论。
它的作用是把赛前分析从“主观感觉”变成“结构化对比”。
当然,足球不是 Excel 表格,比赛也不会因为雷达图好看就按图执行。人类已经够爱假装自己能预测世界了,别让 matplotlib 也背锅。
一、为什么用雷达图?

雷达图适合展示多个维度的能力差异。
比如阿根廷和奥地利这场:
阿根廷的优势可能在:
- 进攻上限
- 控球能力
- 大赛经验
- 核心球员状态
奥地利的优势可能在:
- 高位压迫
- 纵向推进
- 身体对抗
- 比赛节奏冲击
如果只写文字,读者需要自己在脑子里对比。
如果画成雷达图,一眼就能看出两队能力结构差异。
这就是数据可视化的价值。
不是让结论更神秘,而是让信息更直观。
二、安装依赖
本文只需要一个第三方库:
pip install matplotlib
如果你本地已经安装过,可以直接跳过。
查看版本:
python -c "import matplotlib; print(matplotlib.__version__)"
建议使用 Python 3.9 以上版本。
三、准备球队能力数据
这里我们先手动构造一组示例数据。
注意:下面的分数是为了演示代码和图表效果,不是官方评分,也不是赛果预测。
创建文件:
worldcup_radar_chart.py
先写入数据:
teams = {
"阿根廷": {
"进攻": 9.2,
"防守": 8.4,
"控球": 9.0,
"压迫": 8.1,
"反击": 8.3,
"近期状态": 9.1,
},
"奥地利": {
"进攻": 7.8,
"防守": 7.9,
"控球": 7.4,
"压迫": 8.8,
"反击": 8.2,
"近期状态": 8.0,
},
}
这里设计了 6 个维度。
为什么是这 6 个?
因为它们比较适合赛前分析:
- 进攻:能不能创造机会
- 防守:能不能限制对手
- 控球:能不能掌控节奏
- 压迫:能不能逼迫对手失误
- 反击:能不能快速转换
- 近期状态:最近比赛表现是否稳定
阿根廷的优势在控球和进攻。
奥地利的特点是压迫强度高,节奏更直接。
这正好适合用雷达图对比。
四、雷达图的基本原理
雷达图本质上是极坐标图。
普通折线图是横轴、纵轴。
雷达图是把多个指标平均分布在一个圆上,每个指标对应一个角度。
如果有 6 个指标,就把圆分成 6 个方向。
为了让图形闭合,需要把第一项数据再追加到最后。
比如:
values = [9.2, 8.4, 9.0, 8.1, 8.3, 9.1]
values += values[:1]
角度也要闭合:
angles += angles[:1]
不闭合的话,图形就会缺一条边。
那画出来就像球队踢到一半突然下班,视觉上非常不负责任。

五、完整代码
下面是完整可运行代码。
import math
from pathlib import Path
import matplotlib.pyplot as plt
def setup_chinese_font() -> None:
"""
设置中文字体。
不同系统字体名称可能不同,如果中文显示异常,可以修改这里。
"""
plt.rcParams["font.sans-serif"] = [
"Microsoft YaHei",
"SimHei",
"Arial Unicode MS",
"DejaVu Sans",
]
plt.rcParams["axes.unicode_minus"] = False
def close_loop(values: list[float]) -> list[float]:
"""
雷达图需要闭合图形,所以把第一个值追加到最后。
"""
return values + values[:1]
def build_angles(metric_count: int) -> list[float]:
"""
根据指标数量生成每个指标对应的角度。
"""
angles = [
2 * math.pi * index / metric_count
for index in range(metric_count)
]
return angles + angles[:1]
def validate_team_data(teams: dict[str, dict[str, float]]) -> list[str]:
"""
校验所有球队是否使用相同指标。
"""
if not teams:
raise ValueError("teams 数据不能为空")
metric_names = list(next(iter(teams.values())).keys())
for team_name, metrics in teams.items():
current_metrics = list(metrics.keys())
if current_metrics != metric_names:
raise ValueError(
f"{team_name} 的指标不一致:{current_metrics} != {metric_names}"
)
for metric_name, score in metrics.items():
if not 0 <= score <= 10:
raise ValueError(
f"{team_name} 的 {metric_name} 分数必须在 0 到 10 之间"
)
return metric_names
def draw_radar_chart(
teams: dict[str, dict[str, float]],
title: str,
output_file: str,
) -> None:
"""
绘制多球队能力雷达图。
"""
setup_chinese_font()
metric_names = validate_team_data(teams)
metric_count = len(metric_names)
angles = build_angles(metric_count)
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={"polar": True})
for team_name, metrics in teams.items():
values = list(metrics.values())
closed_values = close_loop(values)
ax.plot(angles, closed_values, linewidth=2, label=team_name)
ax.fill(angles, closed_values, alpha=0.15)
ax.set_title(title, fontsize=16, pad=20)
ax.set_xticks(angles[:-1])
ax.set_xticklabels(metric_names, fontsize=11)
ax.set_ylim(0, 10)
ax.set_yticks([2, 4, 6, 8, 10])
ax.set_yticklabels(["2", "4", "6", "8", "10"], fontsize=9)
ax.legend(loc="upper right", bbox_to_anchor=(1.2, 1.1))
ax.grid(True)
output_path = Path(output_file)
fig.tight_layout()
fig.savefig(output_path, dpi=180, bbox_inches="tight")
plt.close(fig)
print(f"雷达图已生成:{output_path.resolve()}")
def main() -> None:
teams = {
"阿根廷": {
"进攻": 9.2,
"防守": 8.4,
"控球": 9.0,
"压迫": 8.1,
"反击": 8.3,
"近期状态": 9.1,
},
"奥地利": {
"进攻": 7.8,
"防守": 7.9,
"控球": 7.4,
"压迫": 8.8,
"反击": 8.2,
"近期状态": 8.0,
},
}
draw_radar_chart(
teams=teams,
title="阿根廷 vs 奥地利 球队能力雷达图",
output_file="argentina_vs_austria_radar.png",
)
if __name__ == "__main__":
main()
六、运行脚本
在终端执行:
python worldcup_radar_chart.py
运行后会生成图片:
argentina_vs_austria_radar.png
这张图可以直接放到 CSDN 正文中。
如果中文显示成方块,通常是系统没有对应中文字体。
可以把这里:
plt.rcParams["font.sans-serif"] = [
"Microsoft YaHei",
"SimHei",
"Arial Unicode MS",
"DejaVu Sans",
]
改成本机已有字体。
Windows 常用:
"Microsoft YaHei"
macOS 常用:
"Arial Unicode MS"
Linux 可以安装中文字体后再配置。
七、代码拆解
1. 为什么要校验数据?
这段函数负责检查不同球队的指标是否一致:
def validate_team_data(teams: dict[str, dict[str, float]]) -> list[str]:
if not teams:
raise ValueError("teams 数据不能为空")
metric_names = list(next(iter(teams.values())).keys())
for team_name, metrics in teams.items():
current_metrics = list(metrics.keys())
if current_metrics != metric_names:
raise ValueError(
f"{team_name} 的指标不一致:{current_metrics} != {metric_names}"
)
for metric_name, score in metrics.items():
if not 0 <= score <= 10:
raise ValueError(
f"{team_name} 的 {metric_name} 分数必须在 0 到 10 之间"
)
return metric_names
如果阿根廷有 6 个指标,奥地利只有 5 个指标,图就会错位。
所以先校验。
别等图画出来才发现数据歪了。那就像比赛踢完才发现球门装反,虽然荒诞,但人类项目里类似事情并不少见。
2. 为什么要闭合数据?
雷达图是一个多边形。
如果不把第一个点追加到最后,线条不会闭合。
def close_loop(values: list[float]) -> list[float]:
return values + values[:1]
角度也一样:
def build_angles(metric_count: int) -> list[float]:
angles = [
2 * math.pi * index / metric_count
for index in range(metric_count)
]
return angles + angles[:1]
指标数量是 6,所以雷达图会把圆分成 6 个方向。
3. 为什么用 polar=True?
这里创建的是极坐标图:
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={"polar": True})
普通坐标图适合折线图、柱状图。
极坐标更适合雷达图。
4. 为什么设置 y 轴范围 0 到 10?
因为我们的评分设定是 10 分制:
ax.set_ylim(0, 10)
ax.set_yticks([2, 4, 6, 8, 10])
如果后续你改成百分制,比如 0 到 100,只要修改这里即可:
ax.set_ylim(0, 100)
ax.set_yticks([20, 40, 60, 80, 100])
八、如何让数据更真实?
上面的评分是演示数据。
如果要更接近真实分析,可以把指标来源拆开。
比如:
进攻能力
可以参考:
- 场均进球
- 场均射门
- 射正率
- 关键传球
- xG
防守稳定
可以参考:
- 场均失球
- 被射门次数
- 抢断
- 拦截
- 门将扑救
控球能力
可以参考:
- 控球率
- 传球成功率
- 中场推进次数
- 后场出球稳定性
压迫强度
可以参考:
- 前场抢断
- 高位逼抢次数
- 对手后场失误次数
- PPDA
反击速度
可以参考:
- 快攻次数
- 转换进攻推进距离
- 反击射门数
- 边路推进成功率
近期状态
可以参考:
- 最近 5 场战绩
- 最近 5 场进球
- 最近 5 场失球
- 核心球员状态
- 伤停情况
如果只是写 CSDN 示例,不需要一口气接入所有真实数据。
先把图画出来,再逐步接真实数据源。
不然一开始就想做全自动体育数据平台,最后很可能只收获一个半成品文件夹和一点精神损耗。开发者对此应该都不陌生。
九、改成读取 JSON 数据
如果不想把数据写死在 Python 代码里,可以单独建一个 teams.json。
{
"阿根廷": {
"进攻": 9.2,
"防守": 8.4,
"控球": 9.0,
"压迫": 8.1,
"反击": 8.3,
"近期状态": 9.1
},
"奥地利": {
"进攻": 7.8,
"防守": 7.9,
"控球": 7.4,
"压迫": 8.8,
"反击": 8.2,
"近期状态": 8.0
}
}
然后修改读取逻辑:
import json
from pathlib import Path
def load_team_data(file_path: str) -> dict[str, dict[str, float]]:
path = Path(file_path)
if not path.exists():
raise FileNotFoundError(f"文件不存在:{file_path}")
with path.open("r", encoding="utf-8") as file:
return json.load(file)
在 main() 中使用:
def main() -> None:
teams = load_team_data("teams.json")
draw_radar_chart(
teams=teams,
title="阿根廷 vs 奥地利 球队能力雷达图",
output_file="argentina_vs_austria_radar.png",
)
这样后续要画法国、挪威、日本、巴西,只需要换 JSON 文件即可。
十、扩展:支持多场比赛批量出图
如果世界杯期间每天要做多场比赛,可以把数据组织成这样:
{
"阿根廷_vs_奥地利": {
"阿根廷": {
"进攻": 9.2,
"防守": 8.4,
"控球": 9.0,
"压迫": 8.1,
"反击": 8.3,
"近期状态": 9.1
},
"奥地利": {
"进攻": 7.8,
"防守": 7.9,
"控球": 7.4,
"压迫": 8.8,
"反击": 8.2,
"近期状态": 8.0
}
},
"法国_vs_伊拉克": {
"法国": {
"进攻": 8.9,
"防守": 8.5,
"控球": 8.6,
"压迫": 8.2,
"反击": 8.7,
"近期状态": 8.8
},
"伊拉克": {
"进攻": 7.0,
"防守": 7.2,
"控球": 6.8,
"压迫": 7.1,
"反击": 7.5,
"近期状态": 6.9
}
}
}
然后循环生成多张图:
def draw_multiple_matches(matches: dict[str, dict[str, dict[str, float]]]) -> None:
for match_name, teams in matches.items():
output_file = f"{match_name}_radar.png"
title = f"{match_name.replace('_', ' ')} 球队能力雷达图"
draw_radar_chart(
teams=teams,
title=title,
output_file=output_file,
)
这样可以把世界杯日更变成一个可复用的小工具。
每天只维护数据,脚本负责生成图表。
十一、雷达图适合放在哪里?
如果你是写 CSDN,建议文章结构这样安排:
- 开头说明世界杯场景
- 给出指标设计
- 给出 Python 代码
- 展示生成图
- 解释图中差异
- 提醒数据只是分析辅助,不是赛果预测
如果是发公众号或今日头条,可以把代码部分删掉,只保留雷达图和图解。
如果是发掘金,可以进一步封装成组件或做成自动化脚本。
同一个选题,换平台可以换表达方式。
这比每天复制一篇“赛前分析报告”清醒得多,终于不像内容流水线在自我催眠。
十二、总结
本文用 Python 和 matplotlib 绘制了一张世界杯球队能力雷达图。
以阿根廷 vs 奥地利为例,我们把球队能力拆成 6 个维度:
- 进攻
- 防守
- 控球
- 压迫
- 反击
- 近期状态
通过雷达图,可以更直观地看到两支球队的风格差异。
阿根廷更突出的是进攻、控球和近期状态。
奥地利更值得关注的是压迫强度和转换速度。
这类图表非常适合世界杯期间做技术化内容:
既能挂热点,又有代码;
既能讲数据,又不写成玄学预测;
既适合 CSDN,也能扩展到公众号、知乎和头条。
技术人追世界杯热点,不一定要写球评。
也可以用 Python 把比赛看点画出来。
轻量补充
如果你长期使用 ChatGPT、Claude、Gemini、Cursor 或 Kiro 这类 AI 工具做代码生成、图表解释、文章改写和数据整理,也可以把 gpt68.com 作为第三方 AI 会员充值平台入口之一了解。它解决的是 AI 工具订阅充值流程问题,不是替代工具本身。使用前建议看清套餐说明、到账说明和售后规则。
更多推荐


所有评论(0)