本文目录导读:

分析球员跑动热点图,需要从数据获取、可视化处理、战术解读三个层面展开,下面是一套可落地的实用脚本思路(以Python为主),涵盖从原始数据到战术洞察的完整流程。
数据来源与格式
热点图分析的前提是带坐标的跑动/触球数据:
| 数据源 | 格式 | 说明 |
|---|---|---|
| StatsBomb Open Data | JSON | 含每个事件的 x,y 坐标(0-120 × 0-80) |
| Opta / Wyscout | CSV/JSON | 需付费,含球员追踪点 |
| 比赛视频 + 追踪系统 | CSV | 每帧球员坐标(如 25fps) |
| 手动标注 | CSV | x, y, timestamp, player_id |
典型字段:player_id, x, y, event_type, timestamp
核心分析脚本框架
数据清洗与坐标归一化
import pandas as pd
import numpy as np
def load_tracking_data(path):
df = pd.read_csv(path)
# 统一到 0-100 × 0-100 的相对坐标(便于跨场地比较)
df['x_norm'] = df['x'] / df['x'].max() * 100
df['y_norm'] = df['y'] / df['y'].max() * 100
# 只保留进攻方向一致的半场(可选:翻转客队)
return df
def normalize_direction(df, team='home'):
if team == 'away':
df['x_norm'] = 100 - df['x_norm']
df['y_norm'] = 100 - df['y_norm']
return df
热点图生成(KDE 核密度估计)
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import gaussian_kde
def plot_heatmap(df, player_id, title=''):
pts = df[df['player_id'] == player_id][['x_norm','y_norm']].values.T
kde = gaussian_kde(pts, bw_method=0.15)
# 生成网格
xi, yi = np.mgrid[0:100:200j, 0:100:200j]
zi = kde(np.vstack([xi.ravel(), yi.ravel()])).reshape(xi.shape)
fig, ax = plt.subplots(figsize=(8,6))
# 画球场底图
draw_pitch(ax)
ax.contourf(xi, yi, zi, levels=20, cmap='hot', alpha=0.7)
ax.set_title(title)
plt.show()
分区统计(Zone Analysis)
把球场切成 3×5 或 6×5 网格,统计各区域停留时长/触球次数:
def zone_stats(df, player_id, x_bins=6, y_bins=5):
sub = df[df['player_id'] == player_id].copy()
sub['zone_x'] = pd.cut(sub['x_norm'], bins=x_bins, labels=False)
sub['zone_y'] = pd.cut(sub['y_norm'], bins=y_bins, labels=False)
# 若数据含时长,用 sum;否则用 count 近似
pivot = sub.pivot_table(index='zone_y', columns='zone_x',
values='duration', aggfunc='sum', fill_value=0)
return pivot
多球员对比热图
def compare_heatmaps(df, players, figsize=(15,5)):
fig, axes = plt.subplots(1, len(players), figsize=figsize)
for ax, pid in zip(axes, players):
pts = df[df['player_id']==pid][['x_norm','y_norm']].values.T
sns.kdeplot(x=pts[0], y=pts[1], cmap='Reds', fill=True,
bw_adjust=0.5, ax=ax, thresh=0.05)
draw_pitch(ax)
ax.set_title(f'Player {pid}')
plt.tight_layout()
战术解读脚本(自动生成洞察)
数据可视化之外,脚本应输出可解释指标:
def tactical_metrics(df, player_id):
sub = df[df['player_id']==pid]
return {
'平均站位_x': sub['x_norm'].mean(), # 进攻推进程度
'平均站位_y': sub['y_norm'].mean(), # 左右偏斜
'活动范围_面积': convex_hull_area(sub), # 覆盖面积
'前场占比': (sub['x_norm']>66).mean(), # 高位活动比例
'边路占比': ((sub['y_norm']<20)|(sub['y_norm']>80)).mean(),
'热点中心': gaussian_kde(sub[['x_norm','y_norm']].T.values).covariance,
}
解读维度:
| 指标 | 战术含义 |
|---|---|
| 平均x偏高 + 面积小 | 固定高位站位(如影锋) |
| 面积大 + 分散 | 扫荡型中场/B2B |
| 边路占比 >60% | 边锋/边后卫 |
| 两个热点峰值 | 双角色球员(如内收边卫) |
| 前场占比低但面积大 | 后场组织核心 |
进阶:时间切片与动态热点
分析不同比赛阶段的热点变化(判断体能下降/战术调整):
def time_sliced_heatmap(df, pid, intervals=[(0,15),(15,30),(30,45),
(45,60),(60,75),(75,90)]):
fig, axes = plt.subplots(2,3, figsize=(15,8))
for ax, (t0,t1) in zip(axes.flat, intervals):
sub = df[(df['player_id']==pid) &
(df['minute']>=t0) & (df['minute']<t1)]
if len(sub) > 10:
sns.kdeplot(data=sub, x='x_norm', y='y_norm',
fill=True, cmap='Blues', ax=ax, thresh=0.05)
draw_pitch(ax); ax.set_title(f'{t0}-{t1}min')
用途:观察下半场活动范围缩小 → 体能问题;后半段热点前移 → 球队追分。
实战注意事项
- 样本量:单场 KDE 易受偶然事件干扰,建议 3-5 场滚动平均。
- bw_method 调参:带宽过小→碎片化;过大→糊成一团,追踪数据建议 0.05-0.1,事件数据 0.15-0.25。
- 坐标方向统一:客队需翻转,否则热点图无法横向对比。
- 区分"跑动"与"触球":追踪数据反映站位,事件数据反映参与度,两者热点含义不同——前者看覆盖,后者看威胁。
- 结合比赛情境:红牌、领先/落后、对手强度都会显著改变热点形态,需在报告中标明。
推荐工具链
- 可视化:mplsoccer(专业球场绘图库)、seaborn KDE
- 数据处理:pandas + scipy.stats.gaussian_kde
- 数据源:StatsBomb Open Data(免费入门)、SkillCorner(追踪数据)
- 快速原型:pyheatmap / plotly 交互式热图
如果需要,我可以针对具体数据格式(如 StatsBomb JSON 或 SkillCorner 追踪 CSV)写出可直接运行的完整脚本。