Learn R Programming

proporz

Calculate seat apportionment for legislative bodies with various methods. These methods include divisor methods (e.g. D'Hondt, Webster or Adams), largest remainder methods and biproportional apportionment.

Mit diesem R-Package können mittels verschiedener Sitzzuteilungsverfahren Wählerstimmen in Abgeordnetensitze umgerechnet werden. Das Package beinhaltet Quoten-, Divisor- und biproportionale Verfahren (Doppelproporz oder "Doppelter Pukelsheim").

Installation

Install the package from CRAN:

install.packages("proporz")

Alternatively, install the development version from Github:

# install.packages("remotes")
remotes::install_github("polettif/proporz")

Apportionment methods overview

Proportional Apportionment

proporz() distributes seats proportionally for a vector of votes according to the following methods:

  • Divisor methods (Wikipedia)
    • D'Hondt, Jefferson, Hagenbach-Bischoff
    • Sainte-Laguë, Webster
    • Adams
    • Dean
    • Huntington-Hill
  • Largest remainder method (Wikipedia)
    • Hare-Niemeyer, Hamilton, Vinton
library(proporz)
votes = c("Party A" = 651, "Party B" = 349, "Party C" = 50)

proporz(votes, n_seats = 10, method = "sainte-lague")
#> Party A Party B Party C
#>       7       3       0

proporz(votes, 10, "huntington-hill", quorum = 0.05)
#> Party A Party B Party C
#>       6       4       0

Biproportional Apportionment

Biproportional apportionment (Wikipedia) is a method to proportionally allocate seats among parties and districts. The method is implemented in biproporz().

We can use the provided 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_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

(district_seats <- uri2020$seats_vector)
#>    Altdorf    Bürglen   Erstfeld Schattdorf 
#>         15          7          6          9

biproporz(votes_matrix, district_seats)
#>      Altdorf Bürglen Erstfeld Schattdorf
#> CVP        5       2        2          3
#> SPGB       4       1        2          2
#> FDP        3       1        1          2
#> SVP        3       3        1          2

You can use pukelsheim() for dataframes in long format as input data. It is a wrapper for biproporz(). zug2018 shows an 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.

# 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),
                      winner_take_one = TRUE)

head(seats_df)
#>   list_id entity_id list_votes seats
#> 1       2      1701       8108     2
#> 2       1      1701       2993     0
#> 3       3      1701      19389     3
#> 4       4      1701      14814     2
#> 5       5      1701       4486     1
#> 6       6      1701      15695     3

The Apportionment scenarios vignette contains more examples. How to adapt biproporz for special use cases is demonstrated in the Modifying biproporz() vignette.

Shiny app

The package provides a basic Shiny app where you can calculate biproportional apportionment on an interactive dashboard. You need to have the packages shiny and shinyMatrix installed.

Copy Link

Version

Install

install.packages('proporz')

Monthly Downloads

198

Version

1.5.2

License

GPL (>= 3)

Issues

Pull Requests

Stars

Forks

Maintainer

Flavio Poletti

Last Published

October 10th, 2025

Functions in proporz (1.5.2)

pivot_to_matrix

Pivot long data.frame to wide matrix and vice versa
lower_apportionment

Lower apportionment
run_app

Use biproportional apportionment interactively in a shiny app
upper_apportionment

Upper apportionment
reached_quorums

Apply a list of quorum functions to a votes matrix
weight_votes_matrix

Create weighted votes matrix
uri2020

Election Data for the Cantonal Council of Uri (2020)
zug2018

Election Data for the Cantonal Council of Zug (2018)
divisor_methods

Divisor methods
apply_quorum

Apply quorum to votes vector or matrix
finland2019

Finnish Parliamentary Elections Data (2019)
biproporz

Biproportional apportionment
district_winner_matrix

Find which party has the most votes in a district
get_divisors

Get district and party divisors from biproporz result
ceil_at

Rounding with predefined thresholds
proporz

Proportional apportionment
largest_remainder_method

Largest remainder method
highest_averages_method

Highest averages method
reached_quorum_total

Check if parties reached the quorum for all votes
reached_quorum_any_district

Check if parties reached a quorum in at least one district
quorum_functions

Create quorum functions for biproportional apportionment
pukelsheim

Biproportional apportionment with data frames
divide_votes_matrix

Calculate raw seat matrix
proporz_methods

List of method names and their implementation
find_matrix_divisors

Find divisors for a matrix with alternate scaling
find_divisor

Find divisor to assign seats