Positional Scoring Rules

Suppose that \(\mathbf{P}\) is an anonymous profile of linear orders (i.e., a Profile object). A scoring vector for \(m\) candidates is a tuple of numbers \(\langle s_1, s_2, \ldots, s_m\rangle\) where for each \(l=1,\ldots, m-1\), \(s_l \ge s_{l+1}\).

Suppose that \(a\in X(\mathbf{P})\) and \(R\in \mathcal{L}(X(\mathbf{P}))\) is linear order on the set of candidates. The rank of \(a\) in \(R\) is one more than the number of candidates ranked above \(a\) (i.e., \(|\{b\mid b\in X(\mathbf{P})\mbox{ and } b\mathrel{R}a\}| + 1\)). The score of \(a\) given \(R\) is \(score(R,a)=s_r\) where \(r\) is the rank of \(a\) in \(R\).

For each anonymous profile \(\mathbf{P}\), \(a\in X(\mathbf{P})\) and scoring vector \(\vec{s}\) for the number of candidates in \(\mathbf{P}\), let

\[ Score_\vec{s}(\mathbf{P},x)= \sum_{R\in\mathcal{L}(X(\mathbf{P}))} \mathbf{P}(R) * score(R, x) \]

A positional scoring rule is defined by specifying a scoring vector for each number of candidates. The winners in \(\mathbf{P}\) according to the positional scoring rule is the set of candidates that maximize their scoring according to the scoring vector for the number of candidates in \(\mathbf{P}\). That is, if \(F\) is a positional scoring rule, then for each profile \(\mathbf{P}\),

\[ F(\mathbf{P}) = \mathrm{argmax}_{a\in X(\mathbf{P})} Score_\vec{s}(\mathbf{P}, a). \]

where \(\vec{s}\) is the scoring vector for the number of candidates in \(\mathbf{P}\).

The most well-known positional scoring rules are:

  1. Plurality: the positional scoring rule for \(\langle 1, 0, \ldots, 0\rangle\).

  2. Borda: the positional scoring rule for \({\langle m-1, m-2, \ldots, 1, 0\rangle}\), where \(m\) is the number of candidates.

  3. Anti-Plurality: the positional scoring rule for \({\langle 0, 0, \ldots, -1\rangle}\).

from pref_voting.profiles import Profile
from pref_voting.scoring_methods import plurality, borda, anti_plurality

prof = Profile([[0, 2, 1], [0, 1, 2], [2, 1, 0], [1, 2, 0]], [3, 2, 1, 4])

prof.display()
plurality.display(prof)
borda.display(prof)
anti_plurality.display(prof)
+---+---+---+---+
| 3 | 2 | 1 | 4 |
+---+---+---+---+
| 0 | 0 | 2 | 1 |
| 2 | 1 | 1 | 2 |
| 1 | 2 | 0 | 0 |
+---+---+---+---+
Plurality winner is {0}
Borda winner is {1}
Anti-Plurality winner is {2}

An arbitrary scoring rule can be defined using the scoring_rule function. For instance, the Two-Approval voting method is a positional scoring rule in which scores are assigned as follows: candidates ranked either in first- or second-place are given a score of 1, otherwise the candidates are given a score of 0.

from pref_voting.profiles import Profile
from pref_voting.scoring_methods import plurality, borda, anti_plurality, scoring_rule

prof = Profile([[0, 2, 1], [0, 1, 2], [2, 1, 0], [1, 2, 0]], [3, 2, 1, 4])

two_approval_score = lambda num_cands, rank: 1 if rank == 1 or rank == 2 else 0

prof.display()
scoring_rule.display(prof, score = two_approval_score)

# for comparison, display the Plurality winner
plurality.display(prof)
+---+---+---+---+
| 3 | 2 | 1 | 4 |
+---+---+---+---+
| 0 | 0 | 2 | 1 |
| 2 | 1 | 1 | 2 |
| 1 | 2 | 0 | 0 |
+---+---+---+---+
Scoring Rule winner is {2}
Plurality winner is {0}

One problem with the above code is that the name of the scoring rule is “Scoring Rule” rather than “Two-Approval”. To define a voting method using the scoring_rule function, use the @vm decorator.

from pref_voting.profiles import Profile
from pref_voting.scoring_methods import plurality, borda, anti_plurality, scoring_rule
from pref_voting.voting_method import vm

@vm(name="Two-Approval")
def two_approval(profile, curr_cands = None):
    """Returns the list of candidates with the largest two-approval score in the profile restricted to curr_cands.
    """

    two_approval_score = lambda num_cands, rank: 1 if rank == 1 or rank == 2 else 0

    return scoring_rule(profile, curr_cands = curr_cands, score=two_approval_score)

prof = Profile([[0, 2, 1], [0, 1, 2], [2, 1, 0], [1, 2, 0]], [3, 2, 1, 4])

prof.display()
two_approval.display(prof)
+---+---+---+---+
| 3 | 2 | 1 | 4 |
+---+---+---+---+
| 0 | 0 | 2 | 1 |
| 2 | 1 | 1 | 2 |
| 1 | 2 | 0 | 0 |
+---+---+---+---+
Two-Approval winner is {2}

Plurality

pref_voting.scoring_methods.plurality(profile, curr_cands=None)[source]

The Plurality score of a candidate \(c\) is the number of voters that rank \(c\) in first place. The Plurality winners are the candidates with the largest Plurality score in the profile restricted to curr_cands.

Parameters:
  • profile (Profile) – An anonymous profile of linear orders on a set of candidates

  • curr_cands (List[int], optional) – If set, then find the winners for the profile restricted to the candidates in curr_cands

Returns:

A sorted list of candidates

See also

The method pref_voting.profiles.Profile.plurality_scores() returns a dictionary assigning the Plurality scores of each candidate.

Example:

from pref_voting.profiles import Profile
from pref_voting.scoring_methods import plurality

prof1 = Profile([[0, 1, 2], [1, 0, 2], [2, 1, 0]], [3, 1, 2])
prof1.display()
print(plurality(prof1)) # [2]
plurality.display(prof1)

prof2 = Profile([[0, 1, 2], [1, 0, 2], [1, 2, 0]], [3, 1, 2])
prof2.display()
print(plurality(prof2)) # [0, 1]
plurality.display(prof2)
+---+---+---+
| 3 | 1 | 2 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 0 | 1 |
| 2 | 2 | 0 |
+---+---+---+
[0]
Plurality winner is {0}
+---+---+---+
| 3 | 1 | 2 |
+---+---+---+
| 0 | 1 | 1 |
| 1 | 0 | 2 |
| 2 | 2 | 0 |
+---+---+---+
[0, 1]
Plurality winners are {0, 1}
pref_voting.scoring_methods.plurality_ranking(profile, curr_cands=None, local=True, tie_breaking=None)[source]

The SWF that ranks the candidates in curr_cands according to their plurality scores. If local is True, then the plurality scores are computed with respect to the profile restricted to curr_cands. Otherwise, the plurality scores are computed with respect to the entire profile.

Parameters:
  • profile (Profile) – An anonymous profile of linear orders on a set of candidates

  • curr_cands (List[int], optional) – The candidates to rank. If None, then all candidates in profile are ranked

  • local (bool, optional) – If True, then the plurality scores are computed with respect to the profile restricted to curr_cands. Otherwise, the plurality scores are computed with respect to the entire profile.

  • tie_breaking (str, optional) – The tie-breaking method to use. If None, then no tie-breaking is used. If “alphabetic”, then the tie-breaking is done alphabetically.

Returns:

A Ranking object

Borda

pref_voting.scoring_methods.borda(edata, curr_cands=None, algorithm='positional')[source]

The Borda score of a candidate is calculated as follows: If there are \(m\) candidates, then the Borda score of candidate \(c\) is \(\sum_{r=1}^{m} (m - r) * Rank(c,r)\) where \(Rank(c,r)\) is the number of voters that rank candidate \(c\) in position \(r\). The Borda winners are the candidates with the largest Borda score in the profile restricted to curr_cands. :param edata: An anonymous profile of linear orders or a MarginGraph. :type edata: Profile, MarginGraph :param curr_cands: If set, then find the winners for the profile restricted to the candidates in curr_cands :type curr_cands: List[int], optional :param algorithm: if “positional”, then the Borda score of a candidate is calculated from each voter’s ranking as described above. If “marginal”, then the Borda score of a candidate is calculated as the sum of the margins of the candidate vs. all other candidates. The positional scores and marginal scores are affinely equivalent. :type algorithm: String

Returns:

A sorted list of candidates

See also

The method pref_voting.profiles.Profile.borda_scores() returns a dictionary assigning the Borda score to each candidate.

Example:

from pref_voting.profiles import Profile
from pref_voting.scoring_methods import borda

prof1 = Profile([[0, 1, 2], [1, 0, 2], [2, 1, 0]], [3, 1, 2])
prof1.display()
print(borda(prof1)) # [0,1]
borda.display(prof1)

prof2 = Profile([[0, 1, 2], [1, 0, 2], [1, 2, 0]], [3, 1, 2])
prof2.display()
print(borda(prof2)) # [1]
borda.display(prof2)
+---+---+---+
| 3 | 1 | 2 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 0 | 1 |
| 2 | 2 | 0 |
+---+---+---+
[0, 1]
Borda winners are {0, 1}
+---+---+---+
| 3 | 1 | 2 |
+---+---+---+
| 0 | 1 | 1 |
| 1 | 0 | 2 |
| 2 | 2 | 0 |
+---+---+---+
[1]
Borda winner is {1}
pref_voting.scoring_methods.borda_ranking(profile, curr_cands=None, local=True, tie_breaking=None)[source]

The SWF that ranks the candidates in curr_cands according to their Borda scores. If local is True, then the Borda scores are computed with respect to the profile restricted to curr_cands. Otherwise, the Borda scores are computed with respect to the entire profile.

Parameters:
  • profile (Profile) – An anonymous profile of linear orders on a set of candidates

  • curr_cands (List[int], optional) – The candidates to rank. If None, then all candidates in profile are ranked

  • local (bool, optional) – If True, then the Borda scores are computed with respect to the profile restricted to curr_cands. Otherwise, the Borda scores are computed with respect to the entire profile.

  • tie_breaking (str, optional) – The tie-breaking method to use. If None, then no tie-breaking is used. If “alphabetic”, then the tie-breaking is done alphabetically.

Returns:

A Ranking object

Borda for ProfilesWithTies

pref_voting.scoring_methods.borda_for_profile_with_ties(profile, curr_cands=None, borda_scores=<function symmetric_borda_scores>)[source]

Borda score for truncated linear orders using different ways of defining the Borda score for truncated linear orders.

Anti-Plurality

pref_voting.scoring_methods.anti_plurality(profile, curr_cands=None)[source]

The Anti-Plurality score of a candidate $c$ is the number of voters that rank $c$ in last place. The Anti-Plurality winners are the candidates with the smallest Anti-Plurality score in the profile restricted to curr_cands.

Parameters:
  • profile (Profile) – An anonymous profile of linear orders on a set of candidates

  • curr_cands (List[int], optional) – If set, then find the winners for the profile restricted to the candidates in curr_cands

Returns:

A sorted list of candidates

Example:

from pref_voting.profiles import Profile
from pref_voting.scoring_methods import anti_plurality

prof1 = Profile([[2, 1, 0], [2, 0, 1], [0, 1, 2]], [3, 1, 2])
prof1.display()
print(anti_plurality(prof1)) # [1]
anti_plurality.display(prof1)

prof2 = Profile([[2, 1, 0], [2, 0, 1], [0, 2, 1]], [3, 1, 2])
prof2.display()
print(anti_plurality(prof2)) # [2]
anti_plurality.display(prof2)
+---+---+---+
| 3 | 1 | 2 |
+---+---+---+
| 2 | 2 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 2 |
+---+---+---+
[1]
Anti-Plurality winner is {1}
+---+---+---+
| 3 | 1 | 2 |
+---+---+---+
| 2 | 2 | 0 |
| 1 | 0 | 2 |
| 0 | 1 | 1 |
+---+---+---+
[2]
Anti-Plurality winner is {2}

Dowdall

pref_voting.scoring_methods.dowdall(profile, curr_cands=None)[source]

The first-ranked candidate gets 1 point, the second-ranked candidate gets 1/2 point, the third-ranked candidate gets 1/3 point, and so on. The Dowdall winners are the candidates with the greatest overall score in the profile restricted to curr_cands.

Parameters:
  • profile (Profile) – An anonymous profile of linear orders on a set of candidates

  • curr_cands (List[int], optional) – If set, then find the winners for the profile restricted to the candidates in curr_cands.

Returns:

A sorted list of candidates

Note

This system is used in Nauru. See, e.g., Jon Fraenkel & Bernard Grofman (2014), “The Borda Count and its real-world alternatives: Comparing scoring rules in Nauru and Slovenia,” Australian Journal of Political Science, 49:2, 186-205, DOI: 10.1080/10361146.2014.900530.

Scoring Rule

pref_voting.scoring_methods.scoring_rule(profile, curr_cands=None, score=lambda num_cands, rank: ...)[source]

A general scoring rule. Each voter assign a score to each candidate using the score function based on their submitted ranking (restricted to candidates in curr_cands). Returns that candidates with the greatest overall score in the profile restricted to curr_cands.

Parameters:
  • profile (Profile) – An anonymous profile of linear orders on a set of candidates

  • curr_cands (List[int], optional) – If set, then find the winners for the profile restricted to the candidates in curr_cands

  • score (function) – A function that accepts two parameters num_cands (the number of candidates) and rank (a rank of a candidate) used to calculate the score of a candidate. The default score function assigns 1 to a candidate ranked in first place, otherwise it assigns 0 to the candidate.

Returns:

A sorted list of candidates

Important

The signature of the score function is:

def score(num_cands, rank):
    # return an int or float
Example:

from pref_voting.profiles import Profile
from pref_voting.scoring_methods import scoring_rule, plurality, borda, anti_plurality

prof = Profile([[0, 1, 2], [1, 0, 2], [2, 1, 0]], [3, 1, 2])
prof.display()
scoring_rule.display(prof) # Uses default scoring function, same a Plurality
plurality.display(prof)

scoring_rule.display(prof, score=lambda num_cands, rank: num_cands - rank) # same a Borda
borda.display(prof)

scoring_rule.display(prof, score=lambda num_cands, rank: -1 if rank == num_cands else 0) # same as Anti-Plurality
anti_plurality.display(prof)
+---+---+---+
| 3 | 1 | 2 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 0 | 1 |
| 2 | 2 | 0 |
+---+---+---+
Scoring Rule winner is {0}
Plurality winner is {0}
Scoring Rule winners are {0, 1}
Borda winners are {0, 1}
Scoring Rule winner is {1}
Anti-Plurality winner is {1}