# Manuel-777/MTG-Arena-Tool

Merge pull request #531 from lusbenjamin/confidence-intervals

`Deck Winrate Confidence Intervals`
Manuel-777 committed Aug 17, 2019
2 parents d92f111 + f2cfc26 commit 8883ee41df292341e19ba65e8200dae636a4451e
Showing with 32 additions and 13 deletions.
1. +13 −0 shared/stats-fns.js
2. +7 −5 window_main/aggregator.js
3. +12 −8 window_main/decks.js
 @@ -131,3 +131,16 @@ function hypergeometricSignificance( let retVal = math.subtract(1, math.multiply(weightedAverage, 2)); return returnBig ? retVal : math.number(retVal); } // Computes the Wald Interval aka Normal Approximation Interval // Useful for quickly estimating the 95% confidence interval // Can produce bad results when sample-size < 20 // https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Normal_approximation_interval // https://www.channelfireball.com/articles/magic-math-how-many-games-do-you-need-for-statistical-significance-in-playtesting/ exports.normalApproximationInterval = normalApproximationInterval; function normalApproximationInterval(matches, wins) { if (!matches) return { winrate: 0, interval: 0 }; const winrate = wins / matches; const interval = 1.96 * Math.sqrt((winrate * (1 - winrate)) / matches); return { winrate, interval }; }
 @@ -20,6 +20,7 @@ const { getReadableEvent, getRecentDeckName } = require("../shared/util"); const { normalApproximationInterval } = require("../shared/stats-fns"); // Default filter values const DEFAULT_DECK = "All Decks"; @@ -68,11 +69,12 @@ class Aggregator { static finishStats(stats) { const { wins, total } = stats; let winrate = 0; if (total) { winrate = Math.round((wins / total) * 100) / 100; } stats.winrate = winrate; const { winrate, interval } = normalApproximationInterval(total, wins); const roundWinrate = x => Math.round(x * 100) / 100; stats.winrate = roundWinrate(winrate); stats.interval = roundWinrate(interval); stats.winrateLow = roundWinrate(winrate - interval); stats.winrateHigh = roundWinrate(winrate + interval); } static getDefaultColorFilter() {
 @@ -211,12 +211,15 @@ function openDecksTab(_filters = {}, scrollTop = 0) { let colClass = getWinrateClass(dwr.winrate); deckWinrateDiv.innerHTML = `\${dwr.wins}:\${ dwr.losses } (\${formatPercent( } (\${formatPercent( dwr.winrate )})`; deckWinrateDiv.title = `\${dwr.wins} matches won : \${ dwr.losses } matches lost`; )} ± \${formatPercent( dwr.interval )})`; deckWinrateDiv.title = `\${formatPercent( dwr.winrateLow )} to \${formatPercent(dwr.winrateHigh)} with 95% confidence (estimated actual winrate bounds, assuming a normal distribution)`; listItem.rightTop.appendChild(deckWinrateDiv); const deckWinrateLastDiv = createDiv( @@ -230,9 +233,10 @@ function openDecksTab(_filters = {}, scrollTop = 0) { deckWinrateLastDiv.innerHTML += `\${formatPercent( drwr.winrate )}`; deckWinrateLastDiv.title = `\${drwr.wins} matches won : \${ drwr.losses } matches lost`; deckWinrateLastDiv.title = `\${formatPercent( drwr.winrateLow )} to \${formatPercent(drwr.winrateHigh)} with 95% confidence (estimated actual winrate bounds, assuming a normal distribution)`; } else { deckWinrateLastDiv.innerHTML += "--"; deckWinrateLastDiv.title = "no data yet";