用Python分析"替补奇兵"的战术价值
"替补奇兵"是体育分析(尤其是足球、篮球)中的经典话题——指替补上场后对比赛产生超预期影响的球员,下面给出一个可落地的Python分析框架,涵盖数据获取、指标构建、可视化与建模。

分析思路框架
数据层 → 指标层 → 模型层 → 可视化层 → 决策层
核心要回答三个问题:
- 谁是奇兵?(识别异常高贡献的替补)
- 奇兵何时上场最有效?(情境分析:比分、时间、对手)
- 奇兵的战术价值有多大?(对比赛结果的因果/相关贡献)
数据准备
数据源建议
| 类型 | 来源 |
|---|---|
| 足球事件数据 | StatsBomb Open Data、FBref、Understat |
| 篮球阵容数据 | NBA Stats API(nba_api)、PbpStats |
| 比赛结果/赔率 | Football-Data.co.uk、FiveThirtyEight |
关键字段(足球为例)
# 每名球员一行
columns = [
'match_id', 'player_id', 'player_name',
'is_starter', # 是否首发
'minute_in', # 上场时间
'minute_out', # 下场时间
'goals', 'assists', 'xg', 'xa', # 进攻贡献
'tackles', 'interceptions', # 防守贡献
'team_score_at_in', 'opp_score_at_in', # 上场时比分
'final_result' # 最终胜负
]
核心指标构建
单位时间贡献率(Per-90)
替补上场时间短,必须做时间归一化:
import pandas as pd
import numpy as np
def per90(df, col, minutes_col='minutes_played'):
return df[col] / df[minutes_col] * 90
df['goal_contrib_per90'] = per90(df, 'goals') + per90(df, 'assists')
df['xg_contrib_per90'] = per90(df, 'xg') + per90(df, 'xa')
"奇兵指数"(Impact Score)
综合进攻+防守+比赛情境的加权分:
def impact_score(row):
attack = 0.5*row['goal_contrib_per90'] + 0.3*row['xg_contrib_per90']
defense = 0.1*row['tackles_per90'] + 0.1*row['interceptions_per90']
# 逆境加成:落后时上场贡献权重更高
clutch = 1.3 if row['team_score_at_in'] < row['opp_score_at_in'] else 1.0
return (attack + defense) * clutch
df['impact_score'] = df.apply(impact_score, axis=1)
替补 vs 首发的分位对比
subs = df[df.is_starter == False]['impact_score']
starts = df[df.is_starter == True]['impact_score']
print("替补中位数:", subs.median())
print("首发中位数:", starts.median())
print("替补90分位:", subs.quantile(0.9)) # 奇兵候选线
奇兵定义:替补出场 + impact_score 位于该队/该联赛替补群体的 90 分位以上。
情境分析:什么时候派奇兵最有效?
按上场时段分组
bins = [0, 60, 70, 80, 90]
labels = ['60前', '60-70', '70-80', '80+']
df['sub_window'] = pd.cut(df['minute_in'], bins=bins, labels=labels)
window_effect = df[df.is_starter==False].groupby('sub_window').agg(
avg_impact=('impact_score','mean'),
win_rate=('final_result', lambda x: (x=='win').mean()),
n=('match_id','count')
)
print(window_effect)
按上场时比分状态分组
df['score_state'] = np.select(
[df.team_score_at_in > df.opp_score_at_in,
df.team_score_at_in == df.opp_score_at_in,
df.team_score_at_in < df.opp_score_at_in],
['领先','平局','落后']
)
state_effect = df[df.is_starter==False].groupby('score_state')['impact_score'].mean()
因果/相关性建模:奇兵是否真的改变结果?
方法1:倾向得分匹配(PSM)
控制"上场时比分、对手强度、剩余时间"等因素,比较有/无奇兵上场的胜率:
from sklearn.linear_model import LogisticRegression from sklearn.neighbors import NearestNeighbors # 处理变量: 是否派上奇兵(sub_has_star) # 协变量: score_state, minute_in, opponent_strength, home_away features = ['score_state_num','minute_in','opp_strength','is_home'] model = LogisticRegression().fit(X[features], y_treat) ps = model.predict_proba(X[features])[:,1] # 最近邻匹配后再比较 win_rate
方法2:比分状态固定下的回归
import statsmodels.formula.api as smf
# 只看落后时换上替补的比赛
tied = df[(df.is_starter==False) & (df.score_state=='落后')]
model = smf.logit('win ~ has_star_sub + minute_in + opp_strength', data=tied).fit()
print(model.summary())
has_star_sub 的系数即"奇兵"在落后情境下对胜率的边际贡献。
可视化(matplotlib / plotly)
import matplotlib.pyplot as plt
# 1. 上场时间 vs 影响值的散点,看"奇兵区"在哪
plt.figure(figsize=(9,6))
for grp, d in df.groupby('is_starter'):
plt.scatter(d['minute_in'], d['impact_score'],
alpha=0.4, label='首发' if grp else '替补')
plt.axhline(subs.quantile(0.9), ls='--', color='red', label='奇兵阈值')
plt.xlabel('上场分钟'); plt.ylabel('Impact Score')
plt.legend(); plt.title('替补奇兵分布图')
# 2. 上场时段 × 平均影响值的热力图
import seaborn as sns
pivot = df[df.is_starter==False].pivot_table(
index='sub_window', columns='score_state',
values='impact_score', aggfunc='mean')
sns.heatmap(pivot, annot=True, cmap='RdYlGn')
输出决策结论(示例)
分析结束后可产出如下洞察表:
| 情境 | 推荐奇兵类型 | 依据 |
|---|---|---|
| 70分钟后落后 | 高xG/xA前锋 | impact_score在落后时最高 |
| 60分钟平局 | 中场绞杀型 | 抢断/拦截per90显著高于均值 |
| 领先需控场 | 经验型 | 失误率低、传球成功率高 |
进阶方向
- 贝叶斯层次模型:用
PyMC估计每名球员的"替补超常发挥"后验分布,避免小样本偏差。 - 多臂老虎机:把"换谁上场"建模为在线决策问题,动态学习最优换人策略。
- 比赛态势嵌入:用
socceraction/ VAEP 框架计算每次动作的期望得分增量,让"奇兵"衡量更细粒度。 - 因果森林:用
econml估计不同比分/时间下替补的异质性处理效应(CATE)。
最小可运行Demo
如果你手上暂时没有真实数据,可用下面代码生成模拟数据跑通全流程:
np.random.seed(42)
n = 2000
df = pd.DataFrame({
'is_starter': np.random.choice([True, False], n, p=[0.7,0.3]),
'minute_in': np.random.randint(45, 90, n),
'minutes_played': np.random.randint(10, 45, n),
'goals': np.random.poisson(0.15, n),
'assists': np.random.poisson(0.1, n),
'xg': np.random.gamma(1, 0.1, n),
'xa': np.random.gamma(1, 0.08, n),
'tackles': np.random.poisson(1.5, n),
'interceptions': np.random.poisson(1.0, n),
'team_score_at_in': np.random.randint(0,3,n),
'opp_score_at_in': np.random.randint(0,3,n),
'final_result': np.random.choice(['win','draw','lose'], n)
})
# 后续套用上面的 per90、impact_score、分组分析即可
这样就能从原始数据 → 奇兵识别 → 情境最优 → 因果验证 → 可视化决策跑通一个完整的Python分析案例,需要我针对足球或篮球某一种运动,细化某一步(比如VAEP、CATE估计或可视化模板)吗?