Allocated Score

From electowiki

Allocated Score is a sequential Multi-Winner Cardinal voting system built on Score voting ballots. Its public branding is Score Then Allocated Rounds or Proportional STAR. This branding is intended to align with single winner STAR voting.

Allocation is the default method of removing voters in a sequential Multi-Member System. Each winner is selected as the Utilitarian winner (ie highest sum of score). After each selection, the Hare quota of ballots which scored that candidate the highest is allocated to this candidate and as such removed from subsequent rounds.

Procedure

Each voter score all candidates on a [0,5] scale

  1. Select the candidate with the highest sum of score as this rounds winner
  2. Set the ballot weight to zero for the Quota of voters ballots which gave the highest scores to that winner
    • If several voters have given the winner the same score at the threshold of the Quota then Fractional Surplus Handling is applied to those voters
  3. Repeat this process until all the seats are filled.

Fractional Surplus Handling to break ties: when calculating which ballots belong to a candidate's quota so they should be allocated to them, if for a particular score, including voters that gave that candidate that score in the quota would make the quota to large and excluding it would make it to small, then exhaust a portion of those vote's ballot weights such that the total weight of the exhausted ballots still equals the hare quota. The reason why Fractional Surplus Handling is preferred is that it preserves the Independence of irrelevant alternatives and Monotonicity criteria.

Python Implementation

Given a Pandas dataframe S with columns representing candidates and rows representing voters the entries would encode the score of all the ballots. For a max score of K and a desired number of winners W.

import pandas as pd
import numpy as np

def Allocated_Score(K, W, S):

    #Normalize score matrix
    ballots = pd.DataFrame(S.values/K, columns=S.columns)
    
    #Find number of voters and quota size
    V = ballots.shape[0]
    quota = V/W
    ballot_weight = pd.Series(np.ones(V),name='weights')
    
    #Populate winners in a loop
    winner_list = []
    while len(winner_list) < W:

        #Select winner
        w = ballots.multiply(ballot_weight, axis="index").sum().idxmax()
    
        #Add winner to list
        winner_list.append(w)
    
        #Create lists for manipulation
        cand_df = pd.concat([ballot_weight,ballots[w]], axis=1).copy() 
        cand_df_sort = cand_df.sort_values(by=[w], ascending=False).copy()  
        
        #find the score where a quota is filled
        split_point = cand_df_sort[cand_df_sort['weights'].cumsum() < quota][w].min()
    
        #Amount of ballot for voters who voted more than the split point
        spent_above = cand_df[cand_df[w] > split_point]['weights'].sum()
        
        #Exhaust all ballots above split point
        if spent_above>0:    
            cand_df.loc[cand_df[w] > split_point, 'weights'] = 0.0
    
        #Amount of ballot for voters who gave a score on the split point
        weight_on_split = cand_df[cand_df[w] == split_point]['weights'].sum()

        #Fraction of ballot on split needed to be spent
        if weight_on_split>0:     
            spent_value = (quota - spent_above)/weight_on_split
    
            #Take the spent value from the voters on the threshold evenly
            cand_df.loc[cand_df[w] == split_point, 'weights'] = cand_df.loc[cand_df[w] == split_point, 'weights'] * (1 - spent_value)
    
        ballot_weight = cand_df['weights'].clip(0.0,1.0)

    return winner_list

Variants

Quota

A common variant is to use Droop quotas instead of Hare quotas to mitigate Free riding.

Sequential Monroe

Sequential Monroe can be thought of as a variant of Allocated Score with a change to the selection method.