Python射门位置分布图实战:从数据可视化到战术洞察(附完整代码)
目录导读
- 引言:为什么足球分析需要“射门位置图”?
- 数据准备:从哪获取射门数据?字段如何清洗?
- 核心代码:用Matplotlib+Seaborn绘制射门热力图与散点图
- 结果解读:如何从图表中读出球队进攻风格与球员热点区域?
- 进阶分析:结合射门结果(进球/射正/射偏)进行多维度可视化
- 常见问题FAQ(Q&A):坐标转换、样本量不足、图例优化等
- 该案例的局限性与未来扩展方向(xG模型融合)
引言:为什么足球分析需要“射门位置分布图”?
在现代足球分析中,射门位置分布图早已不是简单的“点图”,它能够直观揭示球队的进攻倾向(如是否依赖远射、是否擅长边路渗透)、球员个人习惯(如某前锋的“热区”是否集中在点球点附近),甚至能辅助教练制定防守策略,本文通过一个完整的Python案例,演示如何从原始事件数据中提取射门坐标,并绘制出符合专业论文级别的分布图。关键问题是:这个Python案例是否真正分析到位? 我们将从数据清洗、坐标映射、可视化迭代三个层面深度拆解。

数据准备:从哪获取射门数据?字段如何清洗?
数据源建议:
- 公开API:
StatsBomb(免费开放部分赛事)、Understat(含xG值) - 爬虫方案:解析FlashScore或SofaScore的JSON接口(需注意robots.txt规范)
核心字段:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| x | float | 射门点横坐标(0-100,从底线到中线) |
| y | float | 射门点纵坐标(0-100,从左侧边线到右侧) |
| result | string | 进球/射正/射偏/被扑 |
清洗关键点:
- 坐标标准化:绝大多数数据源采用进攻方向从左到右的坐标(即射门方球门在右侧),若数据源方向相反,需用
x_new = 100 - x转换。 - 去除无效值:部分射门可能没有定位(如反击中被犯规),需过滤
x IS NULL。 - 降噪处理:若坐标精度不超过1米,可考虑四舍五入到整数网格,避免热力图过于细碎。
# 示例:数据清洗
import pandas as pd
df = pd.read_csv('shots.csv')
df = df.dropna(subset=['x', 'y'])
# 假设数据为“防守方视角”,转换为进攻方视角
df['x_adj'] = 100 - df['x'] if df['x'].mean() > 50 else df['x']
核心代码:用Matplotlib+Seaborn绘制射门热力图与散点图
1 绘制足球场底图(关键步骤)
使用mplsoccer库(专门用于足球可视化)能省去大量画线代码,但若需纯手写,可仿照下方逻辑:
import matplotlib.pyplot as plt
from matplotlib.patches import Arc, Rectangle, Circle
def draw_pitch(ax):
# 画边界
ax.add_patch(Rectangle((0, 0), 100, 100, fill=False))
# 中圈
ax.add_patch(Circle((50, 50), 9.15, fill=False))
# 罚球区
ax.add_patch(Rectangle((0, 21.5), 16.5, 57, fill=False))
# 球门
ax.add_patch(Rectangle((-2, 45), 2, 10, fill=False, color='black'))
# 点球点
ax.scatter([11], [50], color='black', s=20)
# 弧顶
ax.add_patch(Arc((16.5, 50), 18.3, 18.3, angle=0, theta1=45, theta2=135))
# 调整坐标轴
ax.set_xlim(-5, 105)
ax.set_ylim(-5, 105)
ax.set_aspect('equal')
ax.axis('off')
2 叠加射门散点图
fig, ax = plt.subplots(figsize=(8, 6))
draw_pitch(ax)
# 区分射门结果颜色
colors = {'Goal': 'green', 'On Target': 'blue', 'Off Target': 'red', 'Blocked': 'gray'}
for result, group in df.groupby('result'):
ax.scatter(group['x_adj'], group['y'], s=50, color=colors.get(result, 'black'),
alpha=0.7, label=result, edgecolors='white', linewidth=0.5)
ax.legend(loc='upper right')'射门位置分布(散点)')
3 热力图(KDE密度)
import seaborn as sns
# 注意:seaborn的kdeplot需要调整坐标轴以适应足球场
kde = sns.kdeplot(x=df['x_adj'], y=df['y'], ax=ax, fill=True,
cmap='Reds', alpha=0.6, levels=20, thresh=0.1)
重要:若热力图覆盖了球场线,请调整KDE图层顺序(先画热力图再画球场线),或将热力图透明度下调至0.4。
结果解读:如何从图表中读出球队进攻风格?
假设我们得到以下视觉特征:
- 点球点附近密集红区 → 球队擅长创造中路短传渗透机会,或拥有抢点型前锋。
- 大禁区角两侧高亮 → 习惯边路下底倒三角或内切远射(如罗本区域)。
- 远射散落且颜色深 → 面对密集防守时缺乏耐心,或中场球员远射能力突出。
量化辅助:可计算射门点的期望x坐标均值(如df['x_adj'].mean()),若均值大于80,说明射门普遍来自极近距离,进攻效率高。
进阶分析:结合射门结果的多维度可视化
单一散点图信息量有限,我们可以用分面图(FacetGrid) 分别展示进球、射偏、被扑的坐标分布:
g = sns.FacetGrid(df, col='result', height=4, col_wrap=3) g.map(plt.scatter, 'x_adj', 'y', alpha=0.5) # 但需在每个子图也画上场地图,可自定义函数
或者使用气泡图:气泡大小代表该网格内射门次数,颜色代表进球率(xG转化率)。
# 网格化统计 grid = df.groupby([pd.cut(df['x_adj'], bins=10), pd.cut(df['y'], bins=10)]).size()
常见问题FAQ(Q&A)
Q1:为什么我的射门点全部集中在右侧?
A:因为你没做坐标标准化,检查x均值是否大于50,若大于则用100 - x反转。最稳妥的方法:观察你的数据源声明,如StatsBomb规定x始终为进攻方向。
Q2:样本量很少(比如少于30次射门),热力图会失真吗?
A:是的,建议此时仅用散点图,或提高KDE的bw_method参数(如bw_method=1.5)来平滑。
Q3:如何将射门图与全场事件图(传球、跑位)结合? A:可以绘制同一底图后,用不同颜色和透明度叠加,但需控制信息量,避免重叠严重。
Q4:有没有更专业的足球分析库?
A:推荐mplsoccer(底层是Matplotlib)、pyris(用于模拟),以及statsbombpy(直接拉取SB数据),本例中手绘球场是为了教学展示。
该案例的局限性与未来扩展方向
本案例的局限性:
- 缺乏空间聚合:未区分主客场、对手强弱对射门选择的影响。
- 忽略时序性:没有展现比赛不同阶段(如领先/落后)的射门分布变化。
升级方向:
- 引入xG模型:将热力图改为“射门威胁值”而非单纯次数,结合射门角度和距离计算进球概率。
- 动态可视化:使用
matplotlib.animation随时间变化展示射门点流动,或导出为GIF嵌入比赛报告。 - 聚类分析:用K-Means对射门坐标聚类,识别出“球队A的射门通常来自三个固定区域”。
最终回答“这个案例是否分析到位?” —— 如果仅用于初步战术板展示,该案例已经足够;但若用于深度球探报告,需要整合更多上下文数据,建议读者在掌握基础代码后,进一步接入xG、球员ID等维度,将静态图转化为决策支持工具。
(全文完)