レーティング差推計
by mizarjp
HTML
<script src="//cdn.jsdelivr.net/npm/jstat@latest/dist/jstat.min.js"></script>
<h1>勝敗数→レーティング差推計</h1>
<p class="form">
勝数:<input type="number" id="win" min="0" max="999999999999" step="1" />,
引分:<input type="number" id="draw" min="0" max="999999999999" step="1" />,
敗数:<input type="number" id="lose" min="0" max="999999999999" step="1" />
<button id="calcbtn">recalc</button>
</p>
<p>
<select id="calcptn">
<option value="all">全種計算</option>
<option value="draw0">引分=無効</option>
<option value="draw1" selected>引分=0.5勝</option>
<option value="draw2">引分=0.5勝(端数無効)</option>
<option value="draw3">引分=0.5勝(端数切下げ/切上げ)</option>
</select>
<select id="relptn">
<option value="all" selected>全信頼度</option>
<option value="percent">パーセント点</option>
<option value="sigma">正規分布σ</option>
</select>
</p>
<pre id="preout"></pre>
<table><thead><th>信頼度</th><th colspan="5">信頼区間</th><th>区間幅</th><th>処理区分</th></thead><tbody id="tout"></tbody></table>
<ul>
<li>例: 信頼度「2.326σ: 98.0000000%」の場合、正規分布のグラフ上では 平均値±2.326σ の範囲に 98% が入ること、二項分布において信頼区間の範囲に収まる面積が 98% 、区間の下にぶれる可能性が 1% {=(100%-98%)/2} 、上にぶれる可能性が 1% である事を示しています。</li>
<li>「信頼区間」「区間幅」は、「区間下側のレーティング差(勝率)」~「区間上側のレーティング差(勝率)」、「区間上側のレーティング差-区間下側のレーティング差」をそれぞれ表記しています。</li>
<li>「引分」の信頼区間計算における扱い(例: Win-Draw-Lose: 1-3-1 の場合)<ul>
<li>「引分=無効」: Win-Lose: 1-1 として計算</li>
<li>「引分=0.5勝」: Win-Lose: 2.5-2.5 として計算</li>
<li>「引分=0.5勝(端数無効)」: Win-Lose: 2-2 として計算(引分1を無効扱い)</li>
<li>「引分=0.5勝(端数切下げ/切上げ)」: 下限は Win-Lose: 2-3 、上限は Win-Lose: 3-2 として計算</li>
</ul></li>
<li>入力される値によっては(特に小さすぎたり大きすぎたり、極端な値が入力される場合)、必ずしも表示される精度がそのまま有効な精度とは限りません。</li>
<li><a href="https://github.com/jstat/jstat">jStat</a> - JavaScript Statistical Library</li>
<li><a href="https://github.com/tibigame/HandicappedRook/tree/master/tool">tibigame/HandicappedRook/tool</a> - original code</li>
</ul>
<h2>レーティング差早見表</h2>
<table>
<thead><th>レート差</th><th>勝率</th><th>敗率</th></thead>
<tbody id="elorating"></tbody>
</table>
CSS
p.form {
font-family: monospace;
}
input {
text-align: right;
}
table {
border-collapse: collapse;
}
th {
color: #333;
background-color: #ddd;
border: 1px solid #b9b9b9;
}
td {
padding: 2px;
color: #333;
background-color: #fff;
border: 1px solid #b9b9b9;
font-family: 'Inconsolata', 'Consolas', monospace;
}
td.right {
text-align: right;
}
td.reliability {
background-color: #ffffff;
}
td.draw0 {
background-color: #ffffff;
}
td.draw1 {
background-color: #f7f7f7;
}
td.draw2 {
background-color: #eeeeee;
}
td.draw3 {
background-color: #e6e6e6;
}
JavaScript
function elorating(winrate)
{
if (winrate <= 0) return Number.NEGATIVE_INFINITY;
if (winrate >= 1) return Number.POSITIVE_INFINITY;
return 400 * Math.log10(winrate / (1 - winrate));
}
function elorating_inv(rdiff)
{
return 1 / (1 + Math.exp(-Math.LN10 * 0.0025 * rdiff));
}
function clopper_pearson(k, n, alpha)
{
var alpha2 = (1 - alpha) / 2;
// 勝率・敗率をあわせて計算(0%,100%に近い勝率の情報落ち・桁落ち誤差を減らすため)
// beta.inv(p, a, b) == 1 - beta.inv(1 - p, b, a)
var lower_w = jStat.beta.inv(alpha2, k, n - k + 1); // 勝率
var lower_l = jStat.beta.inv(1 - alpha2, n - k + 1, k); // 敗率
var upper_w = jStat.beta.inv(1 - alpha2, k + 1, n - k); // 勝率
var upper_l = jStat.beta.inv(alpha2, n - k, k + 1); // 敗率
return {
lower: Number.isNaN(lower_w) ? Number.isNaN(lower_l) ? 0 : 1 - lower_l : (lower_w <= 0.5 || Number.isNaN(lower_l)) ? lower_w : 1 - lower_l,
upper: Number.isNaN(upper_w) ? Number.isNaN(upper_l) ? 1 : 1 - upper_l : (upper_w <= 0.5 || Number.isNaN(upper_l)) ? upper_w : 1 - upper_l,
lower_win: lower_w,
lower_lose: lower_l,
upper_win: upper_w,
upper_lose: upper_l,
lowerrate: Number.isNaN(lower_w) ? Number.isNaN(lower_l) ? Number.NEGATIVE_INFINITY : -elForate(lower_l) : (lower_w <= 0.5 || Number.isNaN(lower_l)) ? elorating(lower_w) : -elorating(lower_l),
upperrate: Number.isNaN(upper_w) ? Number.isNaN(upper_l) ? Number.POSITIVE_INFINITY : -elorating(upper_l) : (upper_w <= 0.5 || Number.isNaN(upper_l)) ? elorating(upper_w) : -elorating(upper_l),
k: k,
n: n,
alpha: alpha
};
}
function addtd(parent, text, classes, attr)
{
if(!text) text = "";
if(!classes) classes = "";
if(!attr) attr = {};
var elem = document.createElement("td");
text.split("\n").forEach(function(t, i, a)
{
elem.appendChild(document.createTextNode(t));
if(i + 1 < a.length)
elem.appendChild(document.createElement("br"));
});
classes.split(" ").forEach(function(c){
if(c !== "") elem.classList.add(c);
});
...