
本文介绍如何基于 home_id 和 away_id 两个维度,为同一支队伍(无论作为主场或客场)构建全局、按时间顺序排列的累计得分列,解决传统单列 groupby().cumsum() 无法跨角色聚合的问题。
本文介绍如何基于 `home_id` 和 `away_id` 两个维度,为同一支队伍(无论作为主场或客场)构建全局、按时间顺序排列的累计得分列,解决传统单列 `groupby().cumsum()` 无法跨角色聚合的问题。
在体育数据分析中,一支球队可能在不同场次中分别担任主场(home_id)和客场(away_id),而我们需要统计该队所有出场记录中的总得分(即:当它是 home_id 时累加 home_score,当它是 away_id 时累加 away_score),并按原始数据的时间/行序生成动态运行总计(running total)。这不能通过简单的 groupby('home_id').cumsum() 或 groupby('away_id').cumsum() 单独实现——因为它们彼此隔离,且未对齐到同一逻辑“队伍ID”。
核心思路是:将 home_id 和 away_id 视为同一实体的不同身份,需统一映射到一个“team_id”维度,再按原始行序进行分组累计。但 Pandas 不支持直接对两列做“或关系”的 groupby,因此推荐采用 “长表重构 + 累计计算 + 宽表回填” 的稳健方案:
✅ 推荐方案:熔解 → 统一标识 → 排序累计 → 合并回原表
import pandas as pd
import numpy as np
# 假设 game_data 已加载
# 步骤1:创建长格式视图 —— 每行代表一个“队伍-得分”事件
home_events = game_data[['game_id', 'home_id', 'home_score']].rename(
columns={'home_id': 'team_id', 'home_score': 'score'}
).assign(role='home')
away_events = game_data[['game_id', 'away_id', 'away_score']].rename(
columns={'away_id': 'team_id', 'away_score': 'score'}
).assign(role='away')
# 合并并按原始 game_id 排序(确保时间顺序)
all_events = pd.concat([home_events, away_events], ignore_index=True).sort_values('game_id')
# 步骤2:对每个 team_id 计算累计得分(保留原始出现顺序)
all_events['team_running_total'] = all_events.groupby('team_id')['score'].cumsum()
# 步骤3:将累计值映射回原表 —— 分别提取 home 和 away 对应的累计值
game_data = game_data.merge(
all_events[all_events['role'] == 'home'][['game_id', 'team_id', 'team_running_total']],
left_on=['game_id', 'home_id'],
right_on=['game_id', 'team_id'],
how='left'
).rename(columns={'team_running_total': 'home_total_score'})
game_data = game_data.merge(
all_events[all_events['role'] == 'away'][['game_id', 'team_id', 'team_running_total']],
left_on=['game_id', 'away_id'],
right_on=['game_id', 'team_id'],
how='left'
).rename(columns={'team_running_total': 'away_total_score'})⚠️ 注意事项与优化建议
- 时间顺序至关重要:sort_values('game_id') 必须在熔解后执行,否则 cumsum() 将按拼接顺序而非真实比赛时序计算,导致结果错误;
- 避免 apply(..., include_groups=True) 的陷阱:原答案中使用 groupby(...).apply(..., include_groups=True) 会破坏索引对齐,且 include_groups=False 并不能安全保留原始列结构,易引发 KeyError 或重复索引问题,不推荐用于生产环境;
- 性能提示:若数据量极大(>100万行),可改用 pd.concat 后 set_index(['team_id', 'game_id']).sort_index() 加 groupby(level=0).cumsum() 提升效率;
- 空值处理:合并后检查 home_total_score/away_total_score 是否存在 NaN,确认 game_id 和 ID 列无类型不一致(如 str vs int)。
✅ 验证示例(取前3行)
| game_id | away_id | home_id | away_score | home_score | home_total_score | away_total_score |
|---|---|---|---|---|---|---|
| 446877 | 138 | 134 | 1 | 4 | 4 | 1 |
| 446911 | 141 | 139 | 5 | 3 | 3 | 5 |
| 446873 | 121 | 118 | 3 | 4 | 4 | 3 |
✅ 若后续某场比赛中 home_id == 138(即原 away_id=138 的队伍转为主场),其 home_total_score 将自动叠加此前作为客场的得分,真正实现“一支队伍、全局累计”。
此方法逻辑清晰、可复现性强,适用于任何需跨多角色(如 player_id 在不同 position 出场、supplier_id 在 buy/sell 场景)进行统一运行统计的场景。

















