Example #1
0
        /// <summary>
        /// Calculates the lower bound of the Wilson score for the provided list of voter rankings of a given vote.
        /// Reference: http://www.evanmiller.org/how-not-to-sort-by-average-rating.html
        /// </summary>
        /// <param name="votes">Votes with associated ranks, for the voters who ranked the vote with a given value.</param>
        /// <returns>Returns a numeric evaluation of the overall score of the vote.</returns>
        public static (double score, int count) LowerWilsonRankingScore(VoteStorageEntry votes)
        {
            int n = votes.Value.Count(v => v.Value.MarkerType == MarkerType.Rank);

            if (n == 0)
            {
                return(0, 0);
            }

            double positiveScore = 0.0;
            double negativeScore = 0.0;

            // Add up the sum of the number of voters times the value of each rank.
            // Value of each rank is 1/N.
            foreach (var vote in votes.Value)
            {
                if (vote.Value.MarkerType == MarkerType.Rank && vote.Value.MarkerValue > 0 && vote.Value.MarkerValue < 10)
                {
                    double scaledPositiveScore = PositivePortionOf9RankScale(vote.Value.MarkerValue);

                    positiveScore += scaledPositiveScore;
                    negativeScore += (1.0 - scaledPositiveScore);
                }
            }

            double p̂     = positiveScore / (positiveScore + negativeScore);
            double z      = 1.96;
            double sqTerm = (p̂ * (1 - p̂) + z * z / (4 * n)) / n;

            double lowerWilson = (p̂ + (z * z / (2 * n)) - z * Math.Sqrt(sqTerm)) / (1 + z * z / n);

            return(lowerWilson, n);
        }
Example #2
0
        /// <summary>
        /// Construct vote output per task for scored votes.
        /// </summary>
        /// <param name="votesInTask">The group of votes falling under a task.</param>
        /// <param name="token">Cancellation token.</param>
        private void ConstructScoredOutput(VotesGroupedByTask votesInTask, IEnumerable <CompactVote> compactVotesInTask)
        {
            bool multiline = votesInTask.Any(a => a.Key.Lines.Count > 1);

            if (displayMode == DisplayMode.Compact || displayMode == DisplayMode.CompactNoVoters)
            {
                var orderedResults = compactVotesInTask
                                     .OrderByDescending(a => a.Voters.GetScore().lowerMargin)
                                     .ThenBy(a => a.CurrentLine.CleanContent);

                foreach (var result in orderedResults)
                {
                    var flattened = result.GetFlattenedCompactVote();

                    foreach (var vote in flattened)
                    {
                        var(score, average, lowerMargin) = vote.Voters.GetScore();
                        sb.AppendLine(vote.ToOutputString($"{score}%"));

                        if (displayMode != DisplayMode.CompactNoVoters)
                        {
                            AddCompactNormalVoteVoters(vote);
                        }
                    }

                    if (!(quest.PartitionMode == PartitionMode.ByLine || quest.PartitionMode == PartitionMode.ByLineTask))
                    {
                        sb.AppendLine();
                    }
                }
            }
            else
            {
                var voteResults = votesInTask.Select(v => new { vote = v, score = v.Value.GetScore() });

                var orderedResults = voteResults
                                     .OrderByDescending(a => a.score.lowerMargin)
                                     .ThenByDescending(a => a.score.average)
                                     .ThenBy(a => a.vote.Key.First().CleanContent);

                foreach (var result in orderedResults)
                {
                    VoteStorageEntry resultVote = result.vote;
                    var resultScore             = result.score;

                    var(entryVote, entryStorage) = resultVote;

                    AddScoreVoteSupport(resultScore);
                    AddScoreVoteDisplay(resultVote, resultScore);
                    AddVoterCount(entryStorage.GetNonRankUserCount());
                    AddNonRankVoters(entryStorage);

                    sb.AppendLine();
                }
            }
        }
Example #3
0
        /// <summary>
        /// Construct vote output per task for standard votes.
        /// </summary>
        /// <param name="votesInTask">The group of votes falling under a task.</param>
        /// <param name="token">Cancellation token.</param>
        private void ConstructNormalOutput(VotesGroupedByTask votesInTask, IEnumerable <CompactVote> compactVotesInTask)
        {
            bool multiline = votesInTask.Any(a => a.Key.Lines.Count > 1);

            if (displayMode == DisplayMode.Compact || displayMode == DisplayMode.CompactNoVoters)
            {
                var orderedResults = compactVotesInTask
                                     .OrderByDescending(a => a.Voters.GetSupportCount())
                                     .ThenBy(a => a.CurrentLine.CleanContent);

                foreach (var result in orderedResults)
                {
                    var flattened = result.GetFlattenedCompactVote();

                    foreach (var vote in flattened)
                    {
                        sb.AppendLine(vote.ToOutputString(vote.Voters.GetSupportCount().ToString(CultureInfo.InvariantCulture)));

                        if (displayMode != DisplayMode.CompactNoVoters)
                        {
                            AddCompactNormalVoteVoters(vote);
                        }
                    }

                    if (!(quest.PartitionMode == PartitionMode.ByLine || quest.PartitionMode == PartitionMode.ByLineTask))
                    {
                        sb.AppendLine();
                    }
                }
            }
            else
            {
                var voteResults = votesInTask.Select(v => new { vote = v, supportCount = v.Value.GetSupportCount() });

                var orderedResults = voteResults.OrderByDescending(a => a.supportCount).ThenBy(a => a.vote.Key.First().CleanContent);

                foreach (var result in orderedResults)
                {
                    VoteStorageEntry resultVote = result.vote;
                    int resultSupport           = result.supportCount;

                    var(entryVote, entryStorage) = resultVote;

                    int voterCount = entryStorage.GetNonRankUserCount();
                    if (voterCount != resultSupport)
                    {
                        AddStandardVoteSupport(resultSupport);
                    }
                    AddStandardVoteDisplay(resultVote, resultSupport);
                    AddVoterCount(voterCount);
                    AddNonRankVoters(entryStorage);

                    sb.AppendLine();
                }
            }
        }
Example #4
0
        GetWinningVote(VoteStorage votes)
        {
            var options = GetTopTwoRatedOptions(votes);

            if (options.Count == 1)
            {
                return(options[0]);
            }

            VoteStorageEntry winner =
                GetOptionWithHigherPrefCount(options[0].option, options[1].option);

            return(winner, winner.Key == options[0].option.Key ? options[0].score : options[1].score);
        }
Example #5
0
        /// <summary>
        /// Calculates the inverse Borda score for the provided list of voter rankings of a given vote.
        /// </summary>
        /// <param name="votes">Votes with rank information included.</param>
        /// <returns>Returns a numeric evaluation of the overall score of the vote.</returns>
        public static double InverseBordaScore(VoteStorageEntry votes)
        {
            double voteValue = 0;

            // Value of each rank is 1/N.
            foreach (var vote in votes.Value)
            {
                if (vote.Value.MarkerType == MarkerType.Rank && vote.Value.MarkerValue > 0 && vote.Value.MarkerValue < 10)
                {
                    voteValue += (1.0 / vote.Value.MarkerValue);
                }
            }

            return(voteValue);
        }
Example #6
0
        /// <summary>
        /// Calculates the Borda score for the provided list of voter rankings of a given vote.
        /// </summary>
        /// <param name="votes">Votes with rank information included.</param>
        /// <returns>Returns a numeric evaluation of the overall score of the vote.</returns>
        public static double BordaScore(VoteStorageEntry votes)
        {
            double voteValue = 0;

            // Normalize to 9 points for #1, 8 points for #2, etc.
            foreach (var vote in votes.Value)
            {
                if (vote.Value.MarkerType == MarkerType.Rank && vote.Value.MarkerValue > 0 && vote.Value.MarkerValue < 10)
                {
                    voteValue += (10 - vote.Value.MarkerValue);
                }
            }

            return(voteValue);
        }
Example #7
0
        /// <summary>
        /// Gets the option with higher preference count.
        /// This is the runoff portion of the vote evaluation.  Whichever option has more
        /// people that prefer it over the other, wins.
        /// </summary>
        /// <param name="voterRankings">The voter rankings.  This allows seeing which option each voter preferred.</param>
        /// <param name="option1">The first option up for consideration.</param>
        /// <param name="option2">The second option up for consideration.</param>
        /// <returns>Returns the winning option.</returns>
        private VoteStorageEntry GetOptionWithHigherPrefCount(
            VoteStorageEntry option1,
            VoteStorageEntry option2)
        {
            var voters1 = option1.Value;
            var voters2 = option2.Value;

            var allVoters = voters1.Keys.Concat(voters2.Keys).Distinct().ToList();

            int count1 = 0;
            int count2 = 0;

            foreach (var voter in allVoters)
            {
                if (!voters1.TryGetValue(voter, out var support1))
                {
                    if (voters2.ContainsKey(voter))
                    {
                        count2++;
                    }
                    continue;
                }

                if (!voters2.TryGetValue(voter, out var support2))
                {
                    if (voters1.ContainsKey(voter))
                    {
                        count1++;
                    }
                    continue;
                }

                if (support1.MarkerValue < support2.MarkerValue)
                {
                    count1++;
                }
                else if (support2.MarkerValue > support1.MarkerValue)
                {
                    count2++;
                }
            }

            // If count1==count2, we use the higher scored option, which
            // will necessarily be option1.  Therefore all ties will be
            // in favor of option1, and the only thing we need to check
            // for is if option2 wins explicitly.
            return(count2 > count1 ? option2 : option1);
        }
Example #8
0
        /// <summary>
        /// Rank the vote using Borda math.  Ranks closer to 1 have higher value.
        /// To handle scaling, rank 6 is considered to be worth 0 points, which
        /// means rank 1 is 5 points, and rank 9 is -3 points.
        /// </summary>
        /// <param name="vote">The vote being scored.</param>
        /// <returns>Returns the Borda score based on the voters for the vote.</returns>
        private double GetBordaScore(VoteStorageEntry vote)
        {
            double voteValue = 0;
            int    count     = 0;

            // Add up the sum of the number of voters times the value of each rank.
            // If any voter didn't vote for an option, they effectively add a 0 (rank #6) for that option.
            foreach (var voter in vote.Value)
            {
                if (voter.Key.AuthorType == IdentityType.User && voter.Value.MarkerType == MarkerType.Rank)
                {
                    voteValue += (6 - voter.Value.MarkerValue);
                    count++;
                }
            }

            return(voteValue / count);
        }
Example #9
0
        /// <summary>
        /// Add up the sum of the number of voters times the value of each rank.
        /// Average the results, and then scale by the number of voters who ranked this option.
        /// Ranking value is Borda+1, so that ranks 1 through 9 are given values 2 through 10.
        /// That means first place is 5x as valuable as last place, rather than 9x as valuable.
        /// </summary>
        /// <param name="vote">The vote being scored.</param>
        /// <returns>Returns the Borda score based on the voters for the vote.</returns>
        private double GetBordaScore(VoteStorageEntry vote)
        {
            double voteValue = 0;
            int    count     = 0;

            // Add up the sum of the number of voters times the value of each rank.
            // Average the results, and then scale by the number of voters who ranked this option.
            // Ranking value is Borda+1, so that ranks 1 through 9 are given values 2 through 10.
            // That means first place is 5x as valuable as last place, rather than 9x as valuable.
            foreach (var voter in vote.Value)
            {
                if (voter.Key.AuthorType == IdentityType.User && voter.Value.MarkerType == MarkerType.Rank)
                {
                    voteValue += (1.0 + voter.Value.MarkerValue);
                    count++;
                }
            }

            voteValue = voteValue / count / count;

            return(voteValue);
        }