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);
});
Object.keys(attr).forEach(function(key)
{
elem.setAttribute(key, attr[key]);
});
parent.appendChild(elem);
}
function calc_dist()
{
var calcptn_e = document.getElementById("calcptn");
var calcptn = calcptn_e.options[calcptn_e.selectedIndex].value;
var relptn_e = document.getElementById("relptn");
var relptn = relptn_e.options[relptn_e.selectedIndex].value;
var params = [];
var params_percent = [
0.25, 0.50, 0.75,
0.80, 0.90,
0.95, 0.98, 0.99,
0.998, 0.999,
0.9998, 0.9999,
0.99998, 0.99999,
0.999998, 0.999999,
0.9999998, 0.9999999,
0.99999998, 0.99999999,
0.999999998, 0.999999999
];
var params_sigma = [
0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0
].map(function(v){ return jStat.erf(v * Math.SQRT1_2); });
switch(relptn)
{
case "all":
params = params_percent.concat(params_sigma);
params.sort(function(a, b){ return a - b; });
break;
case "percent":
params = params_percent;
break;
case "sigma":
params = params_sigma;
break;
}
var win = Math.max(Math.round(+document.getElementById("win").value), 0);
var draw = Math.max(Math.round(+document.getElementById("draw").value), 0);
var lose = Math.max(Math.round(+document.getElementById("lose").value), 0);
var match = win + lose;
var allmatch = win + draw + lose;
var pretext =
"Win-Draw-Lose: " + win + "-" + draw + "-" + lose + "\n" +
"有効試合数: " + match + ", " +
"有効勝率: " + (100 * win / match).toFixed(2) + "%, " +
"有効ΔR: " + (win >= lose && match > 0 ? "+" : "") + elorating(win / match).toFixed(2) + "\n" +
"総試合数: " + allmatch + ", " +
"勝率(引分0.5勝換算): " + (100 * (0.5 * draw + win) / allmatch).toFixed(2) + "%, " +
"ΔR(引分0.5勝換算): " + (win >= lose && allmatch > 0 ? "+" : "") + elorating((0.5 * draw + win) / allmatch).toFixed(2) + ", " +
"引分率: " + (100 * draw / allmatch).toFixed(2) + "%";
var docf = document.createDocumentFragment();
params.forEach(function(p)
{
var tr = document.createElement("tr");
addtd(tr, jStat.normal.inv(p * 0.5 + 0.5, 0, 1).toFixed(3) + "σ: " + (p * 100).toFixed(7) + "%", "reliability", calcptn == "all" ? {rowSpan: 4} : {});
if(calcptn == "draw0" || calcptn == "all")
{
var result1 = clopper_pearson(win, match, p);
addtd(tr, (result1.lowerrate >= 0 ? "+" : "") + result1.lowerrate.toFixed(2), "right draw0");
addtd(tr, "(" + (100 * result1.lower).toFixed(2) + "%)", "right draw0");
addtd(tr, "~", "draw0");
addtd(tr, (result1.upperrate >= 0 ? "+" : "") + result1.upperrate.toFixed(2), "right draw0");
addtd(tr, "(" + (100 * result1.upper).toFixed(2) + "%)", "right draw0");
addtd(tr, (result1.upperrate - result1.lowerrate).toFixed(2), "right draw0");
addtd(tr, "引分=無効", "draw0");
docf.appendChild(tr);
tr = document.createElement("tr");
}
if(calcptn == "draw1" || calcptn == "all")
{
var result2 = clopper_pearson(0.5 * draw + win, allmatch, p, "draw1");
addtd(tr, (result2.lowerrate >= 0 ? "+" : "") + result2.lowerrate.toFixed(2), "right draw1");
addtd(tr, "(" + (100 * result2.lower).toFixed(2) + "%)", "right draw1");
addtd(tr, "~", "draw1");
addtd(tr, (result2.upperrate >= 0 ? "+" : "") + result2.upperrate.toFixed(2), "right draw1");
addtd(tr, "(" + (100 * result2.upper).toFixed(2) + "%)", "right draw1");
addtd(tr, (result2.upperrate - result2.lowerrate).toFixed(2), "right draw1");
addtd(tr, "引分=0.5勝", "draw1");
docf.appendChild(tr);
tr = document.createElement("tr");
}
if(calcptn == "draw2" || calcptn == "all")
{
var result3 = clopper_pearson(Math.floor(0.5 * draw) + win, Math.floor(0.5 * draw) * 2 + match, p);
addtd(tr, (result3.lowerrate >= 0 ? "+" : "") + result3.lowerrate.toFixed(2), "right draw2");
addtd(tr, "(" + (100 * result3.lower).toFixed(2) + "%)", "right draw2");
addtd(tr, "~", "draw2");
addtd(tr, (result3.upperrate >= 0 ? "+" : "") + result3.upperrate.toFixed(2), "right draw2");
addtd(tr, "(" + (100 * result3.upper).toFixed(2) + "%)", "right draw2");
addtd(tr, (result3.upperrate - result3.lowerrate).toFixed(2), "right draw2");
addtd(tr, "引分=0.5勝(端数無効)", "draw2");
docf.appendChild(tr);
tr = document.createElement("tr");
}
if(calcptn == "draw3" || calcptn == "all")
{
var result4 = clopper_pearson(Math.floor(0.5 * draw) + win, allmatch, p);
var result5 = clopper_pearson(Math.ceil(0.5 * draw) + win, allmatch, p);
addtd(tr, (result4.lowerrate >= 0 ? "+" : "") + result4.lowerrate.toFixed(2), "right draw3");
addtd(tr, "(" + (100 * result4.lower).toFixed(2) + "%)", "right draw3");
addtd(tr, "~", "draw3");
addtd(tr, (result5.upperrate >= 0 ? "+" : "") + result5.upperrate.toFixed(2), "right draw3");
addtd(tr, "(" + (100 * result5.upper).toFixed(2) + "%)", "right draw3");
addtd(tr, (result5.upperrate - result4.lowerrate).toFixed(2), "right draw3");
addtd(tr, "引分=0.5勝(端数切下げ/切上げ)", "draw3");
docf.appendChild(tr);
tr = document.createElement("tr");
}
});
document.getElementById("preout").innerText = pretext;
var tout = document.getElementById("tout");
while (tout.firstChild) { tout.removeChild(tout.firstChild); }
tout.appendChild(docf);
}
calc_dist();
document.getElementById("win").onchange = calc_dist;
document.getElementById("draw").onchange = calc_dist;
document.getElementById("lose").onchange = calc_dist;
document.getElementById("calcbtn").onclick = calc_dist;
document.getElementById("calcptn").onchange = calc_dist;
document.getElementById("relptn").onchange = calc_dist;
var elor = [
0.5, 0.51, 0.52, 0.53, 0.54, 0.55, 0.56, 0.57, 0.58, 0.59,
0.6, 0.61, 0.62, 0.63, 0.64, 0.65, 0.66, 0.67, 0.68, 0.69,
0.7, 0.71, 0.72, 0.73, 0.74, 0.75, 0.76, 0.77, 0.78, 0.79,
0.8, 0.81, 0.82, 0.83, 0.84, 0.85, 0.86, 0.87, 0.88, 0.89,
0.9, 0.91, 0.92, 0.93, 0.94, 0.95, 0.96, 0.97, 0.98, 0.99,
0.991, 0.992, 0.993, 0.994, 0.995, 0.996, 0.997, 0.998, 0.999,
0.9991, 0.9992, 0.9993, 0.9994, 0.9995, 0.9996, 0.9997, 0.9998, 0.9999,
0.99991, 0.99992, 0.99993, 0.99994, 0.99995, 0.99996, 0.99997, 0.99998, 0.99999
].map(function(v){ return elorating(v); }).concat([
5, 10, 15, 20, 25, 30, 35, 40, 45, 50,
55, 60, 65, 70, 75, 80, 85, 90, 95, 100,
110, 120, 130, 140, 150, 160, 170, 180, 190, 200,
210, 220, 230, 240, 250, 260, 270, 280, 290, 300,
310, 320, 330, 340, 350, 360, 370, 380, 390, 400,
410, 420, 430, 440, 450, 460, 470, 480, 490, 500,
550, 600, 650, 700, 750, 800, 850, 900, 950, 1000,
1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900
]);
elor.sort(function(a, b){ return a - b; });
elor.forEach(function(v)
{
var tr = document.createElement("tr");
addtd(tr, (v >= 0 ? "+" : "") + v.toFixed(2), "right");
addtd(tr, (elorating_inv(v) * 100).toFixed(3) + "%", "right");
addtd(tr, (elorating_inv(-v) * 100).toFixed(3) + "%", "right");
document.getElementById("elorating").appendChild(tr);
});
<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>
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; }
External resources loaded into this fiddle: