Source code for embedded_voting.rules.singlewinner_rules.rule

# -*- coding: utf-8 -*-
"""
This file is part of Embedded Voting.
"""

import numpy as np
from embedded_voting.embeddings.embeddings import Embeddings
from embedded_voting.ratings.ratings import Ratings
from embedded_voting.utils.cached import DeleteCacheMixin, cached_property
from embedded_voting.utils.miscellaneous import ranking_from_scores, winner_from_scores


[docs]class Rule(DeleteCacheMixin): """ The general class of functions for scoring rules. These rules aggregate the scores of every voter to create a ranking of the candidates and select a winner. Parameters ---------- score_components : int The number of components in the aggregated score of every candidate. If `> 1`, we perform a lexical sort to obtain the ranking. embeddings_from_ratings: EmbeddingsFromRatings If no embeddings are specified in the call, this `EmbeddingsFromRatings` object is use to generate the embeddings from the ratings. Default: `EmbeddingsFromRatingsIdentity()`. Attributes ---------- ratings_ : Ratings The ratings of voters on which we run the election. embeddings_ : Embeddings The embeddings of the voters on which we run the election. """ def __init__(self, score_components=1, embeddings_from_ratings=None): self.score_components = score_components self.embeddings_from_ratings = embeddings_from_ratings self.ratings_ = None self.embeddings_ = None def __call__(self, ratings, embeddings=None): """ Parameters ---------- ratings : Ratings or list or np.ndarray The ratings of voters on which we run the election. embeddings : Embeddings or list or np.ndarray The embeddings of the voters on which we run the election. Return ------ Rule The object itself. """ self.delete_cache() self.ratings_ = Ratings(ratings) if embeddings is not None: self.embeddings_ = Embeddings(embeddings, norm=False) elif self.embeddings_from_ratings is not None: self.embeddings_ = self.embeddings_from_ratings(self.ratings_) else: # Useful for rules that do not rely on the embeddings, such as RuleSumRatings. self.embeddings_ = None return self def _score_(self, candidate): """ Return the aggregated score of a given candidate. This should be implemented for each scoring rule. Parameters ---------- candidate : int Index of the candidate for whom we want the score. Return ------ float or tuple if :attr:`score_components` = 1, return a float, otherwise a tuple of length :attr:`score_components`. """ raise NotImplementedError @cached_property def scores_(self): """ Return the aggregated scores of all candidates. Return ------ list The scores of all candidates. The score of each candidate is a float if :attr:`score_components` = 1 and a tuple of length :attr:`score_components` otherwise. """ return [self._score_(candidate) for candidate in range(self.ratings_.n_candidates)]
[docs] def score_(self, candidate): """ Return the aggregated score of a given candidate. Parameters ---------- candidate : int Index of the candidate for whom we want the score. Return ------ float or tuple if :attr:`score_components` = 1, return a float, otherwise a tuple of length :attr:`score_components`. """ # Note for developers: this method is called by the user to prevent from calling _score_ every time. return self.scores_[candidate]
@cached_property def scores_focus_on_last_(self): """ Return the last score component of each candidate, but only if the other score components are maximal. If :attr:`score_components` is 1, return :attr:`scores_`. Otherwise, for each candidate: * Return the last score component if all other components are maximal. * Return 0 otherwise. Note that if the last score component is defined as non-negative, and if it is always positive for the winner, then :attr:`scores_focus_on_last_` is enough to determine which candidate has the best score by lexicographical order. Return ------ float list The scores of every candidates. Examples -------- Cf. :class:`RuleMaxParallelepiped`. """ if self.score_components == 1: return self.scores_ else: max_comp = max(self.scores_) return [s[-1] if s[:-1] == max_comp[:-1] else 0 for s in self.scores_] @cached_property def ranking_(self): """ Return the ranking of the candidates based on their aggregated scores. Return ------ list of int The ranking of the candidates. In case of tie, candidates with lower indices are favored. """ return ranking_from_scores(self.scores_) @cached_property def winner_(self): """ Return the winner of the election. Return ------ int The index of the winner of the election. In case of tie, candidates with lower indices are favored. """ return winner_from_scores(self.scores_) @cached_property def welfare_(self): """ Return the welfare of all candidates, where the welfare is defined as `(score - score_min)/(score_max - score_min)`. If scores are tuple, then `scores_focus_on_last_` is used. If `score_max = score_min`, then by convention, all candidates have a welfare of 1. Return ------ list of float Welfare of all candidates. """ max_score = np.max(self.scores_focus_on_last_) min_score = np.min(self.scores_focus_on_last_) if max_score == min_score: return [1.] * self.ratings_.n_candidates return list((self.scores_focus_on_last_ - min_score) / (max_score - min_score))
[docs] def plot_winner(self, plot_kind="3D", dim=None, fig=None, plot_position=None, show=True): """ Plot the matrix associated to the winner of the election. Cf. :meth:`Embeddings.plot_candidate`. Parameters ---------- plot_kind : str The kind of plot we want to show. Can be ``'3D'`` or ``'ternary'``. dim : list The 3 dimensions we are using for our plot. By default, it is set to ``[0, 1, 2]``. fig : matplotlib figure The figure on which we add the plot. plot_position : list The position of the plot on the figure. Should be of the form ``[n_rows, n_columns, position]``. show : bool If True, displays the figure at the end of the function. Return ------ matplotlib ax The ax with the plot. """ ax = self.embeddings_.plot_candidate(ratings=self.ratings_, candidate=self.winner_, plot_kind=plot_kind, dim=dim, fig=fig, plot_position=plot_position, show=show) return ax
[docs] def plot_ranking(self, plot_kind="3D", dim=None, row_size=5, show=True): """ Plot the matrix associated to each candidate, in the same order than the ranking of the election. Parameters ---------- plot_kind : str The kind of plot we want to show. Can be ``'3D'`` or ``'ternary'``. dim : list The 3 dimensions we are using for our plot. By default, it is set to ``[0, 1, 2]``. row_size : int Number of subplots by row. By default, it is set to 5 by rows. show : bool If True, displays the figure at the end of the function. """ ranking = self.ranking_ titles = ["#%i. Candidate %i" % (i+1, c) for i, c in enumerate(ranking)] self.embeddings_.plot_candidates(ratings=self.ratings_, plot_kind=plot_kind, dim=dim, list_candidates=ranking, list_titles=titles, row_size=row_size, show=show)