
本文详解如何基于 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 构建,是体育数据分析中轻量、可靠、易维护的标准实践。










