def score_better_balance(data: np.ndarray, numwin: int = 1, max_score: int = 5): """Method based off Reddit post by jan_kasimi. https://www.reddit.com/r/EndFPTP/comments/lil4zz/scorebetterbalance_a_proposal_to_fix_some/ Parameters ---------- data : array shaped (a, b) Election voter scores, 0 to max. Data of candidate ratings for each voter, with - `a` Voters represented as each rows - `b` Candidates represented as each column. numwin : int Number of winners to consider. max_score : int, optional Max allowed score for the ballots. The default is 5. Returns ------- winner : TYPE DESCRIPTION. ties : TYPE DESCRIPTION. output : TYPE DESCRIPTION. """ tally = data.sum(axis=0) # STEP 1: Get scores score_winners, score_ties = tools.winner_check(tally, numwin=1) score_winner = score_winners[0] score_margin = (tally[score_winner] - tally) / max_score # STEP 2: Get candidates that can beat score winner head-to-head winner_data = data[:, score_winner:score_winner + 1] head2head_count_wins = data < winner_data head2head_count_losses = data > winner_data count_wins = head2head_count_wins.sum(axis=0) count_losses = head2head_count_losses.sum(axis=0) count_margin = count_wins - count_losses # STEP 3: Combine head-to-head margin with score margin. combined_margin = count_margin - score_margin winner, ties = tools.winner_check(combined_margin, numwin=numwin) output = {} output['scores'] = tally output['win_loss_margin'] = count_margin output['combined_margin'] = combined_margin return winner, ties, output
def test_winnercheck_ties(self): """test winner_check function, ties""" results = [10, 4, 10, 1, 2] w, t = tools.winner_check(results) self.assertIn(0, t) self.assertIn(2, t) self.assertTrue(len(w) == 0) w, t = tools.winner_check(results, 2) self.assertIn(0, w) self.assertIn(2, w) self.assertTrue(len(w) == 2) self.assertTrue(len(t) == 0)
def score(data, numwin=1): """Score voting. Parameters ---------- data : array shaped (a, b) Election voter scores, 0 to max. Data of candidate ratings for each voter, with - `a` Voters represented as each rows - `b` Candidates represented as each column. numwin : int Number of winners to consider Returns ------- winners : array of shape (numwin,) Winning candidates index. ties : array of shape (tienum,) Tied candidates index for the last round, numbering 'tienum'. tally : array of shape (numwin, b) Score summations for each candidate. """ data = np.atleast_2d(data) sums = np.sum(data, axis=0) winners, ties = tools.winner_check(sums, numwin=numwin) output = {} output['tally'] = sums return winners, ties, output
def plurality(data, numwin=1): """Run plurality election. Parameters ---------- data : array shape (a, b) Election scoring data, 0 to 1. If rating data is input, plurality will find maximum rated candidate. numwin : int Number of winners. For numwin > 1, plurality turns into Single No Transferable Vote multi-winner election. Returns ------- winners : array shape (numwin,) Winning candidate indices ties: array shaped(numties,) If there are tied candidates, return candidate indices here. If no ties, return empty array results : array shaped(b,) End vote count """ new = tools.getplurality(ratings=data) sums = np.sum(new, axis=0) winners, ties = tools.winner_check(sums, numwin=numwin) output = {} output['tally'] = sums return winners, ties, output
def bucklin( data, numwin=1, ): vnum, cnum = data.shape quota = np.ceil(vnum / 2.) vcount = np.zeros(cnum) winners = [] history = [] for ii in range(1, cnum): vcount_ii = np.sum(data == ii, axis=0) vcount = vcount + vcount_ii history.append(vcount) vmax = np.max(vcount) if vmax >= quota: winner_ii, ties = winner_check(vcount, numwin=1) winners.extend(winner_ii) if len(winners) >= numwin: break winners = np.array(winners) output = {} output['tally'] = vcount output['history'] = np.array(history) return winners, ties, output
def _winner_plurality_calcs(self): """Plurality winner of election; return -1 if tie found. Returns ------- winner : int Candidate index of plurality winner votes : int Number of votes cast for plurality winner counts : array shape (a,) Vote counts for all candidates """ distances = self._distances ii = np.argmin(distances, axis=1) ulocs, ucounts = np.unique(ii, return_counts=True) counts = np.zeros(distances.shape[1], dtype=int) counts[ulocs] = ucounts votes = np.max(counts) winner, ties = winner_check(counts, numwin=1) if len(ties) > 1: winner = -1 else: winner = winner[0] return winner, votes, counts
def smith_minimax_matrix(data=None, matrix=None): """Condorcet Smith Minimax voting algorithm for ranked ballots. Parameters ---------- ranks : (a, b) array Election voter rankings, from [1 to b]. Data composed of candidate rankings, with - Voters as the rows - Candidates as the columns. Use 0 to specify unranked (and therefore not to be counted) ballots. - a : number of voters dimension. Voters assign ranks for each candidate. - b : number of candidates. A score is assigned for each candidate from 0 to b-1. matrix : (b, b) array Win-loss matrix Returns ------- winners : array of shape (numwin,) Winning candidates index. ties : array of shape (tienum,) Tied candidates index for the last round, numbering `tienum`. """ m = None if data is not None: m = pairwise_rank_matrix(data) win_losses = m - m.T elif matrix is not None: win_losses = np.array(matrix) cnum = len(win_losses) s = smith_set(wl=win_losses) s = list(s) ifail = np.ones(cnum, dtype=bool) ifail[s] = False min_losses = np.min(win_losses, axis=1) min_min = np.min(min_losses) min_losses[ifail] = min_min - 10 #candidates = np.arange(cnum) #imax = np.argmax(min_losses[s]) #winner = candidates[s][imax] winners, ties = winner_check(min_losses, numwin=1) output = {} if m is not None: output['margin_matrix'] = win_losses output['vote_matrix'] = m output['tally'] = min_losses output['smith_set'] = s return winners, ties, output
def v321(data, numwin: int = 1, rng=None, seed=0): data = np.array(data) # Convert to 3 ratings good/ok/bad dmax = data.max() data = data / dmax * 3 data = np.round(data) cnum = data.shape[1] # Get score data sums = np.sum(data, axis=0) # ROUND 1 # Get 3 semi-finalists with most "good" ratings if cnum > 3: tally3 = np.sum(data == 3, axis=0) semifinalists, semi_ties = tools.winner_check(tally3, numwin=3) # tie break semifinalists, _ = v321_tie_resolver(semifinalists, semi_ties, sums, numwin=3, rng=rng, seed=seed, use_random=True) else: semifinalists = np.array([0, 1, 2]) # ROUND 2 # Get 2 finalists with the fewest "bad" ratings if cnum > 2: bad_count = np.sum(data == 0, axis=0) bad_count = bad_count[semifinalists] finalists, fties = tools.winner_check_named(-bad_count, semifinalists) finalists = v321_tie_resolver(finalists, fties, sums, numwin=2, rng=rng, seed=seed + 1, use_random=True) else: finalists = np.array([0, 1]) semifinalists = np.array([0, 1]) # ROUND 3 # Get winner who is rated above the other on more ballots votes1 = data[:, finalists[0]] > data[:, finalists[1]] votes2 = data[:, finalists[0]] < data[:, finalists[1]] final_votes = [votes1, votes2] winners, ties = tools.winner_check_named(final_votes, finalists) output = {} output['sums'] = sums output['semifinalists'] = semifinalists output['finalists'] = finalists return winners, ties, output
def smith_score( data, numwin=1, ): """Smith then score voting variant. Parameters ---------- data : array shaped (a, b) Election voter scores, 0 to max. Data of candidate ratings for each voter, with - `a` Voters represented as each rows - `b` Candidates represented as each column. numwin : int Number of winners to consider Returns ------- winners : (numwin,) array Winning candidates index. ties : (tienum,) array Tied candidates index for the last round, numbering 'tienum'. output : dict sums : (b,) array Score sums for each candidate vote_matrix : (b,b) array Head-to-head count where row-wise candidate score beats colum-wise candidate. smith_set : (a,) array Candidate indices who are within the Smith Set. """ data = np.atleast_2d(data) sums = data.sum(axis=0) cnum = data.shape[1] in_smith = np.zeros(cnum, dtype=bool) m = condcalcs.pairwise_scored_matrix(data) smith = condcalcs.smith_set(vm=m) smith = list(smith) in_smith[smith] = True sums[~in_smith] = 0 winners, ties = winner_check(sums, numwin=1) output = {} output['sums'] = sums output['vote_matrix'] = m output['smith_set'] = smith output['margin_matrix'] = m - m.T return winners, ties, output
def borda( data, numwin=1, ): vnum, cnum = data.shape data1 = data.copy() data1[data1 == 0] = cnum points = cnum - data1 tally = np.sum(points, axis=0) winner, ties = winner_check(tally, numwin=numwin) output = {} output['tally'] = tally return winner, ties, output
def test_winnercheck2(self): results = [15, 15, 10, 10, 8, 1, 2, 3] w, t = tools.winner_check(results, 2) self.assertIn(0, w) self.assertIn(1, w) self.assertTrue(len(w) == 2) w, t = tools.winner_check(results, 3) self.assertIn(0, w) self.assertIn(1, w) self.assertTrue(len(w) == 2) self.assertIn(2, t) self.assertIn(3, t) self.assertTrue(len(t) == 2) w, t = tools.winner_check(results, 4) self.assertIn(0, w) self.assertIn(1, w) self.assertIn(2, w) self.assertIn(3, w) self.assertTrue(len(w) == 4) self.assertTrue(len(t) == 0) print('winner check!!!')
def distributed(data, numwin=1): """ https://electowiki.org/wiki/Distributed_Voting Parameters ---------- data : TYPE DESCRIPTION. numwin : TYPE, optional DESCRIPTION. The default is 1. Returns ------- None. """ vnum, cnum = data.shape ranking = [] history = [] for ii in range(cnum - 1): # normalize ballots sums = np.sum(data, axis=1)[:, None] data = data / sums * 100 # retrieve loser tally = np.sum(data, axis=0) talley[ranking] = np.nan history.append(tally) ii_losers, ii_ties = tools.winner_check(-tally) # Check if candidate has been eliminated if len(losers) > 0: loser = ii_losers[0] data[:, loser] = 0 ranking.append(loser) # Check if there is a tie in elimination elif ii + len(ii_ties) < cnum: data[:, ii_ties] = 0 ranking.extend(ii_ties) if len(ranking) == cnum - 1: pass return
def copeland(data, numwin=1): m = pairwise_rank_matrix(data) win_losses = m - m.T # Create copeland results matrix r_matrix = np.zeros(m.shape) # Give score 1 if more voters prefer candidate to other. r_matrix[win_losses > 0] = 1 # Give score -1 for losses r_matrix[win_losses < 0] = -1 tally = r_matrix.sum(axis=1) winners, ties = winner_check(tally, numwin=numwin) output = {} output['vote_matrix'] = m output['margin_matrix'] = win_losses output['tally'] = tally return winners, ties, output
def star(data, numwin=1): """STAR voting (Score then Automatic Runoff) Parameters ---------- data : (a, b) array Election voter scores, 0 to max. Data of candidate ratings for each voter, with - `a` Voters represented as each rows - `b` Candidates represented as each column. numwwin : int Multi-winners... parameter > 1 not supported!! Returns ------- winners : (numwin,) array Winning candidates index. ties : (tienum,) array Tied candidates index for the last round, numbering 'tienum'. output : dict sums : (b,) array Score sums for all candidates runoff_candidates : (c,) array Candidates that made the runoff runoff_matrix : (c, c) array Votes for and against each candidate in runoff runoff_sums : (c,) array Votes for each candidate in runoff """ ### First Round -- Score Voting data = np.array(data) sums = np.sum(data, axis=0) ### Retrieve Runoff Winners winners, ties = tools.winner_check(sums, numwin=2) runoff_candidates = np.append(winners, ties) runoff_data = data[:, runoff_candidates] ### Second Round -- Automatic majoritarian runoff # # The candidate that beats the most head-to-head competitions wins! # vote_matrix = [] # # Calculate number of positive votes for candidate head to head # for icandidate in runoff_candidates: # iscores = data[:, icandidate : (icandidate+1)] # votes_for = (iscores > runoff_data).sum(axis=0) # # votes_against = (iscores < runoff_data).sum(axis=0) # vote_matrix.append(votes_for) # # votes = votes_for - votes_against # # vote_matrix.append(votes) # vote_matrix = np.array(vote_matrix) # # win_matrix = vote_matrix > 0 # vote_array = np.sum(vote_matrix, axis=1) matrix = condcalcs.pairwise_scored_matrix(runoff_data) vm = condcalcs.VoteMatrix(matrix=matrix) j_runoff_tally = vm.worst_margins jwinner, jties = tools.winner_check(j_runoff_tally) # Calculate winner # jwinners, jties = tools.winner_check(vote_array, numwin=1) winners2 = runoff_candidates[jwinner] ties2 = runoff_candidates[jties] details = {} details['tally'] = sums details['runoff_candidates'] = runoff_candidates details['runoff_matrix'] = matrix # details['runoff_tally'] = j_runoff_tally return winners2, ties2, details
def reweighted_range(data, numwin=1, C_ratio=1.0, maxscore=None): """Multi-winner election using reweighted range voting. https://www.rangevoting.org/RRVr.html Parameters ---------- data : array shaped (a, b) Election voter scores, 0 to max. Data of candidate ratings for each voter, with - `a` Voters represented as each rows - `b` Candidates represented as each column. numwin : int Number of winners to consider C_ratio : float Proportionality factor - C_ratio = 1.0 -- M; Greatest divisors (d'Hondt, Jefferson) proportionality - C_ratio = 0.5 -- M/2; Major fractions (Webster, Saint-Lague) method maxscore : None (default), float Maximum score to use for calculation of C. Use max of data if None. Returns ------- winners : array of shape (numwin,) Winning candidates index. ties : array of shape (tienum,) Tied candidates index for the last round, numbering 'tienum'. round_history : array of shape (numwin, b) Score summations for each candidate, for each round. - rows *numwin* -- Represents each round for total number of winners - columns *b* -- Represents each candidate. - data is net score of each candidate for each round. """ data = np.array(data) data = np.atleast_2d(data) if maxscore is None: maxscore = np.max(data) ballot_num = data.shape[0] C = maxscore * C_ratio # Set initial weights as uniform weights = np.ones(ballot_num) winners = [] # Store winning candidate indices here. ties = [] # Store tie candidagte indices here. round_history = [] # Store the history of net scores for each round. winner_sum = np.zeros( ballot_num) # Store the total score given to winners by each voter for i in range(numwin): # Loop through for number of winners. data_weighted = data * weights[:, None] sums = np.sum( data_weighted, axis=0) # Calculate weighted net score for each candidate winnersi, tiesi = tools.winner_check(sums) if len(winnersi) == 0: winner = tiesi[0] else: winner = winnersi[0] # winnersi = score_winners_check(sums) # Get candidate with greatest score. If tie, return multiple candidates. # winner = winnersi[0] winner_sum = winner_sum + data[:, winner] # Calculate total winning scores from each voter weights = C / (C + winner_sum) # Calculate new weighting logger.debug('scores = ') logger.debug(data) logger.debug('weights = ') logger.debug(weights) # Handle ties for last winner if len(winnersi) > 1: # attempt to break tie using majoritarianism./?? NOT IMPLEMENTED YET.... ties = winnersi else: winners.append(winner) logger.info('\nRound #%d' % i) logger.info('net scores = %s' % sums) logger.info('round winner = %s' % winner) data[:, winner] = 0 round_history.append(sums) winners = np.array(winners) logger.info('winners = %s' % winners) logger.info('ties = %s' % ties) output = {} output['round_history'] = np.array(round_history) return winners, ties, output
def test_winnercheck(self): """test winner_check function""" results = [1, 4, 10] w, t = tools.winner_check(results) self.assertTrue(2 in w) self.assertTrue(len(t) == 0)
def ____BROKEN_irv(data, numwin=1, num_eliminate=None): """ Calculate winners of an election using Instant Runoff Voting Parameters ---------- data : array shaped (a, b) Election voter rankings, from [1 to b]. Data composed of candidate rankings, with - Voters as the rows, with `a` total voters - Candidates as the columns, with `b` total candidates. Use 0 to specify unranked (and therefore not to be counted) ballots. numwin : int Number of winners Returns -------- winners : array of shape (numwin,) Winning candidates index. ties : array of shape (tienum,) Tied candidates index for the last round, numbering 'tienum'. round_history : array of shape (numwin, b) Score summations for each candidate, for each round. data : array of shape (a, b) Re-ordered ranking data after losers have been eliminated, retaining winner ranksings. """ data = np.array(data, copy=True) candidate_num = data.shape[1] if num_eliminate is None: numrounds = max(1, candidate_num - numwin) else: numrounds = num_eliminate logger.info('# of rounds = %s', numrounds) logger.info('# of winners = %s', numwin) # initialize history datas round_history = [] loserbool = np.ones(candidate_num, dtype=bool) for i in range(numrounds): data2 = data.copy() # Set of survivor candidates for each rond survived = set() num_left = candidate_num - (i + 1) # Second loop to check for ties. for j in range(1, candidate_num + 1): vote_index = (data2 == j) vote_count = np.sum(vote_index, axis=0) # Retrieve survivors of the round as winnersj. Add to survivors winnersj, tiesj = winner_check(vote_count, numwin=num_left) survived.update(set(winnersj)) num_left = num_left - len(winnersj) round_history.append(vote_count) logger.info('\nRound %d, rank %d' % (i, j)) logger.info('Counts = %s' % vote_count) logger.info('survivors=%s of %s' % (survived, num_left)) # Break loop if no ties found and continue additional runoff rounds. if len(tiesj) == 0: # Generate loser by inverting survivors loserbool[list(survived)] = False # Zero out loser rank data data[:, loserbool] = 0 data = rcv_reorder(data) logger.info('losers=%s' % np.where(loserbool)[0]) break else: # Zero out winner rank data temporarily for tie checking data2[:, winnersj] = 0 ties = tiesj winners = np.sort(list(survived)) round_history = np.array(round_history) logger.info('winners=%s', winners) logger.info('ties=%s', ties) output = {} output['round_history'] = round_history output['data'] = data return winners, ties, output
def top2runoff(data, numwin=1): """ Emulate top two runoff using ranked data Parameters ---------- data : array shaped (a, b) Election voter rankings, from [1 to b]. Data composed of candidate rankings, with - Voters as the rows, with `a` total voters - Candidates as the columns, with `b` total candidates. Use 0 to specify unranked (and therefore not to be counted) ballots. numwin : int Number of winners Returns -------- winners : array of shape (numwin,) Winning candidates index. ties : array of shape (tienum,) Tied candidates index for the last round, numbering 'tienum'. output : dict talley : array shape (b,) Number of transferred votes obtained by candidates before elimination. """ ### round #1 # if numwin > 1: # raise ValueError('Only numwinner=1 supported') data = np.array(data) candidate_num = data.shape[1] vote_index = data == 1 vote_count = np.sum(vote_index, axis=0) winners, ties = winner_check(vote_count, numwin=2) winners = np.append(winners, ties) loser_bool = np.ones(candidate_num, dtype=bool) loser_bool[winners] = False # zero out all losers data[:, loser_bool] = 0 data = rcv_reorder(data) ### round #2 vote_index = data == 1 vote_count2 = np.sum(vote_index, axis=0) winners2, ties2 = winner_check(vote_count2, numwin=1) output = {} output['tally'] = np.maximum(vote_count, vote_count2) output['runoff_candidates'] = winners output['runoff_tally'] = vote_count2[winners] output['first_tally'] = vote_count return winners2, ties2, output
def sequential_monroe(data, numwin=1, maxscore=None): """Multi-winner score based on Parker_Friedland's Reddit post. https://www.reddit.com/r/EndFPTP/comments/auyxny/can_anyone_give_a_summary_of_multiwinner_methods/ehgkfbl/ 1. For candidate X, sort the ballots in order of highest score given to candidate X to lowest score given to candidate X. 2. Calculate the average score given to X on the first hare quota of those ballots. Record this score as that candidate's hare quota score. See Footnote. 3. Repeat this process for every candidate. 4. Elect the candidate with the highest hare quota score and exhaust the votes that contribute to that candidate's hare quota score. (JCH - for our implementation, because of discretized scores, there may be voter scores that exceed the hare quota. IE, quota is 20 votes, but we have 30 votes of top rating 5.0/5.0. Here there are 10 surplus votes to deal with. We will use fractional exhaustion to take care of this.) 5. Repeat this process until all the seats are filled. Footnote: in purest form, fractional exhaustion would be used to break ties. Parameters ---------- data : (a, b) array Election voter scores, 0 to max. Data of candidate ratings for each voter, with - `a` Voters represented as each row. - `b` Candidates represented as each column. numwin : int Number of winners to consider Returns ------- winners : (numwin,) array Winning candidates index. ties : (tienum,) array Tied candidates index for the last round, numbering `tienum`. round_history : (numwin, b) array Average scores of top quota for each candidate, for each round. - rows *numwin* -- Represents each round for total number of winners - columns *b* -- Represents each candidate. - data is net score of each candidate for each round. """ data = np.array(data) num_candidates = data.shape[1] num_voters = data.shape[0] quota = tools.hare_quota(num_voters, numwin) logger.debug('quota=%s', quota) if maxscore is None: maxscore = data.max() unique_scores = np.arange(maxscore, -1, -1) winners = [] tally_record = [] ties = np.array([], dtype=int) weights = np.ones(num_voters) # Get sort array for candidate's scores from highest to lowest # candidate_sort_indices = [] # for ic in range(num_candidates): # ic_votes = data[:, ic] # ic_sort = np.argsort(ic_votes) # candidate_sort_indices.append(ic_sort) # Find for each required number of winners for ii in range(numwin): tally = [] top_voter_record = [] # unique_scores_record = [] logger.debug('\n\n---------- Iteration #%s --------------', ii) for ic in range(num_candidates): ic_votes = data[:, ic] # ii_sort = candidate_sort_indices[ic] # sorted_votes = data[ii_sort, ic] # sorted_weights = weights[ii_sort] # Get enough ballots to exceed quota for score_floor in unique_scores: top_index = ic_votes >= score_floor top_weights = weights[top_index] top_weight_sum = np.sum(top_weights) if top_weight_sum >= quota: logger.debug( 'top_weight_sum=%.0f @ score_floor=%s', top_weight_sum, score_floor, ) break top_voter_record.append(top_index) # Ballot weighting of canidate's top voters # Score values of candidate's top voters top_scores = ic_votes[top_index] top_voter_num = len(top_scores) # Get unique score values of top voters, sort highest to lowest # unique_scores = np.unique(ic_top_scores)[::-1] # unique_scores_record.append(unique_scores) # Average scores of each candidate within top voter quota mean_score = np.sum(top_scores * top_weights) / top_voter_num tally.append(mean_score) # Top voter index locations for all candidates # top_voter_record.append(candidate_top_voters) # tally = np.array(tally) tally_record.append(tally) logger.debug('New tally:\n %s', tally) # Get winner from mean_scores, check for ties. winners_ii, ties_ii = tools.winner_check(tally, 1) remaining_slots = numwin - len(winners) if len(winners_ii) > 0: winners.extend(winners_ii) elif len(ties_ii) <= remaining_slots: winners_ii = ties_ii winners.extend(ties_ii) logger.debug('Ties found to fill out rest of winner') logger.debug('Tie winners = %s', ties_ii) else: ties = ties_ii break logger.debug('Winners: %s', winners) if len(winners) >= numwin: break ## EXHAUST VOTES OF WINNERS # reduce weight of ballots of the winner's top voters in hare quota. for jj in winners_ii: logger.debug('\n\nAdjusting weights for winner %s', jj) top_index = top_voter_record[jj] # Determine the unique scores associated with the winner candidate_votes = data[:, jj] votes_exhausted = 0 for score_k in unique_scores: # Find which voters have this score voter_locs = np.where(candidate_votes == score_k)[0] # Get net weight of winning voters k_weight = np.sum(weights[voter_locs]) votes_exhausted += k_weight logger.debug('Adding votes for score %s', score_k) logger.debug('votes_exhausted=%.3f', votes_exhausted) # Now we need to calculate the surplus vote per voter. # It's possible that some voters don't have enough # weight to contribute 100%, # so we have to take that from other voters. if votes_exhausted > quota: surplus_weight = votes_exhausted - quota factor = surplus_weight / k_weight weights[voter_locs] = weights[voter_locs] * factor logger.debug('surplus_weight=%.3f', surplus_weight) logger.debug('factor=%.3f', factor) if logger.isEnabledFor(logging.DEBUG): new_weight = np.sum(weights[voter_locs]) logger.debug('new_weight=%.3f', new_weight) logger.debug('residual=%.3f (Should be about zero)', new_weight - surplus_weight) break elif votes_exhausted <= quota: factor = 0.0 weights[voter_locs] = 0.0 logger.debug('new_weight=0') ## Set winner data to zero data[:, jj] = 0 winners = np.array(winners, dtype=int) output = {} output['round_history'] = np.array(tally_record) output['quota'] = quota return winners, ties, output
def condorcet_winners_check(ranks=None, matrix=None, pairs=None, numwin=1, full_ranking=False): """Condorcet winner checker for multiple winners. This check does not resolve cycles. Parameters ---------- ranks : (a, b) array Election voter rankings, from [1 to b]. Data composed of candidate rankings, with - Voters as the rows - Candidates as the columns. Use 0 to specify unranked (and therefore not to be counted) ballots. - a : number of voters dimension. Voters assign ranks for each candidate. - b : number of candidates. A score is assigned for each candidate from 0 to b-1. matrix : (b, b) array Win minus Loss margin matrix pairs : (c, 3) array Win-Loss candidate pairs - column 0 = winning candidate - column 1 = losing candidate - column 2 = margin of victory - column 3 = votes for winner full_ranking : bool (default False) If True evaluate entire ranking of candidates for scored output (ie a metric to tally the degree by which each candidate has won by). Returns ------- winners : (d,) array Index locations of each winner. - b = `numwin` if no ties detected - b > 1 if ties are detected. - winners is empty if Condorcet cycle detected ties : (e,) array Index locations of `e` number of ties scores : (b,) array Rankings of all candidates (The strength of candidates' wins). Only calculated if full_ranking=True. """ if ranks is not None: vm = VoteMatrix(ranks=ranks) vp = VotePairs(vm.pairs) cnum = vm.cnum elif matrix is not None: vm = VoteMatrix(matrix=matrix) vp = VotePairs(vm.pairs) cnum = vm.cnum elif pairs is not None: vm = None vp = VotePairs(pairs) cnum = int(np.max(vp.pairs[:, 0:2]) + 1) else: raise ValueError('either ranks, matrix, or pairs must be specified') if full_ranking: cmax = cnum else: cmax = numwin scores = np.empty(cnum) scores[:] = np.nan # Everyone ties if cycle detected if vp.has_cycle: t = np.arange(cmax) w = np.array([], dtype=int) else: for ii in range(cnum): iwinners = vp.condorcet_winners scores[iwinners] = -ii - 1 vp = vp.prune_winners() scores[np.isnan(scores)] = -ii - 1 w, t = winner_check(scores, numwin=numwin) return w, t, scores
def majority_judgment(data, numwin=1, remove_nulls=True, maxiter=1e5): """Majority judgment (median score). Parameters ---------- data : array shaped (a, b) Election voter scores, 0 to max. Data of candidate ratings for each voter, with - `a` Voters represented as each rows - `b` Candidates represented as each column. numwin : int Number of winners to consider remove_nulls : bool If True (default), remove any ballots that are marked with all zeros. Returns ------- winners : array of shape (numwin,) Winning candidates index. ties : array of shape (tienum,) Tied candidates index for the last round, numbering 'tienum'. sums : array of shape (numwin, b) Median scores for each candidate. """ data = np.atleast_2d(data) vnum, cnum = data.shape round_history = [] maxiter = int(maxiter) if remove_nulls: index = np.all(data == 0, axis=1) data = data[~index] # Check if all scores are zero if data.size == 0: winners = np.array([]) ties = np.arange(cnum) output = {} output['round_history'] = np.zeros((1, cnum)) return winners, ties, output # Sort the data data = np.sort(data, axis=0) def get_medians(dict1, ): # eliminated get -1 score. new = -np.ones(cnum) for k, scores in dict1.items(): logger.debug('scores=\n%s', scores) new[k] = np.percentile(scores, 50, interpolation='lower') return new # Store candidates and associated ballots vdict = dict(zip(range(cnum), data.T)) for jj in range(maxiter): #medians = np.median(data, axis=0) medians = get_medians(vdict) winners, ties = tools.winner_check(medians, numwin=numwin) round_history.append(medians) if len(ties) == 0: break # Eliminate losers d1 = {k: vdict[k] for k in winners} d2 = {k: vdict[k] for k in ties} d1.update(d2) vdict = d1 if len(vdict) == 0: break # Eliminate median grades one-by-one until a winner is found. median_max = medians[ties[0]] for k, scores in vdict.items(): logger.debug('median max=%s', median_max) logger.debug('scores=\n%s', scores) index = np.where(scores == median_max)[0][0] snew = np.delete(scores, index) vdict[k] = snew if len(snew) <= 1: break if jj == maxiter - 2: raise ValueError( 'something wrong with this loop, do not know what') output = {} output['round_history'] = np.array(round_history) #output['tally'] = round_history[-1] return winners, ties, output
def irv_eliminate(data): """Eliminate a candidate using ranked choice, instant runoff voting. Parameters ---------- data : array shaped (a, b) Election voter rankings, from [1 to b]. Data composed of candidate rankings, with - Voters as the rows, with `a` total voters - Candidates as the columns, with `b` total candidates. Use 0 to specify unranked (and therefore not to be counted) ballots. Returns ------- data : array shaped (a, b) Election voter rankings, but with losing/eliminated candidate's data zeroed. loser : int Column integer index of data of losing candidate. - lower will be equal to -1 if no candidates can be eliminated. - loser will be 0 or larger, if a candidate can be eliminated. ties : array shaped (c,) Index locations of tied losing candidates. Empty array if no ties. history : array shaped (n, b) History of each elimination round. Multiple rounds will occur if ties are found during elimination. `n` is number of rounds. """ data = np.array(data, copy=True) candidate_num = data.shape[1] start_losers = np.sum(data, axis=0) == 0 losernum = np.sum(start_losers) round_history = [] logger.debug('irv elimination start.') logger.debug('start losers = %s', start_losers) logger.debug('# losers = %s', losernum) active_bool = np.ones(candidate_num, dtype=bool) active_bool[start_losers] = False tie_bool = np.zeros(candidate_num, dtype=bool) data2 = data.copy() # Handle all zeros array if np.all(start_losers): logger.debug('All zero data input into irv eliminate') ties = np.array([], dtype=int) loser = -1 vote_index = (data2 == 1) vote_count = np.sum(vote_index, axis=0, dtype='float64') round_history.append(vote_count) return data, loser, ties, np.array(round_history) elif (candidate_num - losernum) == 1: logger.debug('All but one candidate already eliminated') ties = np.array([], dtype=int) loser = -1 vote_index = (data2 == 1) vote_count = np.sum(vote_index, axis=0, dtype='float64') round_history.append(vote_count) return data, loser, ties, np.array(round_history) for j in range(1, candidate_num + 1): vote_index = (data2 == j) vote_count = np.sum(vote_index, axis=0, dtype='float64') round_history.append(vote_count) vote_count[~active_bool] = np.nan # Negative votes to get loser rather than winner. losers, ties = winner_check(-vote_count, numwin=1) logger.debug('Eliminating from rank #%d', j) logger.debug('\n,%s', data2) logger.debug('count=%s', vote_count) logger.debug('losers = %s', losers) logger.debug('ties = %s', ties) if len(ties) == 0: loser = losers[0] break else: loser = -1 tie_bool[ties] = True active_bool = tie_bool if len(ties) == 0: # Zero out loser rank data data[:, loser] = 0 data = rcv_reorder(data) return data, loser, ties, np.array(round_history)