Source code for embedded_voting.rules.singlewinner_rules.rule_features

# -*- coding: utf-8 -*-
"""
This file is part of Embedded Voting.
"""
import numpy as np
import matplotlib.pyplot as plt
from embedded_voting.embeddings.embeddings import Embeddings
from embedded_voting.ratings.ratings import Ratings
from embedded_voting.rules.singlewinner_rules.rule import Rule
from embedded_voting.utils.cached import cached_property
from embedded_voting.utils.miscellaneous import normalize


[docs]class RuleFeatures(Rule): """ Voting rule in which the aggregated score of a candidate is the norm of the feature vector of this candidate. Intuitively, for each candidate, her feature on embedding dimension `d` is the ideal rating that a voter of group `d` should put to that candidate. In this model, the actual rating of a voter for this candidate would be a mean of the features, weighted by the voter's embedding: `embeddings[voter, :] @ features[candidate, :]`. Considering all the voters and all the candidates, we then obtain `ratings = embeddings @ features.T`, i.e. `features = (inv(embeddings) @ ratings).T`. Since `embeddings` is not always invertible, we consider in practice `features = (pinv(embeddings) @ ratings).T`. This can be seen as a least-square approximation of the inital model. Finally, the score of a candidate is the Euclidean norm of her vector of features. Examples -------- >>> ratings = Ratings(np.array([[.5, .6, .3], [.7, 0, .2], [.2, 1, .8]])) >>> embeddings = Embeddings(np.array([[1, 1], [1, 0], [0, 1]]), norm=True) >>> election = RuleFeatures()(ratings, embeddings) >>> election.scores_ [0.669..., 0.962..., 0.658...] >>> election.ranking_ [1, 0, 2] >>> election.winner_ 1 >>> election.welfare_ [0.0353..., 1.0, 0.0] """ @cached_property def features_(self): """ This function return the feature vector of all candidates. Return ------ np.ndarray The matrix of features. Its shape is :attr:`~embedded_voting.Ratings.n_candidates`, :attr:`~embedded_voting.Embeddings.n_dim`. """ return np.array((np.linalg.pinv(self.embeddings_) @ self.ratings_).T) def _score_(self, candidate): return np.linalg.norm(self.features_[candidate])
[docs] def plot_features(self, plot_kind="3D", dim: list = None, row_size=5, show=True): """ This function plot the features vector of all candidates in the given dimensions. 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 The number of subplots by row. By default, it is set to 5 plots by row. show : bool If True, plot the figure at the end of the function. """ if dim is None: dim = [0, 1, 2] else: if len(dim) != 3: raise ValueError("The number of dimensions should be 3") n_candidates = self.ratings_.n_candidates n_rows = (n_candidates - 1) // row_size + 1 fig = plt.figure(figsize=(row_size * 5, n_rows * 5)) plot_position = [n_rows, row_size, 1] features = self.features_ for candidate in range(n_candidates): ax = self.embeddings_.plot_candidate(self.ratings_, candidate, plot_kind=plot_kind, dim=dim, fig=fig, plot_position=plot_position, show=False) if plot_kind == "3D": x1 = features[candidate, dim[0]] x2 = features[candidate, dim[1]] x3 = features[candidate, dim[2]] ax.plot([0, x1], [0, x2], [0, x3], color='k', linewidth=2) ax.scatter([x1], [x2], [x3], color='k', s=5) elif plot_kind == "ternary": x1 = features[candidate, dim[0]] x2 = features[candidate, dim[2]] x3 = features[candidate, dim[1]] feature_bis = [x1, x2, x3] feature_bis = np.maximum(feature_bis, 0) size_features = np.linalg.norm(feature_bis) feature_bis = normalize(feature_bis) ax.scatter([feature_bis ** 2], color='k', s=50*size_features+1) plot_position[2] += 1 if show: plt.show() # pragma: no cover