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 |
False
|
Returns:
Type | Description |
---|---|
bool
|
|
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 |
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 |
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 |
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 |
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 |
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
|
Returns:
Type | Description |
---|---|
SingleMarkVote
|
The instantiated |
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 |
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 |
required |
Returns:
Type | Description |
---|---|
SingleMarkVote
|
The instantiated |
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.