开源项目如何分析球员跑动热点图?

wen 开源项目 3

本文目录导读:

开源项目如何分析球员跑动热点图?

  1. 整体技术栈概览
  2. 数据来源
  3. 核心分析流程
  4. 推荐开源项目参考
  5. 完整最小示例(StatsBomb 数据)
  6. 注意事项与避坑

分析球员跑动热点图(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()

注意事项与避坑

  1. 样本量:KDE 在触球点 < 30 时不稳定,可改用网格或增大 bandwidth
  2. 坐标方向:不同数据源 y 轴方向不同(有的从上到下),需统一
  3. 进攻方向:主客队需镜像到同方向才能对比
  4. 时间维度:整场热图会掩盖换位、替补影响,建议按 15 分钟分段
  5. 带宽选择bw_method 过小→噪点,过大→过度平滑,建议 0.1~0.3 网格搜索
  6. 归一化:展示时通常按球员自己总时间归一化,便于横向比较

如果你有具体的数据源(StatsBomb / Metrica / 自采 GPS)或想实现某个特定分析(对比两名边锋的跑位差异”),可以告诉我,我可以给出针对性的代码。

抱歉,评论功能暂时关闭!