实用脚本分析守门员出击范围
分析守门员出击范围是足球数据分析中的经典课题,下面我给出一个完整的实用脚本框架,涵盖数据来源、指标定义和可视化。

核心指标定义
守门员出击范围通常用以下几个维度刻画:
| 指标 | 含义 | 计算方式 |
|---|---|---|
| 平均出击距离 | 每次出击离门线的平均距离 | 出击点坐标到球门线距离的均值 |
| 出击半径(Sweeper Radius) | 覆盖95%出击点的距离 | 出击距离的95分位数 |
| 出击热力图 | 出击位置的空间分布 | 二维KDE |
| 出击倾向 | 高球/地面球/单刀等分类 | 按事件类型分组 |
| 成功出击率 | 出击后是否化解威胁 | 事件结果标签 |
数据来源
常见数据源(推荐优先级):
- StatsBomb Open Data(免费,含门将事件坐标)
- Opta / SkillCorner(商业,追踪数据更精确)
- Wyscout(半商业)
- 自建:用 YOLO + 单应矩阵 从比赛视频提取门将位置
实用脚本(基于 StatsBomb 数据)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Arc, Rectangle
from scipy.stats import gaussian_kde
# ---------- 1. 读取数据 ----------
# StatsBomb 事件数据(示例:某场比赛)
events = pd.read_json('events.json')
# 筛选门将相关事件
gk_events = events[
(events['position'] == 'Goalkeeper') &
(events['type'].isin(['Goal Keeper', 'Pass', 'Ball Recovery']))
].copy()
# ---------- 2. 提取出击事件 ----------
def extract_sweeper_events(df):
"""提取门将离线的出击事件"""
rows = []
for _, ev in df.iterrows():
# StatsBomb 中 Goal Keeper 事件含 'goalkeeper_type'
if ev['type'] == 'Goal Keeper':
gk_type = ev.get('goalkeeper_type', {}).get('name', '')
loc = ev.get('location', [None, None])
if loc[0] is None:
continue
rows.append({
'minute': ev['minute'],
'x': loc[0],
'y': loc[1],
'gk_type': gk_type,
'outcome': ev.get('goalkeeper_outcome', {}).get('name', '')
})
return pd.DataFrame(rows)
sweeper = extract_sweeper_events(gk_events)
# ---------- 3. 计算关键指标 ----------
# StatsBomb 球场:120 x 80,球门线在 x=0(自家)或 x=120
# 假设分析自家半场,球门线 x=0
PITCH_LENGTH = 120
PITCH_WIDTH = 80
sweeper['distance_from_goal'] = sweeper['x'] # 越接近0越靠门
metrics = {
'出击次数': len(sweeper),
'平均出击距离': sweeper['distance_from_goal'].mean(),
'出击距离中位数': sweeper['distance_from_goal'].median(),
'出击半径(95%)': sweeper['distance_from_goal'].quantile(0.95),
'最大出击距离': sweeper['distance_from_goal'].max(),
'成功出击率': (sweeper['outcome'] == 'Success').mean()
}
print(pd.Series(metrics))
# ---------- 4. 可视化:出击热力图 ----------
def plot_sweeper_heatmap(sweeper_df, ax=None):
if ax is None:
fig, ax = plt.subplots(figsize=(12, 8))
# 绘制球场
ax.add_patch(Rectangle((0, 0), 60, 80, fill=False, color='black'))
ax.add_patch(Rectangle((0, 18), 18, 44, fill=False, color='black')) # 禁区
ax.add_patch(Rectangle((0, 30), 6, 20, fill=False, color='black')) # 小禁区
ax.add_patch(Arc((12, 40), 20, 20, theta1=310, theta2=50, color='black'))
# KDE 热力图
if len(sweeper_df) > 3:
xy = sweeper_df[['x', 'y']].values
kde = gaussian_kde(xy.T)
xx, yy = np.meshgrid(np.linspace(0, 60, 100),
np.linspace(0, 80, 100))
zz = kde(np.vstack([xx.ravel(), yy.ravel()])).reshape(xx.shape)
ax.contourf(xx, yy, zz, cmap='Reds', alpha=0.6, levels=10)
# 散点
ax.scatter(sweeper_df['x'], sweeper_df['y'],
c='blue', s=30, alpha=0.7, edgecolor='white')
# 出击半径圈
r = sweeper_df['x'].quantile(0.95)
ax.add_patch(Arc((0, 40), 2*r, 2*r, theta1=-90, theta2=90,
color='green', lw=2, linestyle='--',
label=f'95% 出击半径 ≈ {r:.1f}m'))
ax.set_xlim(-2, 62)
ax.set_ylim(-2, 82)
ax.set_aspect('equal')
ax.set_title('门将出击热力图')
ax.legend()
return ax
plot_sweeper_heatmap(sweeper)
plt.savefig('sweeper_heatmap.png', dpi=150)
plt.show()
# ---------- 5. 按出击类型分组对比 ----------
type_stats = sweeper.groupby('gk_type').agg(
次数=('x', 'count'),
平均距离=('distance_from_goal', 'mean'),
最远距离=('distance_from_goal', 'max'),
成功率=('outcome', lambda s: (s == 'Success').mean())
).round(2)
print(type_stats)
进阶分析
对手压迫下的出击决策
# 计算每场比赛中,对方传球进入门将控制区前的时间 # 判断出击是否“必要”(避免单刀) vs “冒进”
门将风格聚类
用 KMeans 对多名门将的出击特征聚类:
from sklearn.cluster import KMeans features = ['平均出击距离', '出击半径(95%)', '成功出击率', '出击次数/90min'] # 假设 gk_features 是 N 名门将的特征矩阵 kmeans = KMeans(n_clusters=3, random_state=0).fit(gk_features[features]) # 通常聚出:传统门线型 / 现代自由人型 / 混合型
视频提取方案(无API时)
# 使用单应矩阵将像素坐标映射到球场坐标
import cv2
import numpy as np
# 已知球场4个角点的像素坐标和真实坐标
pixel_pts = np.array([[100, 500], [900, 500], [900, 100], [100, 100]])
world_pts = np.array([[0, 0], [120, 0], [120, 80], [0, 80]])
H, _ = cv2.findHomography(pixel_pts, world_pts)
def pixel_to_pitch(px, py):
pt = np.array([px, py, 1.0])
mapped = H @ pt
return mapped[0]/mapped[2], mapped[1]/mapped[2]
配合 YOLOv8 检测门将位置,即可逐帧构建出击轨迹。
结论解读建议
- 出击半径 > 25m:典型“清道夫门将”(如 诺伊尔、埃德森)
- 出击半径 < 12m:传统门线型(如 德赫亚 早年)
- 成功出击率 < 80%:需警惕冒进,尤其在高位防线体系中
- 热力图集中在小禁区:防守策略偏向低位防守
需要我针对某个具体数据源(如 StatsBomb 具体字段、SkillCorner 追踪数据)展开更细致的脚本吗?