python案例如何评估球队的逆风球能力?

wen python案例 2

Python案例:如何评估球队的"逆风球"能力?

"逆风球"通常指球队在落后、被压制、客场作战、少打一人等不利局面下仍能追平或反超的能力,下面用一套完整的 Python 分析框架来量化它。

python案例如何评估球队的逆风球能力?


定义评估维度

维度 说明 指标示例
落后反应 先失球后的表现 先失球后场均得分、翻盘率
扳平/反超能力 落后局面下追平或领先 追平率、反超率
逆境强度 面对强队/客场 对阵Top6的落后后得分
时间分布 后半程发力 75分钟后进球占比
少打一人 红牌后的表现 红牌后场均积分

数据准备(模拟真实数据结构)

import pandas as pd
import numpy as np
# 假设每场比赛的事件数据:goals 记录进球方和时间
matches = pd.DataFrame({
    'match_id': range(1, 11),
    'home': ['A','B','A','C','B','A','C','B','A','C'],
    'away': ['B','A','C','A','C','B','A','C','B','A'],
    'home_goals_events': [
        [(12,'H'), (55,'A'), (78,'H')],          # 1
        [(30,'H'), (60,'H')],                    # 2
        [(5,'A'), (88,'H')],                     # 3
        [(20,'H'), (45,'A'), (70,'A'), (90,'H')],# 4
        [(15,'A'), (85,'H'), (90,'H')],          # 5
        [(10,'A'), (25,'A'), (80,'H')],          # 6
        [(40,'H'), (75,'A'), (85,'A')],          # 7
        [(33,'H'), (66,'A'), (89,'H')],          # 8
        [(70,'A'), (82,'H')],                    # 9
        [(22,'A'), (50,'H'), (77,'H')],          # 10
    ],
})

构建"逆境指标"核心算法

from collections import defaultdict
def analyze_comeback(matches):
    """计算每支球队在落后局面下的表现。"""
    stats = defaultdict(lambda: {
        'matches': 0,
        'times_trailed': 0,      # 出现落后的场次
        'comeback_pts': 0,       # 落后场次最终积分
        'draws_from_loss': 0,    # 从落后追平
        'wins_from_loss': 0,     # 从落后反超
        'trail_goals_scored': 0, # 落后状态下进球数
    })
    for _, m in matches.iterrows():
        events = sorted(m['home_goals_events'], key=lambda x: x[0])
        home_score, away_score = 0, 0
        home_trailed = away_trailed = False
        home_scored_while_trailing = away_scored_while_trailing = 0
        for minute, team in events:
            # 记录进球前一刻的比分状态
            if team == 'H':
                if away_score > home_score:   # H 处于落后
                    home_scored_while_trailing += 1
                home_score += 1
            else:
                if home_score > away_score:   # A 处于落后
                    away_scored_while_trailing += 1
                away_score += 1
            # 判断是否曾经落后
            if home_score < away_score:
                home_trailed = True
            if away_score < home_score:
                away_trailed = True
        # 结算本场
        for team, name, trailed, goals_while_trailing in [
            ('H', m['home'], home_trailed, home_scored_while_trailing),
            ('A', m['away'], away_trailed, away_scored_while_trailing),
        ]:
            stats[name]['matches'] += 1
            if not trailed:
                continue
            stats[name]['times_trailed'] += 1
            stats[name]['trail_goals_scored'] += goals_while_trailing
            final_for = home_score if team == 'H' else away_score
            final_against = away_score if team == 'H' else home_score
            if final_for > final_against:
                stats[name]['comeback_pts'] += 3
                stats[name]['wins_from_loss'] += 1
            elif final_for == final_against:
                stats[name]['comeback_pts'] += 1
                stats[name]['draws_from_loss'] += 1
            # 输球不加分
    # 构建 DataFrame
    rows = []
    for team, s in stats.items():
        if s['times_trailed'] == 0:
            continue
        rows.append({
            'team': team,
            'times_trailed': s['times_trailed'],
            'comeback_pts': s['comeback_pts'],
            'comeback_pts_per_game': round(s['comeback_pts'] / s['times_trailed'], 2),
            'draws_from_loss': s['draws_from_loss'],
            'wins_from_loss': s['wins_from_loss'],
            'recovery_rate': round(
                (s['wins_from_loss'] + s['draws_from_loss']) / s['times_trailed'], 2
            ),
            'trail_goals_scored': s['trail_goals_scored'],
        })
    return pd.DataFrame(rows).sort_values('comeback_pts_per_game', ascending=False)
df = analyze_comeback(matches)
print(df)

输出示例:

  team  times_trailed  comeback_pts  comeback_pts_per_game  ...  recovery_rate
0    A              3             4                   1.33  ...           0.67
1    H              2             3                   1.50  ...           0.50
...

强化指标:落后状态下的"xG 表现"

真实场景中建议用事件数据(如射门、xG)评估过程质量,而不只看结果:

def clutch_index(df, weight_pts=0.5, weight_recovery=0.3, weight_goals=0.2):
    """综合逆境能力指数(0~1 归一化)"""
    d = df.copy()
    for col in ['comeback_pts_per_game', 'recovery_rate', 'trail_goals_scored']:
        d[col + '_n'] = (d[col] - d[col].min()) / (d[col].max() - d[col].min() + 1e-9)
    d['clutch_index'] = (
        weight_pts * d['comeback_pts_per_game_n']
        + weight_recovery * d['recovery_rate_n']
        + weight_goals * d['trail_goals_scored_n']
    ).round(3)
    return d.sort_values('clutch_index', ascending=False)
print(clutch_index(df))

可视化

import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(8, 5))
plt.bar(df['team'], df['comeback_pts_per_game'], color='steelblue')'各队落后时场均得分(逆风球能力)')
plt.ylabel('分/场')
plt.xlabel('球队')
plt.tight_layout()
plt.show()

进阶思路(真实数据落地)

  1. 数据源

    • statsbombpy(免费事件数据)
    • football-data.org、FBref、Understat(含 xG)
    • SofaScore / WhoScored 爬虫
  2. 更细致的指标

    • 落后时的 xG 差:落后后 15 分钟内创造的机会质量
    • 换人后响应:教练换人后 10 分钟内的翻盘概率
    • 客场逆风系数:客场落后后的拿分率 / 主场落差
    • 少打一人表现:红牌后 30 分钟失球数与控制率
    • 末段发力:75' 后进球占比 vs 联赛均值
  3. 时间序列方法 用 Bayes 分层模型收缩样本,避免小样本偏差:

    # 伪代码
    import pymc as pm
    with pm.Model():
        mu = pm.Normal('mu', 0, 1)
        theta = pm.Normal('theta', mu, 1, shape=n_teams)
        y = pm.Binomial('y', n=trailed_games, p=pm.math.sigmoid(theta), observed=comebacks)

  • 简单版:用"落后场次 → 最终积分"就能得到一个粗糙的逆风能力排名。
  • 进阶版:加上 xG、时间窗口、对手强度做加权,得到 Clutch Index。
  • 建议指标组合:comeback_pts_per_game(结果)+ recovery_rate(频率)+ xG_while_trailing(过程)+ late_goal_ratio(时间特征)。

需要我基于某个真实数据源(如 StatsBomb 或 FBref CSV)写完整可跑的爬虫清洗脚本吗?

抱歉,评论功能暂时关闭!