[Racket][Math] Deciphering Uber rating

As a very worrisome neurotic I always been wondering, can I get my internal Uber statistics based only on rating(s)?

Hard to believe, but yes. But you should get as many ratings as possible, an interrupted 'chain' of ratings. Also, you need to predict a minimal and maximal number of ratings you've got. Taxi drivers don't always rate their passengers. To my experience, only half of them get ratings. (Maybe drivers are just too busy or lazy, but this is OK.)

The whole Racket source code. It works in backtracking manner:

#lang racket
(require racket/generator)
(require "my-lib.rkt")

(define (stat-to-rating stat)
  (let ([rides-total (my-sum stat)])
    (let ([rating
           (/
            (my-sum (map * stat (list 5 4 3 2 1)))
            rides-total)])
      ;(displayln (list "stat-to_rating" stat "result" rating))
      rating)))

(define (are-ratings-equal x y) ; almost...
  ;(displayln (list "are-ratings-equal" x y))
  (let ([r (< (abs (- x y)) 0.005)])
    ;(displayln (list "are-ratings-equal -> " r))
    r))

(define count 0) ; global var! hehe

(define (try-rating stat ratings-must-be stat-history rides-max)
  ;(displayln (list "try-rating. stat: " stat "ratings-must-be: " ratings-must-be "stat-history: " stat-history))
  (when (and
         (<= (my-sum stat) rides-max)
         (> (length ratings-must-be) 0)
         (are-ratings-equal (stat-to-rating stat) (first ratings-must-be)))
    (when (eq? (length ratings-must-be) 1)
      (set! count (add1 count))
      (displayln (list "* model" count))
      (displayln (list "stat change" (reverse stat-history)))
      ;(displayln (list "rating change" (map (lambda (x) (~a (stat-to-rating x) #:max-width 4)) (reverse stat-history))))
      ;(displayln (list "final stat:" stat))
      )
    (for ([i (range 5)]) ; [0..4]
      ;(displayln (list "stat-history length:" (length stat-history) "incrementing element: " i))
      (let ([possible-new-rating (increment-element-of-list stat i)])
        (try-rating possible-new-rating (rest ratings-must-be) (cons possible-new-rating stat-history) rides-max)))))

(provide do-all)
(define (do-all rides-min rides-max ratings)
  ; a=fives, b=fours ... e=ones
  (define gen-all-stat
    (generator (rides-min rides-max)
               (for ([a (range rides-max)]) ; [0..rides-total]
                 (for ([b (range rides-max)]) ; [0..rides-total]
                   (when (<= (+ a b) rides-max)
                     (for ([c (range rides-max)]) ; [0..rides-total]
                       (when (<= (+ a b c) rides-max)
                         (for ([d (range rides-max)]) ; [0..rides-total]
                           (when (<= (+ a b c d) rides-max)
                             (for ([e (range rides-max)]) ; [0..rides-total]
                               (when
                                   (and
                                    (<= rides-min (+ a b c d e))
                                    (>= rides-max (+ a b c d e)))
                                 (yield (list a b c d e)))))))))))))

  (define x #f)
  (set! count 0)
  (let loop ()
    (set! x (gen-all-stat rides-min rides-max))
    (when (list? x)
      ;(displayln x)
      (try-rating x ratings (list x) rides-max)
      (loop)))
  (displayln (list "* models total:" count "chain len:" (length ratings)))
  count
  )

For this test...

; test1:
(do-all 1 30 (list 5.00 5.00 5.00 5.00 5.00 5.00 4.86 4.88 4.89 4.90 4.91 4.92 4.92 4.93 4.93 4.94 4.94 4.89 4.89 4.90))

... we can get one (correct) model:

(* model 1)
(stat change ((1 0 0 0 0) (2 0 0 0 0) (3 0 0 0 0) (4 0 0 0 0) (5 0 0 0 0) (6 0 0 0 0) (6 1 0 0 0) (7 1 0 0 0) (8 1 0 0 0) (9 1 0 0 0) (10 1 0 0 0) (11 1 0 0 0) (12 1 0 0 0) (13 1 0 0 0) (14 1 0 0 0) (15 1 0 0 0) (16 1 0 0 0) (16 2 0 0 0) (17 2 0 0 0) (18 2 0 0 0)))
(* models total: 1 chain len: 20)
1

That means my stat is (18 2 0 0 0), i.e., 18 fives, 2 fours.

Reduced test, still one (correct) model:

(do-all 1 30 (list 4.94 4.89 4.89 4.90))
...

(* model 1)
(stat change ((16 1 0 0 0) (16 2 0 0 0) (17 2 0 0 0) (18 2 0 0 0)))
(* models total: 1 chain len: 4)
1

Even more reduced test, 5 possible models, but one (among them) is correct:

(do-all 1 30 (list 4.89 4.89 4.90))

...

 % racket uber2.rkt
(* model 1)
(stat change ((16 2 0 0 0) (17 2 0 0 0) (18 2 0 0 0)))
(* model 2)
(stat change ((17 0 1 0 0) (18 0 1 0 0) (19 0 1 0 0)))
(* model 3)
(stat change ((24 3 0 0 0) (25 3 0 0 0) (26 3 0 0 0)))
(* model 4)
(stat change ((25 1 1 0 0) (26 1 1 0 0) (27 1 1 0 0)))
(* model 5)
(stat change ((26 0 0 1 0) (27 0 0 1 0) (28 0 0 1 0)))
(* models total: 5 chain len: 3)
5

If you have only 'before' rating and 'after':

(do-all 1 30 (list 4.89 4.90))

...

 % racket uber2.rkt
(* model 1)
(stat change ((8 1 0 0 0) (9 1 0 0 0)))
(* model 2)
(stat change ((17 2 0 0 0) (18 2 0 0 0)))
(* model 3)
(stat change ((18 0 1 0 0) (19 0 1 0 0)))
(* model 4)
(stat change ((25 3 0 0 0) (26 3 0 0 0)))
(* model 5)
(stat change ((26 1 1 0 0) (27 1 1 0 0)))
(* model 6)
(stat change ((27 0 0 1 0) (28 0 0 1 0)))
(* models total: 6 chain len: 2)
6

If you have just one rating in your history, you can say something, but this is not enough:

(do-all 1 30 (list 4.90))

...

 % racket uber2.rkt
(* model 1)
(stat change ((9 1 0 0 0)))
(* model 2)
(stat change ((18 2 0 0 0)))
(* model 3)
(stat change ((19 0 1 0 0)))
(* model 4)
(stat change ((19 2 0 0 0)))
(* model 5)
(stat change ((20 0 1 0 0)))
(* model 6)
(stat change ((26 3 0 0 0)))
(* model 7)
(stat change ((27 1 1 0 0)))
(* model 8)
(stat change ((27 3 0 0 0)))
(* model 9)
(stat change ((28 0 0 1 0)))
(* model 10)
(stat change ((28 1 1 0 0)))
(* model 11)
(stat change ((29 0 0 1 0)))
(* models total: 11 chain len: 1)
11

All these tests were executed with assumption that you have at least 1 ride and at most 30 rides. YMMV. Fix this for your data.

But the more precise and full data you have, the more precise results you'll get.

On my computer, it can find models for up to 120 rides max.

It works, because sometimes, there is more information in ratings than in the internal statistics. Let's count, how many ways there are to represent statistics for 30 ridex max?

#lang racket
(require racket/generator)
(require "my-lib.rkt")

(define count 0) ; global var! hehe

(define (do-all rides-min rides-max)
  ; a=fives, b=fours ... e=ones
  (define gen-all-stat
    (generator (rides-min rides-max)
               (for ([a (range rides-max)]) ; [0..rides-total]
                 (for ([b (range rides-max)]) ; [0..rides-total]
                   (when (<= (+ a b) rides-max)
                     (for ([c (range rides-max)]) ; [0..rides-total]
                       (when (<= (+ a b c) rides-max)
                         (for ([d (range rides-max)]) ; [0..rides-total]
                           (when (<= (+ a b c d) rides-max)
                             (for ([e (range rides-max)]) ; [0..rides-total]
                               (when
                                   (and
                                    (<= rides-min (+ a b c d e))
                                    (>= rides-max (+ a b c d e)))
                                 (yield (list a b c d e)))))))))))))

  (define x #f)
  (set! count 0)
  (let loop ()
    (set! x (gen-all-stat rides-min rides-max))
    (when (list? x)
      ;(displayln x)
      (set! count (add1 count))
      (loop)))
  (displayln (list "count" count))
  )

(do-all 1 30)

It's 324626, and $log_2 (324626) \approx 18.3$ bits. How many bits of information in one rating? $log_2 (600) \approx 9.2$ bits.

So you need at least 2-3 ratings.

All the files.


List of my other blog posts.

Yes, I know about these lousy Disqus ads. Please use adblocker. I would consider to subscribe to 'pro' version of Disqus if the signal/noise ratio in comments would be good enough.