よっしーの私的空間

機械学習を中心に興味のあることについて更新します

競走馬をレーティングしてみた(Glicko2 Rating System)

競走馬の強さを評価するために競走馬のレーティングをしてみました。レーティングはGlicko2 Rating Systemというのを使用してみました。Glicko2の概要やPythonでの実装方法については以下の記事にまとめたので、良ければ見てください。
book-read-yoshi.hatenablog.com

レーティングは2015年以降のレース結果を基に実施しました。結果は以下の通りです。上位10頭まで表示してます。感覚と一致しますか??
f:id:t-yoshi-book:20220226223440p:plain

レーティング用に作成したPythonコードは以下の通りです。レースデータの取得はこちらを参考にしました。以下ではレースデータの取得方法は割愛してます。

from glicko2 import glicko2
import pickle
import pandas as pd
pd.set_option('display.max_columns', 100)
import numpy as np
from tqdm.notebook import tqdm as tqdm

# レーティングを行う関数
def calcRatings(players,ranks):
    newPlayers=[]
    for i, (target_player,target_rank) in enumerate(zip(players,ranks)):
        new_target_player = copy.deepcopy(target_player)
        ratings=[]
        rds=[]
        outcomes=[]
        for j, (player, rank) in enumerate(zip(players,ranks)):
            if not i==j:
                ratings.append(player.rating)
                rds.append(player.rd)
                if rank>target_rank:
                    outcomes.append(1)
                elif rank<target_rank:
                    outcomes.append(0)
                elif rank==target_rank:
                    outcomes.append(0.5)
        
        new_target_player.update_player(ratings, rds, outcomes)
        newPlayers.append(new_target_player)
    
    return newPlayers

# 事前に取得した競馬レースデータをロードする。競馬データの取得は以下を参考にした。
# http://houdoukyokucho.com/2020/08/31/post-1617/
with open('df_with_race_data.pickle', 'rb') as f:
    df = pickle.load(f)

# 中止レース等を除外
df = df[df["rank"]!="中止"]
df = df[df["rank"]!="除外"]
df = df[df["rank"]!="取消"]
df = df[df["rank"]!="失格"]

# レート等を格納する列を作成
df["horse_rate"]=np.nan
df["horse_rd"]=np.nan
df["horse_vol"]=np.nan

# レーティング結果を格納するデータフレームを作成
def createRatingTable(df, colName):
    df = df[[colName]].copy()
    df = df.drop_duplicates()
    df["rating"] = 1500
    df["rd"] = 350
    df["vol"] = 0.06
    return df

df_horse_rate = createRatingTable(df, "horse_name")

# レーティング処理を高速化するためにPandasをリストに変換
horse_rates = df_horse_rate.values.tolist()   # 各競走馬の最新のレート情報等を登録するリスト
race_datas = df.values.tolist()    # レースデータを蓄積したリスト

# レース単位に繰り返し計算を行う
finished = []
for i,race_data in enumerate(tqdm(race_datas)):
    target_race_id = race_data[12]  # [12]は列番号なのでデータによって異なる
    if target_race_id not in finished:
        finished.append(target_race_id)
        single_race = [race_data for race_datain race_datas if race_data[12]==target_race_id]
        ranks = [int(horse[15]) for horse in single_race]    # [15]は列番号なのでデータによって異なる
        
        horse_names  = [single_record[3] for single_record in single_race]   # [3]は列番号なのでデータによって異なる

        # 将来的に機械学習等を行うときの説明変数とするために、レース前のレートを記録しておく
        horses = []
        for j, horse_name in enumerate(horse_names):
            # 過去レートを取得
            horse_rate,horse_rd,horse_vol = [horse_rate for horse_rate in horse_rates if horse_rate[0]==horse_name][0][1:4]
            player = glicko2.Player(horse_rate,horse_rd,horse_vol)
            horses.append(player)
            # レースデータのリストに登録 ※以下の[25:28]は列番号なので作成データによって異なる
            race_datas[i+j][25:28] = [horse_rate,horse_rd,horse_vol]

        # 競走馬のレートを計算する
        new_horses  = calcRatings(horses, ranks)

        # 各競走馬の最新のレート情報を記録
        for horse_name, new_horse in zip(horse_names,new_horses):
            for j,horse_rate in enumerate(horse_rates):
                if horse_rate[0] == horse_name:
                    horse_rates[j][1:4]=[new_horse.rating, new_horse.rd, new_horse.vol]

# レートを高い順に表示する
df_rate = pd.DataFrame(horse_rates,columns=["horse_name","rating","rd","vol"])
df_rate.sort_values("rating",ascending=False)