解读后防线的 xG Against(预期失球)
在足球数据分析中,xG Against(xGA) 是评估后防线质量的核心指标之一,下面从概念、数据获取、案例代码到解读方法完整讲一遍。

概念先行
| 指标 | 含义 | 解读方向 |
|---|---|---|
| xG | 预期进球,衡量射门质量 | 越高代表机会越好 |
| xGA (xG Against) | 对手在你球门前创造的预期进球总和 | 越低越好,反映防守质量 |
| xGA/90 | 每90分钟失球预期 | 归一化对比 |
| PSxG | 射门后预期进球(考虑门将) | 区分门将与后防贡献 |
| Goals - xGA | 实际失球 - 预期失球 | 正=运气差/门将差,负=运气好/门将神勇 |
关键要点:xGA 衡量的是机会质量,不是结果,一支球队可能丢球少但 xGA 高(门将救险多),也可能丢球多但 xGA 低(运气差)。
Python 数据获取(以 statsbomb / understat 为例)
方案 A:用 soccerdata 抓 Understat 数据
# pip install soccerdata import soccerdata as sd import pandas as pd # 抓取英超某赛季球队数据 understat = sd.Understat(leagues="ENG-Premier League", seasons=2023) team_stats = understat.read_team_season_stats(stat_type="defense") print(team_stats.head()) # 常见列: xGA, goals_conceded, clean_sheet, ppda ...
方案 B:StatsBomb 开放数据自己算 xGA
from statsbombpy import sb
events = sb.events(match_id=3788741) # 某场比赛
# 提取对手射门(假设主队是分析对象)
shots = events[events['type'] == 'Shot'].copy()
shots['shot_statsbomb_xg'] = shots['shot_statsbomb_xg'].fillna(0)
xga = shots.groupby('team')['shot_statsbomb_xg'].sum()
print(xga) # 每队被射门的 xG 总和 = 该队防线的 xGA
注意:StatsBomb 的
shot_statsbomb_xg是射门方视角的 xG,被射门方视角就是 xGA。
后防线 xGA 案例解读(完整流程)
赛季对比:找出防守最好/最差球队
import matplotlib.pyplot as plt
# team_stats 含列: xGA, goals_conceded
df = team_stats.reset_index()
df['xGA_per90'] = df['xGA'] / df['matches']
df['goals_minus_xGA'] = df['goals_conceded'] - df['xGA'] # 门将/运气修正
df_sorted = df.sort_values('xGA_per90')
print(df_sorted[['team', 'xGA_per90', 'goals_minus_xGA']].head(10))
解读示例:
- xGA/90 低 → 后防线有效限制了对手高质量机会(结构、压迫、站位好)。
- goals_minus_xGA 为负(失球 < xGA) → 门将扑救超常 或 对手射术差 或 运气好。
- goals_minus_xGA 为正(失球 > xGA) → 门将拉胯 / 运气差,但后防线本身未必差。
区分"后防问题"还是"门将问题"
# 用 PSxG 分离门将贡献(需 FBref 数据) # PSxG - Goals Conceded = 门将扑救价值 df['gk_contribution'] = df['psxg'] - df['goals_conceded'] # 后防真实质量 ≈ xGA - 门将超出预期部分 df['defensive_quality'] = df['xGA'] - df['gk_contribution'] * 0.0 # 概念示意
核心逻辑:
实际失球 = 后防允许的机会(xGA) - 门将扑救价值(PSxG - GA)
- xGA 高 + PSxG-GA 高 → 门将神勇掩盖后防漏洞(隐患)。
- xGA 低 + PSxG-GA 低 → 后防扎实,门将正常。
- xGA 高 + PSxG-GA 低 → 后防真差,门将也救不了。
逐场分布看稳定性
# 假设有逐场数据 match_df: [date, team, xGA] import seaborn as sns pivot = match_df.pivot_table(index='date', columns='team', values='xGA') sns.boxplot(data=match_df, x='team', y='xGA') plt.xticks(rotation=90)'各队逐场 xGA 分布')
解读:
- 中位数低、箱体窄 → 防守稳定且优秀。
- 中位数低但箱体大 → 平均值漂亮但偶有崩盘(如被打反击)。
- 长尾上须长 → 存在被大比分打穿的场次,需查具体对手/战术。
结合对手强度(xGA 需上下文)
# 对阵强队 xGA 高是合理的,用对手平均 xG 做校正 df['xGA_vs_expected'] = df['xGA'] - df['opp_avg_xg'] * df['matches']
一支球队对阵曼城 xGA=2.0 不算差,对阵保级队 xGA=1.5 才算差。必须做对手强度校正。
解读时的常见陷阱
- xGA 低 ≠ 防守好:可能是低位大巴 + 门将超神,长期不可持续。
- 样本量:赛季初 5 场 xGA 波动大,至少 10 场以上再下结论。
- xG 模型差异:不同数据商(StatsBomb / Opta / Understat)xG 模型不同,横向对比需同源。
- 定位球未分离:很多平台的 xGA 含定位球,评估"运动战后防"时要拆开
set_piece_xga。 - 红牌/少打一人场次会污染数据,分析前过滤。
一句话总结解读框架
先看 xGA/90 定位防守基线 → 用 Goals−xGA 判断门将/运气 → 用逐场分布看稳定性 → 用对手强度校正 → 拆分定位球找具体短板。
这样一套 Python 流程下来,后防线的 xGA 就不只是一个数字,而是能讲出"是结构问题、门将问题还是运气问题"的完整诊断,需要我针对某支具体球队(比如某赛季阿森纳/曼城)跑一遍完整案例吗?