Bayesian table tennis

Hi all,

Table tennis can be seen as a succession of identical and independent random experiments (the rallies).
The rally-outcomes are mutually independent and are, conditionally upon the server, identically distributed. We will call this the server-model.
Denoting by pa (resp., pb) the probability that player A (resp., player B) wins a rally he/she initiates,
the game is then entirely governed by the bivariate parameter:

(pa, pb) ∈ (0, 1) × (0, 1)

A simpler model is the no-server-model, in which it is assumed that rallies are won with probabilities that do not depend on the server:

pa = 1 − pb = p ∈ (0, 1)

Based on this blog post I wrote the following Stan code that represents the no-server-model:

data {
   int<lower=0> n_sets;                       // number of sets
   int a[n_sets];                             // number of points won by A
   int b[n_sets];                             // number of points won by B
   int x[n_sets];                             // number of points won by winner on own service

parameters {
   real<lower=0> alpha;                        // skill of A
   real<lower=0> beta;                         // skill of B

transformed parameters {
   real<lower=0,upper=1> p_a;                 // probability that A wins point while being the server
   real<lower=0,upper=1> p_b;                 // probability that B wins point while being the server
   p_a = alpha - beta;
   p_b = 1 - p_a;

model {
   for (set in n_sets) {
      // check who is the winner
      if (a[set] > b[set]) {
         p_a_wins = 0;

         for (point in b[set]) {
           p_a_wins += some_funtion_that_includes_pa_and_pb()
         p_b_wins =  1 - p_a_wins;
         1 ~ bernoulli_logit(p_a_wins);
         0 ~ bernoulli_logit((p_b_wins);

       } else {
         p_b_wins = 0;

         for (point in a[set]) {
            p_b_wins += some_funtion_that_includes_pa_and_pb()
         p_a_wins = 1 - p_b_wins;
         0 ~ bernoulli_logit(p_a_wins);
         1 ~ bernoulli_logit(p_b_wins);


I read here that it’s better to use bernoulli_logit(...) than bernoulli(inv_logit(...)) but I am wondering if my implication is correct? Where did the 10 of the below formula go?

In addition, how would the server-model look like in Stan? Thank you

I’m not sure I fully understand your question yet. The blog post that you linked has full Stan code for a version of the no-server model where the observed data is just the outcome of the matches and outcomes are available for many more than just two players.

Your model seems to suggest that you have data on the outcomes of individual points. Given that you assume outcomes to be conditionally independent, that means that the probability of player 1 winning any point is a bernoulli sample from the underlying probability, and since there is only one condition in the no-server model and two conditions in the server model there are binomial sufficient statistics for estimating these probabilities.

The model for the win probabilities of a point played between players 1 and 2 is therefore extremely simple. For example, in the no-server case:

  int N; // total number of points played between player 1 and player 2
  int outcome; // total number of points won by player 1
  real logit_p; // logit-scale probability that player 1 wins a point
  outcome ~ binomial_logit(N, logit_p);

The server model differs almost trivially; it’s just this model twice, once for when player 1 serves and once for when player 2 serves. If desired, you could parameterize this model in such a way that you can put a prior directly on the difference in these probabilities, or you could estimate them completely independently.

Two things, in isolation or together, make the model more complicated. One is if you do not observe the outcome of points, and instead observe only the outcome of matches. That’s where the fancy math from the blog post comes in. The other is of you have data from a bunch of matches involving a bunch of different (but overlapping) players, and you want to use the outcomes (whether of individual rallies or of entire matches) to estimate an index of each player’s relative skill. Your model looks like it wants to estimate these relative skills, but this isn’t well identified or particularly meaningful when you have only two players; the scoring probabilities of player 1 already fully capture the difference in the skill of the two players.

So I’d encourage you to think about:

  • Do you have access to the outcomes of rallies? If so, you can mostly ignore the math in the blog post for model fitting, though the post would still be useful if you want to determine match-level win probabilities as a generated quantity conditional on fitted scoring probabilities.
  • Do you intend to model the relative skills of “many” (>> 2) players?