Skip to content

Instantly share code, notes, and snippets.

@suchowan
Last active November 3, 2023 21:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save suchowan/09277927b196ea44ea5a76d224888874 to your computer and use it in GitHub Desktop.
Save suchowan/09277927b196ea44ea5a76d224888874 to your computer and use it in GitHub Desktop.
MLB 2023 season rating calculation
#require 'pp'
require 'open-uri'
require 'openssl'
require 'matrix'
Encoding.default_external = 'UTF-8'
Encoding.default_internal = 'UTF-8'
#######################################
##
## 勝敗データ取得
##
class Stats
def initialize(h,v)
@stats = {}
url = "https://baseball.yahoo.co.jp/mlb/teams/20210%02d/stats?vsteam=20210%02d" % [h,v]
open(url, 'r:utf-8' ,{:ssl_verify_mode=>OpenSSL::SSL::VERIFY_NONE}) do |source|
source.read.each_line do |line|
case line
when /<h2 class="bb-title02__title">(.+?)<\/h2>/
@team = $1
when /"selected">vs.(.+?)<\/option>/
@vs = $1
when /<th class="bb-quarterTable__head" scope="row">(試合|勝利|敗戦|引分)<\/th>/
@mode = $1
when /<span class="bb-quarterTable__dataLabel">(\d+?)<\/span>/
if @mode
@stats[@mode] ||= $1.to_i
@mode = nil
end
end
end
end
end
def dump
{:team=>@team, :vs=>@vs, :stats=>@stats}
end
RegularSeason =
(Array.new(30) {|h| Array.new(30) {|v| h==v ? nil : Stats.new(h+1,v+1).dump}})
PostSeason = [
{'テキサス・レンジャーズ' =>4, 'アリゾナ・ダイヤモンドバックス'=>1},
{'テキサス・レンジャーズ' =>4, 'ヒューストン・アストロズ' =>3},
{'アリゾナ・ダイヤモンドバックス'=>4, 'フィラデルフィア・フィリーズ' =>3},
{'テキサス・レンジャーズ' =>3, 'ボルティモア・オリオールズ' =>0},
{'アリゾナ・ダイヤモンドバックス'=>3, 'ロサンゼルス・ドジャース' =>0},
{'ヒューストン・アストロズ' =>3, 'ミネソタ・ツインズ ' =>1},
{'フィラデルフィア・フィリーズ' =>3, 'アトランタ・ブレーブス' =>1},
{'テキサス・レンジャーズ' =>2, 'タンパベイ・レイズ' =>0},
{'アリゾナ・ダイヤモンドバックス'=>2, 'ミルウォーキー・ブリュワーズ' =>0},
{'ミネソタ・ツインズ ' =>2, 'トロント・ブルージェイズ' =>0},
{'フィラデルフィア・フィリーズ' =>2, 'マイアミ・マーリンズ' =>0}
]
# pp Stats::RegularSeason
end
#######################################
##
## レーティング計算
##
class RatingTable
#######################################
## クラス定数
##
# レーティング単位(3勝1敗ペースの勝率に対するレーティング差)
RateUnit = 200.0
# レーティング平均値(駒音掲示板の「みなかみ」さんの値に整合させたもの)
RateMean = 2600.0
# 収束の許容誤差
ErrorLimit = 1e-5
#######################################
## 勝敗表作成メソッド(Matrix形式)
##
#
# 初期設定
#
def initialize
#
# 勝敗表の初期化
#
@WinLossHash = Stats::RegularSeason.map {|team|
[(team[0]||team[1])[:team], {'Wins'=>0, 'Losses'=>0}]
}.to_h # 勝敗表(ハッシュ形式)
@Target = @WinLossHash.keys
@Size = @Target.size
#
# 勝敗表の更新 : RegularSeason
#
(0...@Size).each do |i|
(i+1...@Size).each do |j|
team = @Target[i]
vs = @Target[j]
@WinLossHash[team]['Wins' ] += Stats::RegularSeason[i][j][:stats]['勝利']
@WinLossHash[team]['Losses'] += Stats::RegularSeason[i][j][:stats]['敗戦']
@WinLossHash[team][vs ] = Stats::RegularSeason[i][j][:stats]['勝利']
@WinLossHash[vs]['Wins' ] += Stats::RegularSeason[j][i][:stats]['勝利']
@WinLossHash[vs]['Losses' ] += Stats::RegularSeason[j][i][:stats]['敗戦']
@WinLossHash[vs][team ] = Stats::RegularSeason[j][i][:stats]['勝利']
end
end
#
# 勝敗表の更新 : PostSeason
#
Stats::PostSeason.each do |series|
team, vs = series.keys
@WinLossHash[team]['Wins' ] += series[team]
@WinLossHash[team]['Losses'] += series[vs ]
@WinLossHash[team][vs ] += series[team]
@WinLossHash[vs]['Wins' ] += series[vs ]
@WinLossHash[vs]['Losses' ] += series[team]
@WinLossHash[vs][team ] += series[vs ]
end
#
# 勝敗表のマトリクス化
#
@WinLossMatrix = Matrix[*
((0...@Size).collect do |k|
((0...@Size).collect do |i|
@WinLossHash[@Target[k]][@Target[i]] || 0
end)
end)
]
end
#######################################
## レーティング値を計算する
##
def rating
# レートテーブルの初期値(オール 0)
@Rate = Vector[*Array.new(@Size,0)]
begin
# レーティング・テーブルが予測する勝率行列
@WinRateMatrix = Matrix[*
(@Rate.collect do |x|
(@Rate.collect do |y|
# exp(x)
# ---------------
# exp(x) + exp(y)
1/(1+Math.exp(y-x))
end)
end)
]
# 収束補正量の理論値
errorVector = Vector[*
((0...@Size).collect do |k|
numerator = 0 # 分子
#---------------------
denominator = 0 # 分母
(0...@Size).each do |i|
numerator += @WinLossMatrix[k,i] * @WinRateMatrix[i,k] -
@WinRateMatrix[k,i] * @WinLossMatrix[i,k]
#------------------------------------------------------
denominator += @WinRateMatrix[i,k] * @WinRateMatrix[k,i] *
(@WinLossMatrix[k,i] + @WinLossMatrix[i,k])
end
# 0/0 形の場合は、補正なしとする
(numerator == 0) ? 0 : numerator / denominator # <=これが問題
end)
]
# 収束補正量の平均値を 0 とする(平行移動しても結果は変わらない)
errorMean = 0
Array(errorVector).each do |error|
errorMean += error
end
errorVector -= Vector[*Array.new(@Size,errorMean/@Size)]
# 収束補正の実行
@Rate += errorVector
$stderr.printf "|error| : %5.2e\n", errorVector.r
end while (errorVector.r > ErrorLimit)
$stderr.printf "\n"
self
end
#######################################
## 解析結果
##
def result
# レーティングの降順インデックスをつくる
index = (0...@Size).sort do |x,y|
@Rate[y] <=> @Rate[x]
end
# 結果
printf "以下の#{@Size}チームを評価しました:\n\n"
(0...@Size).each do |k|
name = @Target[index[k]]
printf("%3d [%5d] %3d勝%3d敗 %-30s \n",
k+1,
@Rate[index[k]] / Math.log(3) * RateUnit + RateMean,
@WinLossHash[name]['Wins'],
@WinLossHash[name]['Losses'],
name)
end
end
end
##########################################
##
## メイン・ルーチン
##
##
RatingTable.new.rating.result
# 1 [ 2701] 105勝 61敗 ①アトランタ・ブレーブス
# 2 [ 2684] 101勝 64敗 ❶ボルティモア・オリオールズ
# 3 [ 2677] 100勝 65敗 ②ロサンゼルス・ドジャース
# 4 [ 2677] 99勝 65敗 ➍タンパベイ・レイズ
# 5 [ 2653] 103勝 76敗 ❺テキサス・レンジャーズ
# 6 [ 2651] 98勝 77敗 ④フィラデルフィア・フィリーズ
# 7 [ 2645] 92勝 72敗 ➂ミルウォーキー・ブリュワーズ
# 8 [ 2635] 89勝 75敗 ➏トロント・ブルージェイズ
# 9 [ 2635] 96勝 77敗 ➋ヒューストン・アストロズ
# 10 [ 2625] 94勝 85敗 ⑥アリゾナ・ダイヤモンドバックス
# 11 [ 2625] 88勝 74敗  シアトル・マリナーズ
# 12 [ 2615] 84勝 80敗 ⑤マイアミ・マーリンズ
# 13 [ 2614] 90勝 78敗 ➌ミネソタ・ツインズ
# 14 [ 2610] 83勝 79敗  シカゴ・カブス
# 15 [ 2610] 82勝 80敗  ニューヨーク・ヤンキース
# 16 [ 2608] 82勝 80敗  サンディエゴ・パドレス
# 17 [ 2606] 82勝 80敗  シンシナティ・レッズ
# 18 [ 2593] 79勝 83敗  サンフランシスコ・ジャイアンツ
# 19 [ 2593] 78勝 84敗  ボストン・レッドソックス
# 20 [ 2582] 76勝 86敗  ピッツバーグ・パイレーツ
# 21 [ 2581] 75勝 87敗  ニューヨーク・メッツ
# 22 [ 2574] 78勝 84敗  デトロイト・タイガース
# 23 [ 2566] 76勝 86敗  クリーブランド・ガーディアンズ
# 24 [ 2564] 71勝 91敗  ワシントン・ナショナルズ
# 25 [ 2561] 73勝 89敗  ロサンゼルス・エンゼルス
# 26 [ 2559] 71勝 91敗  セントルイス・カージナルス
# 27 [ 2508] 59勝103敗  コロラド・ロッキーズ
# 28 [ 2499] 61勝101敗  シカゴ・ホワイトソックス
# 29 [ 2477] 56勝106敗  カンザスシティ・ロイヤルズ
# 30 [ 2456] 50勝112敗  オークランド・アスレチックス
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment