elsim.methods module

Implements various election methods.

These take collections of ballots (elections) as inputs and return the winner according to the rules of that method.

elsim.methods.approval(election, tiebreaker=None)[source]

Find the winner of an election using approval voting.

The candidate with the largest number of approvals wins. [1]_

Parameters:
  • election (array_like) –

    A 2D collection of approval ballots.

    Rows represent voters, and columns represent candidate IDs. A cell contains 1 if that voter approves of that candidate, otherwise 0.

  • tiebreaker ({'random', 'order', None}, optional) – If there is a tie, and tiebreaker is 'random', a random finalist is returned. If ‘order’, the lowest-ID tied candidate is returned. By default, None is returned for ties.

Returns:

winner – The ID number of the winner, or None for an unbroken tie.

Return type:

int

References

Examples

Voter 0 approves Candidate A (index 0) and B (index 1). Voter 1 approves B and C. Voter 2 approves B and C.

>>> election = [[1, 1, 0],
...             [0, 1, 1],
...             [0, 1, 1],
...             ]

Candidate B (1) gets the most approvals and wins the election:

>>> approval(election)
1
elsim.methods.black(election, tiebreaker=None)[source]

Find the winner of a ranked ballot election using Black’s method.

If a Condorcet winner exists, it is returned, otherwise, the Borda winner is returned. [1]_

Parameters:
  • election (array_like) –

    A collection of ranked ballots. Rows represent voters and columns represent rankings, from best to worst, with no tied rankings. Each cell contains the ID number of a candidate, starting at 0.

    For example, if a voter ranks Curie > Avogadro > Bohr, the ballot line would read [2, 0, 1] (with IDs in alphabetical order).

  • tiebreaker ({'random', 'order', None}, optional) – If there is a tie in the Borda tally, and tiebreaker is 'random', a random finalist is returned. If ‘order’, the lowest-ID tied candidate is returned. By default, None is returned for ties.

Returns:

winner – The ID number of the winner, or None for a Borda count tie.

Return type:

int

References

Examples

Label some candidates:

>>> A, B, C = 0, 1, 2

Specify the ballots for the 5 voters:

>>> election = [[A, C, B],
...             [A, C, B],
...             [B, C, A],
...             [B, C, A],
...             [C, A, B],
...             ]

A is preferred over B by 3 voters. C is preferred over A by 3 voters. C is preferred over B by 3 voters. C is thus the Condorcet winner and wins under Black’s method:

>>> black(election)
2
elsim.methods.borda(election, tiebreaker=None)[source]

Find the winner of a ranked ballot election using the Borda count method.

A voter’s lowest-ranked candidate receives 1 point, second-lowest receives 2 points, and so on. All points are summed, and the highest-scoring candidate wins. [1]_

Parameters:
  • election (array_like) –

    A collection of ranked ballots. Rows represent voters and columns represent rankings, from best to worst, with no tied rankings. Each cell contains the ID number of a candidate, starting at 0.

    For example, if a voter ranks Curie > Avogadro > Bohr, the ballot line would read [2, 0, 1] (with IDs in alphabetical order).

  • tiebreaker ({'random', 'order', None}, optional) – If there is a tie, and tiebreaker is 'random', a random finalist is returned. If ‘order’, the lowest-ID tied candidate is returned. By default, None is returned for ties.

Returns:

winner – The ID number of the winner, or None for an unbroken tie.

Return type:

int

References

Examples

Label some candidates:

>>> A, B, C = 0, 1, 2

Specify the ballots for the 5 voters:

>>> election = [[A, C, B],
...             [A, C, B],
...             [B, C, A],
...             [B, C, A],
...             [C, A, B],
...             ]

Candidate A gets a total of 3+3+1+1+2 = 10 points. Candidate B gets a total of 1+1+3+3+1 = 9 points. Candidate C gets a total of 2+2+2+2+3 = 11 points. Candidate C is the winner:

>>> borda(election)
2
elsim.methods.combined_approval(election, tiebreaker=None)[source]

Find the winner of an election using combined approval voting.

Also known as balanced approval or dis&approval voting, the candidate with the largest number of approvals minus disapprovals wins. [1]_

Parameters:
  • election (array_like) –

    A 2D collection of combined approval ballots.

    Rows represent voters, and columns represent candidate IDs. A cell contains a +1 if that voter approves of that candidate, a -1 if the voter disapproves of that candidate, or a 0 if they are neutral.

  • tiebreaker ({'random', 'order', None}, optional) – If there is a tie, and tiebreaker is 'random', a random finalist is returned. If ‘order’, the lowest-ID tied candidate is returned. By default, None is returned for ties.

Returns:

winner – The ID number of the winner, or None for an unbroken tie.

Return type:

int

References

Examples

Voter 0 approves Candidate A (index 0) and B (index 1). Voter 1 approves of B and disapproves of C. Voter 2 approves of A and disapproves of B and C.

>>> election = [[+1, +1,  0],
...             [ 0, +1, -1],
...             [+1, -1, -1],
...             ]

Candidate A (0) has the highest net approval and wins the election:

>>> combined_approval(election)
0
elsim.methods.condorcet(election)[source]

Find the winner of a ranked ballot election using a Condorcet method.

This does not contain any “tiebreakers”; those will be implemented by other methods’ functions. It is not a Condorcet completion method. [1]_

Parameters:

election (array_like) –

A collection of ranked ballots. Rows represent voters and columns represent rankings, from best to worst, with no tied rankings. Each cell contains the ID number of a candidate, starting at 0.

For example, if a voter ranks Curie > Avogadro > Bohr, the ballot line would read [2, 0, 1] (with IDs in alphabetical order).

Returns:

winner – The ID number of the winner, or None for a Condorcet cycle / tie.

Return type:

int

References

Examples

Label some candidates:

>>> A, B, C = 0, 1, 2

Specify the ballots for the 5 voters:

>>> election = [[A, C, B],
...             [A, C, B],
...             [B, C, A],
...             [B, C, A],
...             [C, A, B],
...             ]

Candidate A is preferred over B by 3 voters. C is preferred over A by 3 voters. C is preferred over B by 3 voters. C is thus the Condorcet winner:

>>> condorcet(election)
2
elsim.methods.condorcet_from_matrix(matrix)[source]

Find the winner of a ranked ballot election using a Condorcet method.

This does not contain any “tiebreakers”; those will be implemented by other methods’ functions. It is not a Condorcet completion method.

Parameters:

matrix (array_like) – A pairwise comparison matrix of candidate vs candidate defeats.

Returns:

winner – The ID number of the winner, or None for a Condorcet cycle / tie.

Return type:

int

Examples

Label some candidates:

>>> A, B, C = 0, 1, 2

Specify the pairwise comparison matrix for the election:

>>> matrix = np.array([[0, 3, 2],
...                    [2, 0, 2],
...                    [3, 3, 0]])

Candidate A (row 0) is preferred over B (column 1) by 3 voters. C (row 2) is preferred over A (column 0) by 3 voters. C (row 2) is preferred over B (column 1) by 3 voters. C is thus the Condorcet winner:

>>> condorcet_from_matrix(matrix)
2
elsim.methods.coombs(election, tiebreaker=None)[source]

Find the winner of an election using Coomb’s method.

If any candidate gets a majority of first-preference votes, they win. Otherwise, the candidate(s) with the most number of last-preference votes is eliminated, votes for eliminated candidates are transferred according to the voters’ preference rankings, and a series of runoff elections are held between the remainders until a candidate gets a majority. [1]_

Parameters:
  • election (array_like) – A collection of ranked ballots. See borda for election format. Currently, this must include full rankings for each voter.

  • tiebreaker ({'random', None}, optional) – If there is a tie, and tiebreaker is 'random', tied candidates are eliminated or selected at random is returned. If ‘order’, the lowest-ID tied candidate is preferred in each tie. By default, None is returned if there are any ties.

Returns:

winner – The ID number of the winner, or None for an unbroken tie.

Return type:

int

References

Examples

Label some candidates:

>>> A, B, C = 0, 1, 2

Specify the ballots for the 5 voters:

>>> election = [[A, C, B],
...             [A, C, B],
...             [B, C, A],
...             [B, C, A],
...             [C, A, B],
...             ]

In the first round, no candidate gets a majority, so Candidate B (1) is eliminated, with 3 out of 5 last-place votes. Voter 2 and 3’s support of B is transferred to Candidate C (2), causing Candidate C to win, with 3 out of 5 votes:

>>> coombs(election)
2
elsim.methods.fptp(election, tiebreaker=None)[source]

Find the winner of an election using first-past-the-post / plurality rule.

The candidate with the largest number of first preferences wins. [1]_

Parameters:
  • election (array_like) – A 2D collection of ranked ballots. (See borda for election format.) Or a 1D array of first preferences only.

  • tiebreaker ({'random', 'order', None}, optional) – If there is a tie, and tiebreaker is 'random', a random finalist is returned. If ‘order’, the lowest-ID tied candidate is returned. By default, None is returned for ties.

Returns:

winner – The ID number of the winner, or None for an unbroken tie.

Return type:

int

References

Examples

Label some candidates:

>>> A, B, C = 0, 1, 2

Specify the ballots for the 5 voters:

>>> election = [[A, C, B],
...             [A, C, B],
...             [B, A, C],
...             [B, C, A],
...             [B, C, A],
...             [C, A, B],
...             ]

Candidate B (1) gets the most first-preference votes, and is the winner:

>>> fptp(election)
1

Single-mark ballots can also be tallied (with ties broken as specified):

>>> election = [A, B, B, C, C]
>>> print(fptp(election))
None

There is a tie between B (1) and C (2). tiebreaker=order always prefers the lower-numbered candidate in a tie:

>>> fptp(election, 'order')
1
elsim.methods.irv(election, tiebreaker=None)[source]

Find the winner of an election using instant-runoff voting.

If any candidate gets a majority of first-preference votes, they win. Otherwise, the candidate(s) with the least number of first-choice votes is eliminated, votes for eliminated candidates are transferred according to the voters’ preference rankings, and a series of runoff elections are held between the remainders until a candidate gets a majority. [1]_

Also known as “the alternative vote”, “ranked-choice voting”, Hare’s method, or Ware’s method.

The votes in each instant-runoff round are calculated from the same set of ranked ballots. If voters are honest and consistent between rounds, then this is also equivalent to the exhaustive ballot method, which uses actual separate runoff elections. [2]_

Parameters:
  • election (array_like) – A collection of ranked ballots. See borda for election format. Currently, this must include full rankings for each voter.

  • tiebreaker ({'random', None}, optional) – If there is a tie, and tiebreaker is 'random', tied candidates are eliminated or selected at random is returned. If ‘order’, the lowest-ID tied candidate is preferred in each tie. By default, None is returned if there are any ties.

Returns:

winner – The ID number of the winner, or None for an unbroken tie.

Return type:

int

References

Examples

Label some candidates:

>>> A, B, C = 0, 1, 2

Specify the ballots for the 5 voters:

>>> election = [[A, C, B],
...             [A, C, B],
...             [B, C, A],
...             [B, C, A],
...             [C, A, B],
...             ]

In the first round, no candidate gets a majority, so Candidate C (2) is eliminated, with 1 out of 5 first-place votes. Voter 4’s support of C is transferred to Candidate A (0), causing Candidate A to win, with 3 out of 5 votes:

>>> irv(election)
0
elsim.methods.matrix_from_scores(election)[source]

Convert a scored ballot election to a pairwise comparison matrix.

Parameters:

election (array_like) –

A 2D collection of score ballots.

Rows represent voters, and columns represent candidate IDs. A cell contains a high score if that voter approves of that candidate, or low score if they disapprove.

Returns:

matrix – A pairwise comparison matrix of candidate vs candidate defeats.

For example, a 3 in row 2, column 5, means that 3 voters preferred Candidate 2 over Candidate 5. Candidates are not preferred over themselves, so the diagonal is all zeros.

Return type:

ndarray

Examples

Voter 0 loves Candidates A (index 0) and B (index 1), but hates C (2). Voter 1 dislikes A, likes B, and loves C. Voter 2 hates A, is lukewarm about B and likes C.

>>> election = [[5, 5, 0],
...             [0, 4, 5],
...             [0, 3, 4]]

So, candidate B is preferred over A by 2 voters, while one is indifferent between them. C is preferred over A by 2 voters, while A is preferred over C by 1 voter. C is preferred over B by 2 voters, while B is preferred over C by 1 voter.

>>> matrix_from_scores(election)
array([[0, 0, 1],
       [2, 0, 1],
       [2, 2, 0]])
elsim.methods.ranked_election_to_matrix(election)[source]

Convert a ranked election to a pairwise comparison matrix.

Each entry in the matrix gives the total number of votes obtained by the row candidate over the column candidate. [1]_

Parameters:

election (array_like) –

A collection of ranked ballots. Rows represent voters and columns represent rankings, from best to worst, with no tied rankings. Each cell contains the ID number of a candidate, starting at 0.

For example, if a voter ranks Curie > Avogadro > Bohr, the ballot line would read [2, 0, 1] (with IDs in alphabetical order).

Returns:

matrix – A pairwise comparison matrix of candidate vs candidate defeats.

For example, a 3 in row 2, column 5, means that 3 voters preferred Candidate 2 over Candidate 5. Candidates are not preferred over themselves, so the diagonal is all zeros.

Return type:

ndarray

References

Examples

Label some candidates:

>>> A, B, C = 0, 1, 2

Specify the ballots for the 5 voters:

>>> election = [[A, C, B],
...             [A, C, B],
...             [B, C, A],
...             [B, C, A],
...             [C, A, B],
...             ]

Convert to a matrix:

>>> ranked_election_to_matrix(election)
array([[0, 3, 2],
       [2, 0, 2],
       [3, 3, 0]], dtype=...)

This shows that Candidate A (row 0) is preferred over B (column 1) by 3 voters. C (row 2) is preferred over A (column 0) by 3 voters. C (row 2) is preferred over B (column 1) by 3 voters.

elsim.methods.runoff(election, tiebreaker=None)[source]

Find the winner of an election using top-two runoff / two-round system.

If any candidate gets a majority of first-preference votes, they win. Otherwise, a runoff election is held between the two highest-voted candidates. [1]_

The votes in the first and second rounds are calculated from the same set of ranked ballots, so this is actually the contingent vote method. [2]_ If voters are honest and consistent between rounds, then the two methods are equivalent. It can also be considered “top-two IRV”, and is closely related to the supplementary vote (which restricts rankings to two).

Parameters:
  • election (array_like) – A collection of ranked ballots. See borda for election format. Currently, this must include full rankings for each voter.

  • tiebreaker ({'random', None}, optional) – If there is a tie, and tiebreaker is 'random', a random finalist is returned. If ‘order’, the lowest-ID tied candidate is returned. By default, None is returned for ties.

Returns:

winner – The ID number of the winner, or None for an unbroken tie.

Return type:

int

References

Examples

Label some candidates:

>>> A, B, C = 0, 1, 2

Specify the ballots for the 5 voters:

>>> election = [[A, C, B],
...             [A, C, B],
...             [B, C, A],
...             [B, C, A],
...             [C, A, B],
...             ]

In the first round, no candidate gets a majority, so Candidate C (2) is eliminated. A runoff is held between the top two: Candidates A (0) and B (1). Voter 4’s support is transferred to Candidate A, causing Candidate A to win, with 3 out of 5 votes:

>>> runoff(election)
0
elsim.methods.score(election, tiebreaker=None)[source]

Find the winner of an election using score (or range) voting.

The candidate with the highest score wins. [1]_

Parameters:
  • election (array_like) –

    A 2D collection of score ballots.

    Rows represent voters, and columns represent candidate IDs. A cell contains a high score if that voter approves of that candidate, or low score if they disapprove

  • tiebreaker ({'random', 'order', None}, optional) – If there is a tie, and tiebreaker is 'random', a random finalist is returned. If ‘order’, the lowest-ID tied candidate is returned. By default, None is returned for ties.

Returns:

winner – The ID number of the winner, or None for an unbroken tie.

Return type:

int

References

Examples

Voter 0 loves Candidates A (index 0) and B (index 1), but hates C (2). Voter 1 dislikes A, likes B, and loves C. Voter 2 hates A, and is lukewarm about B and C.

>>> election = [[5, 5, 0],
...             [0, 4, 5],
...             [0, 3, 3]]

Candidate B (1) gets the highest score and wins the election:

>>> score(election)
1
elsim.methods.sntv(election, n=1, tiebreaker=None)[source]

Find winners of an election using the single non-transferable vote rule.

This is a multi-winner generalization of fptp. The candidates with the largest number of first preferences win. [1]_

Parameters:
  • election (array_like) – A 2D collection of ranked ballots. (See borda for election format.) Or a 1D array of first preferences only.

  • tiebreaker ({'random', 'order', None}, optional) – If there is a tie, and tiebreaker is 'random', random tied candidates are returned. If ‘order’, the lowest-ID tied candidates are returned. By default, None is returned for ties.

Returns:

winner – The ID numbers of the winners, or None for an unbroken tie.

Return type:

set

References

Examples

Label some candidates:

>>> A, B, C, D = 0, 1, 2, 3

Specify the ballots for the 5 voters:

>>> election = [[A, C, B],
...             [A, C, B],
...             [B, A, C],
...             [B, C, A],
...             [B, C, A],
...             [C, A, B],
...             ]

Candidate B (1) gets the most first-preference votes, and Candidate A (0) comes in second. If SNTV is electing two candidates, A and B will win:

>>> sntv(election, 2)
{0, 1}

Single-mark ballots can also be tallied (with ties broken as specified):

>>> election = [A, A, B, B, C, C, D]
>>> print(sntv(election, 2))
None

There is a tie between A (0), B (1) and C (2), and no tiebreaker is specified. If instead we use tiebreaker=order, it always prefers the lower-numbered candidates in a tie:

>>> sntv(election, 2, 'order')
{0, 1}
elsim.methods.star(election, tiebreaker=None)[source]

Find the winner of an election using STAR voting.

The more-preferred of the two highest-scoring candidates wins. [1]_ [2]_

Parameters:
  • election (array_like) –

    A 2D collection of score ballots.

    Rows represent voters, and columns represent candidate IDs. A cell contains a high score if that voter approves of that candidate, or low score if they disapprove.

  • tiebreaker ({'random', 'order', None}, optional) – If there is a tie, and tiebreaker is 'random', a random finalist is returned. If ‘order’, the lowest-ID tied candidate is returned. By default, None is returned for ties.

Returns:

winner – The ID number of the winner, or None for an unbroken tie.

Return type:

int

Notes

If there is a tie during the scoring round (three or more candidates tied for highest score, or two or more tied for second highest), it is broken using a Condorcet method between the tied candidates. [3]

If there is a tie during the automatic runoff (neither candidate is preferred more than the other) then it is broken in favor of the higher-scoring candidate. [3]

If there is still a tie in either round, it is broken according to the tiebreaker parameter.

References

Examples

Voter 0 loves Candidates A (index 0) and B (index 1), but hates C (2). Voter 1 dislikes A, likes B, and loves C. Voter 2 hates A, is lukewarm about B and likes C.

>>> election = [[5, 5, 0],
...             [0, 4, 5],
...             [0, 3, 4]]

Candidates B and C (1 and 2) get the highest scores (12 and 9). Of these, C is preferred on more ballots, and wins the election:

>>> star(election)
2
elsim.methods.utility_winner(utilities, tiebreaker=None)[source]

Find the utilitarian winner of an election. (Dummy “election method”).

Given a set of utilities for each voter-candidate pair, find the candidate who maximizes summed utility across all voters. [1]_

Parameters:
  • utilities (array_like) –

    A 2D collection of utilities.

    Rows represent voters, and columns represent candidate IDs. Higher utility numbers mean greater approval of that candidate by that voter.

  • tiebreaker ({'random', 'order', None}, optional) – If there is a tie, and tiebreaker is 'random', a random finalist is returned. If ‘order’, the lowest-ID tied candidate is returned. By default, None is returned for ties.

Returns:

winner – The ID number of the winner, or None for an unbroken tie.

Return type:

int

References

Examples

Voter 0 loves Candidates A (index 0) and B (index 1), but hates C (2). Voter 1 dislikes A, likes B, and loves C. Voter 2 hates A, and is lukewarm about B and C.

>>> utilities = [[1.0, 1.0, 0.0],
...              [0.1, 0.8, 1.0],
...              [0.0, 0.5, 0.5],
...              ]

Candidate B (1) has the highest overall support and is the utility winner:

>>> utility_winner(utilities)
1