Last time we examined the Glicko-2 model and understood how it worked mathematically. This time we shall focus on implementing the Glicko-2 system for tennis.

Let’s establish a few of the ground rules, 1) we will be using the constants mentioned last week (the base rating as 1500 and the initial deviation as 400 / ln (10. Other constants remain. 2) Despite Glicko-2’s ability to do batch updates (i.e. for a tournament), after trying both methods I found that there is no significant benefit in batch updates and, in fact, updating each match individually proves to have better accuracy. 3) We need to have two ratings per player, one on serve and one on return.

Ostensibly that’s all there is to it. Since there’s not much math to discuss in this entry we’ll look at the programming aspect. A note, where my code is not general enough you shall see pseudo code, but otherwise the code will be in Java.

How to tackle this problem? First let’s create a class that will help us update player ratings as results get added. Results will be a class in themselves, containing relevant information (player 1, player 2, ratings on serve, ratings on return).

Class Glicko2Ratings List<Rating> ratings addResult(result) IF a player from result is in ratings Use existing rating ELSE Use initial rating Add to ratings new Glicko2 ratings for result

That’s the basic formula. You add a new result from your data, upon adding that result, the model checks if that player exists in the ratings, and if so, updates it, otherwise creates a new Rating for that player. Finally you finish adding the result by updating the ratings through the Glicko-2 formula. Below is the code I used for the math in Glicko-2 in Java in case anyone would care to use it. Each of the equations discussed two weeks ago in the first part of the Glicko-2 post are implemented.

private double v(double rating, double ratingJ, double rdJ) { return Math.pow((Math.pow(g(rdJ), 2) * E(rating, ratingJ, rdJ)) * (1 - E(rating, ratingJ, rdJ)), -1); } private double g(double rd) { return 1 / Math.sqrt(1 + 3 * Math.pow(rd, 2) / Math.pow(Math.PI, 2)); } private double E(double rating, double ratingJ, double rdJ) { return 1 / (1 + Math.exp(-g(rdJ) * (rating - ratingJ))); } private double delta(double v, double rdJ, double score, double E) { return v * g(rdJ) * (score - E); } private double volPrime(double rd, double vol, double v, double delta) { double a = Math.log(Math.pow(vol, 2)); double A = a; double B = 0; if (Math.pow(delta, 2) > Math.pow(rd, 2) + v) B = Math.log(Math.pow(delta, 2) - Math.pow(rd, 2) - v); else { double k = 1; while (function(a - k * Math.sqrt(Math.pow(TAU, 2)), rd, v, delta, a) < 0) k++; B = a - k * Math.sqrt(Math.pow(TAU, 2)); } double fnA = function(A, rd, v, delta, a); double fnB = function(B, rd, v, delta, a); while (Math.abs(B - A) > EPSILON) { double C = A + (A - B) * fnA / (fnB - fnA); double fnC = function(C, rd, v, delta, a); if (fnC * fnB < 0) { A = B; fnA = fnB; } else fnA /= 2; B = C; fnB = fnC; } if (Math.abs(B - A) <= EPSILON) return Math.exp(A / 2); else throw new ArithmeticException(); } private double function(double x, double rd, double v, double delta, double a) { return (Math.exp(x) * (Math.pow(delta, 2) - Math.pow(rd, 2) - v - Math .exp(x))) / (2 * Math.pow(Math.pow(rd, 2) + v + Math.exp(x), 2)) - (x - a) / Math.pow(TAU, 2); } private double rdPrime(double rd, double v, double volPrime) { double rdStar = Math.sqrt(Math.pow(rd, 2) + Math.pow(volPrime, 2)); return 1 / Math.sqrt(1 / Math.pow(rdStar, 2) + 1 / v); } private double ratingPrime(double rating, double rdPrime, double g, double score, double E) { return rating + Math.pow(rdPrime, 2) * g * (score - E); }

That’s all there is! Now you must be asking yourself at this point… well I get a random rating now that’s on this 1500 base scale, what do? You can figure out the expected score a player will have by running the function called E (think of expected score in Elo), since that is all that equation is in Glicko-2. There you go, feel free to create and populate your own database of Glicko-2 ratings for tennis now.

Cheers!

Hi! Very informative post. I am thinking to make something similar for squash. Could you explain why you are creating seperate ratings for serve and on return? And most importantly, why you think individual updating leads to better accuracy? Cheers

Hi there – hope you don’t mind the late response – but here are some answers:

With tennis (can’t tell you about squash though), there is often significant variation in terms of a player’s ability to perform on serve vs return. There are some players who have exceptional serve win %s, possibly due to their strength, serve speed, etc. or a number of other factors (a great example is Milos Raonic) – but who may not perform as well on return (relative to other players since serve is almost always better than return in terms of pts won for a player). I think that this level of granularity helps better understand true performance when it comes to tennis. You probably could, and should in my opinion, go into more depth, taking into account aces, which hand a player plays with, etc. It just depends on if each of those variables poses a significant factor at play.

I don’t recall my thoughts on accuracy for individual match vs batch updating from when I originally created this model. But from what I can guess at the moment is that it’s likely because a player’s ability can change throughout the course of a tournament and treating each match as an independent event can help reflect this. But this is just a hypothesis.

Hope this helps answer some of your questions! Best of luck with your model.