本文目录导读:

- 第一步:构建/模拟数据
- 第二步:核心计算(按比赛聚合xGA)
- 第三步:业务解读(关键指标计算)
- 第四步:可视化解读(让数据说话)
- 第五步:高级解读——如何“读懂”这个数字?
- 最终输出:一个自动化报告函数(实战必备)
- 核心思维导图
在足球数据分析中,解读后防线的 xG Against (xGA) 是一个核心技能,它衡量的是对手在面对该球队时,创造出的射门机会的预期进球总值。
xGA 越高,说明对手获得的绝佳机会越多,该队后防线承受的压力越大(或防守质量越差);xGA 越低,说明后防线限制对手的能力越强。
下面我通过一个Python实战案例,从数据清洗、计算、可视化和业务解读四个维度来拆解。
第一步:构建/模拟数据
假设我们有某支球队(曼城”)在5场比赛中的对手射门数据,每条数据代表一次射门。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 模拟数据:5场比赛,对手的射门记录
np.random.seed(42)
data = {
'match_id': [1,1,1,1, 2,2,2, 3,3,3,3,3, 4,4, 5,5,5,5,5,5],
'shot_x': np.random.uniform(75, 100, 20).round(1), # 射门位置的X坐标(进攻方向100为底线)
'shot_y': np.random.uniform(20, 80, 20).round(1), # 射门位置的Y坐标
'body_part': np.random.choice(['head', 'left_foot', 'right_foot'], 20),
'situation': np.random.choice(['open_play', 'corner', 'free_kick', 'penalty'], 20),
'xg': np.random.uniform(0.01, 0.8, 20).round(3)
}
df_shots = pd.DataFrame(data)
print(df_shots.head(5))
第二步:核心计算(按比赛聚合xGA)
计算每场比赛的 xGA(对手xG总和),并加入比赛结果辅助解读。
# 按比赛聚合xGA
xga_per_match = df_shots.groupby('match_id').agg(
xga=('xg', 'sum'),
shots_against=('xg', 'count') # 射门次数
).reset_index()
# 模拟比赛结果(0=平/负,1=胜)
xga_per_match['result'] = [1, 0, 1, 1, 0] # 随便编的
print(xga_per_match)
输出示例:
| match_id | xga | shots_against | result |
|---|---|---|---|
| 1 | 245 | 4 | 1 |
| 2 | 102 | 3 | 0 |
| 3 | 523 | 5 | 1 |
第三步:业务解读(关键指标计算)
这一部分是最重要的,用来做深度分析:
平均每场xGA(防守强度)
avg_xga = xga_per_match['xga'].mean()
print(f"平均每场xGA: {avg_xga:.3f}")
战胜强队 vs 战败弱队(看防守是否“看人下菜”)
# 强强对话(对手强) vs 弱队(对手弱)
# 假设 match_id=1,3 是对付强队,其余是弱队
xga_per_match['opponent_strength'] = ['strong', 'weak', 'strong', 'weak', 'weak']
# 分组计算
grouped_xga = xga_per_match.groupby('opponent_strength')['xga'].agg(['mean', 'std'])
print(grouped_xga)
射门分布特征(防守风险点)
分析对手射门的位置分布,看看后防线是否经常被对手打 “致命区域”(如小禁区/Six-yard box)。
# 添加“射门位置质量”标签
df_shots['zone'] = pd.cut(df_shots['shot_x'], bins=[0, 80, 90, 100],
labels=['远距离', '禁区外-禁区边缘', '禁区内'])
zone_analysis = df_shots.groupby('zone', observed=True).agg(
total_xg=('xg', 'sum'),
avg_xg=('xg', 'mean'),
shot_count=('xg', 'count')
).round(3)
print(zone_analysis)
第四步:可视化解读(让数据说话)
用图表分析xGA的时间分布或对手射门热力图。
# 画每场比赛的xGA柱状图,并用颜色标注胜负
plt.figure(figsize=(10,5))
colors = ['green' if r == 1 else 'red' for r in xga_per_match['result']]
plt.bar(xga_per_match['match_id'], xga_per_match['xga'], color=colors, alpha=0.7)
plt.axhline(y=avg_xga, color='black', linestyle='--', label=f'Avg xGA: {avg_xga:.2f}')
plt.xlabel('Match ID')
plt.ylabel('xG Against')'Expected Goals Against (xGA) per Match')
plt.legend()
plt.show()
第五步:高级解读——如何“读懂”这个数字?
下面是一个决策树式的解读逻辑,你可以直接用这套逻辑来输出分析结论:
场景A:如果某一轮比赛 xGA 极高(> 2.5)
- 后防线被打穿了。
- 深挖:查看
df_shots中是否有大量shot_x > 90(小禁区)的射门?如果是,说明中后卫盯人不紧,禁区前沿缺少保护;如果多为远射(shot_x < 85),说明是“放远射防传球”的策略,xG虚高但实际威胁一般,属于“战术性放投”。
场景B:xGA 低,但输了比赛
- 进攻端的问题更大(预期进球高但没进),防守没问题。
- 深挖:查看本队进球数和xG对比,别错怪后卫线。
场景C:xGA 稳定在 0.8-1.2(优秀区间)
- 后防线是“体系防”而非“个人能力防”。
- 深挖:查看对手射门多发生在禁区外(
shot_x < 85),说明高位防线策略有效,把对手压制在远离球门区域。
最终输出:一个自动化报告函数(实战必备)
你可以写一个函数,一键生成报告:
def defensive_report(df_shots):
report = {}
# 计算xGA
report['Total_xGA'] = df_shots['xg'].sum()
report['Avg_xGA_Per_Game'] = df_shots['xg'].sum() / df_shots['match_id'].nunique()
report['High_Chance_Allowed'] = df_shots[df_shots['xg'] > 0.35]['xg'].count()
report['Penalty_Conceded'] = df_shots[df_shots['situation'] == 'penalty'].shape[0]
# 解读文本
if report['Avg_xGA_Per_Game'] < 1.0:
report['Verdict'] = "防线稳固,限制对手创造高质量机会。"
elif report['Avg_xGA_Per_Game'] < 1.8:
report['Verdict'] = "防线中等,有一定漏洞,但整体可控。"
else:
report['Verdict'] = "防线告急!对手每场都能拿到明显得分机会。"
return report
# 使用
report = defensive_report(df_shots)
print(report)
核心思维导图
- xGA ≠ 丢球数(没有考虑门将扑救和运气,它是“防线+门将”前的防守质量)
- 低xGA + 高丢球 = 门将状态差(不是防线问题)
- 高xGA + 低丢球 = 门将神勇,但防线隐患巨大,迟早崩盘
- 高xGA + 高丢球 = 后防线彻底崩盘,需要立即调整
Python的作用:自动化聚类(如识别对手射门密集区)、时序分析(xGA随比赛走势的变化)、以及将多维数据(位置、角度、射门距离)压缩成单一的战术评分。
你可以带着这份理解,去加载真实的StatsBomb或Understat数据集(通常有xG字段),跑一遍上面的代码,就能对某支球队的后防线水平有直观、量化的认识了,需要我提供真实数据集的获取方式或更复杂的模型(如射门坐标系热力图)吗?