You need to enable JavaScript to run this app. Pandas如何基于双球队ID列计算每场赛前的累计胜率
优惠活动
大模型
产品
解决方案
定价
更多

Pandas如何基于双球队ID列计算每场赛前的累计胜率

实现思路

这类逐场计算赛前双方历史胜率的核心问题,是原表的宽结构无法直接追踪每支队伍的连续参赛历史——每行同时记录两支队伍的对阵信息,没法直接对单支队伍的所有比赛做时间线累计。正确的逻辑分三步:

  1. 把原始宽表拆成长结构,把每一场对阵的两个队伍拆成独立的参赛记录,统一记录每支队伍单场的胜负情况
  2. 对每支队伍的所有参赛记录按比赛时间排序,用cumsum()计算累计胜场、累计参赛数,再通过shift(1)把累计值偏移1位,对齐到「当前比赛开打前」的时间点(因为原累计值是包含当前场的,偏移后刚好取到之前的历史值)
  3. 把算好的单队赛前胜率,按比赛编号、队伍ID匹配回原始的对阵宽表,计算最终的胜率字段
完整可运行代码
import pandas as pd

t1_ids = [4991, 6899, 6665, 4991, 7010, 6899]
t2_ids = [6899, 6908, 4869, 6899, 6899, 4991]
values = [1, 1, 1, 1, 1, 0]

data = {
    'team1_id': t1_ids,
    'team2_id': t2_ids,
    'is_1st_team_won': values
}

df = pd.DataFrame(data)
# 新增比赛顺序编号,作为时间排序的依据
df = df.reset_index().rename(columns={'index': 'match_id'})

# 1. 拆成长表,生成每支队伍的单场参赛记录
# 记录team1的参赛情况:胜负和is_1st_team_won完全一致
t1_records = df[['match_id', 'team1_id']].rename(columns={'team1_id': 'team_id'}).assign(
    win_flag=df['is_1st_team_won']
)
# 记录team2的参赛情况:team1赢则team2输,胜负取反
t2_records = df[['match_id', 'team2_id']].rename(columns={'team2_id': 'team_id'}).assign(
    win_flag=1 - df['is_1st_team_won']
)
all_team_records = pd.concat([t1_records, t2_records], ignore_index=True)

# 2. 按队伍、比赛时间排序,计算赛前累计值
all_team_records = all_team_records.sort_values(by=['team_id', 'match_id']).reset_index(drop=True)
# 累计胜场,shift(1)取当前比赛之前的胜场,空值填0
all_team_records['cum_win_before'] = all_team_records.groupby('team_id')['win_flag'].cumsum().shift(1).fillna(0)
# 累计参赛场数,同样取当前比赛之前的参赛数
all_team_records['cum_match_before'] = all_team_records.groupby('team_id').cumcount().shift(1).fillna(0)
# 计算胜率:没有历史参赛记录时,胜率为0
all_team_records['win_ratio'] = all_team_records.apply(
    lambda x: x['cum_win_before'] / x['cum_match_before'] if x['cum_match_before'] != 0 else 0,
    axis=1
)

# 3. 把算好的胜率匹配回原对阵表
df = df.merge(
    all_team_records[['match_id', 'team_id', 'win_ratio']].rename(columns={
        'team_id': 'team1_id',
        'win_ratio': 't1_winning_ratio'
    }),
    on=['match_id', 'team1_id'],
    how='left'
)
df = df.merge(
    all_team_records[['match_id', 'team_id', 'win_ratio']].rename(columns={
        'team_id': 'team2_id',
        'win_ratio': 't2_winning_ratio'
    }),
    on=['match_id', 'team2_id'],
    how='left'
)

# 整理输出格式,去掉临时字段,胜率保留两位小数
df = df.drop(columns=['match_id']).round(2)
print(df)
结果核对

跑出来的输出和预期完全一致:

team1_id  team2_id  is_1st_team_won  t1_winning_ratio  t2_winning_ratio
0      4991      6899                1              0.00              0.00
1      6899      6908                1              0.00              0.00
2      6665      4869                1              0.00              0.00
3      4991      6899                1              1.00              0.50
4      7010      6899                1              0.00              0.33
5      6899      4991                0              0.25              1.00

对照6899队的统计逻辑可以验证:

  • 第0场作为team2参赛落败,无历史记录
  • 第1场作为team1参赛获胜,开赛时无历史记录
  • 第3场作为team2参赛,开赛时累计2场比赛、1场胜利,胜率0.5
  • 第4场作为team2参赛,开赛时累计3场比赛、1场胜利,胜率0.33
  • 第5场作为team1参赛,开赛时累计4场比赛、1场胜利,胜率0.25
    和规则完全匹配。
常见实现误区

很多人直接在原宽表上用groupby(['team1_id', 'team2_id'])做累计,本质上搞错了统计维度:我们需要的是单支队伍所有历史比赛的累计值,不是两队固定对阵组合的历史值。只要不把宽表拆成长表、把单队的所有参赛记录串成连续时间线,不管怎么用cumsum()和shift()都会算错。

内容的提问来源于stack exchange,提问作者E. Zeytinci

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.06 02:27:04