diff --git a/DESCRIPTION b/DESCRIPTION index 902253f..9c2f876 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: vote Type: Package Title: Election Vote Counting -Version: 2.4-0.9001 -Date: 2023-12-07 +Version: 2.4-0.9002 +Date: 2023-12-08 Author: Hana Sevcikova, Bernard Silverman, Adrian Raftery Maintainer: Hana Sevcikova Description: Counting election votes and determining election results by different methods, including diff --git a/R/check_votes.R b/R/check_votes.R index 7ec8afb..4c937aa 100644 --- a/R/check_votes.R +++ b/R/check_votes.R @@ -140,6 +140,7 @@ impute.ranking <- function(votes, equal.ranking = FALSE, quiet = TRUE){ impvalues <- apply(votes.for.imp, 2, median, na.rm = TRUE) # compute means as well in case we need to solve ordering of duplicates impvalues.means <- apply(votes.for.imp, 2, mean, na.rm = TRUE) + skipped <- c() # iterate over votes that need imputation for(i in which(voters.with.conflict)){ @@ -153,9 +154,15 @@ impute.ranking <- function(votes, equal.ranking = FALSE, quiet = TRUE){ # iterate over ordered candidates previous.imputed <- 0 for(ocan in can.order){ - this.rank <- min(as.integer(round(ranks.to.impute[ocan])), max(votes[i, ], 0)+1) + this.rank <- as.integer(round(ranks.to.impute[ocan])) if(!equal.ranking && this.rank == previous.imputed) this.rank <- this.rank + 1 # avoid duplicates + if(this.rank > sum(votes[i, ] > 0) + 1){ + # if the imputed rank is larger than the maximum preference plus one, set it to 0 + votes[i, where.to.impute[ocan]] <- 0 + skipped <- c(skipped, i) + next + } ranks.to.shift <- !is.na(votes.for.imp[i, ]) & votes[i, ] >= this.rank & ! seq_len(ncol(votes)) %in% where.to.impute votes[i, ranks.to.shift] <- votes[i, ranks.to.shift] + 1 @@ -163,9 +170,13 @@ impute.ranking <- function(votes, equal.ranking = FALSE, quiet = TRUE){ previous.imputed <- this.rank } } - if(!quiet) cat("\nMedian ranks", paste(round(impvalues[cans], 1), collapse = ", "), "for candidate(s)", - paste(colnames(votes)[cans], collapse = ", "), - "was used for imputation into", sum(voters.with.conflict), "vote(s).\n") - + if(!quiet) { + cat("\nMedian ranks used for imputation into", sum(voters.with.conflict), "vote(s) :\n") + print(data.frame(matrix(round(impvalues[cans], 1), byrow = TRUE, nrow = 1, + dimnames = list("rank", colnames(votes)[cans])))) + skipped <- unique(skipped) + if(length(skipped) > 0) + warning("Imputation skipped for votes ", paste(skipped, collapse = ", "), " due to non-missing preferences being too small.") + } return(votes) } diff --git a/man/stv.Rd b/man/stv.Rd index 5d55190..8f1cce0 100644 --- a/man/stv.Rd +++ b/man/stv.Rd @@ -92,7 +92,8 @@ If equal ranking is not alowed (\code{equal.ranking = FALSE}), the argument \cod The \code{correct.ranking} function does the above corrections for all records, regardless if they contain duplicates or not. Its argument \code{partial} determines if ballots are partially set to 0 (\code{TRUE}), or if it is complete re-ranking, as allowed when \code{equal.ranking = TRUE}. It can either be used by calling it explicitely, otherwise it is called by \code{stv} if \code{equal.ranking = TRUE} or \code{invalid.partial = TRUE}. The function is also called from within the \code{\link{condorcet}} function. The \code{remove.candidate} function removes the given candidate(s) and adjusts the ranked votes accordingly by calling the \code{correct.ranking} function. -The function allows to impute missing values. It can be used for example, if a voter has a conflict of interest with one or more candidates and not voting for them would unfairly decrease the chances of those candidates being elected. Preferences to be imputed should be set to \eqn{-1} and the argument \code{impute.missing} to \code{TRUE}. Each such preference is imputed using the median rank value over the remaining votes. When computing the median rank across the votes, any value of zero is replaced by the median of the ranks not used in the corresponding vote. For example, for a ballot \eqn{1,2,3,0,0,0}, the three zeros are replaced by the median of \eqn{4, 5, 6}, i.e. by \eqn{5}, which is then used to impute the missing median rank. This functionality is implemented in the \code{impute.ranking} function, which is called automatically from \code{stv} if \code{impute.missing = TRUE}. It can be used explicitely as well. +The function allows to impute missing values. It can be used for example, if a voter has a conflict of interest with one or more candidates and not voting for them would unfairly decrease the chances of those candidates being elected. Preferences to be imputed should be set to \eqn{-1} and the argument \code{impute.missing} to \code{TRUE}. Each such preference is imputed using the median rank value over the remaining votes. When computing the median rank across the votes, any value of zero is replaced by the median of the ranks not used in the corresponding vote. For example, for a ballot \eqn{1,2,3,0,0,0}, the three zeros are replaced by the median of \eqn{4, 5, 6}, i.e. by \eqn{5}, which is then used to compute the missing median rank. If the final imputed rank is larger than the number of non-zero preferences (e.g. if in a ballot \eqn{1,2,0,-1,0} the imputed value for the fourth candidate would be larger than 3), the preference is set to zero and a warning is issued. +The described functionality is implemented in the \code{impute.ranking} function, which is called automatically from \code{stv} if \code{impute.missing = TRUE}. It can be used explicitely as well. By default, ties in the STV algorithm are resolved using the forwards tie-breaking method, see Newland and Briton (Section 5.2.5). Argument \code{ties} can be set to \dQuote{b} in order to use the backwards tie-breaking method, see O'Neill (2004). In addition, both methods are complemented by the following \dQuote{ordered} method: Prior to the STV election candidates are ordered by the number of 1st preferences. Equal ranks are resolved by moving to the number of 2nd preferences, then 3rd and so on. Remaining ties are broken by random draws. Such complete ordering is used to break any tie that cannot be resolved by the forwards or backwards method. If there is at least one tie during the processing, the output contains a row indicating in which count a tie-break happened (see the \code{ties} element in the Value section for an explanation of the symbols).