I have a book club that reads a book every month. My website allows the readers to give a 5 star rating to each book. We have 10 members and a rule that a member may only vote on a book they have read.

At the end of the year we read a book by the highest rated author.

The problem: If only one person votes for a book but the other 9 didn't finish the that one person determines the sole vote for that book.

Book - number of votes - Average Vote
Catcher in the Rye - 7 - 4.0
Catch 22 - 5 - 3.8
The Hunger Games - 1 - 5.0

In this example hunger games would win the year. But clearly Catcher in the Rye is more favored as most of the group just didn't bother to finish hunger games.

Desired Outcome
An ideal algorithm would weight the votes based on the number possible votes reducing the multiplier as the percentage of votes decreases.

votes < 3 - (33% of total available) Multiply average by .5 3 > votes < 7 - (66% of total available) Multiply average by .75 votes > 7 - Vote stands.

Is their a linear equation I can use to implement this type of weighting? or is there a more logical method of calculating the votes?

Note: I want to avoid making the average by the number of possible voters (add 0 for each non vote) as this would bottom out some of the longer books that people did not get the time to read. A gradual decrease that only considers the votes cast is much preferred.


This question has been plaguing internet ranking sites for a while, e.g. reddit. Fortunately it has a nice theoretical solution.

It involves a bit of statistics; the Ruby code is:

require 'statistics2'

def ci_lower_bound(pos, n, confidence)
if n == 0
return 0
z = Statistics2.pnormaldist(1-(1-confidence)/2)
phat = 1.0*pos/n
(phat + z*z/(2*n) - z * Math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)

  • 1
    $\begingroup$ Nice! But it is hardly THE solution, merely A (albeit elegant) solution. $\endgroup$
    – Nameless
    Apr 22 '15 at 18:20

You can assume some model for votes for a book, e.g. that is basically Gaussian with an unknown mean vote score $\mu$, and some global standard deviation that you calculate for example by computing the average standard deviation $\sigma$ over all books, where you take the average over all books that have more than 1 vote. Then your assumption, under a Gaussian model, is that each voter will assign a vote rating of $k$ with probability proportional to $e^{-(k-\mu)^2/2 \sigma^2}$ (scaled so that the sum of the probabilities of the 5 possible votes is 1). Then for each book, assume the votes are independent and optimize the joint probability of the votes to find the value of $\mu$ that maximizes the probability of the observed votes. Then if you have $n$ votes, the standard deviation of the estimated mean is $\sigma / \sqrt{n}$. So subtract two of these standard deviations from the estimated mean $\mu$ for the book to get an estimated $95\%$ confidence interval lower bound on what the true mean vote is for the book, and rank books according to their lower bounds. This will cause books with higher means but lower number of votes $n$ to be ranked below some books with lower means that have higher number of votes $n$, basically because you can "trust" the calculated value of the mean more as the value of $n$ increases.


Based on @vadim123 response and the link he gave me I built an excel formula:


F9 - Positive Votes / Total Votes
E9 - Normalization Constant. This is calculated by the ruby code but just use 1.96 for most applications to get a 95% accurate prediction. I don't fully understand this but the article basically says just to do this.
D9 - Total Votes.

My system was 5 star so the positive vs total was confusing at first. I used # of stars given as positive and stars possible as Total Votes. So 4/5 stars 4 is positive and 5 is total. Or for multiple voters 4/5 and 3/5 would be 7 positive out of 10 total.

PHP Code using the constant is

function normalizedVotes($positive, $total)
    if ($total == 0)
        return 0;
    $average = $positive/$total;
    $normalized_constant = 1.92;
    $normalized_squared = $normalized_constant^2;
    return ($average + ($normalized_squared / (2*$total)) - $normalized_constant * sqrt(($average * (1 - $average) + $normalized_squared / (4 * $total)) / $total)) / (1 + $normalized_squared / $total);

The formula is long and confusing so please let me know if I got any of the paren wrong.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.