Source code for pref_voting.spatial_profiles

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from pref_voting.utility_functions import *
from pref_voting.utility_profiles import UtilityProfile

[docs] class SpatialProfile(object): """ A spatial profile is a set of candidates and voters in a multi-dimensional space. Each voter and candidate is assigned vector of floats representing their position on each issue. Args: cand_pos (dict): A dictionary mapping each candidate to their position in the space. voter_pos (dict): A dictionary mapping each voter to their position in the space. Attributes: candidates (list): A list of candidates. voters (list): A list of voters. cand_pos (dict): A dictionary mapping each candidate to their position in the space. voter_pos (dict): A dictionary mapping each voter to their position in the space. num_dims (int): The number of dimensions in the space. """ def __init__(self, cand_pos, voter_pos): cand_dims = [len(v) for v in cand_pos.values()] voter_dims = [len(v) for v in voter_pos.values()] assert len(cand_dims) > 0, "There must be at least one candidate." assert len(set(cand_dims)) == 1, "All candidate positions must have the same number of dimensions." assert len(voter_dims) > 0, "There must be at least one voter." assert len(set(voter_dims)) == 1, "All voter positions must have the same number of dimensions." assert cand_dims[0] == voter_dims[0], "Candidate and voter positions must have the same number of dimensions." self.candidates = sorted(list(cand_pos.keys())) self.voters = sorted(list(voter_pos.keys())) self.cand_pos = cand_pos self.voter_pos = voter_pos self.num_dims = len(list(cand_pos.values())[0])
[docs] def voter_position(self, v): """ Given a voter v, returns their position in the space. """ return self.voter_pos[v]
[docs] def candidate_position(self, c): """ Given a candidate c, returns their position in the space. """ return self.cand_pos[c]
[docs] def to_utility_profile(self, utility_function = None): """ Returns a utility profile corresponding to the spatial profile. Args: utility_function (optional, function): A function that takes two vectors and returns a float. The default utility function is the quadratic utility function. Returns: UtilityProfile: A utility profile corresponding to the spatial profile. """ utility_function = quadratic_utility if utility_function is None else utility_function return UtilityProfile([ {c:utility_function(self.voter_position(v), self.candidate_position(c)) for c in self.candidates} for v in self.voters ])
[docs] def to_string(self): """ Returns a string representation of the spatial profile. """ sp_str = '' for c in self.candidates: sp_str += f'C-{c}:{",".join([str(x) for x in self.candidate_position(c)])}_' for v in self.voters: sp_str += f'V-{v}:{",".join([str(x) for x in self.voter_position(v)])}_' return sp_str[:-1]
[docs] @classmethod def from_string(cls, sp_str): """ Returns a spatial profile described by ``sp_str``. ``sp_str`` must be in the format produced by the :meth:`pref_voting.SpatialProfile.write` function. """ cand_positions = {} voter_positions = {} sp_data = sp_str.split('_') for d in sp_data: if d.startswith("C-"): cand,positions = d.split(':') cand_positions[int(cand[2:])] = np.array([float(x) for x in positions.split(',')]) elif d.startswith("V-"): voter,positions = d.split(':') voter_positions[int(voter[2:])] = np.array([float(x) for x in positions.split(',')]) return cls(cand_positions, voter_positions)
[docs] def view(self, show_labels = False): """ Displays the spatial model in a 1D, 2D, or 3D plot. Args: show_labels (optional, bool): If True, displays the labels of each candidate and voter. The default is False. """ assert self.num_dims <= 3, "Can only view profiles with 1, 2, or 3 dimensions" sns.set_theme(style="darkgrid") if self.num_dims == 1: sns.scatterplot(x=[self.voter_position(v)[0] for v in self.voters], y=[1] * len(self.voters), color="blue", label="Voters") sns.scatterplot(x=[self.candidate_position(c)[0] for c in self.candidates], y=[1] * len(self.candidates), color="red", marker='X', label="Candidates") if show_labels: # Adding labels to each point for v in self.voters: plt.annotate(v + 1, (self.voter_position(v)[0], 1)) # Adding labels to each point for c in self.candidates: plt.annotate(c, (self.candidate_position(c)[0], 1)) plt.yticks([]) # this hides the y-axis plt.show() elif self.num_dims == 2: sns.scatterplot(x=[self.voter_position(v)[0] for v in self.voters], y=[self.voter_position(v)[1] for v in self.voters], color="blue", label="Voters") scatter = sns.scatterplot(x=[self.candidate_position(c)[0] for c in self.candidates], y=[self.candidate_position(c)[1] for c in self.candidates], color="red", marker='X', label="Candidates") if show_labels: # Adding labels to each point for v in self.voters: plt.annotate(v + 1, (self.voter_position(v)[0], self.voter_position(v)[1])) for c in self.candidates: plt.annotate(c, (self.candidate_position(c)[0], self.candidate_position(c)[1])) scatter.set(xlabel='Dimension 1', ylabel='Dimension 2') plt.legend() plt.show() elif self.num_dims == 3: fig = plt.figure() ax = fig.add_subplot(111, projection = '3d') x = [self.voter_position(v)[0] for v in self.voters] y = [self.voter_position(v)[1] for v in self.voters] z = [self.voter_position(v)[2] for v in self.voters] ax.scatter(x, y, z, color="blue", label="Voters") x = [self.candidate_position(c)[0] for c in self.candidates] y = [self.candidate_position(c)[1] for c in self.candidates] z = [self.candidate_position(c)[2] for c in self.candidates] ax.scatter(x, y, z, color="red", marker="X", label="Candidates") ax.set_xlabel('Dimension 1') ax.set_ylabel('Dimension 2') ax.set_zlabel('Dimension 3') plt.legend() plt.show()
[docs] def display(self): """ Displays the positions of each candidate and voter in the profile. """ print("Candidates: ") for c in self.candidates: print("Candidate ", c, " position: ", self.candidate_position(c)) print("\nVoters: ") for v in self.voters: print("Voter ", v, " position: ", self.voter_position(v))