Skip to content

Vote

votingsys.vote

Contain the vote implementations.

votingsys.vote.BaseVote

Bases: ABC

Define the base class to implement a vote.

votingsys.vote.BaseVote.equal abstractmethod

equal(other: Any, equal_nan: bool = False) -> bool

Indicate if two vote objects are equal or not.

Parameters:

Name Type Description Default
other Any

The other object to compare.

required
equal_nan bool

Whether to compare NaN's as equal. If True, NaN's in both objects will be considered equal.

False

Returns:

Type Description
bool

True if the two votes are equal, otherwise False.

Example usage:

>>> from collections import Counter
>>> from votingsys.vote import SingleMarkVote
>>> obj1 = SingleMarkVote(Counter({"a": 10, "b": 2, "c": 5, "d": 3}))
>>> obj2 = SingleMarkVote(Counter({"a": 10, "b": 2, "c": 5, "d": 3}))
>>> obj3 = SingleMarkVote(Counter({"a": 10, "b": 2}))
>>> obj1.equal(obj2)
True
>>> obj1.equal(obj3)
False

votingsys.vote.BaseVote.get_num_candidates abstractmethod

get_num_candidates() -> int

Return the number of candidates.

Returns:

Type Description
int

The number of candidates.

Example usage:

>>> from collections import Counter
>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote(Counter({"a": 10, "b": 2, "c": 5, "d": 3}))
>>> vote.get_num_candidates()
4

votingsys.vote.BaseVote.get_num_voters abstractmethod

get_num_voters() -> int

Return the number of voters.

Returns:

Type Description
int

The number of voters.

Example usage:

>>> from collections import Counter
>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote(Counter({"a": 10, "b": 2, "c": 5, "d": 3}))
>>> vote.get_num_voters()
20

votingsys.vote.MultipleWinnersFoundError

Bases: Exception

Raised when multiple winners are found instead of one.

votingsys.vote.RankedVote

Bases: BaseVote

Define the ranked vote.

A ranked vote, also known as a preferential vote, is a voting system in which voters rank candidates or options in order of preference, rather than choosing just one.

Parameters:

Name Type Description Default
ranking DataFrame

A DataFrame with the ranking for each voter. Each column represents a candidate, and each row is a voter ranking. The ranking goes from 0 to n-1, where n is the number of candidates. One column contains the number of voters for this ranking.

required
count_col str

The column with the count data for each ranking.

'count'

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote(
...     pl.DataFrame({"a": [0, 1, 2], "b": [1, 2, 0], "c": [2, 0, 1], "count": [3, 5, 2]})
... )
>>> vote
RankedVote(num_candidates=3, num_voters=10, count_col='count')
>>> vote.ranking
shape: (3, 4)
┌─────┬─────┬─────┬───────┐
│ a   ┆ b   ┆ c   ┆ count │
│ --- ┆ --- ┆ --- ┆ ---   │
│ i64 ┆ i64 ┆ i64 ┆ i64   │
╞═════╪═════╪═════╪═══════╡
│ 0   ┆ 1   ┆ 2   ┆ 3     │
│ 1   ┆ 2   ┆ 0   ┆ 5     │
│ 2   ┆ 0   ┆ 1   ┆ 2     │
└─────┴─────┴─────┴───────┘

votingsys.vote.RankedVote.ranking property

ranking: DataFrame

Return the DataFrame containing the rankings.

votingsys.vote.RankedVote.absolute_majority_winner

absolute_majority_winner() -> str

Compute the winner based on the absolute majority rule.

The candidate receiving more than 50% of the vote is the winner.

Returns:

Type Description
str

The winner based on the absolute majority rule.

Raises:

Type Description
WinnerNotFoundError

if no candidate has the majority of votes.

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame({"a": [0, 1, 2], "b": [1, 2, 0], "c": [2, 0, 1], "count": [3, 6, 2]}),
... )
>>> vote.absolute_majority_winner()
'c'

votingsys.vote.RankedVote.borda_count_winner

borda_count_winner(points: Sequence | None = None) -> str

Compute the winner based on the Borda count rule.

The Borda count method is a ranked voting system where voters list candidates in order of preference. Points are assigned based on position in each ranking. For example, in an election with n candidates, a first-place vote earns n points, second place gets n-1, and so on, down to 1. The candidate with the highest total score across all votes wins. This method considers the overall preferences of voters, not just their top choices.

Parameters:

Name Type Description Default
points Sequence | None

The points associated for each rank. The first value is the point for rank 0, the second value is the point for rank 1, etc. The number of points must be equal to the number of candidates. If no points is given, the default points are [n, n-1, n-2, ..., 1]

None

Returns:

Type Description
str

The winner based on the Borda count rule.

Raises:

Type Description
MultipleWinnersFoundError

if the leading candidates are tied.

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame({"a": [0, 1, 2], "b": [1, 2, 0], "c": [2, 0, 1], "count": [3, 5, 2]}),
... )
>>> vote.borda_count_winner()
'c'

votingsys.vote.RankedVote.borda_count_winners

borda_count_winners(
    points: Sequence | None = None,
) -> tuple[str, ...]

Compute the winner(s) based on the Borda count rule.

The Borda count method is a ranked voting system where voters list candidates in order of preference. Points are assigned based on position in each ranking. For example, in an election with n candidates, a first-place vote earns n points, second place gets n-1, and so on, down to 1. The candidate with the highest total score across all votes wins. This method considers the overall preferences of voters, not just their top choices.

Parameters:

Name Type Description Default
points Sequence | None

The points associated for each rank. The first value is the point for rank 0, the second value is the point for rank 1, etc. The number of points must be equal to the number of candidates. If no points is given, the default points are [n, n-1, n-2, ..., 1]

None

Returns:

Type Description
tuple[str, ...]

The winners based on the Borda count rule. Multiple winners can be returned if the leading candidates are tied. The candiates are sorted by alphabetical order.

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame({"a": [0, 1, 2], "b": [1, 2, 0], "c": [2, 0, 1], "count": [3, 5, 2]}),
... )
>>> vote.borda_count_winners()
('c',)
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame({"a": [0, 1, 2], "b": [1, 0, 2], "c": [2, 0, 1], "count": [1, 1, 1]}),
... )
>>> vote.borda_count_winners(points=[4, 2, 1])
('a', 'b', 'c')

votingsys.vote.RankedVote.borda_counts

borda_counts(
    points: Sequence | None = None,
) -> dict[str, float]

Compute the Borda count for each candidate.

The Borda count method is a ranked voting system where voters list candidates in order of preference. Points are assigned based on position in each ranking. For example, in an election with n candidates, a first-place vote earns n points, second place gets n-1, and so on, down to 1. The candidate with the highest total score across all votes wins. This method considers the overall preferences of voters, not just their top choices.

Parameters:

Name Type Description Default
points Sequence | None

The points associated for each rank. The first value is the point for rank 0, the second value is the point for rank 1, etc. The number of points must be equal to the number of candidates. If no points is given, the default points are [n, n-1, n-2, ..., 1]

None

Returns:

Type Description
dict[str, float]

A dictionary with the Borda count for each candidate. The key is the candidate and the value is the Borda count.

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame({"a": [0, 1, 2], "b": [1, 2, 0], "c": [2, 0, 1], "count": [3, 5, 2]}),
... )
>>> vote.borda_counts()
{'a': 21.0, 'b': 17.0, 'c': 22.0}

votingsys.vote.RankedVote.from_dataframe classmethod

from_dataframe(
    ranking: DataFrame, count_col: str = "count"
) -> RankedVote

Instantiate a RankedVote object from a polars.DataFrame containing the ranking.

Internally, RankedVote uses a compressed DataFrame with the number of occurrences for each ranking. For example if the same ranking is N times in the DataFrame, it will be re-encoded as a single row with a count of N. The "compressed" representation is more efficient because the new DataFrame can be much smaller.

Parameters:

Name Type Description Default
ranking DataFrame

The DataFrame with the ranking for each voter.

required
count_col str

The column that will contain the count values for each ranking.

'count'

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote.from_dataframe(
...     pl.DataFrame(
...         {"a": [0, 1, 2, 1, 0, 0], "b": [1, 2, 0, 2, 1, 1], "c": [2, 0, 1, 0, 2, 2]}
...     )
... )
>>> vote
RankedVote(num_candidates=3, num_voters=6, count_col='count')
>>> vote.ranking
shape: (3, 4)
┌─────┬─────┬─────┬───────┐
│ a   ┆ b   ┆ c   ┆ count │
│ --- ┆ --- ┆ --- ┆ ---   │
│ i64 ┆ i64 ┆ i64 ┆ i64   │
╞═════╪═════╪═════╪═══════╡
│ 0   ┆ 1   ┆ 2   ┆ 3     │
│ 1   ┆ 2   ┆ 0   ┆ 2     │
│ 2   ┆ 0   ┆ 1   ┆ 1     │
└─────┴─────┴─────┴───────┘

votingsys.vote.RankedVote.from_dataframe_with_count classmethod

from_dataframe_with_count(
    ranking: DataFrame, count_col: str = "count"
) -> RankedVote

Instantiate a RankedVote object from a polars.DataFrame containing the rankings and their associated counts.

Parameters:

Name Type Description Default
ranking DataFrame

A DataFrame with the ranking for each voters. Each column represents a candidate, and each row is a voter ranking. The ranking goes from 0 to n-1, where n is the number of candidates. One column contains the number of voters for this ranking.

required
count_col str

The column with the count data for each ranking.

'count'

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame(
...         {
...             "a": [0, 1, 2, 0, 2],
...             "b": [1, 2, 0, 1, 1],
...             "c": [2, 0, 1, 2, 0],
...             "count": [3, 5, 2, 1, 0],
...         }
...     ),
... )
>>> vote
RankedVote(num_candidates=3, num_voters=11, count_col='count')
>>> vote.ranking
shape: (3, 4)
┌─────┬─────┬─────┬───────┐
│ a   ┆ b   ┆ c   ┆ count │
│ --- ┆ --- ┆ --- ┆ ---   │
│ i64 ┆ i64 ┆ i64 ┆ i64   │
╞═════╪═════╪═════╪═══════╡
│ 1   ┆ 2   ┆ 0   ┆ 5     │
│ 0   ┆ 1   ┆ 2   ┆ 4     │
│ 2   ┆ 0   ┆ 1   ┆ 2     │
└─────┴─────┴─────┴───────┘

votingsys.vote.RankedVote.get_candidates

get_candidates() -> tuple[str, ...]

Get the candidate names.

Returns:

Type Description
tuple[str, ...]

The candidate names sorted by alphabetical order.

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame({"a": [0, 1, 2], "b": [1, 2, 0], "c": [2, 0, 1], "count": [3, 6, 2]}),
... )
>>> vote.get_candidates()
('a', 'b', 'c')

votingsys.vote.RankedVote.plurality_counts

plurality_counts() -> dict[str, int]

Compute the plurality count for each candidate, i.e. the number of voters who rank each candidate in first place.

Returns:

Type Description
dict[str, int]

A dictionary with the count of votes for each candidate. The key is the candidate and the value is the number of votes.

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame({"a": [0, 1, 2], "b": [1, 2, 0], "c": [2, 0, 1], "count": [3, 6, 2]}),
... )
>>> vote.plurality_counts()
{'a': 3, 'b': 2, 'c': 6}

votingsys.vote.RankedVote.plurality_winner

plurality_winner() -> str

Compute the winner based on the plurality rule.

This rule is also named First-Past-The-Post (FPTP). The leading candidate, whether or not they have a majority of votes, is the winner.

Returns:

Type Description
str

The winner based on the plurality rule.

Raises:

Type Description
MultipleWinnersFoundError

if the leading candidates are tied.

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame({"a": [0, 1, 2], "b": [1, 2, 0], "c": [2, 0, 1], "count": [3, 6, 2]}),
... )
>>> vote.plurality_winner()
'c'

votingsys.vote.RankedVote.plurality_winners

plurality_winners() -> tuple[str, ...]

Compute the winner(s) based on the plurality rule.

This rule is also named First-Past-The-Post (FPTP). The leading candidate, whether or not they have a majority of votes, is the winner.

Returns:

Type Description
tuple[str, ...]

The winners based on the plurality rule. Multiple winners can be returned if the leading candidates are tied. The candiates are sorted by alphabetical order.

Example usage:

>>> import polars as pl
>>> from votingsys.vote import RankedVote
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame({"a": [0, 1, 2], "b": [1, 2, 0], "c": [2, 0, 1], "count": [3, 6, 2]}),
... )
>>> vote.plurality_winners()
('c',)
>>> vote = RankedVote.from_dataframe_with_count(
...     pl.DataFrame(
...         {"a": [0, 1, 2, 1], "b": [1, 2, 0, 0], "c": [2, 0, 1, 2], "count": [3, 6, 2, 4]}
...     ),
... )
>>> vote.plurality_winners()
('b', 'c')

votingsys.vote.SingleMarkVote

Bases: BaseVote

Define a single-mark vote.

This vote assumes that the voter must mark one and only one candidate.

Parameters:

Name Type Description Default
counter Counter

The counter with the number of votes for each candidate.

required

Raises:

Type Description
ValueError

if at least one count is negative (<0).

ValueError

if the counter is empty.

Example usage:

>>> from collections import Counter
>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote(Counter({"a": 10, "b": 2, "c": 5, "d": 3}))
>>> vote
SingleMarkVote(
  (counter): Counter({'a': 10, 'c': 5, 'd': 3, 'b': 2})
)

votingsys.vote.SingleMarkVote.absolute_majority_winner

absolute_majority_winner() -> str

Compute the winner based on the absolute majority rule.

The candidate receiving more than 50% of the vote is the winner.

Returns:

Type Description
str

The winner based on the absolute majority rule.

Raises:

Type Description
WinnerNotFoundError

if no candidate has the majority of votes.

Example usage:

>>> from collections import Counter
>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote(Counter({"a": 10, "b": 20, "c": 5, "d": 3}))
>>> vote.absolute_majority_winner()
'b'

votingsys.vote.SingleMarkVote.from_dataframe classmethod

from_dataframe(
    frame: DataFrame,
    choice_col: str,
    count_col: str | None = None,
) -> SingleMarkVote

Instantiate a SingleMarkVote object from a polars.DataFrame containing the choices.

Parameters:

Name Type Description Default
frame DataFrame

The input DataFrame containing the choices.

required
choice_col str

The column containing the choices.

required
count_col str | None

The column containing the count for each choice. If None, it assumes the count for each choice is 1.

None

Returns:

Type Description
SingleMarkVote

The instantiated SingleMarkVote.

Example usage:

>>> import polars as pl
>>> from votingsys.vote import SingleMarkVote
>>> # Example without count column
>>> vote = SingleMarkVote.from_dataframe(
...     pl.DataFrame({"first_choice": ["a", "b", "a", "c", "a", "a", "b"]}),
...     choice_col="first_choice",
... )
>>> vote
SingleMarkVote(
  (counter): Counter({'a': 4, 'b': 2, 'c': 1})
)
>>> # Example with count column
>>> vote = SingleMarkVote.from_dataframe(
...     pl.DataFrame(
...         {
...             "first_choice": ["a", "b", "a", "c", "a", "a", "b"],
...             "count": [3, 3, 5, 2, 2, 6, 1],
...         }
...     ),
...     choice_col="first_choice",
...     count_col="count",
... )
>>> vote
SingleMarkVote(
  (counter): Counter({'a': 16, 'b': 4, 'c': 2})
)

votingsys.vote.SingleMarkVote.from_sequence classmethod

from_sequence(votes: Sequence[str]) -> SingleMarkVote

Instantiate a SingleMarkVote object from the sequence of votes.

Parameters:

Name Type Description Default
votes Sequence[str]

The sequence of votes.

required

Returns:

Type Description
SingleMarkVote

The instantiated SingleMarkVote.

Example usage:

>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote.from_sequence(["a", "b", "a", "c", "a", "a", "b"])
>>> vote
SingleMarkVote(
  (counter): Counter({'a': 4, 'b': 2, 'c': 1})
)

votingsys.vote.SingleMarkVote.from_series classmethod

from_series(choices: Series) -> SingleMarkVote

Instantiate a SingleMarkVote object from a polars.Series containing the choices.

Parameters:

Name Type Description Default
choices Series

The polars.Series containing the choices.

required

Returns:

Type Description
SingleMarkVote

The instantiated SingleMarkVote.

Example usage:

>>> import polars as pl
>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote.from_series(pl.Series(["a", "b", "a", "c", "a", "a", "b"]))
>>> vote
SingleMarkVote(
  (counter): Counter({'a': 4, 'b': 2, 'c': 1})
)

votingsys.vote.SingleMarkVote.get_candidates

get_candidates() -> tuple[str, ...]

Get the candidate names.

Returns:

Type Description
tuple[str, ...]

The candidate names sorted by alphabetical order.

Example usage:

>>> from collections import Counter
>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote(Counter({"a": 10, "b": 20, "c": 5, "d": 3}))
>>> vote.get_candidates()
('a', 'b', 'c', 'd')

votingsys.vote.SingleMarkVote.plurality_counts

plurality_counts() -> dict[str, int]

Compute the number of votes for each candidate.

Returns:

Type Description
dict[str, int]

A dictionary with the number of votes for each candidate. The key is the candidate and the value is the number of votes.

Example usage:

>>> from collections import Counter
>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote(Counter({"a": 10, "b": 2, "c": 5, "d": 3}))
>>> vote.plurality_counts()
{'a': 10, 'b': 2, 'c': 5, 'd': 3}

votingsys.vote.SingleMarkVote.plurality_winner

plurality_winner() -> str

Compute the winner based on the plurality rule.

This rule is also named First-Past-The-Post (FPTP). The leading candidate, whether or not they have a majority of votes, is the winner.

Returns:

Type Description
str

The winner based on the plurality rule.

Raises:

Type Description
MultipleWinnersFoundError

if the leading candidates are tied.

Example usage:

>>> from collections import Counter
>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote(Counter({"a": 10, "b": 2, "c": 5, "d": 3}))
>>> vote.plurality_winner()
'a'

votingsys.vote.SingleMarkVote.plurality_winners

plurality_winners() -> tuple[str, ...]

Compute the winner(s) based on the plurality rule.

This rule is also named First-Past-The-Post (FPTP). The leading candidate, whether or not they have a majority of votes, is the winner.

Returns:

Type Description
tuple[str, ...]

The winners based on the plurality rule. Multiple winners can be returned if the leading candidates are tied. The candiates are sorted by alphabetical order.

Example usage:

>>> from collections import Counter
>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote(Counter({"a": 10, "b": 2, "c": 5, "d": 3}))
>>> vote.plurality_winners()
('a',)
>>> vote = SingleMarkVote(Counter({"a": 10, "b": 2, "c": 5, "d": 10}))
>>> vote.plurality_winners()
('a', 'd')

votingsys.vote.SingleMarkVote.super_majority_winner

super_majority_winner(threshold: float) -> str

Compute the winner based on the super majority rule.

The candidate receiving more than X% of the vote is the winner, where X > 0.5.

Parameters:

Name Type Description Default
threshold float

The minimal threshold to find the super majority winner.

required

Returns:

Type Description
str

The winner based on the super majority rule.

Raises:

Type Description
WinnerNotFoundError

if no candidate has the super majority of votes.

ValueError

if the threshold is not valid.

Example usage:

>>> from collections import Counter
>>> from votingsys.vote import SingleMarkVote
>>> vote = SingleMarkVote(Counter({"a": 10, "b": 30, "c": 5, "d": 3}))
>>> vote.super_majority_winner(0.6)
'b'

votingsys.vote.WinnerNotFoundError

Bases: Exception

Raised when no winner can be found.