Interest in alternatives to the most common type of voting (plurality), and in particular Ranked-Choice Voting (RCV), has periodically waxed and waned over the current era of modern democracy. Right now in parts of the US, it has been increasing for a decade or two, and has reached a local peak with the victory of Zohran Mamdani in the Democratic primary for mayor of New York City, which used RCV.
Much of the research I found (though I didn’t dig too hard) was either empirical studies of RCV, or proofs of theoretical aspects of RCV. So, out of curiosity, I decided to do some simulations. Coincidentally, one of the few simulation studies I found was that of a Wolfram summer school student, Sofia Wong (https://community.wolfram.com/groups/-/m/t/2964799). I encourage the reader to look at that post as well and probably first, if you are unfamiliar with voting systems.
This work is both deeper and simpler than Wong’s, which investigates multiple voting methods, and uses a multidimensional preference space to determine the “distance” of a voter to a candidate. Here, we focus on RCV, an analyze how it compares to plurality over many elections in a variety of cases: many or few candidates, many or few voters, and different relative “popularities” of candidates (defined below).
The main results are:
◼
  • RCV wins in every case, but the marginal increase in utility (happiness) is very small.
  • ◼
  • For a few cases, I could not get all tests to show statistical significance.
  • ◼
  • RCV does best when there is more candidates and fewer voters.
  • My conclusion is that collecting more information from voters (a ranked choice), can often lead to slightly greater levels of satisfaction.
    Details:

    Generating ballots and computing the winners, utilities

    In[]:=
    numVoters=10;​​numCands = 5;
    ballots=(*5candidates,4randomdivisionsof"utility"(happiness)(eachvoterhasmaxutility1),eachlabeledbyacandidate*)Table[Sort[Join[RandomReal[1,numCands-1],{1}]]->RandomSample[Range[numCands]],numVoters]
    Out[]=
    {{0.172299,0.754636,0.899329,0.972762,1}{1,2,5,3,4},{0.253154,0.593623,0.649826,0.939197,1}{5,1,2,3,4},{0.0546519,0.631077,0.779897,0.885189,1}{2,3,4,1,5},{0.0263662,0.376277,0.771167,0.89077,1}{2,3,5,1,4},{0.0987935,0.770706,0.771867,0.90828,1}{3,4,1,5,2},{0.0429284,0.336529,0.53372,0.98014,1}{2,4,3,1,5},{0.0733739,0.191093,0.248492,0.353643,1}{2,5,4,3,1},{0.0463696,0.234226,0.313308,0.92331,1}{4,1,3,2,5},{0.178769,0.50784,0.66413,0.915381,1}{3,1,2,5,4},{0.586813,0.605976,0.743765,0.928661,1}{4,3,5,1,2}}
    One “problem” with these ballots is that all candidates are equally popular given a large number of voters, which is not usually the case empirically.
    To fix that, we give RandomSample random weights. These “relative popularities” are uniformly random numbers from 0 to 1, which RandomSample scales to use as its weights in deciding which candidate a voter prefers first, second, and so on (using the same weights for every voter).
    relPopularities = RandomReal[1,numCands]&;​​(*should actually use a better distribution. find empirically?)​​Or: use a hypercube "overton window" and distribute voters in a normal dist, and candidates in a uniform dist (because there is always a candidate for every position (maybe need more candidates than 5 in the model, then)), and take the distribution of distances from a random voter to a random candidate -- which is simple in 1D ("the political spectrum") *)

    Generating a set of ballots

    In[]:=
    generateBallots[numCands_,numVoters_,relPopularities_] :=​​ Table​​ Sort[Join[relPopularities[],{1}]]->​​ RandomSample[(*relPopularities->*)Range[numCands]],​​ numVoters​​ 
    In[]:=
    ballts=generateBallots[numCands,10,relPopularities]
    Out[]=
    {{0.36993,0.503957,0.509451,0.79216,0.899573,1}{5,2,3,1,4},{0.311168,0.54775,0.646789,0.670387,0.719707,1}{1,3,4,2,5},{0.0207576,0.212059,0.255622,0.430864,0.900904,1}{5,3,4,1,2},{0.228114,0.36994,0.556457,0.613476,0.613687,1}{5,1,3,2,4},{0.334707,0.57704,0.583082,0.876722,0.932988,1}{4,5,2,1,3},{0.0746916,0.279887,0.532412,0.773332,0.828952,1}{1,4,5,2,3},{0.294312,0.469508,0.514269,0.70682,0.970832,1}{1,5,3,4,2},{0.0984522,0.693708,0.706301,0.920041,0.968598,1}{1,5,4,3,2},{0.0702401,0.199929,0.301897,0.582752,0.877103,1}{2,4,3,5,1},{0.0418573,0.295673,0.443853,0.634535,0.82936,1}{4,1,2,3,5}}

    RCV on ballots

    doRCV[ballots_] := Module{numVoters = Length[ballots], winner, balts = ballots, morethan2Cands = (Length[ballots[[1,2]]]>2)},​​ While(*Echo@*)morethan2Cands && Last[Sort@Counts[balts[[All,-1,-1]]]] <= numVoters2,(*Print[3433232];*)​​ balts = (*Echo@*)removeWorst[balts]​​ ;​​ ​​ (*winner*)winner = Sort@Counts[balts[[All,-1,-1]]]//Keys//Last,​​ (*total utility*)(*Echo@*)Total[getUtil[#, winner]&/@balts]numVoters​​ ​​;​​​​removeWorst[ballots_] := Withworst = (*Echo@*)First​​ Keys@(*Echo@*)Sort@Join​​ (*0's for cands not already elim'd*)AssociationMap[0&,ballots[[1,2]]],​​ Counts[ballots[[All,-1,-1]]]​​ ,​​ remove[worst]/@ballots​​​​​​remove[worst_] := Functionballot, With{pos =(* Echo@*)First@First@Position[ballot[[2]],worst]},​​ Delete[ballot[[1]],pos] -> Delete[ballot[[2]],pos]​​​​​​getUtil[bal_, winner_] := With{pos = First@First@Position[bal[[2]],winner]},​​ bal[[1,pos]]​​
    In[]:=
    doRCV[ballts]
    Out[]=
    {5,0.671226}
    For this set of ballots, RCV chooses candidate “5”, with average voter satisfaction .671.

    Plurality voting

    In[]:=
    doPlurality[ballots_] := ​​ Module{cnts = (*Echo@*)Sort@Counts[ballots[[All,-1,-1]]], winner, numVoters = Length[ballots]},​​ winner = Keys[cnts][[-1]];​​ ​​ {winner, Total[getUtil[#, winner]&/@ballots]/numVoters}​​ 
    In[]:=
    doPlurality[ballts]
    Out[]=
    {3,0.638917}
    On the same set of ballots, plurality chooses candidate “3”, with average voter satisfaction .639.

    functions to compare plurality to RCV over many elections

    (*compare and summarize the difference between the two methods, for fixed numbers of candidates and voters, and one popularity distribution*)​​compare[numElections_,numCands_,numVoters_,relPopularities_]:=Withres = Table​​ With{b=generateBallots[numCands,numVoters,relPopularities]},​​ {doPlurality[b],doRCV[b]}​​ ,​​ numElections(*numElections*)​​ ​​ ,​​ ​​ "plurality"->Total[res[[All,1,2]]],​​ "RCV"->Total[res[[All,2,2]]],​​ "% agreement"->N[100 Total[Boole[#[[1,1]]===#[[2,1]]]&/@res]/numElections]​​ ​​;​​​​(*a complete report of the above, for each election in the test*)​​multiElectionTable[numElections_,numCands_,numVoters_,relPopularities_]:=Withres = Table​​ With{b=(*Echo@*)generateBallots[numCands,numVoters,relPopularities]},​​ {doPlurality[b],doRCV[b]}​​ ,​​ numElections(*numElections*)​​ ​​ ,​​ res​​

    results for various voter/candidate amounts

    uniformly random candidate popularities, “uniformly random” voter utilities

    Grid[​​Join[{{"# cands \\ # voters",7,15,50,150,500}},#]&[Table[​​If[numVoters===0,numCands,​​Column[​​compare[300,numCands,numVoters,RandomReal[1,numCands]&],​​Alignment->Right​​]],​​{numCands,{(*nodifferenceuntil:*)3,4,5,7,13}},​​{numVoters,{0,7,15,50,150,500}}​​]],​​Frame->All,​​Spacings->{2,2}​​]
    First, note the fairly obvious facts that agreement decreases with the number of candidates, that the total average happiness of voters is always over half of 300, the number of elections (but not much over!).
    The major result seems to be that RCV wins every type of contest, but only by a little, and that little is least when the number of voters is high and the number of candidates is low, which might be the most important/realistic case.
    Therefore, for this one case, we will increase the number of elections to see the difference is statistically significant:

    Difference of two means tests, elections at the most contentious parameters: 3 candidates, 500 voters

    It takes about 2000 elections to see that the difference is statistically significant:

    right-skewed beta popularity distribution, “uniformly random” voter utilities

    difference of two means (3 candidates, 500 voters)

    Here we get less robust statistical significance, even with 3000 elections:

    left-skewed beta popularity distribution, “uniformly random” voter utilities

    difference of two means (3 candidates, 500 voters)

    Here we get very robust statistical significance over all tests:

    CITE THIS NOTEBOOK

    A (slightly) deeper dive into ranked-choice voting​
    by Peter Barendse​
    Wolfram Community, STAFF PICKS, August 4, 2025
    ​https://community.wolfram.com/groups/-/m/t/3524606