Learn R Programming

SafeVote

The goals of SafeVote are to investigate the safety of announcing preliminary results from an election, and to allow experimental study of the safety of a complete ranking of all candidates (as in a party list) that is derived from a small-scale election with preferential ballots.

Installation

You can install the development version of SafeVote from GitHub with:

# install.packages("devtools")
devtools::install_github("cthombor/SafeVote")

Example

This mod of vote.2.3-2 reports the margins of victory in an election.

The value of the safety parameter will affect the completeness of the safeRank ordering of the candidates. Setting safety = 0 will cause safeRank to be a total ranking of the candidates, except in the rare case that there is an exact tie. The “fuzz” $z$ on the vote-differentials in a safeRank clustering of the candidates is $z = s\sqrt{n}$, where $s$ is the value of the safety parameter and $n$ is the number of ballots.

library(SafeVote)
stv(food_election,quiet=TRUE)$rankingTable
#>   Rank    Margin    Candidate Elected SafeRank
#> 1    1 8.0000000    Chocolate       x        1
#> 2    2 0.5548889 Strawberries       x        2
#> 3    3 1.2225556      Oranges                2
#> 4    4 0.7774444       Sweets                2
#> 5    5        NA        Pears                2
stv(food_election,quiet=TRUE,safety=0)$rankingTable
#>   Rank    Margin    Candidate Elected SafeRank
#> 1    1 8.0000000    Chocolate       x        1
#> 2    2 0.5548889 Strawberries       x        2
#> 3    3 1.2225556      Oranges                3
#> 4    4 0.7774444       Sweets                4
#> 5    5        NA        Pears                5

Three safety-testing routines are supplied, to support experimental study of the stochastic behaviour of ballot counting methods.

[testFraction] draws a series of independent samples from a ballot box. Stochastic experimentation with this method will, we hope, help future researchers develop advice, to election officials, on whether a preliminary count is sufficiently stable for them to make a preliminary announcement of the result – without undue risk of having to retract their announcement as had occurred in Hastings NZ in October 2022. As seen below, in the case of the yale_ballots dataset, 400 of the 479 votes were sufficient to establish candidates ATL_19, ATL_10, and ATL_2 as being very likely to be three of the four winners. The balloting was extremely close for the fourth seat. ATL_54 was the eventual winner; but ATL_54 and ATL_27 are ranked approximately 5= as the last 50 ballots are counted. The STV variant used in this experiment has fractional vote-transfers and a Hare quota.

load(SaveVote)
xrHare <- 
  testFraction(yale_ballots,arep=9,ainc=5,astart=400,
               countArgs=list(nseats=4,safety=0.0,quota.hare=TRUE))
plot(xrHare,boxPlot=TRUE,boxPlotCutInterval=10,
     line=FALSE,facetWrap=TRUE,nResults=6)

testFraction may also be useful in determining the extent to which the safety of the preliminary results of an STV election is affected by its quota method. In the case of the 2016 Yale Senate election, the use of a Droop quota, rather than a Hare quota, has a significant influence over the partial results for the fourth seat. This influence is readily explained by the fact that multiple rounds of elimination were required before any candidate achieved the quota. Fewer rounds of elimination are required to reach a Droop quota, so fewer votes are transferred before each seat is filled – which will differentially affect the vote-counts of the candidates still in play for the remaining seats. Furthermore, the Cambridge method of transferring votes may have been employed in the real-world Yale Senate election. Under these rules, the initial numbering of the ballots determines the ballot papers which are consulted when a candidate’s surplus votes are transfered. As noted in the tests of STV v1.0.2, if ballots are selected for a vote-transfer process by a pseudorandom number generator, the seeding of this generator affects the outcome of the election. The per-candidate boxplots in the last decile of vote-counting in our experimental results below (using the Droop quota with fractional vote transfers) illustrate this phenomenon. The median result of the last decile of ballot-counting show ATL_2 having a very small margin of victory for the third seat, and ATL_54 having a slight advantage over ATL_27 in a very close race for the fourth seat.

load(SaveVote)
xrDroop <- 
  testFraction(yale_ballots,arep=9,ainc=5,astart=400,
               countArgs=list(nseats=4,safety=0.0,quota.hare=FALSE))
plot(xrDroop,boxPlot=TRUE,boxPlotCutInterval=10,
     line=FALSE,facetWrap=TRUE,nResults=6)

testAdditions can be used to assess the sensitivity of an STV election to a tactical-voting strategy of “plumping” for a favoured candidate. For example, we find it takes only two “plumping” ballots to shift “Strawberries” from third place to second place in the food_election dataset. Note that in this test we have set the safety parameter of the stv ballot-counting method to zero, so that the output of testAdditions reveals a complete ranking of the candidates unless there is an exact tie.

load(SaveVote)
testAdditions(food_election, arep = 2, favoured = "Strawberries", 
  countArgs = list(safety = 0))
#> 
#> Adding up to 2 stv ballots = ( 3 5 4 1 2 )
#> Testing progress:  1, 2
#> 
#> Results of testAdditions at 2022-12-26 08:25:22
#> 
#> Dataset = food_election, countMethod = stv, rankMethod = safeRank
#> 
#> |          | safety|
#> |:---------|------:|
#> |countArgs |      0|
#> 
#> 
#> |             | ainc| arep|                                                         tacticalBallot|
#> |:------------|----:|----:|----------------------------------------------------------------------:|
#> |otherFactors |    1|    2| c(Oranges = 3, Pears = 5, Chocolate = 4, Strawberries = 1, Sweets = 2)|
#> 
#> Experiment ID, number of ballots in simulated election, ranks, winning margins:
#> 
#> |exptID | nBallots| Oranges| Pears| Chocolate| Strawberries| Sweets| m.Oranges| m.Pears| m.Chocolate| m.Strawberries|  m.Sweets|
#> |:------|--------:|-------:|-----:|---------:|------------:|------:|---------:|-------:|-----------:|--------------:|---------:|
#> |SBK0   |       20|       2|     5|         1|            3|      4| 1.4451111|       2|           8|      1.7774444| 0.7774444|
#> |SBK1   |       21|       2|     5|         1|            3|      4| 0.6673333|       2|           8|      2.6663333| 0.6663333|
#> |SBK2   |       22|       3|     5|         1|            2|      4| 3.4447778|       2|           8|      0.1104444| 0.5552222|

testDeletions deletes ballots sequentially from the ballot box, counting after each deletion. When its results are plotted in inverse order of collection (i.e. in increasing order of the number of ballots $n$) we see one possible evolution of the preliminary results of an election in which ballots are counted in a randomised order (without replacement) from the ballot box. Note that a plot of the results of testFraction has a similar appearance, however the ballot boxes counted in testFraction are independently sampled (“bootstrapped”) from the full dataset of ballots.

load(SaveVote)
xr <- 
  testDeletions(dublin_west,dinc=25,dstart=29988,quiet=FALSE,
                countArgs=list(safety=0.0,nseats=3))
save(xr,file="../s0di25ns3.rdata")
plot(xr,title="testDeletions, file = s0di25ns3")

In the plots above, the “adjusted rank” of a candidate is their ranking $r$ plus their scaled margin of victory. Following the usual convention, the most-popular candidate is at rank 1. Accordingly, we invert the $y$-axis so that the rank-1 candidate is visually dominant. Our default scaling of a margin of victory $m$ is $e^{-cm/\sqrt{n}}$. This exponential scaling makes it possible to see small differences in vote-counts in the small margins of victory which affect the safety of an election result. Note that a very small margin of victory adds almost a whole unit to the candidate’s rank. We introduce the scaling factor $c/\sqrt{n}$ into the exponent as a rough-cut estimate of the standard deviation of the standard deviation of a victory margin in an election with $n$ ballots. A candidate whose margin of victory is a multiple of $\sqrt{n}$ thus has a very small adjustment to their rank in our plots. Our margin-scaling parameter $c$ has the default value of 1, and may be adjusted using the parameter cMargin of plot.SafeVote.

In the sample testDeletions plot above, Morrissey’s adjusted rank is visually very close to McDonald’s adjusted rank when most of the ballots have been counted. This suggests to us that their relative standing in this election is sensitive to small variations in voter behaviour.

One of our primary motivations for developing this package was its possible future use in ranking candidates for the party list of the Green Party of Aotearoa New Zealand. To date, we have found no academic study of methods for ranking candidates using preferential-voting ballots, making it quite possibly a greenfield problem in social-choice research. In private communication of November 2022, Prof. Nicolaus Tideman had offered some advice on our initial proposal for ranking with a Condorcet score. However, because diversity is one of the explicit values of this party, a ranking that is derived from an STV-style ballot-counting process would seem much more appropriate for use by the NZ Greens than any ranking that is derived from a Condorcet scoring process. Indeed, the NZ Greens are currently using a modification of Meek’s STV algorithm, enshrined in Schedule 1A of Local Electoral Regulations 2001, to form its party list. Under its current rules, the NZ Greens rely on a delegated assembly to form an initial list. In a possible future in which the NZ Greens have dozens of elected MPs, the size of this assembly may have to be increased if the sitting MPs seeking re-election are to be safely ranked against each other, and against other candidates in the pool.

We wonder: are $n$ preferential ballots generally sufficient, in real-world elections, to safely rank-order $\sqrt{n}$ candidates? This package is, we hope, a first step toward answering this question.

Copy Link

Version

Install

install.packages('SafeVote')

Monthly Downloads

279

Version

1.0.1

License

GPL (>= 2)

Maintainer

Clark Thomborson

Last Published

October 5th, 2024

Functions in SafeVote (1.0.1)

extractRank

Extract a ranking vector by name from the results of a ballot count
.print.summary.SafeVote

.print method for summary object
.summary.SafeVote

summarises vote-totals for subsequent printing
condorcet

Count votes using the Condorcet voting method.
election.info

prints the basic results of an election
print.summary.SafeRankExpt

Print method for summary.SafeRankExpt
combineRankings

the least upper bound on a pair of rankings
image.SafeVote.condorcet

The image function visualizes the joint distribution of two preferences (if 'all.pref=FALSE') given 'xpref' and 'ypref', as well as the marginal distribution of all preferences (if 'all.pref=TRUE'). The joint distribution can be shown as proportions (if 'proportion=TRUE') or raw vote counts (if 'proportion=FALSE').
image.SafeVote.stv

visualisation of joint and marginal distributions in STV preferences
completeRankingTable

internal method to analyse the partial results of an stv() ballot count, to discover a complete ranking of all candidates. The ranking may depend on the value of nseats, because this affects how votes are transferred.
print.summary.SafeVote.approval

print method for summary object
dublin_west

Dublin West
print.summary.SafeVote.condorcet

print method for summary.SafeVote.condorcet
summary.SafeVote.plurality

summary method for plurality object
summary.SafeVote.score

summary method for score object
correct.ranking

Amend ballots with equal or incomplete preferences
print.summary.SafeVote.plurality

print method for summary of plurality object
ims_stv

IMS STV
plot.SafeVote.stv

plot() method for the result of an stv() ballot-count
print.summary.SafeVote.stv

print() method for a summary() of a SafeVote result
print.summary.SafeVote.score

print method for summary.score object
plot.SafeRankExpt

plot() method for the result of an experiment with varying numbers of ballots
ims_election

IMS Election
ims_approval

IMS Approval
winnerMargin

Find a winner and their margin of victory
invalid.votes

Extracts the invalid.votes member (if any) from the result of a count
remove.candidate

Remove a candidate, amending ballot papers as required
summary.SafeVote.approval

summary method for approval results
summary.SafeVote.condorcet

Summary method for condorcet() results
score

Count votes using the score (or range) method.
check.votes.plurality

undocumented internal method
food_election

Food Election
is.SafeRankExpt

is.SafeRankExpt()
yale_ballots

Yale Faculty Senate 2016
testDeletions

Assess the safety of a preliminary result for an election
new_SafeRankExpt

Constructor for the results of a SafeRank experiment
testFraction

Bootstrapping experiment, with fractional counts of a ballot box.
check.votes.score

undocumented internal method
forwards.tiebreak

Undocumented internal method
rbind_SafeRankExpt

add a row to a SafeRankExpt object
is.valid.vote

undocumented internal method
readHil

read a set of ballots in .HIL format
view.SafeVote.score

view method for score object
loserMargin

Find a loser and their margin of victory
view.SafeVote.plurality

view method for plurality object
view.SafeVote.condorcet

view method for SafeVote.condorcet
ims_plurality

IMS Plurality
ordered.preferences

Undocumented internal method
view.SafeVote.stv

view method for the result of an stv() ballot-count
ims_score

IMS Score
stv

Count preferential ballots using an STV method
solveTiebreak

Undocumented internal method, renamed from 'solve.tiebreak' to avoid confusion with generic solve()
ordered.tiebreak

Undocumented internal method
plurality

Count votes using the plurality method
summary.SafeVote.stv

summary() method for a SafeVote result
translate.ties

Undocumented internal method from original code
uk_labour_2010

UK Labour Party Leader 2010
testAdditions

Test the sensitivity of a result to tactical voting.
prepare.votes

Coerce input 'data' into a matrix
sumOfVotes

internal method, computes column-sums
summary.SafeRankExpt

summary method for SafeRankExpt
view.SafeVote.approval

view method for approval object
view

generic view() for classes defined in this package
a4_hil

Tideman a4_hil
assemble.args.for.check.score

undocumented internal method
backwards.tiebreak

Undocumented internal method
approval

Count votes using the approval method
check.nseats

parameter-checking method for nseats (internal)
assemble.args.for.check.stv

undocumented internal method
as.SafeRankExpt

as.SafeRankExpt()
check.votes.tworound.runoff

undocumented internal method
check.votes.stv

undocumented internal method
check.votes.condorcet

undocumented internal method
check.votes.approval

undocumented internal method
check.ranking

check the validity of a partial ranking
check.votes

undocumented internal method
a3_hil

Tideman a3_hil
a53_hil

Tideman a53_hil
SafeVote-package

SafeVote: Election Vote Counting with Safety Features
extractMargins

extract margins from the results of a ballot count