实用脚本如何结合xGA评估防守表现?

wen 实用脚本 1

本文目录导读:

实用脚本如何结合xGA评估防守表现?

  1. 核心维度拆解
  2. 实用脚本设计(Python示例)
  3. 进阶:可视化雷达图脚本
  4. 核心分析逻辑总结
  5. 实用脚本的适用场景

这是一个非常专业且务实的问题,在足球分析领域,xGA(预期失球数) 本身是衡量“防守结果”的指标,但要评估真正的“防守表现”,我们需要将它与对手的射门质量、防守行为以及门将扑救结合起来。

单纯看xGA高低会有误导性(对手狂轰滥炸但xGA值很高,说明防守体系被击穿;对手传中满天飞但xGA很低,说明防守限制做得好)。

以下是一个结合实用脚本(Python/Pandas)的评估防守表现的逻辑框架和代码思路。

核心维度拆解

要评估防守表现,我们不能只看一个xGA数,需要拆解为以下四个核心维度:

  1. 限制对手射门数量(Volume):对手射门总数(npxG,即排除点球的射门)。
  2. 限制对手射门质量(Quality):对手平均每次射门的xG值(xG per Shot),这反映了防守是否让对手在低威胁区域起脚。
  3. 高位压迫与拦截(Disruption):PPDA(每次防守行动允许对手传球次数)、抢断数、拦截数,这反映了防守的主动性。
  4. 门将的表现(Goalkeeping):xGA与失球数(GA)的差值(PSxG-GA,即门将扑救预期与失球差),这用来剔除门将超神/超鬼对防守数据的污染。

实用脚本设计(Python示例)

假设我们有一份比赛事件数据(如StatsBomb或Wyscout格式),我们编写一个脚本来综合评估。

第一步:数据预处理与基础指标计算

import pandas as pd
import numpy as np
# 假设 df 是包含所有射门事件的DataFrame
# 字段:team(进攻方)、opponent(防守方)、shot_type(射门类型)、xG(该次射门的xG)、
#       location(位置)、body_part(身体部位)、assist_type(助攻类型)
#       home_team, away_team, season
# --- 1. 计算防守方的 xGA(即进攻方的总xG) ---
def calculate_xga(df):
    # 筛选所有射门事件(排除点球)
    shots = df[df['shot_type'] != 'Penalty'].copy()
    # 按比赛和防守方聚合
    xga_data = shots.groupby(['match_id', 'opponent']).agg(
        opponent_shots=('xG', 'count'),      # 对手射门数
        xga_total=('xG', 'sum')              # 总xGA
    ).reset_index()
    # 计算每次射门的平均质量 (xG per Shot)
    xga_data['xG_per_shot'] = xga_data['xga_total'] / xga_data['opponent_shots']
    return xga_data
# --- 2. 限制性防守指标 (PPDA - 每次防守行动允许传球数) ---
# 需要传球数据和防守动作数据
def calculate_ppda(passes, defensive_actions):
    # 防守动作:抢断、拦截、犯规
    actions = defensive_actions[defensive_actions['type'].isin(['Tackle', 'Interception', 'Foul'])].copy()
    # 只统计对方后场2/3区域的防守动作(高位压迫指标)
    # 假设球场的x坐标是0-100,这里仅算示例
    actions['high_press'] = np.where(actions['x'] > 60, 1, 0)  # 示例阈值
    # 计算每个队的PPDA
    ppda_data = passes.groupby(['match_id', 'team']).size().reset_index(name='allowed_passes')
    action_data = actions.groupby(['match_id', 'team']).size().reset_index(name='def_actions')
    # 合并
    ppda_df = ppda_data.merge(action_data, left_on=['match_id', 'team'], right_on=['match_id', 'team'])
    ppda_df['PPDA'] = ppda_df['allowed_passes'] / ppda_df['def_actions']
    return ppda_df

第二步:引入门将表现(PSxG - 射正后预期失球)

这一步非常关键,我们要把门将的扑救从防守体系中剥离出来。

# --- 3. 门将表现评估 (PSxG vs GA) ---
import json
def calculate_gk_performance(events):
    """
    计算门将数据。
    PSxG 是射正后的预期进球,GA 是实际失球。
    PSxG - GA 为正值说明门将表现低于预期(丢球过多),负值说明门将表现超出预期。
    """
    # 筛选射正且导致进球或扑救的事件
    goal_events = events[(events['shot_outcome'] == 'Goal') & (events['shot_type'] == 'Penalty')]
    # 注意:我们需要更精细的数据,这里简化
    psxg_data = events[['match_id', 'team']].drop_duplicates()
    # 假设有一个 column: psxg 和 goals_conceded
    gk_stats = events.groupby(['match_id', 'team']).agg(
        psxg=('psxg', 'sum'),
        goals_conceded=('goals_conceded', 'sum') # 如果是防守方视角
    ).reset_index()
    gk_stats['gk_diff'] = gk_stats['psxg'] - gk_stats['goals_conceded']
    return gk_stats

第三步:综合评分模型(核心)

这个脚本的核心是将上述指标压缩成一个可视化的“防守强度评分”。

# --- 4. 防守表现综合评估函数 ---
def evaluate_defensive_performance(xga_df, ppda_df, gk_df, team_name):
    """
    结合xGA、射门限制、压迫强度、门将表现,输出防守画像。
    """
    team_xga = xga_df[xga_df['team'] == team_name]
    team_ppda = ppda_df[ppda_df['team'] == team_name]
    team_gk = gk_df[gk_df['team'] == team_name]
    # 计算加权得分(基础版)
    # 1. 控制对手射门质量得分(越低越好)
    # 这里假设所有值都是按赛季平均标准化
    league_avg_xg_per_shot = xga_df['xG_per_shot'].mean()
    league_std_xg_per_shot = xga_df['xG_per_shot'].std()
    # 转为Z-score(标准分数)
    z_xg_per_shot = (team_xga['xG_per_shot'].mean() - league_avg_xg_per_shot) / league_std_xg_per_shot
    # Z-score越低,防守越好(因为限制对手射门质量好)
    quality_score = -z_xg_per_shot  # 取负值,让“限制性好”变成正分
    # 2. 压迫指标得分(PPDA越低越好,代表逼抢越凶)
    league_avg_ppda = ppda_df['PPDA'].mean()
    league_std_ppda = ppda_df['PPDA'].std()
    z_ppda = (team_ppda['PPDA'].mean() - league_avg_ppda) / league_std_ppda
    pressure_score = -z_ppda
    # 3. 门将表现得分(gk_diff为负值代表门将发挥好,或者忽略门将,看防守体系本身)
    # 注意:评估防守体系时,通常用“镜前xGA”而不是“实际失球”,
    # 所以我们应该看“门将面对的期望失球”减去“门将实际丢球”。
    # 如果门将表现好(PSxG-GA为正),说明防守体系其实没那么好,但门将兜底了。
    # 为了评估防守表现,我们更关注“非门将因素”,即 xGA - PSxG。
    # 这其实是“防守体系让门将面临什么样质量的射门”。
    # 这里简化为输出防守体系xGA的原始值大小
    overall_xga_rating = team_xga['xga_total'].mean()
    # 输出结果
    print(f"球队: {team_name}")
    print(f"  🎯 对手射门质量 (xG/Shot): {team_xga['xG_per_shot'].mean():.3f} (联赛均值: {league_avg_xg_per_shot:.3f})")
    print(f"  🧤 压迫强度 (PPDA): {team_ppda['PPDA'].mean():.2f} (越低越激进)")
    print(f"  🥅 场均xGA: {overall_xga_rating:.2f}")
    print(f"  📊 综合防守评分: {quality_score + pressure_score:.2f} (正数表示优于联赛平均)")
    return {
        'team': team_name,
        'xGA_per_match': overall_xga_rating,
        'opp_shot_quality': team_xga['xG_per_shot'].mean(),
        'PPDA': team_ppda['PPDA'].mean(),
        'defensive_index': quality_score + pressure_score
    }

进阶:可视化雷达图脚本

这是比较直观的展示方式,方便快速对比。

import matplotlib.pyplot as plt
import math
def plot_defensive_radar(team_stats, league_averages):
    """
    team_stats: 目标球队的各项指标
    league_averages: 联赛平均值
    """
    categories = ['限制射门质量', '低位防守密度', '高位压迫强度', '人盯人执行力', '反抢效率']
    # 计算各项指标的相对水平 (0-1)
    # 注意:有些指标越小越好(如xG per Shot),有些越大越好(如拦截数)
    values = [
        min(team_stats['opp_shot_quality'] / league_averages['opp_shot_quality'], 1) * 100,  # 质量限制
        # 其他维度类似...
        min(team_stats['xGA_per_match'] / league_averages['xGA_per_match'], 1) * 100,
        # ...
    ]
    # 绘制雷达图
    angles = np.linspace(0, 2 * np.pi, len(categories), endpoint=False).tolist()
    values += values[:1]  # 闭合图形
    fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
    ax.fill(angles, values, color='blue', alpha=0.25)
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(categories)
    plt.title('防守表现雷达图')
    plt.show()

核心分析逻辑总结

在实际分析中,评估防守表现时,建议遵循以下数据链路

  1. 看总量(xGA):确认防守体系是否容易被射门。
  2. 看质量(xG per Shot):确认防守是让对手“远射碰运气”还是“单刀直入”。防守好的球队通常能把对手xG/Shot压在0.10以下(联赛均值一般在0.10-0.12)。
  3. 看压迫(PPDA):确认防守是主动出击还是被动挨打。
  4. 扣掉门将(PSxG):如果一支球队xGA很高,但丢球很少,这可能是门将神勇,而非防守出色,反之,如果xGA很低,但失球很多,说明门将拖累了防守数据。

实用脚本的适用场景

  • 球探报告:评估对手防守风格(是低位铁桶阵还是高位逼抢)。
  • 个人表现:评估“防线球员”而不包括门将的贡献。
  • 战术调整:通过对比xGA和PPDA,找出“高压被过”或者“低位防不住远射”的具体问题。

如果你有具体的数据格式(例如是Wyscout JSON还是StatsBomb JSON),我可以帮你写出更精确的清洗和聚合代码,你目前拿到的数据主要包含哪些字段?

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