Profiles#

Suppose that \(X\) is a set of \(n\) candidates. A strict linear order of \(X\), also called a ranking of \(X\), is a relation \(P\subseteq X\times X\) that is

  1. asymmteric: for all \(x, y\in X\), if \(x\mathrel{P} y\) then not \(y\mathrel{P}x\),

  2. transitive: for all \(x, y,z\in X\), if \(x\mathrel{P} y\) and \(y\mathrel{P} z\) then \(x\mathrel{P}z\); and

  3. connected: for all \(x,y\in X\) with \(x\neq y\), either \(x\mathrel{P} y\) or \(y\mathrel{P}x\).

When \(x\mathrel{P}y\), we say “\(x\) is ranked above \(y\)” or “\(x\) is strictly preferred to \(y\)”. Let \(\mathcal{L}(X)\) be the set of all strict linear orders over \(X\).

An anonymous profile is function \(\mathbf{P}:\mathcal{L}(X)\rightarrow \mathbb{N}\) assigning a non-negative number to each ranking.

The Profile class represents an anonymous profile. There are two important assumptions when defining a Profile.

  1. When there are \(n\) candidates, the candidates are \(0, 1, 2, \ldots, n-1\).

  2. A strict linear order of \(X\) is represented by a list of elements from \(X\) of length \(n\) in which each element of \(X\) appears in the list. For instance, [1, 0, 2] represents the rankings \(L\) where: \(1\mathrel{L} 2, 0\mathrel{L}2, \mbox{ and } 1\mathrel{L} 2.\)

A Profile is defined by specifying a list of rankings. It is assumed that any ranking not in this list was not submitted by any voter.

from pref_voting.profiles import Profile
prof = Profile([[0, 1, 2], [2, 1, 0]])
print(f"The rankings are {prof.rankings}")
print(f"The number of voters is {prof.num_voters}")
print(f"The number of candidate is {prof.num_cands}")
print(f"The candidates are {prof.candidates}")
The rankings are [(0, 1, 2), (2, 1, 0)]
The number of voters is 2
The number of candidate is 3
The candidates are [0, 1, 2]

There are two optional keyword parameters for a Profile:

  1. rcounts is a list of integers specifying the number of voters with a given ranking. If rankings is the list of rankings, then it is assumed that the number of voters who submitted ranking rankings[i] is rcounts[i]. If rcounts is not provided, the default value is 1 for each ranking.

  2. cmap is a dictionary mapping the candidates to their names. This is used when displaying a profile.

from pref_voting.profiles import Profile
prof = Profile([[0, 1, 2], [2, 1, 0]], rcounts = [2, 1], cmap = {0:"a", 1:"b", 2:"c"})
print(f"The rankings are {prof.rankings}")
print(f"The number of voters is {prof.num_voters}")
print(f"The number of candidate is {prof.num_cands}")
print(f"The candidates are {prof.candidates}")
prof.display()
The rankings are [(0, 1, 2), (0, 1, 2), (2, 1, 0)]
The number of voters is 3
The number of candidate is 3
The candidates are [0, 1, 2]
+---+---+
| 2 | 1 |
+---+---+
| a | c |
| b | b |
| c | a |
+---+---+

Warning

There are two things to keep in mind when defining a Profile.

  1. The length of rcounts must be equal to the number of rankings used to define the profile.

  2. You cannot skip a number when defining the rankings. That is, the following will produce an error:

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

Profile Methods#

See the next section for a complete list of the methods associated with a Profile.

Margins#

The margin of \(x\) over \(y\) in \(\mathbf{P}\) is

\[Margin_\mathbf{P}(x,y) = \sum_{L\in\mathcal{L}(X), x\mathrel{L}y}\mathbf{P}(L)\ \ \ - \sum_{L\in\mathcal{L}(X), y\mathrel{L}x}\mathbf{P}(L)\]
from pref_voting.profiles import Profile
prof = Profile([[0, 1, 2], [1, 2, 0], [2, 0, 1]], [4, 3, 2])
prof.display()
print(f"The margin of 0 over 1 is {prof.margin(0,1)}")
print(f"The margin of 1 over 0 is {prof.margin(1,0)}")
print(f"The margin of 0 over 2 is {prof.margin(0,2)}")
print(f"The margin of 2 over 0 is {prof.margin(2,0)}")
print(f"The margin of 1 over 2 is {prof.margin(1,2)}")
print(f"The margin of 2 over 1 is {prof.margin(2,1)}")
+---+---+---+
| 4 | 3 | 2 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 2 | 0 |
| 2 | 0 | 1 |
+---+---+---+
The margin of 0 over 1 is 3
The margin of 1 over 0 is -3
The margin of 0 over 2 is -1
The margin of 2 over 0 is 1
The margin of 1 over 2 is 5
The margin of 2 over 1 is -5

There are a number of other methods that related to the margins of a profile.

  1. \(x\) is majority preferred to \(y\) in \(\mathbf{P}\) if \(Margin_\mathbf{P}(x,y) > 0\)

from pref_voting.profiles import Profile
prof = Profile([[0, 1, 2], [1, 2, 0], [2, 0, 1]], [4, 3, 2])
prof.display()
print(f"0 is majority preferred over 1 is {prof.majority_prefers(0, 1)}")
print(f"0 is majority preferred over 2 is {prof.majority_prefers(0, 2)}")
print(f"1 is majority preferred over 2 is {prof.majority_prefers(1, 2)}")
+---+---+---+
| 4 | 3 | 2 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 2 | 0 |
| 2 | 0 | 1 |
+---+---+---+
0 is majority preferred over 1 is True
0 is majority preferred over 2 is False
1 is majority preferred over 2 is True
  1. The margin matrix for a profile \(\mathbf{P}\) is a matrix where the \((i,j)\)-entry is \(Margin_\mathbf{P}(i,j)\).

from pref_voting.profiles import Profile
prof = Profile([[0, 1, 2], [1, 2, 0], [2, 0, 1]], [4, 3, 2])
prof.display()
print(f"The margin matrix for the profile is: {prof.margin_matrix}")
prof.display_margin_matrix
+---+---+---+
| 4 | 3 | 2 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 2 | 0 |
| 2 | 0 | 1 |
+---+---+---+
The margin matrix for the profile is: [[0, 3, -1], [-3, 0, 5], [1, -5, 0]]
  1. The margin graph is created using .margin_graph() and can be displayed using .display_margin_graph().

Voting-Theoretic Attributes of Profiles#

  1. Condorcet candidates:

    • A candidate \(x\) is a Condorcet winner if \(x\) is majority preferred to every other candidate.

    • A candidate \(x\) is a Condorcet loser if every other candidate is majority preferred to \(x\).

    • A candidate \(x\) is a weak Condorcet winner if there is no candidate that is majority preferred to \(x\).

from pref_voting.profiles import Profile

prof = Profile([[0, 1, 2], [1, 2, 0], [2, 0, 1]], [3, 1, 1])
prof.display()
print(f"The Condorcet winner is {prof.condorcet_winner()}")
print(f"The Condorcet loser is {prof.condorcet_loser()}")
print(f"The weak Condorcet winner(s) is(are) {prof.weak_condorcet_winner()}")

prof = Profile([[0, 1, 2, 3], [1, 2, 0, 3], [2, 0, 1, 3]])
prof.display()
print(f"The Condorcet winner is {prof.condorcet_winner()}")
print(f"The Condorcet loser is {prof.condorcet_loser()}")
print(f"The weak Condorcet winner(s) is(are) {prof.weak_condorcet_winner()}")

prof = Profile([[3, 0, 1, 2], [3, 1, 2, 0], [3, 2, 0, 1]])
prof.display()
print(f"The Condorcet winner is {prof.condorcet_winner()}")
print(f"The Condorcet loser is {prof.condorcet_loser()}")
print(f"The weak Condorcet winner(s) is(are) {prof.weak_condorcet_winner()}")

prof = Profile([[0, 1, 2], [1, 2, 0], [2, 0, 1]], [2, 1, 1])
prof.display()
print(f"The Condorcet winner is {prof.condorcet_winner()}")
print(f"The Condorcet loser is {prof.condorcet_loser()}")
print(f"The weak Condorcet winner(s) is(are) {prof.weak_condorcet_winner()}")
+---+---+---+
| 3 | 1 | 1 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 2 | 0 |
| 2 | 0 | 1 |
+---+---+---+
The Condorcet winner is 0
The Condorcet loser is 2
The weak Condorcet winner(s) is(are) [0]
+---+---+---+
| 1 | 1 | 1 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 2 | 0 |
| 2 | 0 | 1 |
| 3 | 3 | 3 |
+---+---+---+
The Condorcet winner is None
The Condorcet loser is 3
The weak Condorcet winner(s) is(are) None
+---+---+---+
| 1 | 1 | 1 |
+---+---+---+
| 3 | 3 | 3 |
| 0 | 1 | 2 |
| 1 | 2 | 0 |
| 2 | 0 | 1 |
+---+---+---+
The Condorcet winner is 3
The Condorcet loser is None
The weak Condorcet winner(s) is(are) [3]
+---+---+---+
| 2 | 1 | 1 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 2 | 0 |
| 2 | 0 | 1 |
+---+---+---+
The Condorcet winner is None
The Condorcet loser is None
The weak Condorcet winner(s) is(are) [0]
  1. Scoring candidates:

    • The Plurality score of a candidate \(c\) in a profile \(\mathbf{P}\) is the number of voters who rank \(c\) is first place.

    • The Borda score of candidate \(c\) is calculated as follows: the score assigned to \(c\) by a ranking is the number of candidates ranked below \(c\), and the Borda score of \(c\) is the sum of the scores assigned to \(c\) by each ranking in the profile.

    • The Copeland score of candidate \(c\) is calculated as follows: \(c\) receives 1 point for every candidate that \(c\) is majority preferred to, 0 points for every candidate that is tied with \(c\), and -1 points for every candidate that is majority preferred to \(c\).

from pref_voting.profiles import Profile

prof = Profile([[0, 1, 2], [1, 2, 0], [2, 0, 1]], [3, 2, 1])
prof.display()
print(f"The Plurality scores for the candidates are {prof.plurality_scores()}")
print(f"The Borda scores for the candidates are {prof.borda_scores()}")
print(f"The Copeland scores for the candidates are {prof.copeland_scores()}")
+---+---+---+
| 3 | 2 | 1 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 2 | 0 |
| 2 | 0 | 1 |
+---+---+---+
The Plurality scores for the candidates are {0: 3, 1: 2, 2: 1}
The Borda scores for the candidates are {0: 7, 1: 7, 2: 4}
The Copeland scores for the candidates are {0: 1.0, 1: 0.0, 2: -1.0}

Profile Class#

class pref_voting.profiles.Profile(rankings, rcounts=None, cmap=None)[source]#

An anonymous profile of linear rankings of \(n\) candidates. It is assumed that the candidates are named \(0, 1, \ldots, n-1\) and a ranking of the candidates is a list of candidate names. For instance, the list [0, 2, 1] represents the ranking in which \(0\) is ranked above \(2\), \(2\) is ranked above \(1\), and \(0\) is ranked above \(1\).

Parameters:
  • rankings (list[list[int]]) – List of rankings in the profile, where a ranking is a list of candidates.

  • rcounts (list[int], optional) – List of the number of voters associated with each ranking. Should be the same length as rankings. If not provided, it is assumed that 1 voters submitted each element of rankings.

  • cmap (dict[int: str], optional) – Dictionary mapping candidates (integers) to candidate names (strings). If not provided, each candidate name is mapped to itself.

Example:

The following code creates a profile in which 2 voters submitted the ranking [0, 1, 2], 3 voters submitted the ranking [1, 2, 0], and 1 voter submitted the ranking [2, 0, 1]:

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

Warning

In profiles with \(n\) candidates, the candidates must be named using the integers \(0, 1, 2, \ldots, n\). So, the following will produce an error: Profile([[0, 1, 3]]).

anonymize()[source]#

Return a profile which is the anonymized version of this profile.

borda_scores(curr_cands=None)[source]#

The Borda scores in the profile restricted to the candidates in curr_cands.

The Borda score for candidate \(c\) is calculate as follows: the score assigned to \(c\) by a ranking is the number of candidates ranked below \(c\). The Borda score is the sum of the score assigned to \(c\) by each ranking in the ballot.

Parameters:

curr_cands (list[int], optional) – restrict attention to candidates in this list. Defaults to all candidates in the profile if not provided.

Returns:

a dictionary associating each candidate in curr_cands with its Borda score.

condorcet_loser(curr_cands=None)[source]#

Returns the Condorcet loser in the profile restricted to curr_cands if one exists, otherwise return None.

A candidate \(c\) is a Condorcet loser if every other candidate is majority preferred to \(c\).

condorcet_winner(curr_cands=None)[source]#

Returns the Condorcet winner in the profile restricted to curr_cands if one exists, otherwise return None.

The Condorcet winner is the candidate that is majority preferred to every other candidate.

copeland_scores(curr_cands=None, scores=(1, 0, -1))[source]#

The Copeland scores in the profile restricted to the candidates in curr_cands.

The Copeland score for candidate \(c\) is calculated as follows: \(c\) receives scores[0] points for every candidate that \(c\) is majority preferred to, scores[1] points for every candidate that is tied with \(c\), and scores[2] points for every candidate that is majority preferred to \(c\). The default scores is (1, 0, -1).

Parameters:
  • curr_cands (list[int], optional) – restrict attention to candidates in this list. Defaults to all candidates in the profile if not provided.

  • scores (tuple[int], optional) – the scores used to calculate the Copeland score of a candidate \(c\): scores[0] is for the candidates that \(c\) is majority preferred to; scores[1] is the number of candidates tied with \(c\); and scores[2] is the number of candidate majority preferred to \(c\). The default value is scores = (1, 0, -1)

Returns:

a dictionary associating each candidate in curr_cands with its Copeland score.

property counts#

Returns a list of the counts of the rankings in the profile. The type is a list of integers.

cycles()[source]#

Return a list of the cycles in the profile.

description()[source]#

Returns a string describing the profile.

display(cmap=None, style='pretty', curr_cands=None)[source]#

Display a profile (restricted to curr_cands) as an ascii table (using tabulate).

Parameters:
  • cmap (dict[int,str], optional) – the candidate map to use (overrides the cmap associated with this profile)

  • style (str --- "pretty" or "fancy_grid" (or any other style option for tabulate)) – the candidate map to use (overrides the cmap associated with this profile)

  • curr_cands (list[int], optional) – list of candidates

Return type:

None

Example:

from pref_voting.profiles import Profile
prof = Profile([[0,1,2], [1,2,0], [2,0,1]], [2, 3, 1])
prof.display()
prof.display(cmap={0:"a", 1:"b", 2:"c"})
+---+---+---+
| 2 | 3 | 1 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 2 | 0 |
| 2 | 0 | 1 |
+---+---+---+
+---+---+---+
| 2 | 3 | 1 |
+---+---+---+
| a | b | c |
| b | c | a |
| c | a | b |
+---+---+---+
display_margin_graph(cmap=None, curr_cands=None)[source]#

Display the margin graph of the profile (restricted to curr_cands) using the cmap. See MarginGraph.

display_margin_graph_with_defeat(defeat, curr_cands=None, show_undefeated=True, cmap=None)[source]#

Display the margin graph of the profile (restricted to curr_cands) with the defeat edges highlighted using the cmap. See MarginGraph.

display_margin_matrix()[source]#

Display the margin matrix using tabulate.

dominates(cand, curr_cands=None)[source]#

Returns the list of candidates that cand is majority preferred to in the profiles restricted to curr_cands.

dominators(cand, curr_cands=None)[source]#

Returns the list of candidates that are majority preferred to cand in the profile restricted to the candidates in curr_cands.

classmethod from_preflib(instance_or_preflib_file, include_cmap=False)[source]#

Convert an preflib OrdinalInstance or file to a Profile. See pref_voting.io.readers.from_preflib.

is_tied(c1, c2)[source]#

Returns True if c1 tied with c2. That is, the same number of voters rank c1 over c2 as c2 over c1.

is_truncated_linear#

The profile is a (truncated) linear order profile. This is needed for compatability with the ProfileWithTies class.

is_uniquely_weighted()[source]#

Returns True if the profile is uniquely weighted.

A profile is uniquely weighted when there are no 0 margins and all the margins between any two candidates are unique.

majority_graph()[source]#

Returns the majority graph of the profile. See MarginGraph.

majority_prefers(c1, c2)[source]#

Returns true if more voters rank \(c1\) over \(c2\) than \(c2\) over \(c1\); otherwise false.

Parameters:
  • c1 (int) – the first candidate

  • c2 (int) – the second candidate

Return type:

bool

margin(c1, c2)[source]#

The number of voters that rank \(c1\) above \(c2\) minus the number of voters that rank \(c2\) above \(c1\).

Parameters:
  • c1 (int) – the first candidate

  • c2 (int) – the second candidate

Return type:

int

margin_graph()[source]#

Returns the margin graph of the profile. See MarginGraph.

property margin_matrix#

A matrix where the \(i, j\) entry is the margin of candidate \(i\) over candidate \(j\).

Type:

Returns the margin matrix of the profile

num_cands#

The number of candidates

num_rank(c, level)[source]#

The number of voters that rank candidate c at position level

Parameters:
  • c (int) – the candidate

  • level (int) – the position of the candidate in the rankings

num_voters#

The number of voters in the election.

plurality_scores(curr_cands=None)[source]#

The plurality scores in the profile restricted to the candidates in curr_cands.

The plurality score for candidate \(c\) is the number of voters that rank \(c\) in first place.

Parameters:

curr_cands (list[int], optional) – restrict attention to candidates in this list. Defaults to all candidates in the profile if not provided.

Returns:

a dictionary associating each candidate in curr_cands with its plurality score.

property ranking_types#

Returns a list of all the type of rankings in the profile as a list of tuples.

property rankings#

Return a list of all individual rankings in the profile. The type is a list of tuples of integers.

property rankings_as_indifference_list#

Return a list of all individual rankings as indifference lists in the profile. An indifference list of a ranking is a tuple of tuples. Since the rankings are linear orders, an indifference list is a tuple of tuples consisting of a single candidate. The return type is a list of indifference lists.

property rankings_counts#

Returns the submitted rankings and the list of counts.

classmethod read(filename, file_format='preflib', csv_format='candidate_columns', items_to_skip=None)[source]#

Read a profile from a file. See pref_voting.io.readers.read.

remove_candidates(cands_to_ignore)[source]#

Remove all candidates from cands_to_ignore from the profile.

Parameters:

cands_to_ignore (list[int]) – list of candidates to remove from the profile

Returns:

a profile with candidates from cands_to_ignore removed and a dictionary mapping the candidates from the new profile to the original candidate names.

Warning

Since the candidates in a Profile must be named \(0, 1, \ldots, n-1\) (where \(n\) is the number of candidates), you must use the candidate map returned to by the function to recover the original candidate names.

Example:

from pref_voting.profiles import Profile
prof = Profile([[0,1,2], [1,2,0], [2,0,1]])
prof.display()
new_prof, orig_cnames = prof.remove_candidates([1])
new_prof.display() # displaying new candidates names
new_prof.display(cmap=orig_cnames) # use the original candidate names
+---+---+---+
| 1 | 1 | 1 |
+---+---+---+
| 0 | 1 | 2 |
| 1 | 2 | 0 |
| 2 | 0 | 1 |
+---+---+---+
+---+---+---+
| 1 | 1 | 1 |
+---+---+---+
| 0 | 1 | 1 |
| 1 | 0 | 0 |
+---+---+---+
+---+---+---+
| 1 | 1 | 1 |
+---+---+---+
| 0 | 2 | 2 |
| 2 | 0 | 0 |
+---+---+---+
strength_matrix(curr_cands=None, strength_function=None)[source]#

Return the strength matrix of the profile. The strength matrix is a matrix where the entry in row \(i\) and column \(j\) is the number of voters that rank the candidate with index \(i\) over the candidate with index \(j\). If curr_cands is provided, then the strength matrix is restricted to the candidates in curr_cands. If strength_function is provided, then the strength matrix is computed using the strength function.

strict_maj_size()[source]#

Returns the strict majority of the number of voters.

support(c1, c2)[source]#

The number of voters that rank \(c1\) above \(c2\)

Parameters:
  • c1 (int) – the first candidate

  • c2 (int) – the second candidate

Return type:

int

support_graph()[source]#

Returns the margin graph of the profile. See SupportGraph.

to_latex(cmap=None, curr_cands=None)[source]#

Returns a string describing the profile (restricted to curr_cands) as a LaTeX table (use the provided cmap or the cmap associated with the profile).

Example:

from pref_voting.profiles import Profile
prof = Profile([[0,1,2], [1,2,0], [2,0,1]], [2, 3, 1])
print(prof.to_latex())
print()
print(prof.to_latex(cmap={0:"a", 1:"b", 2:"c"}))
\begin{tabular}{ccc}
$2$ & $3$ & $1$\\\hline 
$0$ & $1$ & $2$\\ 
$1$ & $2$ & $0$\\ 
$2$ & $0$ & $1$
\end{tabular}

\begin{tabular}{ccc}
$2$ & $3$ & $1$\\\hline 
$a$ & $b$ & $c$\\ 
$b$ & $c$ & $a$\\ 
$c$ & $a$ & $b$
\end{tabular}
to_preflib_instance()[source]#

Returns an instance of the OrdinalInstance class from the preflibtools package. See pref_voting.io.writers.to_preflib_instance.

to_profile_with_ties()[source]#

Returns the profile as a ProfileWithTies

weak_condorcet_winner(curr_cands=None)[source]#

Returns a list of the weak Condorcet winners in the profile restricted to curr_cands (which may be empty).

A candidate \(c\) is a weak Condorcet winner if there is no other candidate that is majority preferred to \(c\).

Note

While the Condorcet winner is unique if it exists, there may be multiple weak Condorcet winners.

write(filename, file_format='preflib', csv_format='candidate_columns')[source]#

Write a profile to a file. See pref_voting.io.writers.write.