From fbcb459851d12f3485a5da5f71437353ccf343bc Mon Sep 17 00:00:00 2001
From: polettif Poletti F (2024).
proporz: Proportional Apportionment.
-R package version 1.4.0, https://github.com/polettif/proporz.
+R package version 1.4.0.9000, https://github.com/polettif/proporz.
Biproportional apportionment (Wikipedia) is a method to proportionally allocate seats among parties and districts. We can use the provided We can use the provided You can use You can use The package provides a basic Shiny app where you can calculate biproportional apportionment on an interactive dashboard. You need to have the packages You can use divisor methods directly: The largest remainder method is also accessible directly: Defines the method how seats in upper and lower apportionment are assigned.
-For a different method for upper and lower apportionment use a vector with two entries.
-The default "round" for the Sainte-Laguë/Webster method is the standard for
-biproportional apportionment and the only method guaranteed to terminate. See
- Defines which method is used to assign seats. The following methods are
+recommended: It is also possible to use any divisor method name listed in Round Round numeric vector >= 0 ( numeric vector or matrix >= 0 ( the rounded vector the rounded vector or matrix Apply row and column divisors to matrix to get non-rounded seat values. matrix divisors to apply to columns divisors to apply to rows matrix with the same dimension as Find a divisor between votes (matrix with only one column or vector, allows to use row/colnames
+within lower bound for divisor search range (is decreased if necessary) upper bound for divisor search range (is increased if necessary) number of seats to distribute (single number) rounding function divisor Find divisors for a matrix with alternate scaling votes_matrix target seats for each column target seats for each row rounding function. Called like
+ list of divisors (column and row) votes matrix matrix with votes by party in rows and votes by district in columns. Apportion method that defines how seats are assigned. The default "round"
-for the Sainte-Laguë/Webster method is the standard for biproportional apportionment
-and the only method guaranteed to terminate. See Apportion method that defines how seats are assigned. The
+following methods are supported: You can provide a custom function that rounds a matrix (i.e. the
+the votes_matrix divided by party and list divisors). It is possible to use any divisor method name listed in 3.3) Alternative methods)
# sort table by D'Hondt seats
-df_bydistrict <- df_bydistrict[order(df_bydistrict[[1]], decreasing = T),]
+df_bydistrict <- df_bydistrict[order(df_bydistrict[[1]], decreasing = TRUE),]
# print parties with at least one seat
knitr::kable(df_bydistrict[rowSums(df_bydistrict) > 0,])
@@ -1087,7 +1087,7 @@
4.2) Distribute seats among di
full_biproportional = biproporz(votes_matrix,
district_seats = sum(district_seats),
- use_list_votes = F)
+ use_list_votes = FALSE)
# party seat distribution has not changed
rowSums(full_biproportional) - rowSums(seats_biproportional)
diff --git a/articles/index.html b/articles/index.html
index 45b6ae5..a66c11f 100644
--- a/articles/index.html
+++ b/articles/index.html
@@ -17,7 +17,7 @@
Citation
@Manual{,
title = {proporz: Proportional Apportionment},
author = {Flavio Poletti},
year = {2024},
- note = {R package version 1.4.0},
+ note = {R package version 1.4.0.9000},
url = {https://github.com/polettif/proporz},
}
diff --git a/favicon-16x16.png b/favicon-16x16.png
index 7684d562aebe2f6b9b3d7cdc22f5c4be52aca2b8..b89df858f25929187c1c7e870e82cc244b41af3f 100644
GIT binary patch
delta 99
zcmdnSwT)|o6Ei0Z3zs?XdWPus&ECu=j2Proportional ApportionmentBiproportional Apportionment
zug2018
data set to illustrate biproportional apportionment with biproporz()
. You need a ‘votes matrix’ as input which shows the number of votes for each party (rows) and district (columns). In this data set, parties are called ‘lists’ and districts ‘entities’.uri2020
data set to illustrate biproportional apportionment with biproporz()
. You need a ‘votes matrix’ as input which shows the number of votes for each party (rows) and district (columns). You also need to define the number of seats per district.
-
votes_df = unique(zug2018[c("list_id", "entity_id", "list_votes")])
-votes_matrix = pivot_to_matrix(votes_df)
-votes_matrix
-#> entity_id
-#> list_id 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711
-#> 1 2993 0 0 0 0 0 0 0 0 0 0
-#> 2 8108 4687 1584 531 279 477 2363 3860 1481 91 22023
-#> 3 19389 9334 4807 1946 396 2844 3523 4702 3310 812 21343
-#> 4 14814 6691 4005 826 379 1654 2842 2624 2713 461 33789
-#> 5 4486 2270 621 198 0 361 728 465 925 0 10131
-#> 6 15695 4705 1750 84 0 51 627 1106 1563 302 21794
-#> 7 21298 8178 2875 1336 399 1450 3715 2610 4063 344 26798
+
(votes_matrix <- uri2020$votes_matrix)
+#> Altdorf Bürglen Erstfeld Schattdorf
+#> CVP 11471 2822 2309 4794
+#> SPGB 11908 1606 1705 2600
+#> FDP 9213 1567 946 2961
+#> SVP 7756 2945 1573 3498
-distr_df = unique(zug2018[c("entity_id", "election_mandates")])
-district_seats = setNames(distr_df$election_mandates, distr_df$entity_id)
-district_seats
-#> 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711
-#> 15 10 6 3 2 4 7 6 6 2 19
pukelsheim()
for dataframes in long format as input data. It is a wrapper for biproporz()
. zug2018
shows an actual election result for the Canton of Zug in a dataframe. We use this data set to create input data for pukelsheim()
. The other parameters are set to reflect the actual election system.
-
biproporz(votes_matrix, district_seats, quorum_any(any_district = 0.05, total = 0.03))
-#> entity_id
-#> list_id 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711
-#> 1 0 0 0 0 0 0 0 0 0 0 0
-#> 2 2 1 1 0 0 0 1 2 1 0 3
-#> 3 3 3 2 1 1 2 2 2 1 1 3
-#> 4 2 2 1 1 0 1 2 1 1 1 5
-#> 5 1 1 0 0 0 0 0 0 0 0 2
-#> 6 3 1 1 0 0 0 0 0 1 0 3
-#> 7 4 2 1 1 1 1 2 1 2 0 3
pukelsheim()
for data.frames in long format as input data. It is a wrapper for biproporz()
.
-
votes_df = unique(zug2018[c("list_id", "entity_id", "list_votes")])
+
# In this data set, parties are called 'lists' and districts 'entities'.
+votes_df = unique(zug2018[c("list_id", "entity_id", "list_votes")])
district_seats_df = unique(zug2018[c("entity_id", "election_mandates")])
seats_df = pukelsheim(votes_df,
district_seats_df,
- quorum = quorum_any(any_district = 0.05, total = 0.03))
+ quorum = quorum_any(any_district = 0.05, total = 0.03),
+ winner_take_one = TRUE)
head(seats_df)
#> list_id entity_id list_votes seats
@@ -201,11 +191,10 @@
Biproportional ApportionmentShiny app
shiny
and shinyMatrix
installed.Function detailsDivisor methods
+
votes = c("Party A" = 690, "Party B" = 370, "Party C" = 210, "Party D" = 10)
# D'Hondt, Jefferson or Hagenbach-Bischoff method
@@ -248,7 +237,7 @@
Divisor methodsLargest remainder method
+
votes = c("I" = 16200, "II" = 47000, "III" = 12700)
# Hamilton, Hare-Niemeyer or Vinton method
diff --git a/news/index.html b/news/index.html
index 1130ba5..58757aa 100644
--- a/news/index.html
+++ b/news/index.html
@@ -17,7 +17,7 @@
Changelog
Source: NEWS.md
proporz (development version)
+biproporz()
and pukelsheim
(#10)proporz 1.4.02024-03-04
Arguments
proporz()
for other methods.round
: Uses the Sainte-Laguë/Webster method (rounding half up) for the upper
+and lower apportionment which is the standard for biproportional apportionment and
+the only method guaranteed to terminate.wto
: "winner take one" works like "round" with a condition that the party that
+got the most votes in a district must get at least one seat ('Majorzbedingung')
+in said district. Seats in the upper apportionment are assigned with
+Sainte-Laguë/Webster. votes_matrix
must have row and column names to use this
+method. See lower_apportionment()
for more details.proporz()
. If you want to
+use a different method for the upper and lower apportionment, provide a list with two
+entries.Rounding with predefined thresholds
x
up if x-floor(x) >= threshold
, otherwise round down.x
up to ceiling(x)
if x-floor(x) >= threshold
,
+otherwise round down to floor(x)
.Rounding with predefined thresholds
Arguments
NaN
is not supported)NaN
is not supported)Arguments
Value
-divide_votes_matrix(M, col_divisors, row_divisors)
Arguments
+ Value
+
+
+M
containing non-rounded seat valuesdivisor_from
and divisor_to
such as
+sum(round_func(votes/divisor))
equals target_seats
find_divisor(votes, divisor_from, divisor_to, target_seats, round_func)
Arguments
+ round_func
)Value
+
+
+Find divisors for a matrix with alternate scaling
+ Source: R/biproportional.R
+ find_matrix_divisors.Rd
find_matrix_divisors(M, seats_cols, seats_rows, round_func)
Arguments
+ round_func(M/row_divisors/col_divisors)
, divisors are applied row/col-wise with
+divide_votes_matrix()
.Value
+
+
+Get district and party divisors from biproporz result
- Source: R/S3.R
+ Source: R/biproportional-divisors.R
get_divisors.Rd
Examples
get_divisors(seats_matrix)
#> $districts
#> Altdorf Bürglen Erstfeld Schattdorf
-#> 2690.000 1193.875 1089.000 1539.000
+#> 2690 1194 1089 1539
#>
#> $parties
#> CVP SPGB FDP SVP
-#> 0.946 1.000 1.000 0.969
+#> 0.946 1.000 1.000 0.970
#>
Calculate lower apportionment
Arguments
Arguments
proporz()
for other methods. It is
-also possible to provide a function that rounds a vector or matrix.round
: The default Sainte-Laguë/Webster method is the standard
+for biproportional apportionment and the only method guaranteed to terminate.wto
: "winner take one" works like "round" with a condition that the party that
+got the most votes in a district must get at least one seat ('Majorzbedingung').
+The condition does not apply in a district if two or more parties have the same
+number of votes and there are not enough seats for these parties. A warning is
+issued in this case. Modify the votes matrix to explicitly break ties.proporz()
.Examples
#> [3,] 2 1 1
#> attr(,"divisors")
#> attr(,"divisors")$districts
-#> [1] 207.81 180.25 161.50
+#> [1] 207 180 161
#>
#> attr(,"divisors")$parties
-#> [1] 1.16 1.01 1.00
+#> [1] 1.15 1.00 1.00
+#>
+
+
+# using "winner take one"
+vm = matrix(c(200,100,10,11), 2,
+ dimnames = list(c("Party A", "Party B"), c("I", "II")))
+district_seats = setNames(c(2,1), colnames(vm))
+ua = upper_apportionment(vm, district_seats)
+
+lower_apportionment(vm, ua$district, ua$party, method = "wto")
+#> I II
+#> Party A 2 0
+#> Party B 0 1
+#> attr(,"divisors")
+#> attr(,"divisors")$districts
+#> I II
+#> 139 22
+#>
+#> attr(,"divisors")$parties
+#> Party A Party B
+#> 0.92 2.00
+#>
+
+# compare to standard method
+lower_apportionment(vm, ua$district, ua$party, method = "round")
+#> I II
+#> Party A 1 1
+#> Party B 1 0
+#> attr(,"divisors")
+#> attr(,"divisors")$districts
+#> I II
+#> 150 21
+#>
+#> attr(,"divisors")$parties
+#> Party A Party B
+#> 0.9 1.2
#>
Biproportional apportionment with data frames
district_seats_df,
quorum,
new_seats_col = "seats",
- use_list_votes = TRUE
+ use_list_votes = TRUE,
+ winner_take_one = FALSE
)
@@ -106,6 +107,11 @@ Arguments
as many votes as there are seats in a district. Set to FALSE
if votes_df
shows the
number of voters (e.g. they can only vote for one party).
Set to TRUE
if the party that got the most votes in a district
+must get at least one seat ('Majorzbedingung') in this district. Default is FALSE
.
Apportion method that defines how seats are assigned, see proporz()
.
Apportion method that defines how seats are assigned, see proporz()
. Default
+is the Saintë-Lague/Webster method.
Weigh list votes by dividing the votes matrix entries by the number
of seats per district. This method is used in upper_apportionment()
if
-use_list_votes
is TRUE
(default).
use_list_votes
is TRUE
(default). The weighted votes are not rounded.