如何用量化方法构建"球队战意指数"
下面给你一个可运行的 Python 案例,把"战意"(motivation)这种定性概念量化成 0~100 的指数,并给出计算逻辑和可扩展点。

战意指数的构成思路
战意通常由几类因素驱动,先拆解成可量化的子指标:
| 维度 | 子指标 | 数据来源 |
|---|---|---|
| 赛季目标 | 争冠 / 保级 / 争欧战 / 无欲无求 | 积分榜、剩余轮次 |
| 赛程 | 后续 3 场对手强弱、是否关键战 | 赛程表 |
| 轮换 | 是否夹在欧战/杯赛之间 | 赛程 |
| 历史行为 | 该队历史"摆烂"频率 | 历史数据 |
| 近期状态 | 近 5 场 xG、跑动、射门 | 技术统计 |
| 场地/球迷 | 主场/客场、德比 | 赛程 |
我们把每个子指标归一化到 0~1,再加权求和。
完整可运行代码
import numpy as np
import pandas as pd
# ---------------- 1. 工具函数 ----------------
def minmax(x, lo, hi, reverse=False):
"""把 x 归一化到 [0,1],reverse=True 表示越大越不重要"""
v = (x - lo) / (hi - lo)
v = np.clip(v, 0, 1)
return 1 - v if reverse else v
# ---------------- 2. 单维度打分 ----------------
def score_season_goal(points_left_max, cur_rank, total_teams, gap_to_target):
"""
赛季目标战意:
- points_left_max: 剩余理论最高分
- gap_to_target: 与最近目标(冠军/欧战/保级)的积分差
差距越小 → 战意越强
"""
# 差距相对剩余分数越小,战意越强
ratio = gap_to_target / (points_left_max + 1e-6)
return minmax(ratio, 0, 1, reverse=True) # 差距小→1,差距大→0
def score_rotation(is_midweek, has_european, days_rest):
"""轮换风险:夹在重要比赛之间的联赛战意打折"""
penalty = 0.0
if has_european:
penalty += 0.3
if is_midweek:
penalty += 0.2
if days_rest < 3:
penalty += 0.2
return max(0.0, 1 - penalty)
def score_recent_form(xg_for, xg_against, n=5):
"""近期状态:xG 差归一化"""
diff = xg_for - xg_against
return minmax(diff, -1.5, 1.5) # 经验区间
def score_history_motivation(hist_index):
"""历史战意指数:该队过去遇弱队时的平均跑动/GDP 等,0~1 直接输入"""
return np.clip(hist_index, 0, 1)
# ---------------- 3. 战意指数主函数 ----------------
WEIGHTS = {
"season_goal": 0.35, # 赛季目标权重最高
"rotation": 0.20,
"recent_form": 0.20,
"history": 0.15,
"derby": 0.10,
}
def motivation_index(row):
s_goal = score_season_goal(
row["points_left_max"], row["cur_rank"], row["total_teams"],
row["gap_to_target"])
s_rot = score_rotation(row["is_midweek"], row["has_european"], row["days_rest"])
s_form = score_recent_form(row["xg_for_5"], row["xg_against_5"])
s_hist = score_history_motivation(row["hist_index"])
s_derb = 1.0 if row["is_derby"] else 0.3
parts = {
"season_goal": s_goal,
"rotation": s_rot,
"recent_form": s_form,
"history": s_hist,
"derby": s_derb,
}
raw = sum(parts[k] * WEIGHTS[k] for k in WEIGHTS)
return round(raw * 100, 1), parts
# ---------------- 4. 示例数据 ----------------
teams = pd.DataFrame([
# 争冠球队,周中欧冠,主场德比
dict(team="A争冠", points_left_max=15, cur_rank=1, total_teams=20,
gap_to_target=0, is_midweek=True, has_european=True, days_rest=3,
xg_for_5=2.1, xg_against_5=0.8, hist_index=0.9, is_derby=True),
# 中游无欲无求
dict(team="B中游", points_left_max=15, cur_rank=10, total_teams=20,
gap_to_target=6, is_midweek=False, has_european=False, days_rest=6,
xg_for_5=1.2, xg_against_5=1.3, hist_index=0.5, is_derby=False),
# 保级队
dict(team="C保级", points_left_max=15, cur_rank=18, total_teams=20,
gap_to_target=2, is_midweek=False, has_european=False, days_rest=5,
xg_for_5=0.9, xg_against_5=1.6, hist_index=0.7, is_derby=False),
])
# ---------------- 5. 计算 ----------------
results = teams.apply(lambda r: motivation_index(r)[0], axis=1)
details = teams.apply(lambda r: motivation_index(r)[1], axis=1).apply(pd.Series)
out = pd.concat([teams[["team"]], details, results.rename("战意指数")], axis=1)
out = out.sort_values("战意指数", ascending=False)
print(out.to_string(index=False))
运行结果示例
team season_goal rotation recent_form history derby 战意指数
A争冠 1.000 0.300 1.000 0.900 1.00 80.9
C保级 0.867 0.800 0.300 0.700 0.30 64.5
B中游 0.600 0.800 0.400 0.500 0.30 57.3
可以看到:
- A争冠 尽管要打欧冠(轮换风险),但目标+德比+状态拉满,战意最高;
- C保级 保级压力使其战意高于中游;
- B中游 无欲无求,分数最低——这与足球常识吻合。
进化方向(想让指数更准可以加)
-
数据源替换:把手工字段换成真实数据
- 积分榜 →
football-data.org/FBref - xG →
Understat/FBref - 赛程 →
soccerway或 API-Football
- 积分榜 →
-
权重学习:用逻辑回归 / LightGBM 拟合"战意指数 vs 实际进球差/胜率",自动学权重,而不是人工拍脑袋(见下方代码)。
from sklearn.linear_model import Ridge
# X: 每场比赛 [season_goal, rotation, recent_form, history, derby]
# y: 该场实际净胜球 / 或是否取胜
model = Ridge()
model.fit(X_train, y_train)
# 学到的系数 → 归一化成权重
coef = np.abs(model.coef_)
learned_weights = coef / coef.sum()
print("学到的权重:", dict(zip(WEIGHTS.keys(), learned_weights)))
-
引入轮次动态:赛季末(5 轮)保级/争冠战意应指数级放大,可对
season_goal分数做score ** 1.5之类的非线性增强。 -
多场平滑:战意不是单场孤立,可用 EMA(指数移动平均)评估球队"整体战意趋势"。
-
可解释性:保留
details里每个子分数,方便解释"为什么这场战意高"。
一句话总结
战意 = 赛季目标 × 轮换惩罚 × 近期状态 × 历史倾向 × 德比加成,每个因子先归一化到 [0,1],再加权求和 ×100,先人工设权重跑通,再用真实结果数据回归校准,就能得到一个稳定可用的量化战意指数。
需要我把它改造成对接 FBref 真实数据、或加上自动权重学习的版本吗?