7.4 构造示例①:期望进球 xG 的简化模型思路
构造示例这一篇讲如何从零构造一个 xG 模型。为了让思路清晰,我们采用教学版简化模型。
一、xG 的本质:把射门转成概率
每一次射门,都可以看作一次「成功概率未知的伯努利试验」。xG 就是这次射门转化为进球的概率估计。
所以问题变成:如何用已知信息估计这个概率?
二、三个可用信号
| 信号 | 直觉 | 可用性 |
|---|---|---|
| 射门距离 | 越近越容易进 | 需要坐标数据;若无,可用区域分档替代 |
| 射门方式 | 点球 > 单刀 > 头球 > 远射 | 通常可得 |
| 比赛情境 | 快攻机会质量高于阵地战 | 可从事件流推断 |
三、教学版:用「射门区域分档」构造 xG
如果没有精确坐标,可以把射门区域粗分为 5 档,用历史数据统计每一档的进球率。
第一步:定义区域与初始估计
| 区域 | 说明 | 经验进球率区间 |
|---|---|---|
| 点球点附近 | 小禁区及点球点周围 | 30% ~ 40% |
| 禁区内(近) | 大禁区内部靠近球门 | 15% ~ 25% |
| 禁区内(远) | 大禁区边缘 | 6% ~ 12% |
| 禁区外近距 | 弧顶一带 | 3% ~ 6% |
| 禁区外远距 | 30 米外 | 1% ~ 3% |
第二步:用历史数据标定
import pandas as pd
# 假设 shots 是一份历史射门记录,含 region 与 is_goal 两列
# shots = pd.DataFrame({'region': [...], 'is_goal': [...]})
def calibrate(shots):
"""用历史样本标定每个区域的真实进球率"""
result = shots.groupby('region')['is_goal'].agg(['sum', 'count'])
result['rate'] = result['sum'] / result['count']
# 样本量太小的区域需要收缩(见下方说明)
return result
print(calibrate(shots))
第三步:处理小样本问题(关键技巧)
如果某个区域只有 12 次射门、2 个进球,直接算出的 16.7% 极不可靠。必须做「收缩估计(Shrinkage)」:
def shrunk_rate(goals, shots, prior_rate, prior_weight=200):
"""收缩估计:样本少时向先验靠拢,样本多时靠近真实值
goals / shots : 该区域的实际进球数与射门数
prior_rate : 先验进球率(可用全局平均或经验值)
prior_weight : 先验的等效样本量,越大越保守
"""
return (goals + prior_rate * prior_weight) / (shots + prior_weight)
# 示例:该区域 12 脚射门 2 个进球,先验 8%,先验权重 200
print(shrunk_rate(2, 12, 0.08)) # 输出约为 0.083,被拉向先验
为什么收缩估计是关键技术?
这是把「足球数据样本小」这个现实问题处理好的核心手法。
不做收缩,模型会对小样本区域的极端值过度反应;做了收缩,输出会稳定得多。
几乎所有成熟的体育数据模型都会用到类似的平滑技术。
第四步:合成单场 xG
def team_xg(shots, rate_table):
"""把一支球队的所有射门换算为期望进球并求和"""
total = 0.0
for region in shots:
total += rate_table.get(region, 0.02) # 未知区域用保守默认值
return total
四、如何做得更接近专业水准
| 升级方向 | 做法 | 效果 |
|---|---|---|
| 增加特征维度 | 加入射门方式、是否快攻、防守压力 | 显著提升 |
| 改用逻辑回归 | 把特征映射为 0~1 的概率,而不是分档查表 | 显著提升 |
| 加入防守方信息 | 考虑防守球员位置与门将站位 | 中等提升 |
| 按联赛分别标定 | 不同联赛的防守强度不同 | 中等提升 |
逻辑回归的接口
from sklearn.linear_model import LogisticRegression
# X: 每次射门的特征矩阵(距离、角度、射门方式等)
# y: 是否进球(0/1)
model = LogisticRegression(max_iter=1000, C=1.0)
model.fit(X_train, y_train)
# 预测每脚射门的进球概率,即 xG
xg_values = model.predict_proba(X_test)[:, 1]
五、两个必须避免的错误
错误一:数据穿越(Look-ahead Bias)
如果你用「整赛季」的数据标定进球率,再回头计算「该赛季第 3 轮」的 xG,就发生了数据穿越——你用了未来信息。
正确做法:标定必须只用「当前时点之前」的数据,或者用「上赛季」的标定表。
错误二:不同口径的 xG 混用
不同模型的 xG 不可直接比较。你的模型算出的 1.8,和别人报告里的 1.8 含义不同。全流程必须用同一套。
六、教学示例与生产数据的差异说明
再次提醒(见 7.1 边界声明):
本教学示例的输出数值,与球小策接口返回的 xG 不会一致。
原因是特征集合、分箱方式、标定周期、收缩参数都有差异。这是有意设计,请勿做逐位比对。
你应当做的是:用你自己的数据,构造适合你自己用途的 xG。
下一步该看什么
- 把单场值变成趋势 → 7.5 滚动窗口构造攻防强度实时指数
- 如何验证你的模型 → 7.9 验证指标有效性
足球赛事前瞻 | AI 智能分析 | 足球数据解读 - 球小策