python案例如何分析边锋内切打法的威胁?

wen python案例 2

用Python分析边锋内切打法的威胁——案例思路与代码

下面给你一个可落地的分析框架,用公开的足球事件数据(StatsBomb Open Data 免费)来量化"边锋内切"的威胁,核心思路是:识别内切事件 → 度量推进价值 → 关联后续进攻结果。

python案例如何分析边锋内切打法的威胁?


分析框架设计

步骤 目标 关键指标
数据获取 拿到带坐标的事件数据 StatsBomb / Wyscout
定义内切 识别边锋从边路向中路带球 起终点 x 位移、y 向中路收敛
威胁度量 量化这次内切的价值 推进距离、进入危险区、xT/VAEP
结果关联 内切后是否形成射门/助攻 后续N秒射门概率、xG
可视化 热图、传球网络、内切走廊 pitch plot

核心代码实现

环境与数据

# pip install statsbombpy mplsoccer pandas numpy
from statsbombpy import sb
import pandas as pd
import numpy as np
from mplsoccer import Pitch
import matplotlib.pyplot as plt
# 免费公开数据:某场比赛
matches = sb.matches(competition_id=11, season_id=90)  # 西甲某赛季
match_id = matches.iloc[0]['match_id']
events = sb.events(match_id=match_id)

定义"内切"事件

内切 = 边锋/边前卫的带球(carry),起点在边路(|y-40| > 20),终点更靠近中路,且向前推进。

def extract_carries(events):
    df = events[events['type'] == 'Carry'].copy()
    df = df.dropna(subset=['location', 'carry_end_location'])
    df['x'] = df['location'].apply(lambda l: l[0])
    df['y'] = df['location'].apply(lambda l: l[1])
    df['end_x'] = df['carry_end_location'].apply(lambda l: l[0])
    df['end_y'] = df['carry_end_location'].apply(lambda l: l[1])
    return df
def is_inside_cut(carry):
    """判断是否为内切:从边路向中路带球"""
    start_wide = abs(carry['y'] - 40) > 20          # 起点在边路
    toward_center = abs(carry['end_y'] - 40) < abs(carry['y'] - 40)  # 向中路收敛
    forward = carry['end_x'] - carry['x'] > 0        # 向前
    return start_wide and toward_center and forward
carries = extract_carries(events)
carries['is_cut'] = carries.apply(is_inside_cut, axis=1)
cuts = carries[carries['is_cut']]
print(f"内切次数: {len(cuts)}")

威胁度量:推进距离 + 危险区 + xT

def threat_score(carry):
    dx = carry['end_x'] - carry['x']
    dy = abs(carry['y'] - 40) - abs(carry['end_y'] - 40)  # 向中路位移
    # 进入进攻三区加权
    zone_bonus = 1.5 if carry['end_x'] > 80 else 1.0
    # 进入禁区加权
    box_bonus = 2.0 if (carry['end_x'] > 102 and 18 < carry['end_y'] < 62) else 1.0
    return (dx * 0.5 + dy * 0.3) * zone_bonus * box_bonus
cuts = cuts.copy()
cuts['threat'] = cuts.apply(threat_score, axis=1)
print(cuts[['player', 'x','y','end_x','end_y','threat']].sort_values('threat', ascending=False).head(10))

进阶:用 xT(Expected Threat)网格替代手写权重,把带球起点终点映射到 16×12 网格的价值表,xT_end - xT_start 就是这次内切创造的预期威胁增量,Karun Singh 的 xT 表可直接复用。

关联后续进攻结果(关键)

内切本身不是目的,要看它是否引出射门/助攻,用时间窗口匹配后续事件:

def link_outcome(cuts, events, window=8):
    results = []
    for _, c in cuts.iterrows():
        t = c['timestamp']
        later = events[(events['timestamp'] > t)]
        # 取同一进攻内后续N个事件,看是否出现射门
        nxt = later.head(window)
        shot = nxt[nxt['type'] == 'Shot']
        results.append({
            'player': c['player'],
            'threat': c['threat'],
            'led_to_shot': len(shot) > 0,
            'xG': shot['shot_statsbomb_xg'].sum() if len(shot) else 0
        })
    return pd.DataFrame(results)
outcome = link_outcome(cuts, events)
print(outcome.groupby('player').agg(
    cuts=('threat','count'),
    avg_threat=('threat','mean'),
    shot_rate=('led_to_shot','mean'),
    total_xG=('xG','sum')
).sort_values('total_xG', ascending=False))

可视化:内切走廊图

pitch = Pitch(pitch_type='statsbomb', line_zorder=2)
fig, ax = pitch.draw(figsize=(10, 7))
# 画所有内切轨迹
for _, c in cuts.iterrows():
    pitch.arrows(c['x'], c['y'], c['end_x'], c['end_y'],
                 color='#c72c41', width=1.5, headwidth=4, alpha=0.6, ax=ax)
# 起点热图
pitch.kdeplot(cuts['x'], cuts['y'], ax=ax, cmap='Reds', alpha=0.4, levels=50)
ax.set_title('边锋内切轨迹与起点热区', fontsize=15)
plt.show()

分析结论怎么读

跑完后你会得到一张球员维度表,解读方式:

  • 内切次数多 + shot_rate 高 → 内切真正转化为威胁(如罗本式"内切+远射")
  • 内切次数多 + shot_rate 低 → 内切后陷入密集防守,需要下底或回传,威胁被高估
  • avg_threat 高但 xG 低 → 推进到危险区但缺少终结,说明队友接应不足
  • 对比左右路:把 y < 40 和 y > 40 分开统计,验证"逆足边锋内切更威胁"的假设

进阶方向

  1. VAEP / xT 替代手写打分:更严谨地评估每次带球的价值增量。
  2. 防守反应分析:内切后防守方是否收缩中路(用防守事件坐标变化衡量)。
  3. 机器学习建模:以"内切后是否形成射门"为目标做逻辑回归,特征含起终点、防守人数、比赛时段。
  4. 对手针对性:按对手分组,看哪支球队最怕内切(内切后失球/被射最多)。

需要的话我可以把 xT 网格接入、VAEP 打分或单场比赛完整可运行脚本补全给你,你手头有具体数据源(StatsBomb / Wyscout / 自采)吗?

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