实用脚本如何分析球员跑动热点图?

wen 实用脚本 3

本文目录导读:

实用脚本如何分析球员跑动热点图?

  1. 数据准备
  2. 核心分析脚本
  3. 进阶分析指标
  4. 可视化升级方向
  5. 典型分析场景
  6. 完整工作流脚本结构

分析球员跑动热点图,本质上是对时空轨迹数据进行密度估计和可视化,下面从数据到洞察,给出一套可直接落地的实用脚本思路。

数据准备

热点图分析的输入通常是两类数据:

数据类型 来源 格式
追踪数据 Hawk-Eye / Stats Perform / Catapult GPS 每秒 x,y 坐标
事件数据 Opta / Wyscout 传球、跑动事件点
import pandas as pd
import numpy as np
# 示例:追踪数据格式
# columns: timestamp, player_id, x, y
df = pd.read_csv("tracking.csv")
# 标准球场:105m × 68m,坐标常归一化到 0-1 或 0-100

核心分析脚本

坐标标准化 + 方向对齐

def normalize_coords(df, pitch_length=105, pitch_width=68):
    df['x_norm'] = df['x'] / pitch_length
    df['y_norm'] = df['y'] / pitch_width
    return df
# 若需统一进攻方向(如把左向右进攻统一)
def flip_if_needed(df, team_attacking_left):
    if team_attacking_left:
        df['x_norm'] = 1 - df['x_norm']
        df['y_norm'] = 1 - df['y_norm']
    return df

核密度估计(KDE)生成热点

from scipy.stats import gaussian_kde
import matplotlib.pyplot as plt
from mplsoccer import Pitch
def plot_heatmap(df, player_name, bw=0.15):
    x = df['x_norm'].values
    y = df['y_norm'].values
    # 核密度估计
    kde = gaussian_kde(np.vstack([x, y]), bw_method=bw)
    # 在网格上求密度
    grid_x, grid_y = np.mgrid[0:1:200j, 0:1:200j]
    positions = np.vstack([grid_x.ravel(), grid_y.ravel()])
    density = kde(positions).reshape(grid_x.shape)
    # 绘图
    pitch = Pitch(pitch_type='statsbomb', line_color='black')
    fig, ax = pitch.draw(figsize=(10, 7))
    pitch.kdeplot(x, y, ax=ax, cmap='YlOrRd', fill=True, levels=100, alpha=0.7)
    ax.set_title(f"{player_name} 跑动热点图")
    plt.savefig(f"{player_name}_heatmap.png", dpi=150)

按比赛阶段拆分(进攻/防守/不同15分钟)

def split_by_phase(df, phase_col='phase'):
    """phase: 'attack', 'defense', 'transition'"""
    return {phase: sub for phase, sub in df.groupby(phase_col)}
# 或按时间分段查看体能衰减
df['period'] = pd.cut(df['minute'], bins=[0,15,30,45,60,75,90], 
                      labels=['0-15','15-30','30-45','45-60','60-75','75-90'])

进阶分析指标

单纯热点图只能看“在哪”,结合以下指标才能回答“为什么”:

覆盖面积与集中度

from scipy.spatial import ConvexHull
def coverage_metrics(df):
    points = df[['x_norm','y_norm']].values
    hull = ConvexHull(points)
    area = hull.volume  # 2D 中 volume 即面积
    # 熵:分布均匀度
    hist, _, _ = np.histogram2d(df['x_norm'], df['y_norm'], bins=10)
    p = hist.flatten() / hist.sum()
    p = p[p > 0]
    entropy = -np.sum(p * np.log(p))
    return {'覆盖面积': area, '分布熵': entropy}

分区停留时间

def zone_time(df, zone='final_third'):
    if zone == 'final_third':
        mask = df['x_norm'] > 2/3
    elif zone == 'box':
        mask = (df['x_norm'] > 0.83) & (df['y_norm'].between(0.21, 0.79))
    return mask.sum() / len(df)  # 占比即时间占比

与其他球员对比(相对热点)

def relative_heatmap(df, league_avg_density):
    """减去同位置平均密度,得到'异常'区域"""
    player_density = compute_kde(df)
    diff = player_density - league_avg_density
    # 红色=异常活跃区,蓝色=异常沉寂区
    return diff

可视化升级方向

技术 用途
mplsoccer 标准球场底图,一行代码出图
Plotly + Dash 交互式热点,可切换球员/时段
Hexbin 比 KDE 更快,适合大样本
箭头场 展示平均跑动方向(quiver)
3D 曲面 密度 + 时间轴,看动态变化
# 交互式版本(plotly)
import plotly.express as px
fig = px.density_heatmap(df, x='x_norm', y='y_norm', 
                         nbinsx=30, nbinsy=20,
                         color_continuous_scale='Hot')
fig.show()

典型分析场景

  1. 边后卫:看是否覆盖整条边路走廊 → 纵向分布应偏窄、横向跨度大
  2. 后腰:应形成“盾形”热点,集中在中圈附近
  3. 伪9号:热点应回撤到中场,与禁区内形成两个峰
  4. 体能对比:下半场热点收缩 → 跑动能力下降信号

完整工作流脚本结构

project/
├── data/
│   ├── tracking.csv
│   └── events.csv
├── src/
│   ├── load.py          # 数据读取 + 清洗
│   ├── normalize.py     # 坐标标准化
│   ├── kde.py           # 密度计算
│   ├── metrics.py       # 覆盖率/熵/分区
│   └── visualize.py     # mplsoccer/plotly 出图
└── notebooks/
    └── analysis.ipynb

如果告诉我你手上的数据格式(比如是 Opta 事件数据、Catapult GPS,还是 StatsBomb 360),我可以给出更具体的字段映射和可运行代码。

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