From 25dbae3010d8d54b57acfa9c9b9e89c6ea551c23 Mon Sep 17 00:00:00 2001 From: Flavio Poletti Date: Tue, 12 Dec 2023 17:44:36 +0100 Subject: [PATCH] update docs use roxygen markdown, use 4 space indentation everywhere --- DESCRIPTION | 1 + R/S3.R | 2 +- R/biproportional-check.R | 2 +- R/biproportional.R | 6 +- R/divisor.R | 18 +- R/methods.R | 54 ++-- R/quorum.R | 16 +- R/quota.R | 34 +-- R/round.R | 62 ++-- R/shinyapp.R | 418 +++++++++++++-------------- man/apport_methods.Rd | 2 +- man/divisor_ceiling.Rd | 2 +- man/lower_apportionment.Rd | 12 +- man/pivot_to_matrix.Rd | 4 +- man/proporz.Rd | 14 +- man/quorum_all.Rd | 2 +- man/quorum_any.Rd | 10 +- man/reached_quorum_any_district.Rd | 2 +- man/reached_quorum_total.Rd | 2 +- man/reached_quorums.Rd | 10 +- man/upper_apportionment.Rd | 2 +- tests/testthat/test-biproportional.R | 4 +- tests/testthat/test-divisor.R | 6 +- tests/testthat/test-proporz.R | 94 +++--- tests/testthat/test-quota.R | 36 +-- tests/testthat/test-utils.R | 4 +- tests/testthat/test-zug2018.R | 58 ++-- 27 files changed, 437 insertions(+), 440 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 089c812..5291835 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -10,6 +10,7 @@ License: GPL (>=3) Encoding: UTF-8 LazyData: true RoxygenNote: 7.2.3 +Roxygen: list(markdown = TRUE) Depends: R (>= 3.6.0) Suggests: diff --git a/R/S3.R b/R/S3.R index 3b6b493..8a38508 100644 --- a/R/S3.R +++ b/R/S3.R @@ -7,7 +7,7 @@ print.proporz_matrix = function(x, ...) { #' @export as.matrix.proporz_matrix = function(x, ...) { - matrix(x, nrow = nrow(x), dimnames = dimnames(x)) + matrix(x, nrow = nrow(x), dimnames = dimnames(x)) } #' Get district and party divisors diff --git a/R/biproportional-check.R b/R/biproportional-check.R index 22fed10..bcf9138 100644 --- a/R/biproportional-check.R +++ b/R/biproportional-check.R @@ -86,7 +86,7 @@ prep_district_seats = function(district_seats, votes_matrix, return(district_seats) } -prep_district_seats_df <- function(district_seats_df) { +prep_district_seats_df = function(district_seats_df) { ds_df = district_seats_df[order(district_seats_df[[1]]),] district_seats <- ds_df[[2]] names(district_seats) <- ds_df[[1]] diff --git a/R/biproportional.R b/R/biproportional.R index 16341a0..537c451 100644 --- a/R/biproportional.R +++ b/R/biproportional.R @@ -69,7 +69,6 @@ #' #> SDP UUS 97107 6 #' #> KOK UUS 114243 7 #' -#' @md #' @export pukelsheim = function(votes_df, district_seats_df, quorum, @@ -214,7 +213,6 @@ pukelsheim = function(votes_df, district_seats_df, #' #' #' @importFrom stats setNames -#' @md #' @export biproportional = function(votes_matrix, district_seats, @@ -277,7 +275,7 @@ biproporz = biproportional #' @param method Apportion method that defines how seats are assigned, #' see \link{proporz}. #' -#' @seealso \link{biproportional}, \link{lower_apportionment} +#' @seealso \code{\link{biproportional}}, \code{\link{lower_apportionment}} #' #' @return named list with column/district seats and row/party seats #' @export @@ -356,7 +354,7 @@ upper_apportionment = function(votes_matrix, district_seats, #' #' @return seat matrix with column and row divisors stored in attributes #' -#' @seealso \link{biproportional}, \link{upper_apportionment} +#' @seealso \code{\link{biproportional}}, \code{\link{lower_apportionment}} #' #' @references Oelbermann, K. F. (2016). Alternate scaling algorithm for #' biproportional divisor methods. Mathematical Social Sciences, 80, 25-32. diff --git a/R/divisor.R b/R/divisor.R index c8394d6..55c68d3 100644 --- a/R/divisor.R +++ b/R/divisor.R @@ -16,15 +16,15 @@ #' #' @export highest_averages_method = function(party_votes, n_seats, divisors) { - check_votes(party_votes) - check_n_seats(n_seats) - if(length(party_votes) == 1) { return(n_seats) } - stopifnot(all(!is.na(party_votes))) - if(n_seats == 0) { return(rep(0, length(party_votes))) } + check_votes(party_votes) + check_n_seats(n_seats) + if(length(party_votes) == 1) { return(n_seats) } + stopifnot(all(!is.na(party_votes))) + if(n_seats == 0) { return(rep(0, length(party_votes))) } stopifnot(is.null(dim(divisors))) if(length(divisors) == 1) { - divisors <- seq(from = divisors, by = 1, length.out = n_seats) + divisors <- seq(from = divisors, by = 1, length.out = n_seats) } n_parties = length(party_votes) @@ -82,7 +82,7 @@ divisor_round = function(votes, n_seats, quorum = 0) { hzv(votes, n_seats, 0.5) } -#' #' Divisor method rounding up +#' Divisor method rounding up #' #' Also known as: Adams method #' @@ -102,7 +102,7 @@ divisor_ceiling = function(votes, n_seats, quorum = 0) { #' @seealso \code{\link{proporz}} #' @export divisor_harmonic = function(votes, n_seats, quorum = 0) { - check_n_seats(n_seats) + check_n_seats(n_seats) if(n_seats < length(votes[votes > 0])) { stop("With harmonic rounding there must be at least as many seats as there are parties with non-zero votes", call. = F) } @@ -123,7 +123,7 @@ divisor_harmonic = function(votes, n_seats, quorum = 0) { #' @seealso \code{\link{proporz}} #' @export divisor_geometric = function(votes, n_seats, quorum = 0) { - check_n_seats(n_seats) + check_n_seats(n_seats) nn = seq(1, n_seats) divisors = sqrt((nn-1)*nn) diff --git a/R/methods.R b/R/methods.R index 1e63a02..0cf750f 100644 --- a/R/methods.R +++ b/R/methods.R @@ -29,35 +29,35 @@ #' #' @export apport_methods = list( - "divisor_floor" = "floor", - "d'hondt" = "floor", - "jefferson" = "floor", - "divisor_round" = "round", - "sainte-lague" = "round", - "webster" = "round", - "divisor_ceiling" = "ceiling", - "adams" = "ceiling", - "dean" = "harmonic", - "huntington-hill" = "geometric", - "hill-huntington" = "geometric", - "hare-niemeyer" = "quota_largest_remainder", - "hamilton" = "quota_largest_remainder", - "vinton" = "quota_largest_remainder", - "hagenbach-bischoff" = "floor", - "ceiling" = "ceiling", - "round" = "round", - "floor" = "floor", - "harmonic" = "harmonic", - "geometric" = "geometric", - "quota_largest_remainder" = "quota_largest_remainder" + "divisor_floor" = "floor", + "d'hondt" = "floor", + "jefferson" = "floor", + "divisor_round" = "round", + "sainte-lague" = "round", + "webster" = "round", + "divisor_ceiling" = "ceiling", + "adams" = "ceiling", + "dean" = "harmonic", + "huntington-hill" = "geometric", + "hill-huntington" = "geometric", + "hare-niemeyer" = "quota_largest_remainder", + "hamilton" = "quota_largest_remainder", + "vinton" = "quota_largest_remainder", + "hagenbach-bischoff" = "floor", + "ceiling" = "ceiling", + "round" = "round", + "floor" = "floor", + "harmonic" = "harmonic", + "geometric" = "geometric", + "quota_largest_remainder" = "quota_largest_remainder" ) get_apport_method = function(method_name) { - method_name <- tolower(method_name) - if(!method_name %in% names(apport_methods)) { - stop("Unknown apportion method: ", method_name, ".\nAvailable: ", - paste0(names(apport_methods), collapse=", ")) - } + method_name <- tolower(method_name) + if(!method_name %in% names(apport_methods)) { + stop("Unknown apportion method: ", method_name, ".\nAvailable: ", + paste0(names(apport_methods), collapse=", ")) + } - apport_methods[[method_name]] + apport_methods[[method_name]] } diff --git a/R/quorum.R b/R/quorum.R index 7289cb4..e1ddefc 100644 --- a/R/quorum.R +++ b/R/quorum.R @@ -1,6 +1,6 @@ #' Create a quorum function where \emph{all} quorums must be reached #' -#' \code{quorum_any} and \code{quorum_all} are normally used in [biproportional()] +#' `quorum_any` and `quorum_all` are normally used in [biproportional()] #' or [pukelsheim()]. Missing quorum parameters are ignored. #' #' @param any_district Vote threshold a party must reach in \emph{at least} @@ -13,7 +13,7 @@ #' as number of votes. Must be greater than 0. #' Uses [reached_quorum_total()]. #' -#' @seealso [quorum_any()] +#' @seealso \code{\link{quorum_any}} #' #' @note Currently only two quorums are implemented (any_district and total). #' If you only use one quorum, [quorum_any()] and [quorum_all()] are identical. @@ -71,7 +71,6 @@ #' #> C 0 0 #' #> D 0 0 #' -#' @md #' @export quorum_all = function(any_district, total) { create_quorum_function_list("ALL", any_district, total) @@ -79,14 +78,14 @@ quorum_all = function(any_district, total) { #' Create a quorum function where \emph{at least one} quorum must be reached #' -#' \code{quorum_any} and \code{quorum_all} are normally used in [biproportional()] +#' `quorum_any` and `quorum_all` are normally used in [biproportional()] #' or [pukelsheim()]. Missing quorum parameters are ignored. #' #' @returns a function(votes_matrix) which then returns a boolean vector with #' length equal to the number of lists/parties (votes_matrix rows), #' denoting whether a party has reached at least one quorum. #' -#' @seealso [quorum_all()] +#' @seealso `quorum_all` #' #' @note Currently only two quorums are implemented. #' @@ -126,7 +125,8 @@ create_quorum_function_list = function(type, any_district, total) { #' as number of votes. Must be greater than 0. #' #' @returns boolean vector with length equal to the number of lists/parties -#' (votes_matrix rows) whether they reached the quorum or not +#' (`votes_matrix` rows) whether they reached the quorum or not + #' @examples #' votes_matrix = matrix(c(502, 55, 80, 10, 104, 55, 0, 1), ncol = 2) #' @@ -134,7 +134,6 @@ create_quorum_function_list = function(type, any_district, total) { #' reached_quorums(votes_matrix, quorum_functions) #' #> [1] TRUE TRUE TRUE FALSE #' -#' @md #' @export reached_quorum_total = function(votes_matrix, quorum_total) { stopifnot(quorum_total > 0) @@ -157,7 +156,6 @@ reached_quorum_total = function(votes_matrix, quorum_total) { #' Must be greater than 0. #' #' @inherit reached_quorum_total return -#' @md #' @export reached_quorum_any_district = function(votes_matrix, quorum_districts) { stopifnot(quorum_districts > 0) @@ -174,7 +172,7 @@ reached_quorum_any_district = function(votes_matrix, quorum_districts) { #' #' @param votes_matrix votes matrix #' @param quorum_funcs List of quorum functions. If list, the attribute "type" -#' must be set which indicates whether "ALL" or "ANY" +#' must be set which indicates whether `ALL` or `ANY` #' (i.e. at least one) quorum must be reached. #' #' @note This is a low-level implementation for quorum calculations and is diff --git a/R/quota.R b/R/quota.R index 9b1ff92..b5af301 100644 --- a/R/quota.R +++ b/R/quota.R @@ -9,29 +9,29 @@ #' @seealso \code{\link{proporz}} #' @export quota_largest_remainder = function(votes, n_seats, throw_equal_remainder_error = TRUE) { - check_n_seats(n_seats) - check_votes(votes) + check_n_seats(n_seats) + check_votes(votes) - if(length(votes) == 1) { - return(n_seats) - } - if(n_seats == 0) { - return(rep(0, length(votes))) - } + if(length(votes) == 1) { + return(n_seats) + } + if(n_seats == 0) { + return(rep(0, length(votes))) + } quota = n_seats*votes/sum(votes) seats_base = floor(quota) - seats_rem = rep(0, length(votes)) + seats_rem = rep(0, length(votes)) - if(sum(seats_base) < n_seats) { - remainder = quota - seats_base - check_equal_entries(remainder[remainder > 0]) + if(sum(seats_base) < n_seats) { + remainder = quota - seats_base + check_equal_entries(remainder[remainder > 0]) - n_seats_remaining = n_seats - sum(seats_base) - seats_rem <- rep(0, length(votes)) - order_index = order(remainder, decreasing = TRUE) - seats_rem[order_index[1:n_seats_remaining]] <- 1 - } + n_seats_remaining = n_seats - sum(seats_base) + seats_rem <- rep(0, length(votes)) + order_index = order(remainder, decreasing = TRUE) + seats_rem[order_index[1:n_seats_remaining]] <- 1 + } return(seats_base + seats_rem) } diff --git a/R/round.R b/R/round.R index 7953c58..4e381c9 100644 --- a/R/round.R +++ b/R/round.R @@ -2,48 +2,48 @@ #' @param x numeric value #' @param threshold threshold in 0..1 ceil_at = function(x, threshold) { - stopifnot(!is.na(threshold)) + stopifnot(!is.na(threshold)) if(any(x < 0)) { - stop("x cannot be negative") - } + stop("x cannot be negative") + } values = c(x) if(is.numeric(threshold)) { - if(threshold < 0 || threshold > 1) { - stop("Threshold argument must be in [0,1]") - } + if(threshold < 0 || threshold > 1) { + stop("Threshold argument must be in [0,1]") + } threshold <- floor(values) + threshold - } else if(threshold == "harmonic") { - threshold <- threshold_harmonic(values) - } else if(threshold == "geometric") { - threshold <- threshold_geometric(values) - } else { - stop('Numeric value, "harmonic" or "geometric" expected for threshold argument') - } + } else if(threshold == "harmonic") { + threshold <- threshold_harmonic(values) + } else if(threshold == "geometric") { + threshold <- threshold_geometric(values) + } else { + stop('Numeric value, "harmonic" or "geometric" expected for threshold argument') + } - ceiled = ceiling(values) - floor_index = values < threshold - ceiled[floor_index] <- floor(values)[floor_index] + ceiled = ceiling(values) + floor_index = values < threshold + ceiled[floor_index] <- floor(values)[floor_index] - if(is.matrix(x)) { - ceiled_matrix = matrix(ceiled, nrow = nrow(x), ncol = ncol(x)) - return(ceiled_matrix) - } else { - return(ceiled) - } + if(is.matrix(x)) { + ceiled_matrix = matrix(ceiled, nrow = nrow(x), ncol = ncol(x)) + return(ceiled_matrix) + } else { + return(ceiled) + } } get_round_function = function(method_name) { - m = get_apport_method(method_name) - method_thresholds = list( - "ceiling" = 0, - "round" = 0.5, - "floor" = 1, - "harmonic" = "harmonic", - "geometric" = "geometric" - ) + m = get_apport_method(method_name) + method_thresholds = list( + "ceiling" = 0, + "round" = 0.5, + "floor" = 1, + "harmonic" = "harmonic", + "geometric" = "geometric" + ) - function(x) ceil_at(x, method_thresholds[[m]]) + function(x) ceil_at(x, method_thresholds[[m]]) } threshold_harmonic = function(x) { diff --git a/R/shinyapp.R b/R/shinyapp.R index 9c300fe..528ffc2 100644 --- a/R/shinyapp.R +++ b/R/shinyapp.R @@ -15,222 +15,222 @@ #' } #' @export run_app = function(votes_matrix = NULL, district_seats = NULL) { - # load packages / "import" #### - if (!requireNamespace("shiny", quietly = TRUE)) { - stop("Please install shiny: install.packages('shiny')") - } - if (!requireNamespace("shinyMatrix", quietly = TRUE)) { - stop("Please install shinyMatrix: install.packages('shinyMatrix')") - } - examples = proporz::biproporz_examples - tags = shiny::tags - fluidRow = shiny::fluidRow - column = shiny::column - observeEvent = shiny::observeEvent - sidebarPanel = shiny::sidebarPanel - - # default parameters #### - if(is.null(votes_matrix)) { - base_votes_matrix = create_empty_votes_matrix(3, 4) - } else { - base_votes_matrix = votes_matrix - } - if(is.null(district_seats)) { - base_district_seats_mtrx = create_seats_matrix(base_votes_matrix) - } else { - base_district_seats_mtrx = create_seats_matrix(base_votes_matrix, district_seats) - } - - apport_methods_choices = unique(unname(unlist(apport_methods)))[c(2,1,3:6)] - - # UI #### - ui = shiny::fluidPage( - shiny::titlePanel("Biproportional Apportionment"), - # UI input #### - fluidRow( - # input matrix #### - column(9, - tags$h3(tags$strong("Input")), - shinyMatrix::matrixInput( - inputId = "votes_matrix", - label = "Vote Matrix (click into matrix to edit votes and names)", - value = base_votes_matrix, - class = "numeric", - cols = list( - names = TRUE, - editableNames = TRUE - ), - rows = list( - names = TRUE, - editableNames = TRUE - ) - ), - shinyMatrix::matrixInput( - inputId = "district_seats_matrix", - label = "Seats per district", - value = base_district_seats_mtrx, - class = "numeric", - cols = list( - names = TRUE - ), - rows = list( - names = TRUE - ) - )), - # input options #### - column(width = 3, - sidebarPanel(width = 12, - shiny::selectInput("load_example", "Load example", c("...", "Zug 2018", "Uri 2020", "Wikipedia EN", "Wikipedia DE")), - tags$hr(), - tags$h4(tags$strong("Edit input table"), style = "margin-bottom:1em"), - shiny::numericInput("n_cols", "number of district", 4, min = 2), - shiny::numericInput("n_rows", "number of parties", 3, min = 2), - shiny::actionButton("run_update_matrix", "set table dimensions"), - shiny::checkboxInput("set_seats_per_district", "define seats per district", TRUE) - ) - )), - tags$hr(), - # UI output #### - fluidRow( - # output matrix #### - column(9, - tags$h3(tags$strong("Output")), - tags$p(shiny::checkboxInput("show_seat_totals", "Show seat totals", FALSE)), - tags$div(shiny::tableOutput("biproporz_result")) - ), - # output options #### - column(3, - sidebarPanel(width = 12, - tags$h4(tags$strong("Apportionment parameters"), style = "margin-bottom:1em"), - shiny::numericInput("quorum_districts", "Quorum (districts)", 0, min = 0), - shiny::numericInput("quorum_total", "Quorum (total)", 0, min = 0), - shiny::checkboxInput("quorum_all", "Both quorums necessary", FALSE), - shiny::checkboxInput("use_list_votes", "Use list votes", TRUE) - ) - ) - ) - ) - - # server #### - server = function(input, output, session) { - - # run biproportional apportionment #### - output$biproporz_result <- shiny::renderTable( - run_biproporz(), - digits = 0, - rownames = TRUE) - - run_biproporz = function() { - if(sum(input$votes_matrix) == 0) return(NULL) - if(sum(input$district_seats_matrix) == 0) return(NULL) - if(any(nchar(colnames(input$votes_matrix)) == 0)) return(NULL) - - district_seats = input$district_seats_matrix - if(ncol(district_seats) > 1 && isTRUE(input$set_seats_per_district)) { - colnames(district_seats) <- colnames(input$votes_matrix) - shinyMatrix::updateMatrixInput(session, "district_seats_matrix", district_seats) - } - district_seats <- district_seats[1,] - - .quorum = get_quorum_function(input$quorum_districts, input$quorum_total, input$quorum_all) - - bp = biproporz(input$votes_matrix, district_seats, - quorum = .quorum, - use_list_votes = input$use_list_votes) - - # add seat totals - if(input$show_seat_totals) { - bpt <- matrix(NA, nrow = nrow(bp)+1, ncol = ncol(bp)+1) - bpt[2:nrow(bpt),2:ncol(bpt)] <- bp - bpt[1,1:ncol(bpt)] <- c(sum(bp), colSums(bp)) - bpt[2:nrow(bpt),1] <- rowSums(bp) - colnames(bpt) <- c("TOTAL", colnames(bp)) - rownames(bpt) <- c("TOTAL", rownames(bp)) - bp <- bpt - } - - return(bp) - } - - # update inputs #### - observeEvent(input$run_update_matrix, { - vm = create_empty_votes_matrix(input$n_rows, input$n_cols) - update_input_matrices(vm) - }) - - update_input_matrices = function(votes_matrix, district_seats = rep(0, ncol(votes_matrix))) { - shinyMatrix::updateMatrixInput(session, "votes_matrix", votes_matrix) - - dsm = create_seats_matrix(votes_matrix, district_seats) - shinyMatrix::updateMatrixInput(session, "district_seats_matrix", dsm) - } - - # seats per district option #### - observeEvent(input$set_seats_per_district, { - if(sum(input$votes_matrix) == 0) return(NULL) - if(sum(input$district_seats_matrix) == 0) return(NULL) - if(any(nchar(colnames(input$votes_matrix)) == 0)) return(NULL) - - if(input$set_seats_per_district) { - tmp_seats = upper_apportionment(input$votes_matrix, - district_seats = sum(input$district_seats_matrix)) - - m = create_seats_matrix(input$votes_matrix, tmp_seats$district) - } else { - m = create_seats_matrix(input$votes_matrix, sum(input$district_seats_matrix)) - } - shinyMatrix::updateMatrixInput(session, "district_seats_matrix", m) - }) - - # Load examples #### - observeEvent(input$load_example, { - if(input$load_example == "Zug 2018") { - set_inputs(quorum_districts = 0.05, quorum_total = 0.03) - update_input_matrices(examples$zug_2018$votes, examples$zug_2018$seats) - } else if(input$load_example == "Uri 2020") { - set_inputs() - update_input_matrices(examples$uri_2020$votes, examples$uri_2020$seats) - } else if(input$load_example == "Wikipedia EN") { - set_inputs(use_list_votes = FALSE, set_seats_per_district = FALSE) - update_input_matrices(examples$wikipedia_en$votes, examples$wikipedia_en$seats) - } else if(input$load_example == "Wikipedia DE") { - set_inputs() - update_input_matrices(examples$wikipedia_de$votes, examples$wikipedia_de$seats) - } else { - return() - } - }) - - set_inputs = function(quorum_districts = 0, quorum_total = 0, use_list_votes = TRUE, set_seats_per_district = TRUE) { - shiny::updateCheckboxInput(session, "use_list_votes", value = use_list_votes) - shiny::updateNumericInput(session, "quorum_districts", value = quorum_districts) - shiny::updateNumericInput(session, "quorum_total", value = quorum_total) - shiny::updateCheckboxInput(session, "set_seats_per_district", value = set_seats_per_district) - } - } - - # Run the application #### - shiny::shinyApp(ui = ui, server = server) + # load packages / "import" #### + if (!requireNamespace("shiny", quietly = TRUE)) { + stop("Please install shiny: install.packages('shiny')") + } + if (!requireNamespace("shinyMatrix", quietly = TRUE)) { + stop("Please install shinyMatrix: install.packages('shinyMatrix')") + } + examples = proporz::biproporz_examples + tags = shiny::tags + fluidRow = shiny::fluidRow + column = shiny::column + observeEvent = shiny::observeEvent + sidebarPanel = shiny::sidebarPanel + + # default parameters #### + if(is.null(votes_matrix)) { + base_votes_matrix = create_empty_votes_matrix(3, 4) + } else { + base_votes_matrix = votes_matrix + } + if(is.null(district_seats)) { + base_district_seats_mtrx = create_seats_matrix(base_votes_matrix) + } else { + base_district_seats_mtrx = create_seats_matrix(base_votes_matrix, district_seats) + } + + apport_methods_choices = unique(unname(unlist(apport_methods)))[c(2,1,3:6)] + + # UI #### + ui = shiny::fluidPage( + shiny::titlePanel("Biproportional Apportionment"), + # UI input #### + fluidRow( + # input matrix #### + column(9, + tags$h3(tags$strong("Input")), + shinyMatrix::matrixInput( + inputId = "votes_matrix", + label = "Vote Matrix (click into matrix to edit votes and names)", + value = base_votes_matrix, + class = "numeric", + cols = list( + names = TRUE, + editableNames = TRUE + ), + rows = list( + names = TRUE, + editableNames = TRUE + ) + ), + shinyMatrix::matrixInput( + inputId = "district_seats_matrix", + label = "Seats per district", + value = base_district_seats_mtrx, + class = "numeric", + cols = list( + names = TRUE + ), + rows = list( + names = TRUE + ) + )), + # input options #### + column(width = 3, + sidebarPanel(width = 12, + shiny::selectInput("load_example", "Load example", c("...", "Zug 2018", "Uri 2020", "Wikipedia EN", "Wikipedia DE")), + tags$hr(), + tags$h4(tags$strong("Edit input table"), style = "margin-bottom:1em"), + shiny::numericInput("n_cols", "number of district", 4, min = 2), + shiny::numericInput("n_rows", "number of parties", 3, min = 2), + shiny::actionButton("run_update_matrix", "set table dimensions"), + shiny::checkboxInput("set_seats_per_district", "define seats per district", TRUE) + ) + )), + tags$hr(), + # UI output #### + fluidRow( + # output matrix #### + column(9, + tags$h3(tags$strong("Output")), + tags$p(shiny::checkboxInput("show_seat_totals", "Show seat totals", FALSE)), + tags$div(shiny::tableOutput("biproporz_result")) + ), + # output options #### + column(3, + sidebarPanel(width = 12, + tags$h4(tags$strong("Apportionment parameters"), style = "margin-bottom:1em"), + shiny::numericInput("quorum_districts", "Quorum (districts)", 0, min = 0), + shiny::numericInput("quorum_total", "Quorum (total)", 0, min = 0), + shiny::checkboxInput("quorum_all", "Both quorums necessary", FALSE), + shiny::checkboxInput("use_list_votes", "Use list votes", TRUE) + ) + ) + ) + ) + + # server #### + server = function(input, output, session) { + + # run biproportional apportionment #### + output$biproporz_result <- shiny::renderTable( + run_biproporz(), + digits = 0, + rownames = TRUE) + + run_biproporz = function() { + if(sum(input$votes_matrix) == 0) return(NULL) + if(sum(input$district_seats_matrix) == 0) return(NULL) + if(any(nchar(colnames(input$votes_matrix)) == 0)) return(NULL) + + district_seats = input$district_seats_matrix + if(ncol(district_seats) > 1 && isTRUE(input$set_seats_per_district)) { + colnames(district_seats) <- colnames(input$votes_matrix) + shinyMatrix::updateMatrixInput(session, "district_seats_matrix", district_seats) + } + district_seats <- district_seats[1,] + + .quorum = get_quorum_function(input$quorum_districts, input$quorum_total, input$quorum_all) + + bp = biproporz(input$votes_matrix, district_seats, + quorum = .quorum, + use_list_votes = input$use_list_votes) + + # add seat totals + if(input$show_seat_totals) { + bpt <- matrix(NA, nrow = nrow(bp)+1, ncol = ncol(bp)+1) + bpt[2:nrow(bpt),2:ncol(bpt)] <- bp + bpt[1,1:ncol(bpt)] <- c(sum(bp), colSums(bp)) + bpt[2:nrow(bpt),1] <- rowSums(bp) + colnames(bpt) <- c("TOTAL", colnames(bp)) + rownames(bpt) <- c("TOTAL", rownames(bp)) + bp <- bpt + } + + return(bp) + } + + # update inputs #### + observeEvent(input$run_update_matrix, { + vm = create_empty_votes_matrix(input$n_rows, input$n_cols) + update_input_matrices(vm) + }) + + update_input_matrices = function(votes_matrix, district_seats = rep(0, ncol(votes_matrix))) { + shinyMatrix::updateMatrixInput(session, "votes_matrix", votes_matrix) + + dsm = create_seats_matrix(votes_matrix, district_seats) + shinyMatrix::updateMatrixInput(session, "district_seats_matrix", dsm) + } + + # seats per district option #### + observeEvent(input$set_seats_per_district, { + if(sum(input$votes_matrix) == 0) return(NULL) + if(sum(input$district_seats_matrix) == 0) return(NULL) + if(any(nchar(colnames(input$votes_matrix)) == 0)) return(NULL) + + if(input$set_seats_per_district) { + tmp_seats = upper_apportionment(input$votes_matrix, + district_seats = sum(input$district_seats_matrix)) + + m = create_seats_matrix(input$votes_matrix, tmp_seats$district) + } else { + m = create_seats_matrix(input$votes_matrix, sum(input$district_seats_matrix)) + } + shinyMatrix::updateMatrixInput(session, "district_seats_matrix", m) + }) + + # Load examples #### + observeEvent(input$load_example, { + if(input$load_example == "Zug 2018") { + set_inputs(quorum_districts = 0.05, quorum_total = 0.03) + update_input_matrices(examples$zug_2018$votes, examples$zug_2018$seats) + } else if(input$load_example == "Uri 2020") { + set_inputs() + update_input_matrices(examples$uri_2020$votes, examples$uri_2020$seats) + } else if(input$load_example == "Wikipedia EN") { + set_inputs(use_list_votes = FALSE, set_seats_per_district = FALSE) + update_input_matrices(examples$wikipedia_en$votes, examples$wikipedia_en$seats) + } else if(input$load_example == "Wikipedia DE") { + set_inputs() + update_input_matrices(examples$wikipedia_de$votes, examples$wikipedia_de$seats) + } else { + return() + } + }) + + set_inputs = function(quorum_districts = 0, quorum_total = 0, use_list_votes = TRUE, set_seats_per_district = TRUE) { + shiny::updateCheckboxInput(session, "use_list_votes", value = use_list_votes) + shiny::updateNumericInput(session, "quorum_districts", value = quorum_districts) + shiny::updateNumericInput(session, "quorum_total", value = quorum_total) + shiny::updateCheckboxInput(session, "set_seats_per_district", value = set_seats_per_district) + } + } + + # Run the application #### + shiny::shinyApp(ui = ui, server = server) } # helper functions #### create_empty_votes_matrix = function(nrows, ncols) { - m = matrix(0, nrows, ncols) - colnames(m) <- paste("District ", 1:ncols) - rownames(m) <- paste("Party ", 1:nrows) - return(m) + m = matrix(0, nrows, ncols) + colnames(m) <- paste("District ", 1:ncols) + rownames(m) <- paste("Party ", 1:nrows) + return(m) } create_seats_matrix = function(votes_matrix, - district_seats = rep(0, ncol(votes_matrix))) { - if(length(district_seats) == 1) { - district_seats_matrix = matrix(district_seats, 1, 1, - dimnames = list("seats", "total")) - } else { - district_seats_matrix = matrix(district_seats, nrow = 1, - dimnames = list("seats", colnames(votes_matrix))) - } - return(district_seats_matrix) + district_seats = rep(0, ncol(votes_matrix))) { + if(length(district_seats) == 1) { + district_seats_matrix = matrix(district_seats, 1, 1, + dimnames = list("seats", "total")) + } else { + district_seats_matrix = matrix(district_seats, nrow = 1, + dimnames = list("seats", colnames(votes_matrix))) + } + return(district_seats_matrix) } get_quorum_function = function(q_districts, q_total, q_all) { diff --git a/man/apport_methods.Rd b/man/apport_methods.Rd index fc48795..5ac1558 100644 --- a/man/apport_methods.Rd +++ b/man/apport_methods.Rd @@ -11,7 +11,7 @@ An object of class \code{list} of length 21. apport_methods } \description{ -Names can be used in [proporz()] or [biproportional()], the list entries +Names can be used in \code{\link[=proporz]{proporz()}} or \code{\link[=biproportional]{biproportional()}}, the list entries denote the actual implementation. } \details{ diff --git a/man/divisor_ceiling.Rd b/man/divisor_ceiling.Rd index 193753f..bf29de1 100644 --- a/man/divisor_ceiling.Rd +++ b/man/divisor_ceiling.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/divisor.R \name{divisor_ceiling} \alias{divisor_ceiling} -\title{#' Divisor method rounding up} +\title{Divisor method rounding up} \usage{ divisor_ceiling(votes, n_seats, quorum = 0) } diff --git a/man/lower_apportionment.Rd b/man/lower_apportionment.Rd index 57974d9..dae7ff7 100644 --- a/man/lower_apportionment.Rd +++ b/man/lower_apportionment.Rd @@ -43,20 +43,20 @@ the party divisors. The following two correction steps are executed until this objective is satisfied: \itemize{ - \item modify the party divisors such that the apportionment within each - party is correct with the chosen rounding method, - \item modify the regional divisors such that the apportionment within the - region is correct with the chosen rounding method. +\item modify the party divisors such that the apportionment within each +party is correct with the chosen rounding method, +\item modify the regional divisors such that the apportionment within the +region is correct with the chosen rounding method. } } \note{ The iterative process in the lower apportionment is only guaranteed to - terminate with Sainte-Laguë/Webster method. +terminate with Sainte-Laguë/Webster method. } \references{ Oelbermann, K. F. (2016). Alternate scaling algorithm for biproportional divisor methods. Mathematical Social Sciences, 80, 25-32. } \seealso{ -\link{biproportional}, \link{upper_apportionment} +\code{\link{biproportional}}, \code{\link{lower_apportionment}} } diff --git a/man/pivot_to_matrix.Rd b/man/pivot_to_matrix.Rd index 9d52e56..b1d7beb 100644 --- a/man/pivot_to_matrix.Rd +++ b/man/pivot_to_matrix.Rd @@ -14,6 +14,6 @@ Pivot long data.frame to wide matrix } \note{ This function exists because I wanted to have no dependencies. - Wrangling with reshape isn't fun at all and it became glaringly - apparent why better tools like tidyr exist +Wrangling with reshape isn't fun at all and it became glaringly +apparent why better tools like tidyr exist } diff --git a/man/proporz.Rd b/man/proporz.Rd index cabb472..05cb9d4 100644 --- a/man/proporz.Rd +++ b/man/proporz.Rd @@ -23,17 +23,17 @@ Calculate seat apportionment for legislative bodies. \details{ The following methods are available: \itemize{ - \item{d'hondt, jefferson, hagenbach-bischoff, floor (use \code{\link{divisor_floor}})} - \item{sainte-lague, webster, round (use \code{\link{divisor_round}})} - \item{adams, ceiling (use \code{\link{divisor_ceiling}})} - \item{dean, harmonic (use \code{\link{divisor_harmonic}})} - \item{huntington-hill, hill-huntington, geometric (use \code{\link{divisor_geometric}})} - \item{hare-niemeyer, hamilton, vinton, quota_largest_remainder (use \code{\link{quota_largest_remainder}})} +\item{d'hondt, jefferson, hagenbach-bischoff, floor (use \code{\link{divisor_floor}})} +\item{sainte-lague, webster, round (use \code{\link{divisor_round}})} +\item{adams, ceiling (use \code{\link{divisor_ceiling}})} +\item{dean, harmonic (use \code{\link{divisor_harmonic}})} +\item{huntington-hill, hill-huntington, geometric (use \code{\link{divisor_geometric}})} +\item{hare-niemeyer, hamilton, vinton, quota_largest_remainder (use \code{\link{quota_largest_remainder}})} } } \note{ Seats can also be apportioned among regions instead of parties. The parameter - \code{votes} is then normally used with census data (e.g. population counts). +\code{votes} is then normally used with census data (e.g. population counts). } \examples{ votes = c("Party A" = 651, "Party B" = 349, "Party C" = 50) diff --git a/man/quorum_all.Rd b/man/quorum_all.Rd index 8c1bed4..1c11a69 100644 --- a/man/quorum_all.Rd +++ b/man/quorum_all.Rd @@ -81,5 +81,5 @@ biproporz(votes_matrix, seats, quorum = quorum_all(any_district = 0.1, total = 1 } \seealso{ -\code{\link[=quorum_any]{quorum_any()}} +\code{\link{quorum_any}} } diff --git a/man/quorum_any.Rd b/man/quorum_any.Rd index f23c66c..1d4b35a 100644 --- a/man/quorum_any.Rd +++ b/man/quorum_any.Rd @@ -20,12 +20,12 @@ Uses \code{\link[=reached_quorum_total]{reached_quorum_total()}}.} } \value{ a function(votes_matrix) which then returns a boolean vector with - length equal to the number of lists/parties (votes_matrix rows), - denoting whether a party has reached at least one quorum. +length equal to the number of lists/parties (votes_matrix rows), +denoting whether a party has reached at least one quorum. } \description{ -\code{quorum_any} and \code{quorum_all} are normally used in [biproportional()] -or [pukelsheim()]. Missing quorum parameters are ignored. +\code{quorum_any} and \code{quorum_all} are normally used in \code{\link[=biproportional]{biproportional()}} +or \code{\link[=pukelsheim]{pukelsheim()}}. Missing quorum parameters are ignored. } \note{ Currently only two quorums are implemented. @@ -80,5 +80,5 @@ biproporz(votes_matrix, seats, quorum = quorum_all(any_district = 0.1, total = 1 } \seealso{ -[quorum_all()] +\code{quorum_all} } diff --git a/man/reached_quorum_any_district.Rd b/man/reached_quorum_any_district.Rd index f672f48..006c691 100644 --- a/man/reached_quorum_any_district.Rd +++ b/man/reached_quorum_any_district.Rd @@ -16,7 +16,7 @@ Must be greater than 0.} } \value{ boolean vector with length equal to the number of lists/parties -(votes_matrix rows) whether they reached the quorum or not +(\code{votes_matrix} rows) whether they reached the quorum or not } \description{ Base implementation, used by \code{\link[=quorum_any]{quorum_any()}} and \code{\link[=quorum_all]{quorum_all()}}. diff --git a/man/reached_quorum_total.Rd b/man/reached_quorum_total.Rd index 6a45b56..331c311 100644 --- a/man/reached_quorum_total.Rd +++ b/man/reached_quorum_total.Rd @@ -15,7 +15,7 @@ as number of votes. Must be greater than 0.} } \value{ boolean vector with length equal to the number of lists/parties -(votes_matrix rows) whether they reached the quorum or not +(\code{votes_matrix} rows) whether they reached the quorum or not } \description{ Base implementation, used by \code{\link[=quorum_any]{quorum_any()}} and \code{\link[=quorum_all]{quorum_all()}}. diff --git a/man/reached_quorums.Rd b/man/reached_quorums.Rd index e215ee2..8bd99fa 100644 --- a/man/reached_quorums.Rd +++ b/man/reached_quorums.Rd @@ -10,21 +10,21 @@ reached_quorums(votes_matrix, quorum_funcs) \item{votes_matrix}{votes matrix} \item{quorum_funcs}{List of quorum functions. If list, the attribute "type" -must be set which indicates whether "ALL" or "ANY" +must be set which indicates whether \code{ALL} or \code{ANY} (i.e. at least one) quorum must be reached.} } \value{ boolean vector with length equal to the number of lists/parties -(votes_matrix rows) whether they reached the quorum or not +(\code{votes_matrix} rows) whether they reached the quorum or not } \description{ Apply a list of quorum functions to a votes matrix } \note{ This is a low-level implementation for quorum calculations and is - called within [biproportional()]. There's generally no need to call it - directly. +called within \code{\link[=biproportional]{biproportional()}}. There's generally no need to call it +directly. } \seealso{ -[quorum_all()], [quorum_any()] to create a list of quorum functions. +\code{\link[=quorum_all]{quorum_all()}}, \code{\link[=quorum_any]{quorum_any()}} to create a list of quorum functions. } diff --git a/man/upper_apportionment.Rd b/man/upper_apportionment.Rd index fa165d1..f8b9dd0 100644 --- a/man/upper_apportionment.Rd +++ b/man/upper_apportionment.Rd @@ -49,5 +49,5 @@ allocated. Thus, after the upper apportionment is done, the final strength of a party/region within the parliament is definite. } \seealso{ -\link{biproportional}, \link{lower_apportionment} +\code{\link{biproportional}}, \code{\link{lower_apportionment}} } diff --git a/tests/testthat/test-biproportional.R b/tests/testthat/test-biproportional.R index 2c100a7..bb29f33 100644 --- a/tests/testthat/test-biproportional.R +++ b/tests/testthat/test-biproportional.R @@ -247,8 +247,8 @@ test_that("use_list_votes=FALSE", { test_that("biproportional methods", { vm_19 = pivot_to_matrix(suomi19_votes) bip19 = biproportional(vm_19, suomi19_distr_seats, - use_list_votes = FALSE, - method = c("floor", "round")) + use_list_votes = FALSE, + method = c("floor", "round")) dhondt19 = proporz(rowSums(vm_19), 30, "d'hondt") expect_equal(rowSums(bip19), dhondt19) }) diff --git a/tests/testthat/test-divisor.R b/tests/testthat/test-divisor.R index 875dcdf..e2d24cd 100644 --- a/tests/testthat/test-divisor.R +++ b/tests/testthat/test-divisor.R @@ -5,9 +5,9 @@ test_that("divisor_floor", { v1 = c(4160, 3380, 2460) n1 = 10 e1 = c(4,4,2) - expect_equal(divisor_floor(v1, n1), e1) - expect_equal(proporz(v1, n1, "d'hondt"), e1) - expect_equal(proporz(v1, n1, "jefferson"), e1) + expect_equal(divisor_floor(v1, n1), e1) + expect_equal(proporz(v1, n1, "d'hondt"), e1) + expect_equal(proporz(v1, n1, "jefferson"), e1) }) # http://www.wahlrecht.de/verfahren/stlague12.html diff --git a/tests/testthat/test-proporz.R b/tests/testthat/test-proporz.R index 4b2afe6..e4746a6 100644 --- a/tests/testthat/test-proporz.R +++ b/tests/testthat/test-proporz.R @@ -1,66 +1,66 @@ context("proporz") test_that("generic proporz", { - expect_equal( - proporz(c(216, 310, 32), 20, "jefferson"), - proporz(c(216, 310, 32), 20, "D'hondt")) - expect_equal( - proporz(c(216, 310, 32), 20, "Hagenbach-Bischoff"), - proporz(c(216, 310, 32), 20, "hare-niemeyer")) - expect_error(proporz(1,1, "unkown method")) + expect_equal( + proporz(c(216, 310, 32), 20, "jefferson"), + proporz(c(216, 310, 32), 20, "D'hondt")) + expect_equal( + proporz(c(216, 310, 32), 20, "Hagenbach-Bischoff"), + proporz(c(216, 310, 32), 20, "hare-niemeyer")) + expect_error(proporz(1,1, "unkown method")) }) test_that("proporz parameter range", { - method_list = unique(unlist(apport_methods, use.names = F)) + method_list = unique(unlist(apport_methods, use.names = F)) - set.seed(0) - for(n_parties in 1:2) { - for(n_parties_zero in 0:2) { - for(n_seats in 0:6) { - for(method in method_list) { - votes = .sample_votes(n_parties, n_parties_zero) + set.seed(0) + for(n_parties in 1:2) { + for(n_parties_zero in 0:2) { + for(n_seats in 0:6) { + for(method in method_list) { + votes = .sample_votes(n_parties, n_parties_zero) - if(method == "harmonic" && n_seats < n_parties) { - expect_error(proporz(votes, n_seats, method), - "With harmonic rounding there must be at least as many seats as there are parties with non-zero votes") - } else { - seats = proporz(votes, n_seats, method) - expect_equal(length(seats), length(votes)) - expect_equal(sum(seats), n_seats) - } - } - } - } - } + if(method == "harmonic" && n_seats < n_parties) { + expect_error(proporz(votes, n_seats, method), + "With harmonic rounding there must be at least as many seats as there are parties with non-zero votes") + } else { + seats = proporz(votes, n_seats, method) + expect_equal(length(seats), length(votes)) + expect_equal(sum(seats), n_seats) + } + } + } + } + } - # unsupported values - for(method in method_list) { - for(n_seats in list(NA, NULL, -1, c(1, 1))) { - expect_error(proporz(c(100, 10, 5), n_seats, method), "n_seats must be one number >= 0") - } - } - for(method in method_list) { - for(votes in list(NA, NULL, -1)) { - expect_error(proporz(votes, 3, method), "votes must be numeric >= 0", fixed = TRUE) - } - } + # unsupported values + for(method in method_list) { + for(n_seats in list(NA, NULL, -1, c(1, 1))) { + expect_error(proporz(c(100, 10, 5), n_seats, method), "n_seats must be one number >= 0") + } + } + for(method in method_list) { + for(votes in list(NA, NULL, -1)) { + expect_error(proporz(votes, 3, method), "votes must be numeric >= 0", fixed = TRUE) + } + } }) test_that("all method names", { - for(m in names(apport_methods)) { - x = proporz(c(10, 20, 5), 3, m) - expect_length(x, 3) - } + for(m in names(apport_methods)) { + x = proporz(c(10, 20, 5), 3, m) + expect_length(x, 3) + } }) test_that("undefined result errors", { - expect_error(proporz(c(1, 10, 10), 1, "round"), - "Result is undefined, equal quotient for parties: 2 & 3", fixed = T) - expect_equal(proporz(c(1, 10, 10), 2, "round"), c(0,1,1)) + expect_error(proporz(c(1, 10, 10), 1, "round"), + "Result is undefined, equal quotient for parties: 2 & 3", fixed = T) + expect_equal(proporz(c(1, 10, 10), 2, "round"), c(0,1,1)) - expect_error(quota_largest_remainder(c(10, 10, 0), 1), - "Result is undefined: Equal remainder for two parties (position 1 & 2)", - fixed = TRUE) + expect_error(quota_largest_remainder(c(10, 10, 0), 1), + "Result is undefined: Equal remainder for two parties (position 1 & 2)", + fixed = TRUE) }) test_that("huntington-hill", { diff --git a/tests/testthat/test-quota.R b/tests/testthat/test-quota.R index dd9d027..e297b1d 100644 --- a/tests/testthat/test-quota.R +++ b/tests/testthat/test-quota.R @@ -2,25 +2,25 @@ context("quota") # https://de.wikipedia.org/wiki/Hare-Niemeyer-Verfahren test_that("quota_largest_remainder", { - v1 = c(216, 310, 22, 32) - n1 = 60 - e1 = c(23,32,2,3) - expect_equal(quota_largest_remainder(v1, n1), e1) - expect_equal(proporz(v1, n1, "hare-niemeyer"), e1) - expect_equal(proporz(v1, n1, "hamilton"), e1) - expect_equal(proporz(v1, n1, "vinton"), e1) + v1 = c(216, 310, 22, 32) + n1 = 60 + e1 = c(23,32,2,3) + expect_equal(quota_largest_remainder(v1, n1), e1) + expect_equal(proporz(v1, n1, "hare-niemeyer"), e1) + expect_equal(proporz(v1, n1, "hamilton"), e1) + expect_equal(proporz(v1, n1, "vinton"), e1) - # https://en.wikipedia.org/wiki/Largest_remainder_method - votes2 = c(47000, 16000, 15800, 12000, 6100, 3100) - seats_actual2 = quota_largest_remainder(votes2, 10) - seats_expected2 = c(5, 2, 1, 1, 1, 0) - expect_equal(seats_actual2, seats_expected2) + # https://en.wikipedia.org/wiki/Largest_remainder_method + votes2 = c(47000, 16000, 15800, 12000, 6100, 3100) + seats_actual2 = quota_largest_remainder(votes2, 10) + seats_expected2 = c(5, 2, 1, 1, 1, 0) + expect_equal(seats_actual2, seats_expected2) - # Wikipedia DE - votes3 = c(720257, 323524, 257466, 213138, 144392, 88315) - seats_expected3 = c(13, 6, 5, 4, 2, 1) - seats_actual3 = quota_largest_remainder(votes3, 31) - expect_equal(seats_actual3, seats_expected3) + # Wikipedia DE + votes3 = c(720257, 323524, 257466, 213138, 144392, 88315) + seats_expected3 = c(13, 6, 5, 4, 2, 1) + seats_actual3 = quota_largest_remainder(votes3, 31) + expect_equal(seats_actual3, seats_expected3) - expect_error(quota_largest_remainder(numeric(), numeric())) + expect_error(quota_largest_remainder(numeric(), numeric())) }) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index a8f5384..2c645f8 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -22,8 +22,8 @@ test_that("quorum", { test_that("stupid pivot functions", { df0 = data.frame(id = c("a", "a", "a", "a", "b", "b"), - key = c("w", "x", "y", "z", "x", "y"), - value = 1:6, stringsAsFactors = F) + key = c("w", "x", "y", "z", "x", "y"), + value = 1:6, stringsAsFactors = F) matrix1 = pivot_to_matrix(df0) expect_equal(pivot_to_matrix(df0[c(2,1,3)]), diff --git a/tests/testthat/test-zug2018.R b/tests/testthat/test-zug2018.R index 0ae078a..78cd20e 100644 --- a/tests/testthat/test-zug2018.R +++ b/tests/testthat/test-zug2018.R @@ -5,49 +5,49 @@ context("zug18") # https://www.zg.ch/behoerden/staatskanzlei/kanzlei/abstimmungen-und-wahlen/wahlen-kr/archiv-2018/downloads/2018-kr-dpt-gesamtergebnis-sitzverteilung.pdf seats_mtrx_exp = matrix( - c(0,3,3,5,2,3,3, - 0,0,2,1,0,0,1, - 0,1,1,1,0,1,2, - 0,0,1,1,0,0,1, - 0,2,3,2,1,3,4, - 0,1,3,2,1,1,2, - 0,1,2,1,0,1,1, - 0,2,2,1,0,0,1, - 0,1,2,2,0,0,2, - 0,0,1,1,0,0,0, - 0,0,1,0,0,0,1), - nrow = 7 + c(0,3,3,5,2,3,3, + 0,0,2,1,0,0,1, + 0,1,1,1,0,1,2, + 0,0,1,1,0,0,1, + 0,2,3,2,1,3,4, + 0,1,3,2,1,1,2, + 0,1,2,1,0,1,1, + 0,2,2,1,0,0,1, + 0,1,2,2,0,0,2, + 0,0,1,1,0,0,0, + 0,0,1,0,0,0,1), + nrow = 7 ) test_that("data input", { - expect_equal(sum(seats_mtrx_exp), 80) - expect_equal(unname(colSums(seats_mtrx_exp)), c(19,4,6,3,15,10,6,6,7,2,2)) - expect_equal(unname(rowSums(seats_mtrx_exp)), c(0,11,21,17,4,9,18)) + expect_equal(sum(seats_mtrx_exp), 80) + expect_equal(unname(colSums(seats_mtrx_exp)), c(19,4,6,3,15,10,6,6,7,2,2)) + expect_equal(unname(rowSums(seats_mtrx_exp)), c(0,11,21,17,4,9,18)) }) colnames(seats_mtrx_exp) <- c("Zug", "Oberägeri", "Unterägeri", "Menzingen", - "Baar", "Cham", "Hünenberg", "Steinhausen", - "Risch", "Walchwil", "Neuheim") + "Baar", "Cham", "Hünenberg", "Steinhausen", + "Risch", "Walchwil", "Neuheim") rownames(seats_mtrx_exp) <- 1:7 names(dimnames(seats_mtrx_exp)) <- c("list_id", "entity_name") test_that("pukelsheim with zug2018 is as expected", { - votes_df = unique(zug2018[c("list_id", "entity_name", "list_votes")]) - district_seats_df = unique(zug2018[c("entity_name", "election_mandates")]) + votes_df = unique(zug2018[c("list_id", "entity_name", "list_votes")]) + district_seats_df = unique(zug2018[c("entity_name", "election_mandates")]) - seats_df = pukelsheim(votes_df, - district_seats_df, - quorum = quorum_any(any_district = 0.05, - total = 0.03)) - seats_mtrx = pivot_to_matrix(seats_df[c(1,2,4)]) + seats_df = pukelsheim(votes_df, + district_seats_df, + quorum = quorum_any(any_district = 0.05, + total = 0.03)) + seats_mtrx = pivot_to_matrix(seats_df[c(1,2,4)]) - expect_equal(seats_mtrx[,colnames(seats_mtrx_exp)], seats_mtrx_exp) + expect_equal(seats_mtrx[,colnames(seats_mtrx_exp)], seats_mtrx_exp) }) test_that("upper apportionment for districts", { - votes_df = unique(zug2018[c("list_id", "entity_name", "list_votes")]) - votes_matrix = pivot_to_matrix(votes_df) + votes_df = unique(zug2018[c("list_id", "entity_name", "list_votes")]) + votes_matrix = pivot_to_matrix(votes_df) - x = biproporz(votes_matrix, 80) - expect_equal(sum(x), 80) + x = biproporz(votes_matrix, 80) + expect_equal(sum(x), 80) })