예제 #1
0
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
예제 #2
0
    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)
예제 #3
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
예제 #4
0
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
예제 #5
0
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
예제 #6
0
    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
예제 #7
0
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
예제 #8
0
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
예제 #9
0
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
예제 #10
0
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
예제 #11
0
    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!!!')
예제 #12
0
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
예제 #13
0
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
예제 #14
0
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
예제 #15
0
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
예제 #16
0
 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)
예제 #17
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
예제 #18
0
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
예제 #19
0
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
예제 #20
0
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
예제 #21
0
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
예제 #22
0
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)