グリコレーティングの計算用PHPコード

シンプルバージョン

シンプルなグリコレーティング (Glicko Rating System) 用のPHPコードです。

<?php

define('PERIOD', 15);
define('YEAR', 2);

define('C', sqrt((350 ** 2 - 50 ** 2) / (365 * YEAR / PERIOD)));
define('Q', log(10) / 400);

$a0 = ['r'=>1500, 'rd'=>350, 'day'=>7];
$b0 = ['r'=>1500, 'rd'=>350, 'day'=>7];
$score = [1, 2];

[$a, $b] = calc_glicko($a0, $b0, $score);

echo 'A=' , $a['r'], ', B=', $b['r'] , ', ';
echo 'RD_A=' , $a['rd'], ', RD_B=', $b['rd'];

exit;

function calc_glicko($a, $b, $sc) {

  $s = $sc[0] > $sc[1] ? 1 : ($sc[0] < $sc[1] ? 0 : 0.5);

  $rd_a = rd_now($a['rd'], $a['day']);
  $rd_b = rd_now($b['rd'], $b['day']);

  $g_a = g_rd($rd_b);
  $g_b = g_rd($rd_a);

  $e_a = ex($a['r'], $b['r'], $g_a);
  $e_b = ex($b['r'], $a['r'], $g_b);

  $d2_a = d2($g_a, $e_a);
  $d2_b = d2($g_b, $e_b);

  $dr_a = round(k_factor($rd_a, $d2_a) * $g_a * ($s - $e_a), 0);
  $dr_b = round(k_factor($rd_b, $d2_b) * $g_b * ((1 - $s) - $e_b), 0);

  return [
    ['r' => $a['r'] + $dr_a, 'rd'=> rd_new($rd_a, $d2_a)],
    ['r' => $b['r'] + $dr_b, 'rd'=> rd_new($rd_b, $d2_b)]
  ];
}

function rd_now($rd, $day) {
  return min(sqrt($rd ** 2 + C ** 2 * intdiv($day, PERIOD)), 350);
}
function g_rd($rd) {
  return 1 / sqrt(1 + (3 * Q ** 2 * $rd ** 2) / pi() ** 2);
}
function ex($r1, $r2, $g) {
  return 1 / (1 + pow(10, ($g * ($r2 - $r1) / 400)));
}
function d2($g, $e) {
  return 1 / (Q ** 2 * $g ** 2 * $e * (1 - $e));
}
function k_factor($rd, $d2) {
  return Q / (1 / $rd ** 2 + 1 / $d2);
}
function rd_new($rd, $d2) {
  return round(sqrt((1 / $rd ** 2 + 1 / $d2) ** -1), 0);
}

まず、PERIODとYEARという定数を設定しています。
これは、レーティング偏差が50から350まで減衰するのにどれだけかかるのかを計算する式にある定数cを求めるために必要です。

PERIOD(レーティング期間, 単位は日)は、最後の試合があってからこの日数の間はレーティング偏差は変わらない期間のことです。
YEAR(年数)は、最後の試合があってから、レーティング偏差が350まで大きくなる(信頼度が最大まで落ちる)までの期間のことです。

チェスの場合、PERIODが1ヶ月、YEARは10年~40年に設定されているようです。チェスが個人競技でプレーヤーの実力も急には変わらないだろうという意味で長めに設定されています。
一方、サッカーの場合はチームスポーツであり、毎試合メンバーが変わり、毎年選手が加入・退団することもあって、チェスよりもずっと早く、実力が変化するものと考えられます。

ここでは仮に、PERIODを半月(15日)、YEARを2年と設定しました。
2年も経てば、別のチームと言っても問題ないかな、という経験則からです。

$a0と$b0に、各チームのレーティング値(r)とレーティング偏差(rd)、前の試合からの経過日数(day)を代入し、$scoreに試合結果(得点)を入れてから、calc_glicko()に値を渡しています。

計算の結果、新しいレーティング値(r)と新しいレーティング偏差(rd)が返ってきますので、それを表示して終わりです。

応用バージョン

次に、得点差とホームアドバンテージを考慮したバージョンです。

<?php

define('PERIOD', 15);
define('YEAR', 2);
define('HA', 50);

define('C', sqrt((350 ** 2 - 50 ** 2) / (365 * YEAR / PERIOD)));
define('Q', log(10) / 400);

$a0 = ['r'=>1500, 'rd'=>350, 'day'=>7];
$b0 = ['r'=>1500, 'rd'=>350, 'day'=>7];
$score = [1, 2];

[$a, $b] = calc_glicko2($a0, $b0, $score);

echo 'A=' , $a['r'], ', B=', $b['r'] , ', ';
echo 'RD_A=' , $a['rd'], ', RD_B=', $b['rd'];

exit;

function calc_glicko2($a, $b, $sc) {

  $dg = $sc[0] - $sc[1];
  $s = $dg == 0 ? 0.5 : 0.5 / (2 ** -$dg + 1) + 0.5 * ($dg > 0);

  $rd_a = rd_now($a['rd'], $a['day']);
  $rd_b = rd_now($b['rd'], $b['day']);

  $g_a = g_rd($rd_b);
  $g_b = g_rd($rd_a);

  $e_a = ex($a['r'] + HA, $b['r'], $g_a);
  $e_b = ex($b['r'], $a['r'] + HA, $g_b);

  $d2_a = d2($g_a, $e_a);
  $d2_b = d2($g_b, $e_b);

  $dr_a = round(k_factor($rd_a, $d2_a) * $g_a * ($s - $e_a), 0);
  $dr_b = round(k_factor($rd_b, $d2_b) * $g_b * ((1 - $s) - $e_b), 0);

  return [
    ['r' => $a['r'] + $dr_a, 'rd'=> rd_new($rd_a, $d2_a)],
    ['r' => $b['r'] + $dr_b, 'rd'=> rd_new($rd_b, $d2_b)]
  ];
}

function rd_now($rd, $day) {
  return min(sqrt($rd ** 2 + C ** 2 * intdiv($day, PERIOD)), 350);
}
function g_rd($rd) {
  return 1 / sqrt(1 + (3 * Q ** 2 * $rd ** 2) / pi() ** 2);
}
function ex($r1, $r2, $g) {
  return 1 / (1 + pow(10, ($g * ($r2 - $r1) / 400)));
}
function d2($g, $e) {
  return 1 / (Q ** 2 * $g ** 2 * $e * (1 - $e));
}
function k_factor($rd, $d2) {
  return Q / (1 / $rd ** 2 + 1 / $d2);
}
function rd_new($rd, $d2) {
  return round(sqrt((1 / $rd ** 2 + 1 / $d2) ** -1), 0);
}

シンプルバージョンとほとんど変わりませんが、
定数HA(ホームアドバンテージ)を最初に加え、
calc_glicko2() の最初の2行で得点差によって$sの値を変化させて、
$e_a、$e_b(期待勝率)を求める時に、ホームチーム側にHAを足しています。

以上がPHPを使ったグリコレーティングの算出コードです。
定数の値は仮です。

PHP code for Glicko Rating System (Soccer Teams ver.)
Including goal difference and home advantage

この記事が気に入ったらサポートをしてみませんか?