本文目录导读:

分析球员跑动热点图(Heatmap)是足球数据分析中非常实用的一项技能,热点图能够直观展示球员在球场上的活动范围和活跃程度,帮助教练评估战术执行、体能分配和跑动习惯。
下面我为你提供一套完整的实用分析脚本方案,包含数据获取思路、核心Python脚本以及深度分析维度。
数据获取思路(前置条件)
在写脚本之前,你需要有数据源,通常有三种方式:
- 官方API(如StatsBomb, Wyscout):最专业,包含坐标数据,但需要权限。
- 公开事件数据(如Kaggle数据集):包含
x,y坐标(通常归一化到0-120 x 0-80)。 - 视频识别(OpenCV + YOLO):通过计算机视觉从比赛录像中提取球员位置。
假设:下面的脚本默认你有一份CSV或DataFrame数据,包含字段:player_id(球员)、x(0-120)、y(0-80)、match_id(场次)、timestamp(时间戳)。
核心分析脚本(Python + Matplotlib/Plotly)
这是最实用的部分,包含数据清洗、单场热点图和多场聚合图。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.ndimage import gaussian_filter
# ======================
# 第一步:数据清洗与标准化
# ======================
def load_and_prepare_data(file_path):
"""
加载数据并标准化坐标到 [0, 1] 区间。
标准数据通常是 0-120 (x) 和 0-80 (y)。
"""
df = pd.read_csv(file_path)
# 假设你的原始数据列名为 'x_loc', 'y_loc'
# 如果是其他数据源,请修改这里
if 'x_loc' in df.columns:
df.rename(columns={'x_loc':'x', 'y_loc':'y'}, inplace=True)
# 归一化到 0-1(方便画图)
df['x_norm'] = df['x'] / 120.0
df['y_norm'] = df['y'] / 80.0
# 去除无效坐标(有些数据点可能为0,0但没实际意义)
df = df[(df['x_norm'] > 0.01) & (df['y_norm'] > 0.01)]
return df
# ======================
# 第二步:绘制单场热点图(核心)
# ======================
def plot_heatmap_single_match(df, player_name, match_id, save_path=None):
"""
使用核密度估计(KDE)绘制球员跑动热点图。
"""
# 筛选特定球员和比赛
player_data = df[(df['player'] == player_name) & (df['match_id'] == match_id)]
if len(player_data) == 0:
print(f"没有找到 {player_name} 在 {match_id} 的数据")
return
# 创建热力图网格(120 x 80 的分辨率)
heatmap, xedges, yedges = np.histogram2d(
player_data['x'], player_data['y'],
bins=[120, 80],
range=[[0, 120], [0, 80]]
)
# 使用高斯平滑让热力图更柔和,去掉噪点
heatmap_smooth = gaussian_filter(heatmap, sigma=2)
# 开始绘图
fig, ax = plt.subplots(figsize=(10, 7))
# 反转Y轴(球场通常从上往下看)
im = ax.imshow(heatmap_smooth.T, extent=[0, 120, 0, 80],
origin='lower', cmap='Reds', aspect='auto',
interpolation='bilinear', alpha=0.8)
# 绘制球场轮廓(简化版)
draw_pitch(ax)
# 添加颜色条(表示活跃度)
cbar = plt.colorbar(im, ax=ax, shrink=0.8)
cbar.set_label('跑动频率', fontsize=12)
ax.set_title(f'{player_name} 跑动热点图 (比赛 {match_id})', fontsize=14, fontweight='bold')
ax.set_xlabel('球场长度 (0-120m)', fontsize=10)
ax.set_ylabel('球场宽度 (0-80m)', fontsize=10)
# 关闭坐标轴刻度,看起来更干净
ax.set_xticks([])
ax.set_yticks([])
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
print(f"图片已保存至: {save_path}")
plt.show()
# ======================
# 辅助函数:绘制球场
# ======================
def draw_pitch(ax):
"""绘制一个简化的足球场轮廓(用于背景)"""
# 外框
ax.plot([0, 0, 120, 120, 0], [0, 80, 80, 0, 0], color='black', linewidth=1.5)
# 中线
ax.plot([60, 60], [0, 80], color='black', linewidth=1)
ax.plot([0, 120], [40, 40], color='black', linewidth=0.5, linestyle='--')
# 中圈
circle = plt.Circle((60, 40), 10, color='black', fill=False, linewidth=1)
ax.add_patch(circle)
# 禁区
ax.add_patch(mpatches.Rectangle((0, 18), 16, 44, fill=False, edgecolor='black'))
ax.add_patch(mpatches.Rectangle((104, 18), 16, 44, fill=False, edgecolor='black'))
# 球门
ax.add_patch(mpatches.Rectangle((-2, 30), 2, 20, fill=False, edgecolor='black'))
ax.add_patch(mpatches.Rectangle((120, 30), 2, 20, fill=False, edgecolor='black'))
# ======================
# 第三步:多场次聚合分析(评估跑动稳定性)
# ======================
def plot_aggregated_heatmap(df, player_name, min_matches=3):
"""
聚合一个球员多场比赛的跑动数据,得出赛季热点倾向图。
这能更真实反映球员的战术位置感。
"""
player_data = df[df['player'] == player_name]
if player_data['match_id'].nunique() < min_matches:
print(f"数据不足,要求至少 {min_matches} 场比赛的数据")
return
# 同样用直方图聚合
heatmap, xedges, yedges = np.histogram2d(
player_data['x'], player_data['y'],
bins=[120, 80],
range=[[0, 120], [0, 80]]
)
heatmap_smooth = gaussian_filter(heatmap, sigma=2.5) # 稍微多平滑一点
fig, ax = plt.subplots(figsize=(10, 7))
im = ax.imshow(heatmap_smooth.T, extent=[0, 120, 0, 80],
origin='lower', cmap='plasma', aspect='auto',
alpha=0.8)
draw_pitch(ax)
# 添加等值线,更直观标出高密度区域(核心跑动区)
contours = ax.contour(heatmap_smooth.T, extent=[0, 120, 0, 80],
levels=np.percentile(heatmap_smooth, [50, 80, 95]),
colors='blue', alpha=0.5, linewidths=0.8)
ax.clabel(contours, inline=True, fontsize=8, fmt='%.0f')
cbar = plt.colorbar(im, ax=ax, shrink=0.8)
cbar.set_label('累计跑动次数')
match_count = player_data['match_id'].nunique()
ax.set_title(f'{player_name} 赛季聚合热点图 ({match_count}场比赛)', fontsize=14, fontweight='bold')
ax.set_xticks([])
ax.set_yticks([])
plt.tight_layout()
plt.show()
# ======================
# 第四步:量化指标计算(比图更重要的数据)
# ======================
def calculate_run_metrics(df, player_name, match_id=None):
"""
输出量化指标,辅助分析师做判断。
"""
if match_id:
data = df[(df['player'] == player_name) & (df['match_id'] == match_id)]
else:
data = df[df['player'] == player_name]
if len(data) == 0:
return None
# 计算“重心”(质心)——球员平均站位
centroid_x = np.mean(data['x'])
centroid_y = np.mean(data['y'])
# 计算“覆盖面”——90%的数据点所在区域
x_coverage = np.percentile(data['x'], 90) - np.percentile(data['x'], 10)
y_coverage = np.percentile(data['y'], 90) - np.percentile(data['y'], 10)
# 计算“冲刺距离”——通过时间戳计算(需要时间字段)
# 这里假设有 'timestamp' 字段
if 'timestamp' in data.columns:
data = data.sort_values('timestamp')
dx = np.diff(data['x'])
dy = np.diff(data['y'])
distances = np.sqrt(dx**2 + dy**2)
# 过滤掉过于微小的移动(静态站桩)
high_speed_runs = distances[distances > 2.0] # 假设每帧移动超过2米算冲刺
sprint_distance = np.sum(high_speed_runs)
else:
sprint_distance = 0
metrics = {
'球员': player_name,
'样本点数': len(data),
'核心站位 (X,Y)': (round(centroid_x, 1), round(centroid_y, 1)),
'进攻纵深覆盖 (米)': round(x_coverage, 1),
'横向覆盖 (米)': round(y_coverage, 1),
'估计冲刺距离 (米)': round(sprint_distance, 1)
}
return metrics
# ======================
# 主程序入口示例
# ======================
if __name__ == "__main__":
# 示例数据路径(你需要替换为你自己的数据)
df = load_and_prepare_data('your_tracking_data.csv')
# 1. 看某球员单场热点图
plot_heatmap_single_match(df, '梅西', match_id='M001', save_path='messi_heatmap.png')
# 2. 看赛季聚合图
plot_aggregated_heatmap(df, '梅西', min_matches=10)
# 3. 输出量化指标
metrics = calculate_run_metrics(df, '梅西', match_id='M001')
print("量化指标如下:")
for key, value in metrics.items():
print(f"{key}: {value}")
深度解读:如何从热点图看战术?
脚本只是工具,分析师的洞察才是关键,拿到图后,建议按以下三观去拆解:
微观视角(单场)
- 红黄区域:是高频活动区,检查是否覆盖了本方半场和对方半场。
- 空白区:如果是中场球员,热点图在中场中路密集(如布斯克茨),说明是“节拍器”;如果热点图几乎不进入禁区,说明是“纯组织者”,缺少前插。
宏观视角(多场)
- 流动性:聚合图如果是“一团火”集中在局部,说明球员定位固定;如果是“长条形”或“覆盖全场”,说明是B2B(从禁区到禁区)中场或边路球员。
- 纵向覆盖:对比
calculate_run_metrics输出的进攻纵深覆盖值,如果是边后卫,通常攻势覆盖在20m-70m之间,如果超过80m,说明战术非常激进。
异常侦测
- 下半场热点稀疏:对比上下半场球员数据(需要将timestamp分段),如果下半场活跃范围明显缩小,存在体能下降或对手针对性限制的问题。
- 反手侧热区:如果一名右路球员,热点图大量出现在左路,可能战术要求换位,或者是自由人踢法。
进阶技巧(针对生产环境)
- 时空切片:将比赛划分为每15分钟一段,查看热点变化,前30分钟高压逼抢区在对方半场,后30分钟缩回本方,说明体能下降。
- 对手差异化:对阵强队(高位逼抢)和弱队(阵地防守),热点图会完全不同,建议按对手实力分组绘制。
- 速度叠加:将热点图分为“慢走/步行”、“小跑”、“高速冲刺”四个图层。“冲刺热区” 往往比总热区更有战术价值(它代表最关键的压迫或反击时机)。
如果还没有现成的坐标数据,建议先从 StatsBomb公开数据集 或 Kaggle 上下载免费数据练习,掌握了上述脚本逻辑后,你就可以针对任何球员生成专业的跑动分析报告了。
如果遇到特定数据格式解析问题(比如Wyscout的JSON结构),欢迎随时提供字段名,我可以帮你调整适配代码。
版权声明: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直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播