Tutorial 9: Subtractive compensatory system

In this tutorial we will learn how to compute and re-apportion negative adjustment seats to compensate overhang seats.

Germany 2025 - Bundestag

Mmmhh, err… yeah… Germany… this is gonna be hard…

Each Land is divided into one-seats constituencies where a candidate is elected using the first vote. A candidate can be allied to a party or can be independent.

 1- name: Länder
 2  type: group
 3  method: first_past_the_post
 4  meta:
 5    notes: |
 6        Wahlkreise.
 7        First vote: mandates for state districts.
 8  divisions:
 9
10  - name: Schleswig-Holstein
11    type: group
12    # we will need this later
13    aggregate: yes
14    groupby: id
15    divisions:
16    - name: Flensburg – Schleswig
17      # constituencies are tagged for future references
18      groups: [mandates]
19    ...
20  ...

In a similar way to Scotland - Scottish Parliament (Highlands and Islands), each Land has a second vote where only parties can be chosen. But instead of adjusting seats at the Land level, seats will be compensated once for all Germany (known as Oberverteilung, upper distribution).

 1- name: Zweistimme
 2  type: group
 3  method: noop
 4  meta:
 5    notes: |
 6        second votes: party lists for each state
 7  divisions:
 8  - name: Landesliste - Schleswig-Holstein
 9  - name: Landesliste - Hamburg
10  - name: Landesliste - Niedersachsen
11  - name: Landesliste - Bremen
12  - name: Landesliste - Nordrhein-Westfalen
13  - name: Landesliste - Hessen
14  - name: Landesliste - Rheinland-Pfalz
15  - name: Landesliste - Baden-Württemberg
16  - name: Landesliste - Bayern
17  - name: Landesliste - Saarland
18  - name: Landesliste - Berlin
19  - name: Landesliste - Brandenburg
20  - name: Landesliste - Mecklenburg-Vorpommern
21  - name: Landesliste - Sachsen
22  - name: Landesliste - Sachsen-Anhalt
23  - name: Landesliste - Thüringen

630 seats will be allocated in the upper distribution among the parties with at least 5% of votes, or parties with at least 3 constituencies or parties from minority groups.

Seats won by first vote by parties or independent candidates that can’t participate in the upper distribution will be subtracted from the initial 630 seats.

This is the definitive allocation, so if a party won more seats than the upper distribution says it should have, the seats surplus will be subtracted later.

 1- name: Oberverteilung
 2  type: compensatory
 3  mode: mixed_member
 4  seats: 630
 5  exclude: votes < 5%
 6  # 3 constituencies or minority groups are allowed
 7  include: "any{ #mandates.alliance_quorum >= 3 => alliance | #mandates.alliance_group = minority => alliance }"
 8  # mandates from first vote will be compensated
 9  first_vote: "alliance[#mandates]"
10  # the adjustment process will use the aggregated second vote
11  candidates: alliance[Zweistimme]
12  # seats from non-participant candidates will be subtracted
13  subtract_excluded_candidates: yes
14  # return both the levelling seats and the mandates
15  skip_initial_seats: no
16  meta:
17    notes: |
18        Federal party seats allocation using second vote.

Once the definitive seats have been allocated to parties, it’s time to give it back to Länder and constituencies. This process is known as Unterverteilung (sub-distribution).

 1- name: Unterverteilung
 2  type: reapportionment
 3  strategy: candidate
 4  adjustment: Oberverteilung
 5  # the score comes from unaggregated second vote
 6  candidates: Zweistimme:-1
 7  # the party vote will be injected to Länder
 8  first_vote: Zweistimme:-1
 9  # change the names from second vote districts to first vote districts
10  map_districts:
11    Landesliste - Schleswig-Holstein: Schleswig-Holstein
12    Landesliste - Hamburg: Hamburg
13    Landesliste - Niedersachsen: Niedersachsen
14    Landesliste - Bremen: Bremen
15    Landesliste - Nordrhein-Westfalen: Nordrhein-Westfalen
16    Landesliste - Hessen: Hessen
17    Landesliste - Rheinland-Pfalz: Rheinland-Pfalz
18    Landesliste - Baden-Württemberg: Baden-Württemberg
19    Landesliste - Bayern: Bayern
20    Landesliste - Saarland: Saarland
21    Landesliste - Berlin: Berlin
22    Landesliste - Brandenburg: Brandenburg
23    Landesliste - Mecklenburg-Vorpommern: Mecklenburg-Vorpommern
24    Landesliste - Sachsen: Sachsen
25    Landesliste - Sachsen-Anhalt: Sachsen-Anhalt
26    Landesliste - Thüringen: Thüringen
27  meta:
28    notes: |
29        State seats allocation for each party.

The attribute map_districts allows to rename districts in the result.

Once the seats have been distributed to each Land, the levelling seats are subtracted from mandates.

 1- name: levelling seats
 2  type: compensatory
 3  # produce negative seats for overhang seats
 4  mode: subtract_overhang_seats
 5  # reuse what we have
 6  method: noop
 7  # noop needs initial seats from first vote
 8  propagate_initial_seats: yes
 9  # mandates
10  first_vote: Länder:1
11  # sub-distribution
12  candidates: Unterverteilung
13  # we take seats from the Unterverteilung result
14  initial_seats: from_results
15  # return new seats only
16  skip_initial_seats: yes
17  meta:
18    notes: |
19        Allocate levelling seats (subtract mandates from Unterverteilung)

Overhang seats will be subtracted from the mandates.

 1- name: negative levelling seats
 2  # we take parties with negative seats (overhang seats)
 3  exclude: levelling seats.seats >= 0
 4  include: levelling seats.seats < 0
 5  # we are just filtering out
 6  method: noop
 7  # we use the levelling seats
 8  candidates: levelling seats
 9  initial_seats: from_results
10
11- name: mandates surplus
12  type: reapportionment
13  method: limited_voting
14  strategy: candidate
15  relative: votes
16  # only parties that are losing mandates
17  exclude: "#mandates.seats < 1"
18  adjustment: negative levelling seats
19  # the score
20  candidates: "#mandates"
21  # the destination
22  first_vote: "#mandates"

Now, the final schema:

  1name: Bundestag 2025
  2type: group
  3method: iterative_divisor
  4method_params:
  5    signpost_f: sainte_lague
  6divisions:
  7
  8- name: Länder
  9  type: group
 10  method: first_past_the_post
 11  meta:
 12    notes: |
 13        Wahlkreise.
 14        First vote: mandates for state districts.
 15  divisions:
 16  - name: Schleswig-Holstein
 17    type: group
 18    # we will need this later
 19    aggregate: yes
 20    groupby: id
 21    divisions:
 22    - name: Flensburg – Schleswig
 23      # constituencies are tagged for future references
 24      groups: [mandates]
 25    ...
 26  ...
 27
 28- name: Zweistimme
 29  type: group
 30  method: noop
 31  meta:
 32    notes: |
 33        second votes: party lists for each state
 34  divisions:
 35  - name: Landesliste - Schleswig-Holstein
 36  - name: Landesliste - Hamburg
 37  - name: Landesliste - Niedersachsen
 38  - name: Landesliste - Bremen
 39  - name: Landesliste - Nordrhein-Westfalen
 40  - name: Landesliste - Hessen
 41  - name: Landesliste - Rheinland-Pfalz
 42  - name: Landesliste - Baden-Württemberg
 43  - name: Landesliste - Bayern
 44  - name: Landesliste - Saarland
 45  - name: Landesliste - Berlin
 46  - name: Landesliste - Brandenburg
 47  - name: Landesliste - Mecklenburg-Vorpommern
 48  - name: Landesliste - Sachsen
 49  - name: Landesliste - Sachsen-Anhalt
 50  - name: Landesliste - Thüringen
 51
 52- name: Oberverteilung
 53  type: compensatory
 54  mode: mixed_member
 55  seats: 630
 56  exclude: votes < 5%
 57  # 3 constituencies or minority groups are allowed
 58  include: "any{ #mandates.alliance_quorum >= 3 => alliance | #mandates.alliance_group = minority => alliance }"
 59  # mandates from first vote will be compensated
 60  first_vote: "alliance[#mandates]"
 61  # the adjustment process will use the aggregated second vote
 62  candidates: alliance[Zweistimme]
 63  # seats from non-participant candidates will be subtracted
 64  subtract_excluded_candidates: yes
 65  # return both the levelling seats and the mandates
 66  skip_initial_seats: no
 67  meta:
 68    notes: |
 69        Federal party seats allocation using second vote.
 70
 71- name: Unterverteilung
 72  type: reapportionment
 73  strategy: candidate
 74  adjustment: Oberverteilung
 75  # the score comes from unaggregated second vote
 76  candidates: Zweistimme:-1
 77  # the party vote will be injected to Länder
 78  first_vote: Zweistimme:-1
 79  # change the names from second vote districts to first vote districts
 80  map_districts:
 81    Landesliste - Schleswig-Holstein: Schleswig-Holstein
 82    Landesliste - Hamburg: Hamburg
 83    Landesliste - Niedersachsen: Niedersachsen
 84    Landesliste - Bremen: Bremen
 85    Landesliste - Nordrhein-Westfalen: Nordrhein-Westfalen
 86    Landesliste - Hessen: Hessen
 87    Landesliste - Rheinland-Pfalz: Rheinland-Pfalz
 88    Landesliste - Baden-Württemberg: Baden-Württemberg
 89    Landesliste - Bayern: Bayern
 90    Landesliste - Saarland: Saarland
 91    Landesliste - Berlin: Berlin
 92    Landesliste - Brandenburg: Brandenburg
 93    Landesliste - Mecklenburg-Vorpommern: Mecklenburg-Vorpommern
 94    Landesliste - Sachsen: Sachsen
 95    Landesliste - Sachsen-Anhalt: Sachsen-Anhalt
 96    Landesliste - Thüringen: Thüringen
 97  meta:
 98    notes: |
 99        State seats allocation for each party.
100
101- name: levelling seats
102  type: compensatory
103  # produce negative seats for overhang seats
104  mode: subtract_overhang_seats
105  # reuse what we have
106  method: noop
107  # noop needs initial seats from first vote
108  propagate_initial_seats: yes
109  # mandates
110  first_vote: Länder:1
111  # sub-distribution
112  candidates: Unterverteilung
113  # we take seats from the Unterverteilung result
114  initial_seats: from_results
115  # return new seats only
116  skip_initial_seats: yes
117  meta:
118    notes: |
119        Allocate levelling seats (subtract mandates from Unterverteilung)
120
121- name: negative levelling seats
122  # we take parties with negative seats (overhang seats)
123  exclude: levelling seats.seats >= 0
124  include: levelling seats.seats < 0
125  # we are just filtering out
126  method: noop
127  # we use the levelling seats
128  candidates: levelling seats
129  initial_seats: from_results
130
131- name: mandates surplus
132  type: reapportionment
133  method: limited_voting
134  strategy: candidate
135  relative: votes
136  # only parties that are losing mandates
137  exclude: "#mandates.seats < 1"
138  adjustment: negative levelling seats
139  # the score
140  candidates: "#mandates"
141  # the destination
142  first_vote: "#mandates"