预测杯赛决赛紧张程度的Python案例
这是一个有趣的机器学习/数据分析问题,下面给出一个完整的实战案例,用特征工程 + 机器学习来预测决赛的"紧张程度"。

问题定义
"紧张程度"量化指标(可组合成综合分数 tension_score,0-100):
- 比分差距(越小越紧张)
- 加时/点球(有则大幅加分)
- 红黄牌数量
- 犯规次数
- 射门/射正差距
- 历史交手平局率
- 两队实力差距(FIFA排名/ELO差值)
完整代码实现
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_absolute_error, r2_score
import matplotlib.pyplot as plt
# ============ 1. 构造/加载数据 ============
# 真实项目可从 FIFA API、Kaggle 世界杯数据集加载
# 这里构造示例数据(每行是一场决赛)
np.random.seed(42)
n = 200
data = pd.DataFrame({
# 赛前特征
'elo_diff': np.random.randint(0, 400, n), # 两队ELO差
'h2h_draw_rate': np.random.rand(n), # 历史平局率
'h2h_avg_goals': np.random.uniform(1.5, 4.0, n), # 历史场均进球
'rest_days_diff': np.random.randint(-3, 4, n), # 休息天数差
'is_host_playing': np.random.randint(0, 2, n), # 东道主是否参赛
'final_experience': np.random.randint(0, 5, n), # 大赛决赛经验差
# 赛后特征(用于训练,也可用赛中实时数据)
'goal_diff': np.random.choice([0,0,0,1,1,2,3], n),
'went_to_extra': np.random.choice([0,1], n, p=[0.7,0.3]),
'went_to_penalty': np.random.choice([0,1], n, p=[0.9,0.1]),
'yellow_cards': np.random.randint(0, 8, n),
'red_cards': np.random.randint(0, 3, n),
'total_fouls': np.random.randint(15, 40, n),
'shot_diff': np.random.randint(0, 10, n),
})
# ============ 2. 构造目标:紧张程度分数 ============
def calc_tension(row):
score = 0
score += max(0, 40 - row['goal_diff'] * 15) # 比分接近
score += row['went_to_extra'] * 15 # 加时
score += row['went_to_penalty'] * 20 # 点球
score += min(row['yellow_cards'] * 2, 10) # 黄牌
score += row['red_cards'] * 5 # 红牌
score += max(0, 10 - row['shot_diff']) # 射门接近
score += (400 - row['elo_diff']) / 10 # 实力接近 → 更紧张
score += row['h2h_draw_rate'] * 10
return max(0, min(100, score))
data['tension_score'] = data.apply(calc_tension, axis=1)
# ============ 3. 建模 ============
# 假设赛前只能拿到"赛前特征"来做预测(避免数据泄漏)
pre_match_features = [
'elo_diff', 'h2h_draw_rate', 'h2h_avg_goals',
'rest_days_diff', 'is_host_playing', 'final_experience'
]
X = data[pre_match_features]
y = data['tension_score']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
models = {
'RandomForest': RandomForestRegressor(n_estimators=300, random_state=42),
'GradientBoosting': GradientBoostingRegressor(n_estimators=300, learning_rate=0.05,
max_depth=3, random_state=42)
}
for name, model in models.items():
model.fit(X_train_s, y_train)
pred = model.predict(X_test_s)
print(f"{name}: MAE={mean_absolute_error(y_test, pred):.2f}, "
f"R²={r2_score(y_test, pred):.3f}")
# ============ 4. 特征重要性 ============
best = models['GradientBoosting']
imp = pd.Series(best.feature_importances_, index=pre_match_features).sort_values()
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文显示
plt.rcParams['axes.unicode_minus'] = False
imp.plot(kind='barh', figsize=(8, 4), title='赛前特征对紧张程度的影响')
plt.tight_layout(); plt.show()
# ============ 5. 预测一场新决赛 ============
new_final = pd.DataFrame([{
'elo_diff': 50, # 两队实力非常接近
'h2h_draw_rate': 0.4, # 历史交手平局率高
'h2h_avg_goals': 2.1, # 场均进球偏低
'rest_days_diff': 0,
'is_host_playing': 1,
'final_experience': 1
}])
score = best.predict(scaler.transform(new_final))[0]
print(f"\n预测紧张程度分数:{score:.1f} / 100")
关键设计要点
特征设计分两阶段
| 阶段 | 特征 | 用途 |
|---|---|---|
| 赛前 | ELO差、历史平局率、休息天数、决赛经验 | 赛前预测 |
| 赛中实时 | 当前比分差、红黄牌、加时标志 | 实时紧张度预测 |
目标标签的三种构造方式
- 规则打分(如上):简单可控,但主观
- 专家标注:真实比赛人工打分 0-10
- 信号替代:用"最后10分钟观众心率/社交媒体峰值"作代理目标
避免数据泄漏
预测"赛前紧张程度"时,绝不能使用赛后特征(goal_diff、went_to_penalty),上面的示例把它们放在数据表里,但建模时只用了 pre_match_features。
进阶方向
- 实时紧张度曲线:用逐分钟事件流(进球、红牌、射门、控球率),LSTM/Transformer 预测每时刻紧张度。
- 概率化输出:用
QuantileRegressor输出紧张度的 25%/75% 分位,给"最可能范围"。 - 多模态:结合社交媒体情绪(NLP)、赔率变化(博彩数据)提升准确率。
- 可解释性:用
SHAP解释每场决赛为何被判为"特别紧张"。
# SHAP 示例 import shap explainer = shap.TreeExplainer(best) shap_values = explainer.shap_values(X_test_s) shap.summary_plot(shap_values, X_test, feature_names=pre_match_features)
现实案例参考
- 2022世界杯决赛(阿根廷vs法国):ELO接近、历史平局多、进攻火力相当 → 模型会给出极高紧张度(实际3-3后点球)。
- 2018世界杯决赛(法国vs克罗地亚):ELO差较大 → 赛前预测紧张度偏低(实际4-2较开放)。
如果你有真实数据集(如 Kaggle "FIFA World Cup" 或 football-data.org API),我可以帮你改写上面的代码接入真实数据,或者进一步做逐分钟实时紧张度预测模型,你更倾向哪个方向?