#!/usr/bin/env python
import logging
from abc import ABC
from collections import Counter
from matplotlib import pyplot as plt
from mapof.elections.cultures_ import generate_approval_votes
from mapof.elections.objects.Election import Election
from mapof.core.inner_distances import hamming
from mapof.core.utils import *
import mapof.elections.persistence.election_imports as imports
import mapof.elections.persistence.election_exports as exports
from mapof.elections.cultures.params import *
[docs]
class ApprovalElection(Election, ABC):
""" Approval Election class. """
def __init__(self,
experiment_id=None,
election_id=None,
culture_id=None,
num_candidates=None,
variable=None,
**kwargs):
super().__init__(experiment_id=experiment_id,
election_id=election_id,
culture_id=culture_id,
num_candidates=num_candidates,
**kwargs)
self.variable = variable
self.approvalwise_vector = []
self.reverse_approvals = []
self.import_approval_election()
def import_approval_election(self):
if self.is_imported and self.experiment_id is not None:
try:
fake = imports.check_if_fake(self.experiment_id, self.election_id, 'app')
if fake:
self.culture_id, self.params, self.num_voters, self.num_candidates = \
imports.import_fake_app_election(self.experiment_id, self.election_id)
else:
self.votes, self.num_voters, self.num_candidates, self.params, \
self.culture_id, self.num_options, self.quantities, self.distinct_votes \
= imports.import_real_app_election(
experiment_id=self.experiment_id,
election_id=self.election_id,
is_shifted=self.is_shifted)
except:
pass
if self.params is None:
self.params = {}
if self.culture_id is not None:
self.params, self.printing_params = update_params_approval(self.params,
self.printing_params,
self.variable,
self.culture_id,
self.num_candidates)
[docs]
def votes_to_approvalwise_vector(self) -> None:
""" Convert votes to approvalwise vectors """
approvalwise_vector = np.zeros([self.num_candidates])
for vote in self.votes:
for c in vote:
approvalwise_vector[c] += 1
approvalwise_vector = approvalwise_vector / self.num_voters
self.approvalwise_vector = np.sort(approvalwise_vector)
def compute_reverse_approvals(self):
self.reverse_approvals = [set(i for i, vote in enumerate(self.votes) if c in vote)
for c in range(self.num_candidates)]
def get_reverse_approvals(self):
if self.reverse_approvals is None or self.reverse_approvals == []:
self.compute_reverse_approvals()
return self.reverse_approvals
def prepare_instance(self, is_exported=None, is_aggregated=True):
self.votes = generate_approval_votes(culture_id=self.culture_id,
num_candidates=self.num_candidates,
num_voters=self.num_voters,
params=self.params)
if not self.fake:
c = Counter(map(tuple, self.votes))
counted_votes = [[count, list(row)] for row, count in c.items()]
counted_votes = sorted(counted_votes, reverse=True)
self.quantities = [a[0] for a in counted_votes]
self.distinct_votes = [a[1] for a in counted_votes]
self.num_options = len(counted_votes)
else:
self.quantities = [self.num_voters]
self.num_options = 1
if is_exported:
exports.export_approval_election(self, is_aggregated=is_aggregated)
def _compute_distances_between_votes(self, distance_id='hamming'):
distances = np.zeros([self.num_voters, self.num_voters])
for v1 in range(self.num_voters):
for v2 in range(self.num_voters):
if distance_id == 'hamming':
distances[v1][v2] = hamming(self.votes[v1], self.votes[v2])
elif distance_id == 'jaccard':
if len(self.votes[v1].union(self.votes[v2])) == 0:
distances[v1][v2] = 1
else:
distances[v1][v2] = 1 - len(
self.votes[v1].intersection(self.votes[v2])) / len(
self.votes[v1].union(self.votes[v2]))
self.distances['vote'] = distances
if self.is_exported:
exports.export_distances(self, object_type='vote')
return distances
def _compute_distances_between_candidates(self, distance_id='hamming'):
self.compute_reverse_approvals()
distances = np.zeros([self.num_candidates, self.num_candidates])
for c1 in range(self.num_candidates):
for c2 in range(self.num_candidates):
if distance_id == 'hamming':
distances[c1][c2] = hamming(self.reverse_approvals[c1],
self.reverse_approvals[c2])
elif distance_id == 'jaccard':
if len(self.reverse_approvals[c1].union(self.reverse_approvals[c2])) == 0:
distances[c1][c2] = 1
else:
distances[c1][c2] = 1 - len(
self.reverse_approvals[c1].intersection(self.reverse_approvals[c2])) \
/ len(
self.reverse_approvals[c1].union(self.reverse_approvals[c2]))
self.distances['candidate'] = distances
if self.is_exported:
exports.export_distances(self, object_type='candidate')
return distances
def compute_distances(self, object_type=None, distance_id='hamming'):
if object_type is None:
object_type = self.object_type
if object_type == 'vote':
return self._compute_distances_between_votes(distance_id=distance_id)
elif object_type == 'candidate':
return self._compute_distances_between_candidates(distance_id=distance_id)
[docs]
def print_map(
self,
show=True,
radius=None,
name=None,
alpha=0.1,
s=30,
object_type=None,
double_gradient=False,
saveas=None,
color='blue',
marker='o',
title_size=20,
annotate=False
):
""" Print a map of the elections. """
if object_type is None:
object_type = self.object_type
if object_type == 'vote':
length = self.num_voters
votes = self.votes
elif object_type == 'candidate':
length = self.num_candidates
votes = self.get_reverse_approvals()
else:
logging.warning(f'Incorrect object type: {object_type}')
length = 0
if object_type is None:
object_type = self.object_type
plt.figure(figsize=(6.4, 6.4))
X = []
Y = []
for elem in self.coordinates[object_type]:
X.append(elem[0])
Y.append(elem[1])
start = False
if start:
plt.scatter(X[0], Y[0],
color='sienna',
s=1000,
alpha=1,
marker='X')
if double_gradient:
for i in range(length):
x = float(self.points['voters'][i][0])
y = float(self.points['voters'][i][1])
plt.scatter(X[i], Y[i], color=[0, y, x], s=s, alpha=alpha)
else:
plt.scatter(X, Y, color=color, s=s, alpha=alpha, marker=marker)
if annotate:
for i in range(len(X)):
plt.annotate(i, (X[i], Y[i]), color='black')
avg_x = np.mean(X)
avg_y = np.mean(Y)
if radius:
plt.xlim([avg_x - radius, avg_x + radius])
plt.ylim([avg_y - radius, avg_y + radius])
plt.axis('off')
if saveas is not None:
if saveas == 'default':
saveas = f'{self.label}_{object_type}'
file_name = os.path.join(os.getcwd(), "images", name, f'{saveas}.png')
plt.savefig(file_name, bbox_inches='tight', dpi=100)
if show:
plt.show()
else:
plt.clf()
plt.close()