Giter Site home page Giter Site logo

glicko2js's Introduction

Glicko 2 javascript implementation

Build Status

The Glicko-2 rating system is a method for assessing a player's strength in games of skill, such as chess and go. The algorithm is explained by its author, Mark E. Glickman, on http://glicko.net/glicko.html.

Each player begins with a rating, a rating deviation (accuracy of the rating) and a volatility (speed of rating evolution). These values will evolve according to the outcomes of matches with other players.

Installation

As a node.js module

glicko2.js is available as a npm module.

Install with npm:

npm install glicko2

or with yarn:

yarn add glicko2

In the browser

You just need to include the glicko2.js script. See index.html in the example folder.

<script src="glicko2.js"></script>

With bower

bower install glicko2
<script src="bower_components/glicko2/glicko2.js"></script>

Usage

In node.js, just require the module :

import { Glicko2 } from 'glicko2';

or

const { Glicko2 } = require('glicko2');

First we initiate a ranking manager and create players with initial ratings, rating deviations and volatilities.

const ranking = new Glicko2({
  // tau : "Reasonable choices are between 0.3 and 1.2, though the system should
  //      be tested to decide which value results in greatest predictive accuracy."
  // If not set, default value is 0.5
  tau: 0.5,

  // rating : default rating
  // If not set, default value is 1500
  rating: 1500,

  // rd : Default rating deviation
  //     small number = good confidence on the rating accuracy
  // If not set, default value is 350
  rd: 200,

  // vol : Default volatility (expected fluctation on the player rating)
  // If not set, default value is 0.06
  vol: 0.06,
});

// Create players
const Ryan = ranking.makePlayer();
const Bob = ranking.makePlayer(1400, 30, 0.06);
const John = ranking.makePlayer(1550, 100, 0.06);
const Mary = ranking.makePlayer(1700, 300, 0.06);

We can then enter results, calculate the new ratings...

const matches = [];

matches.push([Ryan, Bob, 1]); //Ryan won over Bob
matches.push([Ryan, John, 0]); //Ryan lost against John
matches.push([Ryan, Mary, 0.5]); //A draw between Ryan and Mary

ranking.updateRatings(matches);

... and get these new ratings.

console.log("Ryan new rating: " + Ryan.getRating());
console.log("Ryan new rating deviation: " + Ryan.getRd());
console.log("Ryan new volatility: " + Ryan.getVol());

Get players list

const players = ranking.getPlayers();

Predict outcome

const expected = ranking.predict(Ryan, Bob); // or Ryan.predict(Bob);
console.log("Ryan has " + (expected * 100) + "% chances of winning against Bob in the next match");

When to update rankings

You should not update the ranking after each match. The typical use of glicko is to calculate the ratings after each tournament (ie collection of matches in a period of time). A player rating will evolve after a tournament has finished, but not during the tournament.

Here is what says Mark E. Glickman about the number of matches in a tournament or rating period (cf. http://www.glicko.net/glicko/glicko2.pdf) :

The Glicko-2 system works best when the number of games in a rating period is moderate to large, say an average of at least 10-15 games per player in a rating period.

How to deal with a big database of players

If you don't want to load all the players and new matches at once in memory in order to update rankings, here is a strategy you can follow:

Say you want to update rankings each week, and you have a history of the rankings data (rating, rating deviation, volatility) for each player.

At the end of the week, for each player (even those that did not play):

  • load from the database:
    • the player ranking data for the current week
    • the matches the player has played during the week
    • for each match, the player's opponent data for the current week
  • add the player and his opponents to a new glicko instance
  • add the matches to the glicko instance
  • update glicko rankings
  • save the player updated ranking data to database as the next week player data

It is important to update rankings even for players that did not play : this is the way the system can increase their rating deviation over time.

At the last step, don't overwrite the player current week data, as it will be loaded when calculating its opponents new rankings.

Support for multiple competitors matches (experimental)

Note: the glicko2 algorithm was not designed for multiple competitors matches, this is a naive workaround whose results should be taken whith caution.

You can enter results from games where multiple competitors play against each other at the same time (ie swimming, racing...).

First make "Race" objects by entering the results in an array of "positions", where each position is an array of players at this position :

const race1 = glicko.makeRace(
    [
        [Ryan], //Ryan won the race
        [Bob, John], //Bob and John ended ex aequo at the 2nd position
        [Mary] // Mary 4th position
    ]
);

const race2 = glicko.makeRace(
    [
        [Mary], // won
        [Bob],  // 2nd
        [John], // 3rd
        [Ryan], // 4th
    ]
);

Then convert the races to the equivalent matches :

const matches1 = race1.getMatches();
const matches2 = race2.getMatches();

const allMatches = matches1.concat(matches2)

ranking.updateRatings(allMatches);

You can also update ratings for one race without converting to matches :

ranking.updateRatings(race1);

Testing

Run unit tests with:

npm run test

License

This library is under MIT License.

glicko2js's People

Contributors

alcalyn avatar dependabot[bot] avatar icecream17 avatar kenany avatar mmai avatar timurbk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

glicko2js's Issues

Question about increased RD over time

In reading about Glicko2 I read that the RD of a player should increase over time if they haven't played any matches. Is that accurate and if so, does this library have a mechanism to handle it? If we only track rating, RD, and volitility how would the system know if a match hasn't been played for a long time?

Does not clear "matches" array for subsequent tournaments

The bug can be seen in the example "index.html".

The first tournaments results are correct. But in subsequent tournaments it reapplies the first tournament results (as if the matches were repeated), resulting in incorrect rating calculation.

The issue can be clearly seen by having a 2nd tournament without matches, but with a rating update.

UPDATE: After looking more into it, the issue may be due to not clearing outcomes after each tournament.

Issues when reaching around 200 games

Hi. I am currently using this package to calculate player rating after matches (5v5). This works splendid, except when a player is closing in on around 200 games played. It then swings wildly, to the point that it will overflow an int after 1-3 games. Any idea as to what might cause this?

[Suggestion] Change affect of races on rating, deviation, and volatility

To my knowledge Dr. Glickman doesn't talk about >2 person competitions at all, so the math behind the Race features is unique to this module. My understanding is that the way it's currently implemented, when people compete in a race the rating system treats that as if they each participated in a separate complete match with each other player.

This implies that if a player loses a game with 7 other competitors, it would be the same as losing to each of those competitors individually. While I would agree that losing a game with 7 competitors should be more impactful than losing 1 game to 1 competitor, I don't think it's the same as losing 7 separate games to single competitors. It ignores volatility and applies way too much confidence for a single performance.

I don't have a great solution for this. It seems like it would take a modification to the Glicko-2 algorithm itself to make this accurate. It "feels" like it should be akin to losing 7 games for how it affects your score, but should only affect your volatility as a single game would. Confidence would maybe be somewhere in the middle? I'm not sure.

If someone who has a better grasp of the algorithm itself and how all the pieces fits together has an idea of how this should work, it would be great if you chimed in.

What would be helpful in the interim would be some kind of dynamic weighting that could be passed into a match that would reduce it's overall affect on the results. Either something global, like "matchWeight" or something more granular, like "ratingWeight", "rdWeight", and "volWeight". These could be globally configured before every match, or passed into the Race and match objects.

If none of that, I'll probably need to do some kind of post processing hack, or download and edit the source rather than using the package, neither of which are great.

Add support for prediction?

Hi!

Thanks for this! Haven't fully tested it yet, but seems to be working pretty well.

I was wondering if you considered adding a function that calculates the estimated outcome of a match based on the data available. Basically you provide the pairing and it returns the expected outcome, which is similar to the E() function, but not quite. Found this in an r/chess thread that looks good:
Let P2 = Expected outcome for player 2. Then:
P2 = 1 / (1 + e-A)
with
A = g(sqrt(r12+r22)) * (s2-s1))
and
g(x) = 1/sqrt(1+3x2/pi2)

Best,
Mariano.

[Suggestion] Reduce player initialization complexity

In order to initialize a player they have to be entered in the //Make players section and then also in the var players = [ section. I have to manage a tournament with a huge number of participants and this doubles the work.

is there any way to initialize a player only with glicko.makePlayer? Maybe an optional mode to reduce complexity.

Many thanks for your work. I follow and use it since the beginning. In 2013 I reported a bug: #2

explanation on your example

Can you explain this example more please. It looks like 1700, 1400, 1550 are the players original ratings and 0.06 is the volatility. But what the values 300, 30, and 100 for and why are they different per player.

Bob = glicko.makePlayer(1400, 30, 0.06);
John = glicko.makePlayer(1550, 100, 0.06);

Typescript

I try to put this file in typescript but maybe its not finished but you have a "get started" ...

const scalingFactor = 173.7178;

class Race {
    matches: any[]
    constructor(results: any) { this.matches = this.computeMatches(results); }
    computeMatches = function (results) {
        let players: { player: string, position: number }[] = [];
        let position = 0;

        results.forEach(function (rank) {
            position += 1;
            rank.forEach(function (player) { players.push({ "player": player, "position": position }); })
        })

        function computeMatches(players: any) {
            if (players.length === 0) return [];

            const player1 = players.shift()
            const player1_results = players.map(function (player2) {
                return [player1.player, player2.player, (player1.position < player2.position) ? 1 : 0.5];
            });

            return player1_results.concat(computeMatches(players));
        }
        return computeMatches(players)
    }
}

class Player {
    _tau = 0; defaultRating = 1500; volatility_algorithm: (v: any, delta: any) => number | undefined = undefined; id = ""; adv_ranks: number[] = []; adv_rds: number[] = []; outcomes = []; __rating = 0; __rd = 0; __vol = 0;

    getRating = function () { return this.__rating * scalingFactor + this.defaultRating; };
    setRating = function (rating) { this.__rating = (rating - this.defaultRating) / scalingFactor; };
    getRd = function () { return this.__rd * scalingFactor; };
    setRd = function (rd) { this.__rd = rd / scalingFactor; };
    getVol = function () { return this.__vol; };
    hasPlayed = function () { return this.outcomes.length > 0; };
    _preRatingRD = function () { this.__rd = Math.sqrt(Math.pow(this.__rd, 2) + Math.pow(this.__vol, 2)); };

    addResult = function (opponent: Player, outcome) {
        this.adv_ranks.push(opponent.__rating);
        this.adv_rds.push(opponent.__rd);
        this.outcomes.push(outcome);
    };

    update_rank = function () {
        if (!this.hasPlayed()) { this._preRatingRD(); return; }
        const v: number = this._variance();
        const delta = this._delta(v);
        this.__vol = this.volatility_algorithm(v, delta);
        this._preRatingRD();
        this.__rd = 1 / Math.sqrt((1 / Math.pow(this.__rd, 2)) + (1 / v));

        let tempSum = 0;
        for (let i = 0, len = this.adv_ranks.length; i < len; i++) {
            tempSum += this._g(this.adv_rds[i]) * (this.outcomes[i] - this._E(this.adv_ranks[i], this.adv_rds[i]));
        }
        this.__rating += Math.pow(this.__rd, 2) * tempSum;
    };

    _variance = function (): number {
        let tempSum = 0;
        for (let i = 0, len = this.adv_ranks.length; i < len; i++) {
            let tempE = this._E(this.adv_ranks[i], this.adv_rds[i]);
            tempSum += Math.pow(this._g(this.adv_rds[i]), 2) * tempE * (1 - tempE);
        }
        return 1 / tempSum;
    };

    // The Glicko E function.
    _E = function (p2rating: number, p2RD: number) { return 1 / (1 + Math.exp(-1 * this._g(p2RD) * (this.__rating - p2rating))); };

    predict = function (p2) {
        const diffRD = Math.sqrt(Math.pow(this.__rd, 2) + Math.pow(p2.__rd, 2));
        return 1 / (1 + Math.exp(-1 * this._g(diffRD) * (this.__rating - p2.__rating)));
    };

    _g = function (RD: number) { return 1 / Math.sqrt(1 + 3 * Math.pow(RD, 2) / Math.pow(Math.PI, 2)); };

    // The delta function of the Glicko2 system.
    // Calculation of the estimated improvement in rating (step 4 of the algorithm)
    _delta = function (v: number) {
        let tempSum = 0;
        for (let i = 0, len = this.adv_ranks.length; i < len; i++) {
            tempSum += this._g(this.adv_rds[i]) * (this.outcomes[i] - this._E(this.adv_ranks[i], this.adv_rds[i]));
        }
        return v * tempSum;
    };

    _makef = function (delta: number, v: number, a: number) {
        return function (x) {
            return Math.exp(x)
                * (Math.pow(delta, 2)
                    - Math.pow(this.__rd, 2)
                    - v - Math.exp(x)) / (2 * Math.pow(Math.pow(this.__rd, 2)
                        + v + Math.exp(x), 2)) - (x - a) / Math.pow(this._tau, 2);
        };
    };
    constructor(rating: number, rd: number, vol: number, tau: number, default_rating: number, volatility_algorithm: (v: any, delta: any) => number, id: string) {
        this.volatility_algorithm = volatility_algorithm; this.setRating(rating); this.setRd(rd);
        this._tau = tau; this.__rating = rating; this.__rd = rd; this.__vol = vol; this.id = id; this.defaultRating = default_rating;
    }
}

class Glicko2 {
    settings: Partial<{ _tau: number, rd: number, vol: number, volatility_algorithm: any, rating: number }> = {};
    _default_rating = 1500 || this.settings.rating;
    _default_rd = this.settings.rd || 350;
    _tau: number = 0.5;
    _default_vol = this.settings.vol || 0.06;
    _volatility_algorithm = volatility_algorithms[this.settings.volatility_algorithm || 'newprocedure'];
    players = [];
    players_index = 0;

    constructor(settings: Partial<{ _tau: number, rd: number, vol: number, volatility_algorithm: any, rating: number }>) { this.settings = settings; this._tau = settings._tau }

    makeRace = function (results) { return new Race(results) };
    removePlayers = function () { this.players = []; this.players_index = 0; };
    getPlayers = function () { return this.players; };
    cleanPreviousMatches = function () {
        for (let i = 0, len = this.players.length; i < len; i++) {
            this.players[i].adv_ranks = [];
            this.players[i].adv_rds = [];
            this.players[i].outcomes = [];
        }
    };

    calculatePlayersRatings = function () {
        const keys = Object.keys(this.players);
        for (let i = 0, len = keys.length; i < len; i++) { this.players[keys[i]].update_rank(); }
    };

    addMatch = function (player1, player2, outcome) {
        let pl1 = this._createInternalPlayer(player1.rating, player1.rd, player1.vol, player1.id);
        let pl2 = this._createInternalPlayer(player2.rating, player2.rd, player2.vol, player2.id);
        this.addResult(pl1, pl2, outcome);
        return { pl1: pl1, pl2: pl2 };
    };

    makePlayer = function (rating, rd, vol) {
        //We do not expose directly createInternalPlayer in order to prevent the assignation of a custom player id whose uniqueness could not be guaranteed
        return this._createInternalPlayer(rating, rd, vol);
    };

    _createInternalPlayer = function (rating, rd, vol, id) {
        if (id === undefined) { id = this.players_index; this.players_index = this.players_index + 1; }
        else { const candidate = this.players[id]; if (candidate !== undefined) { return candidate; } }
        const player = new Player(rating || this._default_rating, rd || this._default_rd, vol || this._default_vol,
            this._tau, this._default_rating, this._volatility_algorithm, id);

        this.players[id] = player;
        return player;
    };
    addResult = function (player1: Player, player2: Player, outcome: number) { player1.addResult(player2, outcome); player2.addResult(player1, 1 - outcome); };
    updateRatings = function (matches) {
        if (matches instanceof Race) { matches = matches.matches; }
        if (typeof (matches) !== 'undefined') {
            this.cleanPreviousMatches();
            for (let i = 0, len = matches.length; i < len; i++) { const match = matches[i]; this.addResult(match[0], match[1], match[2]); }
        }
        this.calculatePlayersRatings();
    };
    predict = function (player1, player2) { return player1.predict(player2); };
}

const volatility_algorithms = {
    oldprocedure: function (v, delta) {
        const sigma = this.__vol;
        const phi = this.__rd;
        const tau = this._tau;
        let a, x1, x2, x3, y1, y2, y3, upper;
        let result;

        upper = find_upper_falsep(phi, v, delta, tau);
        a = Math.log(Math.pow(sigma, 2));
        y1 = equation(phi, v, 0, a, tau, delta);
        if (y1 > 0) { result = upper; }
        else {
            x1 = 0; x2 = x1; y2 = y1; x1 = x1 - 1; y1 = equation(phi, v, x1, a, tau, delta);
            while (y1 < 0) { x2 = x1; y2 = y1; x1 = x1 - 1; y1 = equation(phi, v, x1, a, tau, delta); }
            for (let i = 0; i < 21; i++) {
                x3 = y1 * (x1 - x2) / (y2 - y1) + x1;
                y3 = equation(phi, v, x3, a, tau, delta);
                if (y3 > 0) { x1 = x3; y1 = y3; }
                else { x2 = x3; y2 = y3; }
            }
            if (Math.exp((y1 * (x1 - x2) / (y2 - y1) + x1) / 2) > upper) { result = upper; }
            else { result = Math.exp((y1 * (x1 - x2) / (y2 - y1) + x1) / 2); }
        }
        return result;

        function new_sigma(sigma, phi, v, delta, tau) {
            const a = Math.log(Math.pow(sigma, 2));
            let x = a;
            let old_x = 0;
            while (x != old_x) {
                old_x = x;
                const d = Math.pow(phi, 2) + v + Math.exp(old_x);
                const h1 = -(old_x - a) / Math.pow(tau, 2) - 0.5 * Math.exp(old_x) / d + 0.5 * Math.exp(old_x) * Math.pow((delta / d), 2);
                const h2 = -1 / Math.pow(tau, 2) - 0.5 * Math.exp(old_x) * (Math.pow(phi, 2) + v) / Math.pow(d, 2) + 0.5 * Math.pow(delta, 2) * Math.exp(old_x) * (Math.pow(phi, 2) + v - Math.exp(old_x)) / Math.pow(d, 3);
                x = old_x - h1 / h2;
            }
            return Math.exp(x / 2);
        }

        function equation(phi: number, v: number, x: number, a: number, tau: number, delta: number) {
            const d = Math.pow(phi, 2) + v + Math.exp(x);
            return -(x - a) / Math.pow(tau, 2) - 0.5 * Math.exp(x) / d + 0.5 * Math.exp(x) * Math.pow((delta / d), 2);
        }

        function new_sigma_bisection(sigma, phi, v, delta, tau) {
            let a, x1, x2, x3;
            a = Math.log(Math.pow(sigma, 2));
            if (equation(phi, v, 0, a, tau, delta) < 0) {
                x1 = -1;
                while (equation(phi, v, x1, a, tau, delta) < 0) { x1 = x1 - 1; }
                x2 = x1 + 1;
            }
            else {
                x2 = 1;
                while (equation(phi, v, x2, a, tau, delta) > 0) { x2 = x2 + 1; }
                x1 = x2 - 1;
            }

            for (let i = 0; i < 27; i++) {
                x3 = (x1 + x2) / 2;
                if (equation(phi, v, x3, a, tau, delta) > 0) { x1 = x3; }
                else { x2 = x3; }
            }
            return Math.exp((x1 + x2) / 4);
        }

        function Dequation(phi: number, v: number, x: number, tau: number, delta: number) {
            const d = Math.pow(phi, 2) + v + Math.exp(x);
            return -1 / Math.pow(tau, 2) - 0.5 * Math.exp(x) / d + 0.5 * Math.exp(x) * (Math.exp(x) + Math.pow(delta, 2)) / Math.pow(d, 2) - Math.pow(Math.exp(x), 2) * Math.pow(delta, 2) / Math.pow(d, 3);
        }

        function find_upper_falsep(phi: number, v: number, delta: number, tau: number) {
            let x1: number, x2: number, x3: number, y1: number, y2: number, y3: number;
            y1 = Dequation(phi, v, 0, tau, delta);
            if (y1 < 0) { return 1; }
            else {
                x1 = 0; x2 = x1; y2 = y1; x1 = x1 - 1; y1 = Dequation(phi, v, x1, tau, delta);
                while (y1 > 0) { x2 = x1; y2 = y1; x1 = x1 - 1; y1 = Dequation(phi, v, x1, tau, delta); }
                for (let i = 0; i < 21; i++) {
                    x3 = y1 * (x1 - x2) / (y2 - y1) + x1;
                    y3 = Dequation(phi, v, x3, tau, delta);
                    if (y3 > 0) { x1 = x3; y1 = y3; }
                    else { x2 = x3; y2 = y3; }
                }
                return Math.exp((y1 * (x1 - x2) / (y2 - y1) + x1) / 2);
            }
        }
    },
    newprocedure: function (v: number, delta: number) {
        let A = Math.log(Math.pow(this.__vol, 2));
        let f = this._makef(delta, v, A);
        let epsilon = 0.0000001;

        let B: undefined | number;
        let k: undefined | number;
        if (Math.pow(delta, 2) > Math.pow(this.__rd, 2) + v) { B = Math.log(Math.pow(delta, 2) - Math.pow(this.__rd, 2) - v); }
        else {
            k = 1;
            while (f(A - k * this._tau) < 0) { k = k + 1; }
            B = A - k * this._tau;
        }
        let fA = f(A); let fB = f(B); let C, fC;
        while (Math.abs(B - A) > epsilon) {
            C = A + (A - B) * fA / (fB - fA);
            fC = f(C);
            if (fC * fB <= 0) { A = B; fA = fB; }
            else { fA = fA / 2; }
            B = C;
            fB = fC;
        }
        return Math.exp(A / 2);
    },
    newprocedure_mod: function (v: number, delta: number) {
        let A = Math.log(Math.pow(this.__vol, 2));
        const f = this._makef(delta, v, A);
        const epsilon = 0.0000001;
        let B: number, k: number;
        if (delta > Math.pow(this.__rd, 2) + v) { B = Math.log(delta - Math.pow(this.__rd, 2) - v); }
        else {
            k = 1;
            while (f(A - k * this._tau) < 0) { k = k + 1; }
            B = A - k * this._tau;
        }

        let fA = f(A); let fB = f(B);
        let C, fC;
        while (Math.abs(B - A) > epsilon) {
            C = A + (A - B) * fA / (fB - fA);
            fC = f(C);
            if (fC * fB < 0) { A = B; fA = fB; }
            else { fA = fA / 2; }
            B = C;
            fB = fC;
        }
        return Math.exp(A / 2);
    },
    oldprocedure_simple: function (v: number, delta: number) {
        let a = Math.log(Math.pow(this.__vol, 2)); let tau = this._tau; let x0 = a; let x1 = 0; let d, h1, h2;
        while (Math.abs(x0 - x1) > 0.00000001) {
            x0 = x1;
            d = Math.pow(this.__rating, 2) + v + Math.exp(x0);
            h1 = -(x0 - a) / Math.pow(tau, 2) - 0.5 * Math.exp(x0) / d + 0.5 * Math.exp(x0) * Math.pow(delta / d, 2);
            h2 = -1 / Math.pow(tau, 2) - 0.5 * Math.exp(x0) * (Math.pow(this.__rating, 2) + v) / Math.pow(d, 2) + 0.5 * Math.pow(delta, 2) * Math.exp(x0) * (Math.pow(this.__rating, 2) + v - Math.exp(x0)) / Math.pow(d, 3);
            x1 = x0 - (h1 / h2);
        }
        return Math.exp(x1 / 2);
    }
};

Is Glicko usable for team games?

Hi!
This is not really an issue, sorry... :-)

Is Glicko usable for team games? I mean, is there a possibility to take into account the strength of each player's team-mates, when calculating new ranking after a tournament?

I did also try Trueskill (https://www.npmjs.org/package/trueskill), which implements Trueskill algorithm from Microsoft, but it seem oriented to one-to-one games, too... :-(

Handling bonus points

Let's say I wanted to factor in other measures, like man of the match or a high score record. Is there a good way to go about handling this in glicko?

doubts about volatility computation

Hi Henri,
upon testing this lib I've come across issues regarding the computation of the volatility parameter, namely that

  1. adaptions are usually wayyy too small to be reasonable, and
  2. one can create scenarios in which the parameter gets messed up completely by the algorithms.

(Everything that follows is under the assumption that I didn't mess something up... in which case I'd appreciate a quick heads up)

Basically what I did was play a single rating period, in which a very strong and very weak low-RD player with standard volatility compete N times, and the weak one always wins. This should lead to a jump in volatility for the strong player.
I've checked against the spreadsheet based calculator http://www.bjcox.com/?page_id=20&did=11 which works like a charm and gives reasonable values for the volatility. With glicko2js, the following happens, depending on the number N.

  • N < 30: the volatility is adapted, but only in decimal places that really don't matter (173*volatility should be of a magnitude sufficient to influence the RD). For N=29, for instance, we end up with about 0.08, but the spreadsheet suggests 0.3! Your test using the example from Glickman's PDF doesn't see this issue, as there the correct adjustment is already almost zero. In any case I would suggest adding a test where the volatility actually sees an update, for instance this one!
  • N is 30. Suddenly the volatility gets messed up completely, so there's definitely something wrong:
    Mary new rating: -7643,
    Mary new rating deviation: 235,
    Mary new volatility: 6.199250862403905,

I've used the below code.

var glicko2 = require('glicko2'),
    ranking = new glicko2.Glicko2(),
    Mary = ranking.makePlayer(1700, 30, 0.06),
    Bob = ranking.makePlayer(1000, 30, 0.06),
    matches = [],
    num = 10,
    i;
console.log('Mary is high-ranked. Bob is low-ranked. Both have small RD.')
console.log("Mary rating: " + Mary.getRating());
console.log("Mary rating deviation: " + Mary.getRd());
console.log("Mary volatility: " + Mary.getVol());
console.log("Bob rating: " + Bob.getRating());
console.log("Bob rating deviation: " + Bob.getRd());
console.log("Bob volatility: " + Bob.getVol());
for (i = 0; i < num; i += 1) {
    matches.push([Mary, Bob, 0]);
}
console.log('Mary lost against Bob %d times in a row.', num);

ranking.updateRatings(matches);

console.log("Mary new rating: " + Mary.getRating());
console.log("Mary new rating deviation: " + Mary.getRd());
console.log("Mary new volatility: " + Mary.getVol());

// num = 30: volatility goes crazy
// num < 30: volatility doesn't go crazy (<0.08) but in fact is too low
// num ~ 10: volatility stays so close to 0.06 it's not even funny. It should be around 0.3!

npm version behind latest

Hello, thanks for making this library, I've learned a lot from it.

It looks like the version hosted on npm is missing the latest fix (for the undetypeof(matches)fined typo). If you have time, are you able to bump the version and upload it to npm?

Cheers

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.