计算网球选手胜负连 streak 的完整 Python 实现教程

18次阅读

计算网球选手胜负连 streak 的完整 Python 实现教程

本文详解如何基于 pandas dataframe 高效计算每位球员的动态胜负连 streak(胜为正数、负为负数),支持跨列识别(player1_id / player2_id 双向匹配),并保持时间顺序逻辑。

在网球等对战类体育数据分析中,球员的当前胜负连 streak(即连续获胜或失败的场次数)是刻画状态、预测表现的关键特征。但与简单累计不同,streak 具有「重置性」:一旦胜败结果反转,计数需从 ±1 重新开始(例如:赢→赢→输 → streak 从 +2 变为 −1)。更复杂的是,同一球员可能交替出现在 player1_id 或 player2_id 列中,需统一追踪其历史战绩。

以下提供一种高效、可读性强且无需分组排序的纯 python + Pandas 实现方案,适用于大规模赛事数据(百万级记录亦可稳定运行)。

✅ 核心思路

  • 使用字典 cnt 维护每个唯一球员 ID 的实时 streak 值(初始化为 0);
  • 按原始数据顺序逐行遍历(关键:必须保证 tourney_date 已升序排序,否则 streak 逻辑失效);
  • 对每行:
    • 记录当前 player1_id 和 player2_id 的旧 streak 值(用于新列);
    • 更新 player1_id 的 streak:若 target == 1(player1 胜),则 +1;否则 −1;
    • 更新 player2_id 的 streak:若 target == 1(player1 胜 ⇒ player2 败),则 −1;否则 +1;
  • 使用 max(…, +1) 和 min(…, −1) 并非必需(原答案此处存在逻辑偏差),正确更新应直接按胜负符号累加/重置 —— 我们将在下方修正并给出鲁棒实现。

✅ 推荐实现(修正版,逻辑严谨)

import pandas as pd import numpy as np  # 示例数据构建(确保已按 tourney_date 升序排列!) df = pd.DataFrame({     'tourney_date': ['2022-10-31', '2023-02-06', '2023-02-06', '2023-02-06', '2023-02-06', '2023-02-20', '2023-02-20'],     'player1_id': [100000, 123456, 100000, 100000, 345612, 432154, 100000],     'player2_id': [209950, 100000, 543210, 876543, 100000, 100000, 929292],     'target': [0, 0, 1, 1, 1, 1, 0] })  # ✅ 正确 streak 更新逻辑:先记录旧值,再按胜负更新 player_ids = pd.concat([df['player1_id'], df['player2_id']]).unique() streak_map = {pid: 0 for pid in player_ids}  # 初始化所有球员 streak 为 0  player1_streaks, player2_streaks = [], []  for _, row in df.iterrows():     p1, p2, t = row['player1_id'], row['player2_id'], row['target']      # 记录当前球员「赛前」streak(即本场开始前的状态)     player1_streaks.append(streak_map[p1])     player2_streaks.append(streak_map[p2])      # 更新 streak:胜 → +1,败 → -1;首次出场为 0,首胜后变为 +1,首败后变为 -1     if t == 1:  # player1 胜 → player1 streak +1,player2 streak -1         streak_map[p1] = streak_map[p1] + 1 if streak_map[p1] > 0 else 1         streak_map[p2] = streak_map[p2] - 1 if streak_map[p2] < 0 else -1     else:  # t == 0,player1 败 → player1 streak -1,player2 streak +1         streak_map[p1] = streak_map[p1] - 1 if streak_map[p1] < 0 else -1         streak_map[p2] = streak_map[p2] + 1 if streak_map[p2] > 0 else 1  # 添加新列 df['player1_streak'] = player1_streaks df['player2_streak'] = player2_streaks  print(df)

? 输出效果(符合题设预期)

tourney_date  player1_id  player2_id  target  player1_streak  player2_streak 0   2022-10-31      100000      209950       0               0               0 1   2023-02-06      123456      100000       0               0              -1 2   2023-02-06      100000      543210       1               1               0 3   2023-02-06      100000      876543       1               2               0 4   2023-02-06      345612      100000       1               0               3 5   2023-02-20      432154      100000       1               0              -1 6   2023-02-20      100000      929292       0              -2               0

⚠️ 关键注意事项

  • 时间顺序是前提:务必先执行 df = df.sort_values(‘tourney_date’).reset_index(drop=True),否则 streak 将完全错误;
  • 首次出场处理:规则明确「第一场为 0」,因此我们记录的是 赛前 streak,而非赛后值(题设示例表中 player1_streak 第一行即为 0);
  • 性能优化建议:对超大数据集(>100 万行),可用 df.itertuples() 替代 iterrows() 提速 3–5 倍;
  • 扩展性提示:如需同时计算「最长历史 streak」或「当前 streak 长度」,可在同一遍历中用额外字典维护 max_win_streak[pid]、max_lose_streak[pid]。

该方法避免了 groupby().apply() 的高开销与复杂状态管理,以 O(n) 时间复杂度完成双方向动态 streak 构建,是体育数据分析中轻量、可靠、易维护的标准实践。

text=ZqhQzanResources