I've glanced through fivestar issues and the issue of Total Vote Count vs. average has been talked about but I haven't seen a clear discussion about tying the votes to an existing average to reduce outliers.

Example:

PHOTO A has 1 vote @ 10 stars (10 star ranking)
PHOTO B has 2 votes @ 9 stars (9 star ranking)

Which is more representative? Also, most people aren't inclined to rate content that is bad (why would someone look at something they weren't interested in or didn't like?) so a lot of rankings skew towards the high end of Fivestar.

Proposal:

Use the concept of the Bayesian Average to help weigh the rankings to be more representative
http://en.wikipedia.org/wiki/Bayesian_average

Concept:

You would need to determine the weighted average to base the calculation and how much the average would weigh on the votes.

Example: For 1-10 star ranking, the website admin decides to use 5 stars as the weighted average for a certain number of votes (say 10 votes). This makes 1 vote for 10 stars have far less impact than before without the weight:

(5 stars x 10 votes + 10 stars x 1 votes )/11 votes = 5.45 stars

Fivestar Tools:

In the Fivestar Module, there could be the option to pre-load a content type with a weighted average of votes, such as have the content type start with 10 votes of 5 stars. These numbers would be selected by the Admin.

Additionally, it could be automated by changing as the # of votes occur, throughout the website or content type. This could be implemented by finding the average number of votes per node (say like 10 votes) and using the average of the range of votes (5 for 1-10, 2.5 for 1-5, etc) for the values.The site admin would just enable/disable the function. This could conceptually be tweaked to reduce the impact of weight:

(avg votes x .1 [arbitrary modifier, could be .3 or .5]) + (votes x stars) / (votes + (avg votes x .1 [arbitrary modifier])) = dynamic weighted star ranking that changes as the number of votes do throughout the content type / website.

Just a Thought:
I've used drupal for about two years now (off and on) without PHP coding anything so I really don't understand the back end of how a lot of this stuff works. I didn't see a lot of discussion about this so I thought I would bring it forward. I think this is valuable for people that want rankings but don't have thousands of users ranking nodes to quickly create a representative star ranking.

My Website that uses FiveStar:
http://www.thirdgoal.info

Example of unweighted rankings:
http://thirdgoal.info/allphotos/list?tid=All
(the highest ranked images aren't the most voted upon and aren't representative of the field)

Comments

Summit’s picture

+1, absolutely agree to get this in.
Right now I see no difference in ranking of the highest score with 1 vote or with 1000 votes. I would asume that highest average with the most votes should be ranked first, but this is not the case..
Or I do something wrong in my view off course..

greetings, Martijn

Weka’s picture

+1
I was hoping to use Fivestar for a photo competition on a local community website. Right now I am stuck on the fact that when I create a view and sort the submitted photos using Vote results: Value, average vote value is used and a photo with 1 vote of 10 is placed higher than photo with total of 3 votes 10 each.
PHOTO A has 1 vote @ 10 stars
PHOTO B has 3 votes @ 10 stars
Is there any way around this in Fivestar?
Is anyone aware of a module that is better suited for this type of rating?

estevan_carlos’s picture

Agreed.

I haven't used Fivestar yet but I would argue that a rating system is only as good as it's algorithm.

Weka’s picture

I think that the #281850: Total Score Option would be a better solution to this problem.

Taxoman’s picture

Version: 6.x-1.19 » 7.x-2.x-dev
zazinteractive’s picture

I'm gonna try to do this for the d6 version.

Wooster’s picture

Subscribe

ericduran’s picture

This really isn't a fivestar issue. This would need to be more votingapi and thats what we used to calculate the average.

ericduran’s picture

Status: Active » Closed (won't fix)

Closing as per my previous comment.

Taxoman’s picture

Project: Fivestar » Voting API
Status: Closed (won't fix) » Active

Lets get some feedback from the VotingAPI queue before we close this.

myke’s picture

Also please see this post:

http://drupal.org/node/328748

I absolutely agree that this needs to be in Voting API as an optional feature, if there isn't a way to create an extension to add it into Voting API...

Bayesian rating:

br = ( (avg_num_votes * avg_rating) + (this_num_votes * this_rating) ) / (avg_num_votes + this_num_votes)

Legend:

avg_num_votes: The average number of votes of all items that have num_votes>0
avg_rating: The average rating of each item (again, of those that have num_votes>0)
this_num_votes: number of votes for this item
this_rating: the rating of this item

(Credit where credit is due: This was taken from http://www.thebroth.com/blog/118/bayesian-rating )

dgale’s picture

I had a Drupal 6 site using VotingAPI and Fivestar and I needed Bayesian rankings. I hacked up a module that did it. Or at least seemed to. I recently needed to update that size to Drupal 7. So I had to update that custom Bayesian module to working with 7.x version of VotingAPI.

Here is the code that I am using. Let me be clear: I barely know what I am doing. For all I know, this is super inefficient and completely inaccurate. I just saw the same post on thebroth that other people have referenced and then worked out from there. I'm not a PHP developer. This is almost entirely the result of Googling "How do I do X in Y"

I'm posting this code in the hopes that someone who actually knows what they're doing could point out the glaring flaws and help make it better. At the very least, this code might help someone who is trying to get a Bayesian type thing going with voting API.

function bayesian_votingapi_results_alter(&$vote_results, $content_type, $content_id) {

	/**** bayesian average formula borrowed from http://www.thebroth.com/blog/118/bayesian-rating
	br = ( (avg_num_votes * avg_rating) + (this_num_votes * this_rating) ) / (avg_num_votes + this_num_votes)
	
	Legend:

	avg_num_votes: The average number of votes of all items that have num_votes>0 
	avg_rating: The average rating of each item (again, of those that have num_votes>0) 
	this_num_votes: number of votes for this item 
	this_rating: the rating of this item 
	*/
	
	$sql  = "SELECT count(`entity_id`)/count(distinct `entity_id`) FROM votingapi_vote";
	$avg_num_votes = db_query($sql)->fetchfield(0);

	$sql  = "SELECT AVG(`value`) 'avg_rating' ";
	$sql .= "FROM {votingapi_vote}";

	$avg_rating = db_query($sql)->fetchfield(0);
	
	$sql  = "SELECT COUNT(*) as 'this_num_votes' ";
	$sql .= "FROM {votingapi_vote} ";
	$sql .= "where entity_type = :type ";
	$sql .= "and entity_id = :id";

	$this_num_votes = db_query($sql, array(':type' => $content_type, ':id' => $content_id))->fetchfield(0);	

	$sql  = "SELECT Avg(`value`) as 'this_rating' ";
	$sql .= "FROM {votingapi_vote} ";
	$sql .= "where entity_type = :type ";
	$sql .= "and entity_id = :id";

	$this_rating = db_query($sql, array(':type' => $content_type, ':id' => $content_id))->fetchfield(0);
	
	$bayesian = ( ($avg_num_votes * $avg_rating) + ($this_num_votes * $this_rating) ) / ($avg_num_votes + $this_num_votes);
		
	$vote_results['vote']['percent']['bayesian'] = $bayesian;	
}

function bayesian_votingapi_metadata_alter(&$data) {

	// Make "Bayesian Average" one of the filtering options in Views when creating a relationship to voting results
	$data['functions']['bayesian'] = array(
		'name' => t('Bayesian Average'), 
		'description' => t('That magic Bayesian thing. I don\'t know much about. I just hacked up code from the Internet'), 
		'module' => 'bayesian',
	);
}



Pol’s picture

Hi all,

Thanks for the idea but I think the result is not good. The $avg_num_votes changes everytime you submit a vote, so, each bayesian values needs to be recalculated each time.

I modified the function to do it, I know it's not the best solution, but until now, it fullfills my needs:


function _bayesian_get_avg_value($tag = 'vote') {
    $sql  = "SELECT AVG(`value`) 'avg_rating' ";
    $sql .= "FROM {votingapi_vote} ";
    $sql .= "WHERE tag = :tag";
    return db_query($sql, array(':tag' => $tag))->fetchfield(0);
}

function _bayesian_get_num_votes($tag = 'vote') {
    $sql  = "SELECT count(`entity_id`)/count(distinct `entity_id`) ";
    $sql .= "FROM {votingapi_vote} ";
    $sql .= "WHERE tag = :tag";
    return db_query($sql, array(':tag' => $tag))->fetchfield(0);
}

function _bayesian_get_num_votes_by_id($content_type, $tag, $id) {
    $sql  = "SELECT COUNT(*) as 'this_num_votes' ";
    $sql .= "FROM {votingapi_vote} ";
    $sql .= "WHERE entity_type = :type ";
    $sql .= "AND entity_id = :id ";
    $sql .= "AND tag = :tag";
    return db_query($sql, array(':type' => $content_type, ':tag' => $tag, ':id' => $id))->fetchfield(0);    
}

function _bayesian_get_rating_by_id($content_type, $tag, $id) {
    $sql  = "SELECT Avg(`value`) as 'this_rating' ";
    $sql .= "FROM {votingapi_vote} ";
    $sql .= "WHERE entity_type = :type ";
    $sql .= "AND tag = :tag ";
    $sql .= "AND entity_id = :id";
    return db_query($sql, array(':type' => $content_type, ':tag' => $tag, ':id' => $id))->fetchfield(0);
}

function bayesian_votingapi_results_alter(&$vote_results, $content_type, $content_id) {
    /**** bayesian average formula borrowed from http://www.thebroth.com/blog/118/bayesian-rating
    br = ( (avg_num_votes * avg_rating) + (this_num_votes * this_rating) ) / (avg_num_votes + this_num_votes)
    Legend:
    avg_num_votes: The average number of votes of all items that have num_votes>0
    avg_rating: The average rating of each item (again, of those that have num_votes>0)
    this_num_votes: number of votes for this item
    this_rating: the rating of this item
    */
    
    $tag = array_keys($vote_results);
    $tag = $tag[0];
    
    $avg_num_votes = _bayesian_get_num_votes($tag);
    $avg_rating = _bayesian_get_avg_value($tag);
    $this_num_votes = _bayesian_get_num_votes_by_id($content_type, $tag, $content_id);    
    $this_rating = _bayesian_get_rating_by_id($content_type, $tag, $content_id);

    $bayesian = ( ($avg_num_votes * $avg_rating) + ($this_num_votes * $this_rating) ) / ($avg_num_votes + $this_num_votes);
    $vote_results[$tag]['percent']['bayesian'] = $bayesian;

    $sql  = "SELECT DISTINCT `entity_id` ";
    $sql .= "FROM {votingapi_cache} ";
    $sql .= "WHERE tag = :tag";
    $results = db_query($sql, array(':tag' => $tag));
    
    foreach ($results as $record) {
        
      $this_num_votes = _bayesian_get_num_votes_by_id($content_type, $tag, $record->entity_id); 
      $this_rating = _bayesian_get_rating_by_id($content_type, $tag, $record->entity_id);

      $bayesian = ( ($avg_num_votes * $avg_rating) + ($this_num_votes * $this_rating) ) / ($avg_num_votes + $this_num_votes);

      $num_updated = db_update('votingapi_cache')
        ->fields(array(
          'value' => $bayesian,
        ))
        ->condition('function', 'bayesian', '=')
        ->condition('entity_id', $record->entity_id, '=')
        ->execute();        
    }
  }

function bayesian_votingapi_metadata_alter(&$data) {
    // Make "Bayesian Average" one of the filtering options in Views when creating a relationship to voting results
    $data['functions']['bayesian'] = array(
        'name' => t('Bayesian Average'),
        'description' => t('That magic Bayesian thing. I don\'t know much about. I just hacked up code from the Internet'),
        'module' => 'bayesian',
    );
}

Tell me what you think.

Pol’s picture

markus_cz’s picture

This discussion resulted in the creation of the Bayesian VotingAPI module, which is now outdated. (https://www.drupal.org/project/votingapi_bayesian)

Is there anything like it for Drupal 8, now in 2020? I've been searching left and right but it seems that there's no other module that would enable me to display any sort of weighted average. Or is there? I find it hard to believe that so many people are using VotingAPI but everyone seems to be happy to use a simple average. If you could point me to any solution, I'd be grateful.

Alternatively, I tried to autoconvert the 7 version of the Bayesian module to 8 but even though I managed to install the module in Drupal 8, it doesn't do anything. Something must be broken and I don't know what. (I have literally no coding skills.) If anyone wants to take a look at it, see here:
https://www.drupal.org/project/votingapi_bayesian/issues/3048016

TR’s picture

Status: Active » Closed (won't fix)
Issue tags: -Average, -ranking, -rating, -avg, -weighted votes, -bayesian

@markus_cz: If you want to see the votingapi_bayesian functionality ported to Drupal 8, then please help out with that module in that module's issue queue. You can become a maintainer for that project if you want to work on the port to D8.