后防线 xG Against 解读:Python 实战案例
xG Against 是什么
xG Against(xGA) 是对手在你方球门前创造的预期进球总和,用来量化"你的防线让对手获得了多少高质量机会"。

- xGA 越低 → 防线越稳固(不是靠门将神扑)
- xGA 高但失球少 → 门将超神 or 运气好,不可持续
- xGA 低但失球多 → 门将拖后腿 or 运气差
它比"失球数"更能反映防线的真实防守质量,因为它剥离了门将扑救和运气的干扰。
数据准备(用 understat 或 FBref)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from understat import Understat
import asyncio
# 方式1:直接从 FBref 抓取(以英超为例)
def get_team_defense_data(url):
tables = pd.read_html(url)
# FBref 的 squad defensive 表通常是第 2 个
df = tables[1]
df.columns = df.columns.droplevel(0) # 处理多层表头
return df
url = "https://fbref.com/en/comps/9/defense/2023-2024-Premier-League-Stats"
df = get_team_defense_data(url)
# 关注字段
cols = ['Squad', 'MP', 'Goals Against', 'xGA', 'xGA/90', 'PSxG', 'PSxG-GA']
df = df[cols].dropna()
df['GA-xGA'] = df['Goals Against'] - df['xGA'] # 关键差值
print(df.sort_values('xGA').head(10))
核心解读维度
单队 xGA 排名(基础层)
df_sorted = df.sort_values('xGA/90')
fig, ax = plt.subplots(figsize=(10, 10))
colors = ['#2ecc71' if x < df['xGA/90'].mean() else '#e74c3c' for x in df_sorted['xGA/90']]
ax.barh(df_sorted['Squad'], df_sorted['xGA/90'], color=colors)
ax.axvline(df['xGA/90'].mean(), color='black', linestyle='--', label='联赛平均')
ax.set_xlabel('xGA per 90 (越低越好)')
ax.set_title('各队防线 xGA/90 排名')
plt.legend()
plt.tight_layout()
plt.show()
解读:
- 绿色 = 防线强于联赛平均
- 红色 = 防线弱于联赛平均
- 与积分榜对比能看出"防线是否是球队成绩驱动因素"
GA vs xGA 差值分析(门将层)
fig, ax = plt.subplots(figsize=(8, 8))
ax.scatter(df['xGA'], df['Goals Against'], s=80, alpha=0.7)
# 画 y=x 参考线
lim = max(df['xGA'].max(), df['Goals Against'].max()) + 5
ax.plot([0, lim], [0, lim], 'k--', alpha=0.5, label='实际=预期')
for _, row in df.iterrows():
ax.annotate(row['Squad'], (row['xGA'], row['Goals Against']),
fontsize=7, alpha=0.7)
ax.set_xlabel('xGA(对手预期进球)')
ax.set_ylabel('GA(实际失球)')
ax.set_title('防线 xGA vs 实际失球')
ax.legend()
plt.tight_layout()
plt.show()
解读图:
- 点在参考线下方 → 门将/运气省了球(PSxG-GA 为正,好门将)
- 点在参考线上方 → 门将拖后腿(如 23/24 的 Onana 早期)
- 点离参考线远 → 结果有较大随机性 or 门将影响大
区分"防线质量" vs "门将扑救"
# FBref 中的关键指标
# PSxG (Post-Shot xG) = 射正球后的预期进球(已排除封堵)
# PSxG-GA > 0 表示门将扑救表现好于预期
df['goalkeeping_impact'] = df['PSxG'] - df['Goals Against']
df['defense_quality'] = df['xGA'] - df['PSxG'] # 越小说明防线把对手射门质量压制得越低
fig, ax = plt.subplots(figsize=(8, 8))
ax.scatter(df['defense_quality'], df['goalkeeping_impact'],
s=80, c=df['GA-xGA'], cmap='RdYlGn_r', edgecolor='k')
for _, row in df.iterrows():
ax.annotate(row['Squad'], (row['defense_quality'], row['goalkeeping_impact']),
fontsize=7)
ax.axvline(0, color='grey', lw=0.8)
ax.axhline(0, color='grey', lw=0.8)
ax.set_xlabel('防线质量(越右越差:让对手射门质量高)')
ax.set_ylabel('门将贡献(越上越好:扑救超预期)')
ax.set_title('防线 vs 门将:功劳归属')
plt.colorbar(ax.collections[0], label='GA - xGA')
plt.tight_layout()
plt.show()
四象限解读:
| 门将好 | 门将差 | |
|---|---|---|
| 防线差 | 靠门将硬撑 | 灾难区(丢球多) |
| 防线好 | 顶级防守 | 门将拖后腿 |
时间序列:防守趋势
# 用 understat 抓逐场数据
async def get_match_xga(team):
async with Understat('epl', '2023') as u:
return await u.get_team_results(team)
matches = asyncio.run(get_match_xga('Arsenal'))
df_m = pd.DataFrame(matches)
df_m['datetime'] = pd.to_datetime(df_m['datetime'])
df_m = df_m.sort_values('datetime')
# 计算滚动 5 场 xGA 均值
df_m['xGA_rolling'] = df_m['xGA'].rolling(5).mean()
plt.figure(figsize=(12, 5))
plt.plot(df_m['datetime'], df_m['xGA_rolling'], marker='o')
plt.axhline(df_m['xGA_rolling'].mean(), color='red', linestyle='--')'阿森纳赛季防线 xGA 滚动趋势(5场)')
plt.ylabel('xGA(滚动均值)')
plt.tight_layout()
plt.show()
解读:
- 曲线上升 → 防线恶化(伤病?战术调整?)
- 曲线下降 → 防线收紧(新援融入?阵型变化?)
进阶解读角度
对手强度调整
# 按对手强队/弱队分组
strong_teams = ['Man City', 'Arsenal', 'Liverpool', 'Aston Villa']
df['opponent_tier'] = df['Squad'].apply(
lambda x: 'Strong' if x in strong_teams else 'Weak')
# 但 xGA 是累计的,需要拆逐场数据来算
真正的解读:把 xGA 按对手强度拆分——对强队时 xGA 表现如何,比总 xGA 更能反映防线的"抗压能力"。
分阶段(定位球 vs 运动战)
FBref 提供:
xGA - Set Pieces(定位球)xGA - Open Play(运动战)xGA - Penalty KicksxGA - Corners/Free Kicks
set_piece_xga = df[['Squad', 'xGA', 'Set Piece xGA']].copy() set_piece_xga['sp_ratio'] = set_piece_xga['Set Piece xGA'] / set_piece_xga['xGA'] # 定位球 xGA 占比高的球队 → 防空/盯人训练不足
与 PPDA / 高位逼抢结合
# PPDA 越低 = 逼抢越凶 = 允许对手传球越少 # 高 PPDA + 低 xGA = 典型"低位防守"(如穆里尼奥风格) # 低 PPDA + 低 xGA = 典型"高位逼抢"(如克洛普风格)
报告模板(三段式解读)
对一支球队的防线 xGA 解读:
总体水平:本赛季 xGA/90 = 1.15,联赛第 4 低,防线处于前列。
与失球对比:GA = 42,xGA = 41.3,GA-xGA ≈ +0.7,说明门将表现基本符合预期,防线成果真实。
分解:定位球 xGA 占比 28% 偏高,运动战 xGA 联赛前 3 低,说明开放比赛防守优秀,但定位球是短板,可针对训练防空。
常见误区
| 误区 | 正确理解 |
|---|---|
| xGA 低 = 失球一定少 | ❌ 门将差/运气差会导致 GA 高 |
| 看单场 xGA 就下结论 | ❌ 有随机性,至少 5-10 场滚动 |
| 忽略对手强度 | ❌ 对弱队刷低 xGA 没说服力 |
| 忽视定位球 | ❌ FBref 分类数据要看 |
推荐工具组合
- 数据源:FBref(免费、分类细)、Understat(逐场 xG)、StatsBomb(付费、最细)
- Python 库:
pandas+matplotlib/plotly+mplsoccer(画图) - 可视化:雷达图对比多队、散点图看相关、热力图看分时段
如果你有具体某队/某赛季的数据,我可以帮你写一段针对性解读代码。