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
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}\),
where \(\vec{s}\) is the scoring vector for the number of candidates in \(\mathbf{P}\).
The most well-known positional scoring rules are:
Plurality: the positional scoring rule for \(\langle 1, 0, \ldots, 0\rangle\).
Borda: the positional scoring rule for \({\langle m-1, m-2, \ldots, 1, 0\rangle}\), where \(m\) is the number of candidates.
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 tocurr_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 tocurr_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 incurr_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¶
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 tocurr_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 incurr_cands
). Returns that candidates with the greatest overall score in the profile restricted tocurr_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) andrank
(a rank of a candidate) used to calculate the score of a candidate. The defaultscore
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}