本文目录导读:

分析球员跑动热点图(Positional Heatmap)是体育数据分析中的经典任务,下面从开源项目角度,介绍一套完整的技术方案,涵盖数据来源、处理流程、可视化到进阶分析。
整体技术栈概览
原始数据 → 坐标转换 → 密度估计 → 可视化 → 战术洞察
(追踪/事件) (标准化) (KDE/网格) (热力图) (聚类/指标)
常用开源工具:
| 环节 | 工具/库 |
|---|---|
| 数据处理 | pandas, numpy, polars |
| 空间计算 | scipy, shapely, geopandas |
| 密度估计 | scipy.stats.gaussian_kde, sklearn |
| 可视化 | matplotlib, seaborn, plotly, mplsoccer |
| 足球专用 | mplsoccer, soccerdata, kloppy |
| 追踪数据 | StatsBomb Open Data, Metrica Sports |
数据来源
事件数据(Event Data)
- StatsBomb Open Data:免费,含球员每次触球坐标(x, y)
- FBref / Understat:聚合统计数据
追踪数据(Tracking Data)
- Metrica Sports:开源样本数据,含每帧 25fps 的 22 名球员坐标
- SkillCorner:部分公开
数据结构示例(Metrica)
frame,player_id,x,y 1,5,0.52,0.31 1,6,0.48,0.29 ...
坐标通常归一化为 [0,1],需转换为标准球场尺寸(105×68 米)。
核心分析流程
步骤 1:坐标标准化
import numpy as np
def normalize_to_pitch(x, y, pitch_length=105, pitch_width=68):
"""将归一化坐标转为米制"""
return x * pitch_length, y * pitch_width
def mirror_if_needed(x, y, team_direction):
"""统一进攻方向(都向右攻)"""
if team_direction == 'left':
x = 1 - x
y = 1 - y
return x, y
步骤 2:密度估计(热力图核心)
方法 A:高斯核密度估计(KDE) — 最常用
from scipy.stats import gaussian_kde
import numpy as np
def compute_kde(x_coords, y_coords, grid_size=100):
positions = np.vstack([x_coords, y_coords])
kde = gaussian_kde(positions, bw_method=0.15)
# 生成球场网格
xgrid = np.linspace(0, 105, grid_size)
ygrid = np.linspace(0, 68, grid_size)
X, Y = np.meshgrid(xgrid, ygrid)
Z = kde(np.vstack([X.ravel(), Y.ravel()])).reshape(X.shape)
return X, Y, Z
方法 B:二维直方图(网格法) — 计算快
def grid_density(x, y, bins=(21, 14)):
H, _, _ = np.histogram2d(x, y, bins=bins, range=[[0,105],[0,68]])
return H
方法 C:Binned Statistic — 适合按时间/事件加权
from scipy.stats import binned_statistic_2d
stat, _, _, _ = binned_statistic_2d(x, y, values=speed,
statistic='mean',
bins=[21, 14],
range=[[0,105],[0,68]])
步骤 3:可视化(mplsoccer 推荐)
from mplsoccer import Pitch
import matplotlib.pyplot as plt
pitch = Pitch(pitch_type='statsbomb', line_zorder=2)
fig, ax = pitch.draw(figsize=(12, 8))
# 绘制热力图
pitch.kdeplot(x_coords, y_coords, ax=ax,
cmap='hot', fill=True, levels=100, alpha=0.7)
plt.savefig('heatmap.png', dpi=150)
进阶:网格热图
pitch = Pitch(pitch_type='statsbomb') fig, ax = pitch.draw() bs = pitch.bin_statistic(x, y, statistic='count', bins=(12, 8)) pitch.heatmap(bs, ax=ax, cmap='Reds', edgecolors='#22312b')
步骤 4:进阶分析
(1) 按比赛阶段分时段热图
# 上半场 vs 下半场 mask_1h = time <= 45 ax1 = pitch.kdeplot(x[mask_1h], y[mask_1h]) ax2 = pitch.kdeplot(x[~mask_1h], y[~mask_1h])
(2) 位置聚类(球员角色识别)
from sklearn.cluster import KMeans # 将每次触球位置聚类,识别球员活动区域 kmeans = KMeans(n_clusters=4).fit(np.column_stack([x, y])) # 质心可解释为:防守区、中场、进攻三区、边路等
(3) 与球队平均热图对比
diff = kde_player - kde_team_avg # 正值 = 球员比队友更常出现的区域 → 揭示个人职责
(4) 输出量化指标
- 活动范围(Convex Hull 面积)
- 平均位置(质心)
- 垂直/水平分散度(标准差)
- 进攻三区触球占比
- 左右路偏好
from scipy.spatial import ConvexHull hull = ConvexHull(np.column_stack([x, y])) area = hull.volume # 2D 中 volume 即面积
推荐开源项目参考
| 项目 | 用途 |
|---|---|
| mplsoccer | 足球专用绘图(热图、传球图、雷达图) |
| soccerdata | 抓取 FBref/Understat 数据 |
| kloppy | 标准化不同供应商的追踪/事件数据 |
| football-data | 赛程与结果数据 |
| socceraction | 事件数据 → 动作价值(VAEP) |
| StatsBomb Open Data | 免费高质量事件数据 |
完整最小示例(StatsBomb 数据)
from statsbombpy import sb from mplsoccer import Pitch import matplotlib.pyplot as plt # 1. 拉取梅西在某场比赛的事件 events = sb.events(match_id=3788741) messi = events[events.player == 'Lionel Messi'] # 2. 提取坐标 x, y = messi.location.apply(lambda p: p[0]), messi.location.apply(lambda p: p[1]) # 3. 绘制 pitch = Pitch(pitch_type='statsbomb') fig, ax = pitch.draw() pitch.kdeplot(x, y, ax=ax, cmap='hot', fill=True, levels=50) plt.show()
注意事项与避坑
- 样本量:KDE 在触球点 < 30 时不稳定,可改用网格或增大 bandwidth
- 坐标方向:不同数据源 y 轴方向不同(有的从上到下),需统一
- 进攻方向:主客队需镜像到同方向才能对比
- 时间维度:整场热图会掩盖换位、替补影响,建议按 15 分钟分段
- 带宽选择:
bw_method过小→噪点,过大→过度平滑,建议 0.1~0.3 网格搜索 - 归一化:展示时通常按球员自己总时间归一化,便于横向比较
如果你有具体的数据源(StatsBomb / Metrica / 自采 GPS)或想实现某个特定分析(对比两名边锋的跑位差异”),可以告诉我,我可以给出针对性的代码。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播