实用脚本如何分析守门员的出击范围?

wen 实用脚本 3

实用脚本分析守门员出击范围

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

实用脚本如何分析守门员的出击范围?

核心指标定义

守门员出击范围通常用以下几个维度刻画:

指标 含义 计算方式
平均出击距离 每次出击离门线的平均距离 出击点坐标到球门线距离的均值
出击半径(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 追踪数据)展开更细致的脚本吗?

上一篇实用脚本认为这场比赛的含金量高吗?

下一篇当前分类已是最新一篇

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