Skip to content

Commit

Permalink
add candidate types (e.g., party affiliation) to spatial profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
epacuit committed Sep 27, 2024
1 parent c96fa70 commit 2656410
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 13 deletions.
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
author = 'Wes Holliday and Eric Pacuit'

# The full version, including alpha/beta/rc tags
release = '1.13.31'
release = '1.14.0'


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion pref_voting/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.13.31'
__version__ = '1.14.0'
28 changes: 23 additions & 5 deletions pref_voting/generate_spatial_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ def generate_spatial_profile(num_cands, num_voters, num_dims, cand_cov = None, v

return profs[0] if num_profiles == 1 else profs

def generate_spatial_profile_polarized(cand_clusters, voter_clusters, num_profiles = 1):
def generate_spatial_profile_polarized(
cand_clusters,
voter_clusters,
cluster_types = None,
num_profiles = 1):
"""
Generates a spatial profile with polarized clusters of candidates and voters.
Expand All @@ -80,6 +84,9 @@ def generate_spatial_profile_polarized(cand_clusters, voter_clusters, num_profil
A list of tuples of the form (mean, covariance, number of candidates) for each cluster of candidates.
voter_clusters : list
A list of tuples of the form (mean, covariance, number of voters) for each cluster of voters.
cluster_types : dict, optional
A list of the same length as cand_cluster that associates each cluster to the type of candidate. The default is None.
num_profiles : int, optional
The number of profiles to generate. The default is 1.
Expand All @@ -89,10 +96,13 @@ def generate_spatial_profile_polarized(cand_clusters, voter_clusters, num_profil
A spatial profile with polarized clusters of candidates and voters.
"""

cluster_types = cluster_types if cluster_types is not None else list(range(len(cand_clusters)))
cand_samples = list()
candidate_types = {}
total_num_cands = 0
for cand_cluster in cand_clusters:
for cluster_idx, cand_cluster in enumerate(cand_clusters):
cand_mean, cand_cov, num_cands = cand_cluster
candidate_types.update({cidx: cluster_types[cluster_idx] for cidx in range(total_num_cands, total_num_cands+num_cands)})
total_num_cands += num_cands
cluster_samples = np.random.multivariate_normal(cand_mean, cand_cov,
size=(num_profiles,num_cands))
Expand All @@ -117,7 +127,8 @@ def generate_spatial_profile_polarized(cand_clusters, voter_clusters, num_profil
profs = [SpatialProfile({cidx: cand_samples[pidx][cidx]
for cidx in range(total_num_cands)},
{vidx: voter_samples[pidx][vidx]
for vidx in range(total_num_voters)})
for vidx in range(total_num_voters)},
candidate_types=candidate_types)
for pidx in range(num_profiles)]

return profs[0] if num_profiles == 1 else profs
Expand All @@ -126,7 +137,8 @@ def generate_spatial_profile_polarized(cand_clusters, voter_clusters, num_profil
def generate_spatial_profile_polarized_cands_randomly_polarized_voters(
cand_clusters,
num_voters,
voter_distributions,
voter_distributions,
cluster_types = None,
num_profiles = 1):
"""
Generates a spatial profile with polarized clusters of candidates and voters.
Expand All @@ -139,6 +151,8 @@ def generate_spatial_profile_polarized_cands_randomly_polarized_voters(
The number of voters.
voter_distributions : list
A list of tuples of the form (mean, covariance, prob) for each distribution of voters, where prob is the probability that a voter is assigned to this cluster.
cluster_types : dict, optional
A list of the same length as cand_cluster that associates each cluster to the type of candidate. The default is None.
num_profiles : int, optional
The number of profiles to generate. The default is 1.
Expand All @@ -148,10 +162,13 @@ def generate_spatial_profile_polarized_cands_randomly_polarized_voters(
A spatial profile with polarized clusters of candidates and voters.
"""

cluster_types = cluster_types if cluster_types is not None else list(range(len(cand_clusters)))
cand_samples = list()
candidate_types = {}
total_num_cands = 0
for cand_cluster in cand_clusters:
cand_mean, cand_cov, num_cands = cand_cluster
candidate_types.update({cidx: cluster_types[cluster_idx] for cidx in range(total_num_cands, total_num_cands+num_cands)})
total_num_cands += num_cands
cluster_samples = np.random.multivariate_normal(cand_mean, cand_cov, size=(num_profiles,num_cands))
if len(cand_samples) == 0:
Expand All @@ -174,7 +191,8 @@ def generate_spatial_profile_polarized_cands_randomly_polarized_voters(
profs = [SpatialProfile({cidx: cand_samples[pidx][cidx]
for cidx in range(total_num_cands)},
{vidx: all_potential_voters[voters_clusters_assignment[pidx][vidx]][pidx][vidx]
for vidx in range(num_voters)})
for vidx in range(num_voters)},
candidate_types=candidate_types)
for pidx in range(num_profiles)]

return profs[0] if num_profiles == 1 else profs
2 changes: 1 addition & 1 deletion pref_voting/generate_weighted_majority_graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import networkx as nx
from itertools import combinations
from helper import sublists, compositions, enumerate_compositions, convex_lexicographic_sublists
from pref_voting.helper import sublists, compositions, enumerate_compositions, convex_lexicographic_sublists
from pref_voting.weighted_majority_graphs import MarginGraph
import numpy as np
from scipy.stats import multivariate_normal
Expand Down
2 changes: 0 additions & 2 deletions pref_voting/margin_based_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -1068,8 +1068,6 @@ def ranked_pairs_zt(

return ranked_pairs_tb(profile, curr_cands = curr_cands, tie_breaker = tb_ranking, strength_function = strength_function)

@vm(name="Ranked Pairs Defeat TB",
input_types=[ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MARGIN_GRAPH])
def ranked_pairs_defeat_tb(edata, curr_cands = None, tie_breaker = None, strength_function = None, return_list = False):
"""
Returns the Ranked Pairs defeat relation produced by the Ranked Pairs algorithm with a fixed tie-breaker.
Expand Down
19 changes: 18 additions & 1 deletion pref_voting/spatial_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ class SpatialProfile(object):
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.
cand_types (dict): A dictionary mapping each candidate to their type (e.g., party affiliation).
"""
def __init__(self, cand_pos, voter_pos):
def __init__(self, cand_pos, voter_pos, candidate_types=None):

cand_dims = [len(v) for v in cand_pos.values()]
voter_dims = [len(v) for v in voter_pos.values()]
Expand All @@ -35,6 +36,7 @@ def __init__(self, cand_pos, voter_pos):
self.cand_pos = cand_pos
self.voter_pos = voter_pos
self.num_dims = len(list(cand_pos.values())[0])
self.candidate_types = candidate_types or {c:'unknown' for c in self.candidates}

def voter_position(self, v):
"""
Expand All @@ -48,6 +50,21 @@ def candidate_position(self, c):
"""
return self.cand_pos[c]

def candidate_type(self, c):
"""
Given a candidate c, returns their type.
"""
return self.candidate_types[c]

def set_candidate_types(self, cand_types):
"""
Sets the types of each candidate.
"""

assert set(cand_types.keys()) == set(self.candidates), "The candidate types must be specified for all candidates."

self.candidate_types = candidate_types

def to_utility_profile(self,
utility_function = None,
uncertainty_function=None,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"

[tool.poetry]
name = "pref_voting"
version = "1.13.31"
version = "1.14.0"
description = "pref_voting is a Python package that contains tools to reason about elections and margin graphs, and implementations of voting methods."
authors = ["Eric Pacuit <[email protected]>"]
license = "MIT"
Expand Down
119 changes: 119 additions & 0 deletions scratch.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

setuptools.setup(
name="pref_voting",
version="1.13.31",
version="1.14.0",
author="Eric Pacuit",
author_email='[email protected]',
description="pref_voting is a Python packaging that contains tools to reason about election profiles and margin graphs, and implementations of a variety of preferential voting methods.",
Expand Down
1 change: 1 addition & 0 deletions tests/test_all_vms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def test_all_profile_vms():
prof = Profile([[0, 1], [1, 0]], rcounts=[1, 2])
for vm in voting_methods:
if ElectionTypes.PROFILE in vm.input_types:
print(vm.name)
if vm.name != "Pareto":
assert vm(prof) == [1]
else:
Expand Down

0 comments on commit 2656410

Please sign in to comment.