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
asymmteric: for all \(x, y\in X\), if \(x\mathrel{P} y\) then not \(y\mathrel{P}x\),
transitive: for all \(x, y,z\in X\), if \(x\mathrel{P} y\) and \(y\mathrel{P} z\) then \(x\mathrel{P}z\); and
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
.
When there are \(n\) candidates, the candidates are \(0, 1, 2, \ldots, n-1\).
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
:
rcounts
is a list of integers specifying the number of voters with a given ranking. Ifrankings
is the list of rankings, then it is assumed that the number of voters who submitted rankingrankings[i]
isrcounts[i]
. Ifrcounts
is not provided, the default value is 1 for each ranking.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
.
The length of
rcounts
must be equal to the number of rankings used to define the profile.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
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.
\(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
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]]
The margin graph is created using
.margin_graph()
and can be displayed using.display_margin_graph()
.
Voting-Theoretic Attributes of Profiles¶
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]
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]])
.- 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\), andscores[2]
points for every candidate that is majority preferred to \(c\). The defaultscores
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\); andscores[2]
is the number of candidate majority preferred to \(c\). The default value isscores = (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.
- 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 thecmap
. SeeMarginGraph
.
- 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 thecmap
. SeeMarginGraph
.
- dominates(cand, curr_cands=None)[source]¶
Returns the list of candidates that
cand
is majority preferred to in the profiles restricted tocurr_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 incurr_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 withc2
. That is, the same number of voters rankc1
overc2
asc2
overc1
.
- 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 positionlevel
- 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.
- randomly_truncate(truncation_prob_list=None)[source]¶
Given a truncation_prob_list that determines the probability that a ballot will be truncated at each position, return the randomly truncated profile.
If truncation_prob_list is None, then the truncation probability distribution is uniform.
- 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 incurr_cands
. Ifstrength_function
is provided, then the strength matrix is computed using the strength function.
- 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 providedcmap
or thecmap
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 thepreflibtools
package. Seepref_voting.io.writers.to_preflib_instance
.
- to_utility_profile(seed=None)[source]¶
Returns the profile as a UtilityProfile using the function Utility.from_linear_profile to generate the utility function. So, it assigns a random utility that represents the ranking.
- 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.