4. Ordinal preferences

In this notebook, we are going to see how to implement an election with ordinal preferences in our model of voters with embeddings.

An election with ordinal preferences corresponds to an election in which each voter gives a ranking of the candidates instead of giving a different score to each candidate. It has been studied a lot and many rules exists for this model (Plurality, Borda, k-approval, Condorcet, Instand Runoff, Maximin, etc.).

[1]:
import embedded_voting as ev
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(442)

Classic election

Let’s run an election with 5 candidates and 100 voters. We obtain the following profile:

[2]:
n_voters = 100
n_candidates = 5
n_dimensions = 3

embeddingsGen = ev.EmbeddingsGeneratorPolarized(n_voters, n_dimensions)
ratingsGen = ev.RatingsFromEmbeddingsCorrelated(coherence=0.6, n_dim=n_dimensions, n_candidates=n_candidates)

embeddings = embeddingsGen(polarisation=0.5)
profile = ratingsGen(embeddings)

embeddings.plot_candidates(profile, "3D")
embeddings.plot_candidates(profile, "ternary")
../_images/notebooks_ordinal_5_0.png
../_images/notebooks_ordinal_5_1.png
[3]:
election = ev.RuleSVDNash()
election(profile, embeddings)
[3]:
<embedded_voting.rules.singlewinner_rules.rule_svd_nash.RuleSVDNash at 0x19b7b143240>

We can also print all the information about the results of this rule:

[4]:
print('Scores : ', election.scores_)
print('Ranking : ', election.ranking_)
print('Winner : ', election.winner_)
print('Welfare : ', election.welfare_)
Scores :  [53.97439136824531, 47.0012710723997, 27.897020264969875, 54.46431821175544, 63.55019310806972]
Ranking :  [4, 3, 0, 1, 2]
Winner :  4
Welfare :  [0.7314179643431743, 0.5358359238181288, 0.0, 0.7451594298129144, 1.0]

Positional scoring rules

Now, let’s assume that instead of asking a score vector to each voter, we ask for a ranking of the candidate, and apply some rule with all the rankings.

A broad family of rule are positional scoring rule. A positional scoring rule is characterized by a vector \(p = (p_1, \ldots, p_m)\) such that each voter \(v_i\) gives \(p_j\) points to the voters with rank \(j\). The winner is the candidate with the maximum total score.

We can adapt this idea to scores between \(0\) and \(1\) by setting the score given by the voter \(v_i\) to candidate \(c_j\) as \(\frac{p_k}{p_n}\) if the candidate \(c_j\) is ranked at position \(k\) in the ranking of \(v_i\).

For instance, if the positional scoring rule is \((2, 1, 1, 1, 0)\), each voter gives a score of \(1\) to her favorite candidate, \(0\) to her least favorite candidate and \(\frac{1}{2}\) to every other candidate:

[5]:
ordinal_election = ev.RulePositional([2, 1, 1, 1, 0], rule=ev.RuleSVDNash())
ordinal_election(profile, embeddings)
[5]:
<embedded_voting.rules.singlewinner_rules.rule_positional.RulePositional at 0x19b78b52710>

If we plot the profile of the candidates now, it is very different than before:

[6]:
ordinal_election.plot_fake_ratings("3D")
ordinal_election.plot_fake_ratings("ternary")
../_images/notebooks_ordinal_13_0.png
../_images/notebooks_ordinal_13_1.png
[7]:
print('Scores : ', ordinal_election.score_(1))
print('Ranking : ', ordinal_election.ranking_)
print('Winner : ', ordinal_election.winner_)
Scores :  36.58919819094699
Ranking :  [4, 3, 0, 1, 2]
Winner :  4

Plurality

Plurality is the positional scoring rule defined by the scoring vector \((1, 0, \ldots, 0)\). It is equivalent to saying that each voter only vote for his favorite candidate. We can see that in that case, almost nobody voted for candidate \(c_4\):

[8]:
plurality_election = ev.RulePositionalPlurality(n_candidates, rule=ev.RuleSVDNash())
plurality_election(profile, embeddings)
plurality_election.plot_fake_ratings("3D")
../_images/notebooks_ordinal_17_0.png
[9]:
print('Scores : ', plurality_election.scores_)
print('Ranking : ', plurality_election.ranking_)
print('Winner : ', plurality_election.winner_)
Scores :  [9.491165112035668, 6.254785145245596, 0j, 5.271663297309905, 19.956241701329688]
Ranking :  [4, 0, 1, 3, 2]
Winner :  4

Veto

The Veto is the opposite of Plurality. In this rule, every voter votes for all candidates but one. That is why it looks like every candidate is liked by a lot of voters:

[10]:
veto_election = ev.RulePositionalVeto(n_candidates, rule=ev.RuleSVDNash())
veto_election(profile, embeddings)
veto_election.plot_fake_ratings("3D")
../_images/notebooks_ordinal_21_0.png
[11]:
print('Scores : ', veto_election.scores_)
print('Ranking : ', veto_election.ranking_)
print('Winner : ', veto_election.winner_)
Scores :  [89.53416233306291, 71.28984292198523, 45.10431048514931, 107.41444287970856, 115.7630932057704]
Ranking :  [4, 3, 0, 1, 2]
Winner :  4

k-Approval

K-approval is the rule in between Plurality and Veto. Each voter votes for his k favorite candidates only. For instance, with \(k=3\) :

[12]:
kapp_election = ev.RulePositionalKApproval(n_candidates, k=3, rule=ev.RuleSVDNash())
kapp_election(profile, embeddings)
kapp_election.plot_fake_ratings("3D")
../_images/notebooks_ordinal_25_0.png
[13]:
print('Scores : ', kapp_election.scores_)
print('Ranking : ', kapp_election.ranking_)
print('Winner : ', kapp_election.winner_)
Scores :  [54.598541290954344, 34.53096196174671, 4.654730263274076, 59.83731740897562, 87.9835379096844]
Ranking :  [4, 3, 0, 1, 2]
Winner :  4

Borda

Borda use the scoring vector \((m-1, m-2, \ldots, 1, 0)\) where \(m\) is the total number of candidates.

[14]:
borda_election = ev.RulePositionalBorda(n_candidates, rule=ev.RuleSVDNash())
borda_election(profile, embeddings)
borda_election.plot_fake_ratings("3D")
../_images/notebooks_ordinal_29_0.png
[15]:
print('Scores : ', borda_election.scores_)
print('Ranking : ', borda_election.ranking_)
print('Winner : ', borda_election.winner_)
Scores :  [43.259760423282884, 32.05532869328766, 8.87628566982566, 46.946353853379726, 66.4341676382639]
Ranking :  [4, 3, 0, 1, 2]
Winner :  4

Instant Runoff Voting (IRV)

Finally, we implemented Instant Runoff Voting which is not a positional scoring rule.

In this voting system, at each step, every voter votes for his favorite candidate, and the candidate with the lowest score is eliminated. Consequently, we perform \(m-1\) elections before we can find the winner. The ranking obtained is the inverse of the order in which the candidates are eliminated.

[16]:
irv_election = ev.RuleInstantRunoff(rule=ev.RuleSVDNash())
irv_election(profile, embeddings)
[16]:
<embedded_voting.rules.singlewinner_rules.rule_instant_runoff.RuleInstantRunoff at 0x19b7b762748>
[17]:
print('Ranking : ', irv_election.ranking_)
print('Winner : ', irv_election.winner_)
Ranking :  [4, 0, 1, 3, 2]
Winner :  4

You can see that we can obtain different rankings depending on the ordinal voting rule that we use.