Last active
November 3, 2023 21:50
-
-
Save suchowan/09277927b196ea44ea5a76d224888874 to your computer and use it in GitHub Desktop.
MLB 2023 season rating calculation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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