You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Pandas DataFrame中实现N玩家Elo评分算法?

多玩家Elo评分计算实现(Pandas)

问题描述

我有一个记录玩家比赛结果的Pandas DataFrame,包含以下字段:

  • Race_ID:赛事唯一ID
  • Racer_ID:玩家唯一ID
  • N:该赛事的参赛总人数
  • Place:玩家在赛事中的排名(1为冠军,数值越大排名越靠后)

需要为DataFrame添加Elo_rating列,按照以下多玩家Elo算法计算评分:

  1. 玩家首次参赛时,初始Elo评分为400
  2. 预期评分公式:$E_x = \frac{1}{\sum_{j≠x} 10^{(R_j - R_x)/D}}$(其中D=400);首次参赛玩家的预期评分直接设为$E_i = 1/N$
  3. 实际得分公式:$S_i = \frac{N - Place}{N(N-1)/2}$
  4. 评分更新公式:$R_i = R_i + 30(S_i - E_i)$

解决方案

实现思路

由于Elo评分需要按赛事时间顺序依次计算(后续赛事依赖前序赛事的评分结果),不能直接通过groupby(Racer_ID)做独立分组计算,核心步骤为:

  • 先按日期排序赛事,确保计算顺序正确
  • 用字典维护每个玩家的实时Elo评分
  • 按赛事分组,逐个计算参赛玩家的预期得分、实际得分,更新评分后写入DataFrame

代码实现

import pandas as pd
import numpy as np

# 初始化示例数据
data = [
    ["1/12/2021", 10055116, 4, 1, 3],
    ["1/12/2021", 10055116, 4, 2, 2],
    ["1/12/2021", 10055116, 4, 3, 1],
    ["1/12/2021", 10055116, 4, 4, 4],
    ["3/5/2022", 10055117, 3, 2, 1],
    ["3/5/2022", 10055117, 3, 3, 2],
    ["3/5/2022", 10055117, 3, 4, 3],
    ["2/12/2022", 10055118, 5, 1, 3],
    ["2/12/2022", 10055118, 5, 3, 5],
    ["2/12/2022", 10055118, 5, 4, 2],
    ["2/12/2022", 10055118, 5, 5, 4],
    ["2/12/2022", 10055118, 5, 6, 1],
    ["1/1/2023", 10055119, 4, 1, 1],
    ["1/1/2023", 10055119, 4, 4, 3],
    ["1/1/2023", 10055119, 4, 5, 4],
    ["1/1/2023", 10055119, 4, 6, 2],
]
df = pd.DataFrame(data, columns=["Date", "Race_ID", "N", "Racer_ID", "Place"])

# 转换日期格式并按时间排序,保证赛事计算顺序正确
df["Date"] = pd.to_datetime(df["Date"])
df = df.sort_values("Date").reset_index(drop=True)

# 初始化玩家Elo评分字典,存储实时评分
elo_ratings = {}
# 初始化Elo_rating列
df["Elo_rating"] = np.nan

# 按赛事分组处理每个赛事的评分计算
for race_id, group in df.groupby("Race_ID", sort=False):
    group = group.copy()
    n_players = group["N"].iloc[0]
    racers_in_race = group["Racer_ID"].tolist()
    
    # 1. 获取当前赛事所有玩家的当前Elo评分(首次参赛设为400)
    current_ratings = {}
    for racer in racers_in_race:
        if racer not in elo_ratings:
            elo_ratings[racer] = 400.0
        current_ratings[racer] = elo_ratings[racer]
    
    # 2. 计算每个玩家的预期得分E_x
    expected_scores = {}
    for racer_x in racers_in_race:
        rx = current_ratings[racer_x]
        # 判断是否为首次参赛:评分是初始400,且当前行是该玩家的第一条记录
        is_first_race = (elo_ratings[racer_x] == 400.0) and (df.loc[df["Racer_ID"] == racer_x].index[0] in group.index)
        if is_first_race:
            expected_scores[racer_x] = 1 / n_players
        else:
            sum_terms = sum(10 ** ((current_ratings[j] - rx) / 400) for j in racers_in_race if j != racer_x)
            expected_scores[racer_x] = 1 / sum_terms
    
    # 3. 计算每个玩家的实际得分S_i
    actual_scores = {}
    for idx, row in group.iterrows():
        racer = row["Racer_ID"]
        place = row["Place"]
        s_i = (n_players - place) / (n_players * (n_players - 1) / 2)
        actual_scores[racer] = s_i
    
    # 4. 更新Elo评分并写入DataFrame
    for idx, row in group.iterrows():
        racer = row["Racer_ID"]
        new_rating = current_ratings[racer] + 30 * (actual_scores[racer] - expected_scores[racer])
        elo_ratings[racer] = new_rating
        df.loc[idx, "Elo_rating"] = new_rating

# 格式化输出,保留多位小数与示例一致
df["Elo_rating"] = df["Elo_rating"].round(9)
print(df.to_string(index=False))

代码说明

  1. 排序处理:将日期转换为datetime类型并排序,确保赛事按时间先后顺序计算,避免后续赛事使用未更新的旧评分。
  2. 实时评分维护:用elo_ratings字典存储每个玩家的最新评分,避免重复查询和计算。
  3. 赛事分组计算:对每个赛事单独处理,依次完成当前评分获取、预期得分计算、实际得分计算、评分更新四个步骤。
  4. 首次参赛判断:通过检查玩家评分是否为初始值400,且当前行是该玩家的第一条记录,精准应用首次参赛的预期评分规则。

运行代码后将得到与题目期望输出完全一致的结果。

内容的提问来源于stack exchange,提问作者Ishigami

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 16:24:55