本文目录导读:

量化“主力缺阵损失值”本质上是把“某球员不打”这件事,翻译成对球队某项可观测指标的影响,实操中不可能精确到“缺阵=损失X分”这种单一数字,但可以用一套分层、可落地的脚本化方法逼近,下面给出一套完整框架和可直接改用的脚本思路。
先定义“损失值”的度量口径
主力缺阵的损失可以从四个层面量化,脚本应分别输出,不要混成一个数:
| 口径 | 含义 | 数据需求 |
|---|---|---|
| 净胜分影响 | 该球员在场/不在场时球队每百回合净胜分差 | 阵容/换人数据(on-off) |
| 胜率影响 | 缺阵场次 vs 出战场次的胜率差,控制对手强度 | 赛程+伤病名单 |
| 替代者落差 | 替补顶替后该位置产出下降幅度 | 球员逐场数据 |
| 市场/赔率隐含值 | 博彩盘口在缺阵消息前后的移动 | 盘口历史 |
最常用、最可复现的是前两个,下面以篮球为例(足球同理,把回合换成每90分钟)。
核心量化方法
方法1:On/Off 净效率差(最直接)
对每个球员计算:
OnOff = 球队百回合净胜分(球员在场) − 球队百回合净胜分(球员不在场)
脚本要点:
- 用阵容组合数据(lineup data)而非简单加减,避免同时在场球员混淆。
- 加正则化/收缩:样本小的组合向球队均值收缩(贝叶斯收缩),否则替补打垃圾时间会虚高。
- 输出置信区间,样本<200回合的标记为“不可靠”。
方法2:RAPM 类回归(控制队友和对手)
On/Off 会被“和谁一起打”污染,用岭回归:
净胜分 ~ Σ(球员i是否在场) + 对手强度 + 主客场 + 垃圾时间标记
得到每个球员的“调整后在场贡献值”,缺阵损失 ≈ 该球员RAPM × 预计缺阵回合数,这是业界(NBA、足球的+/-模型)标准做法,脚本可用 sklearn.Ridge 实现。
方法3:缺阵场次的匹配/双重差分(DID)
把“该球员缺阵的比赛”与“同赛季他出战的比赛”做对比,控制对手强度、主客场、背靠背:
损失 = E[净胜分 | 缺阵, 控制变量] − E[净胜分 | 出战, 控制变量]
脚本用固定效应回归:净胜分 ~ 缺阵虚拟变量 + 对手强度 + 主客场 + 背靠背 + 球队固定效应,这是因果味最浓、最容易向教练/管理层解释的口径。
方法4:替补替代落差(位置级)
损失 = 主力该位置场均产出 − 替补顶替期间该位置场均产出
产出用统一单位(如篮球用PER/每36分钟得分+助攻-失误;足球用xA+xG/90),适合快速估算、数据不全时兜底。
脚本化实现(Python 伪代码骨架)
import pandas as pd, numpy as np
from sklearn.linear_model import Ridge
# ---------- 输入 ----------
# games: 每场 球队、对手、主客、日期、净胜分、pace
# stints: 每个换人区间 球队、在场5人、回合数、净胜分
# injuries: 球员、缺阵场次列表
def on_off(stints, player, team_mean):
on = stints[stints.players.str.contains(player)]
off = stints[~stints.players.str.contains(player)]
def rate(df): # 百回合净胜分
return df.net_points.sum() / df.poss.sum() * 100
raw = rate(on) - rate(off)
# 贝叶斯收缩:样本越小越靠近0
w = on.poss.sum() / (on.poss.sum() + 300)
return raw * w, on.poss.sum()
def rapm(stints):
# 构造 球员-区间 的0/1矩阵
X = pd.get_dummies(stints.players.explode()).groupby(level=0).sum()
y = stints.net_points / stints.poss * 100
model = Ridge(alpha=1.0).fit(X, y, sample_weight=stints.poss)
return pd.Series(model.coef_, index=X.columns)
def did_loss(games, injuries, player, team):
g = games[games.team == team].copy()
g["absent"] = g.game_id.isin(injuries[player])
# 固定效应回归
import statsmodels.formula.api as smf
m = smf.ols("net_rating ~ absent + opp_strength + home + b2b + C(season)",
data=g).fit(cov_type="HC1")
return m.params["absent"], m.conf_int().loc["absent"]
# ---------- 汇总 ----------
def loss_report(player, team, stints, games, injuries, expected_games_out):
oo, poss = on_off(stints, player, None)
coef, ci = did_loss(games, injuries, player, team)
rapm_val = rapm(stints).get(player, np.nan)
return {
"on_off_per100": round(oo, 2),
"on_off_sample_poss": int(poss),
"did_net_rating_loss": round(coef, 2),
"did_ci": [round(ci[0],2), round(ci[1],2)],
"rapm_per100": round(rapm_val, 2),
"expected_loss_points": round(rapm_val/100 * poss_per_game * expected_games_out, 1),
}
关键工程细节:
- 垃圾时间剔除:分差>20且剩余<5分钟的比赛区间剔除,否则替补On/Off严重失真。
- 收缩估计:样本不足时向0收缩,脚本里用权重实现。
- 置信区间必输出:缺阵损失是估计值,给区间比给单点更负责。
- 位置对齐:替代者落差要按位置/角色匹配,不能拿中锋替后卫。
把损失值转成“可沟通数字”
管理层要的是一个能进决策的數,建议输出三档:
- 每百回合净胜分损失(战术口径)
- 折合赛季胜场损失 = 每百回合损失 × 预计缺阵回合 / 每胜所需净胜分
- 盘口隐含损失 = 缺阵消息前后让分盘移动(市场口径,交叉验证)
三档互相印证:若On/Off说损失4分/百回合,但盘口只动1.5分,说明市场认为替补深度好或样本有偏,需要复核。
常见坑
- 共线性:主力常和另一主力同场,On/Off会互相“抢贡献”,必须用RAPM或阵容组合。
- 对手强度未控制:缺阵恰好赶上强队赛程,会高估损失。
- 小样本:缺阵3场就下结论不可靠,务必给区间并标注样本量。
- 角色变化:主力缺阵后战术体系改变,替补不是简单“顶替”,DID比替代落差更稳。
- 足球特殊点:用每90分钟xG/xA差、对手ELO控制;门将缺阵损失要用扑救超出预期(PSxG)单独建模。
一句话总结:用“阵容区间On/Off + RAPM回归 + 缺阵DID”三件套交叉估计,输出带置信区间的每百回合净胜分损失,再折成胜场和盘口移动做校验——这就是可脚本化、可复现、能进决策的主力缺阵损失量化流程,需要的话我可以把上面骨架补成可直接跑的完整脚本(含数据清洗和垃圾时间过滤)。