Przygotuj dane 2015¶
Prezentacja faktycznego i hipotetycznego podziału mandatów w wyborach do Sejmu 2015.
© Patryk Czarnik
Operacje wstępne¶
Skrypt korzysta z biblioteki Pandas.
import pandas as pd
import numpy as np
#pd.set_option('display.float_format', '{:.2f}'.format)
path = 'dane/2015/robocze/'
MANDATY_SEJM = 460
Wczytanie danych¶
Dane pochodzą z serwisu wybory.gov.pl
i odpowiadają oficjalnym wynikom wyborów.
Korzystam z plików wstępnie przygotowanych przez skrypt przygotuj_dane_2015
.
def_kom = pd.read_csv(path + 'def_kom_2015.csv', sep=';', index_col=0)
def_okr = pd.read_csv(path + 'def_okr_2015.csv', sep=';', index_col=0)
wyn_okr = pd.read_csv(path + 'kom_okr_2015.csv', sep=';', index_col=0)
wyn_okr.columns = wyn_okr.columns.astype('int')
wyn_okr.columns.name = 'Nr listy'
stat_okr = pd.read_csv(path + 'stat_okr_2015.csv', sep=';', index_col=0)
def_kom
Nazwa pełna | Nazwa | Skrót | Próg | |
---|---|---|---|---|
Nr listy | ||||
1 | Komitet Wyborczy Prawo i Sprawiedliwość | Prawo i Sprawiedliwość | PiS | 5.0 |
2 | Komitet Wyborczy Platforma Obywatelska RP | Platforma Obywatelska RP | PO | 5.0 |
3 | Komitet Wyborczy Partia Razem | Partia Razem | Razem | 5.0 |
4 | Komitet Wyborczy KORWiN | KORWiN | KORWiN | 5.0 |
5 | Komitet Wyborczy Polskie Stronnictwo Ludowe | Polskie Stronnictwo Ludowe | PSL | 5.0 |
6 | Koalicyjny Komitet Wyborczy Zjednoczona Lewica... | Zjednoczona Lewica SLD+TR+PPS+UP+Zieloni | Lewica | 8.0 |
7 | Komitet Wyborczy Wyborców „Kukiz'15” | „Kukiz'15” | Kukiz | 5.0 |
8 | Komitet Wyborczy Nowoczesna Ryszarda Petru | Nowoczesna Ryszarda Petru | Nowoczesna | 5.0 |
9 | Komitet Wyborczy Wyborców JOW Bezpartyjni | JOW Bezpartyjni | Bezpartyjni | 5.0 |
10 | Komitet Wyborczy Wyborców Zbigniewa Stonogi | Zbigniewa Stonogi | Stonoga | 5.0 |
11 | Komitet Wyborczy Wyborców Ruch Społeczny Rzec... | Ruch Społeczny Rzeczypospolitej Polskiej | RSRP | 5.0 |
12 | Komitet Wyborczy Wyborców Zjednoczeni dla Śląska | Zjednoczeni dla Śląska | ZŚ | 5.0 |
13 | Komitet Wyborczy Samoobrona | Samoobrona | Samoobrona | 5.0 |
14 | Komitet Wyborczy Wyborców Grzegorza Brauna „Sz... | Grzegorza Brauna „Szczęść Boże!” | Braun | 5.0 |
15 | Komitet Wyborczy Kongres Nowej Prawicy | Kongres Nowej Prawicy | KNP | 5.0 |
16 | Komitet Wyborczy Wyborców Mniejszość Niemiecka | Mniejszość Niemiecka | MN | 0.0 |
17 | Komitet Wyborczy Wyborców Obywatele do Parlamentu | Obywatele do Parlamentu | OP | 5.0 |
map_kom_skrot = dict(def_kom["Skrót"])
def_okr
Okręg | Liczba mandatów | |
---|---|---|
Nr okręgu | ||
1 | Legnica | 12 |
2 | Wałbrzych | 8 |
3 | Wrocław | 14 |
4 | Bydgoszcz | 12 |
5 | Toruń | 13 |
6 | Lublin | 15 |
7 | Chełm | 12 |
8 | Zielona Góra | 12 |
9 | Łódź | 10 |
10 | Piotrków Trybunalski | 9 |
11 | Sieradz | 12 |
12 | Kraków I (południe) | 8 |
13 | Kraków II (północ) | 14 |
14 | Nowy Sącz | 10 |
15 | Tarnów | 9 |
16 | Płock | 10 |
17 | Radom | 9 |
18 | Siedlce | 12 |
19 | Warszawa I (miasto) | 20 |
20 | Warszawa II (okręg) | 12 |
21 | Opole | 12 |
22 | Krosno | 11 |
23 | Rzeszów | 15 |
24 | Białystok | 14 |
25 | Gdańsk | 12 |
26 | Gdynia | 14 |
27 | Bielsko-Biała | 9 |
28 | Częstochowa | 7 |
29 | Gliwice | 9 |
30 | Rybnik | 9 |
31 | Katowice | 12 |
32 | Sosnowiec | 9 |
33 | Kielce | 16 |
34 | Elbląg | 8 |
35 | Olsztyn | 10 |
36 | Kalisz | 12 |
37 | Konin | 9 |
38 | Piła | 9 |
39 | Poznań | 10 |
40 | Koszalin | 8 |
41 | Szczecin | 12 |
map_okr = dict(def_okr["Okręg"])
assert def_okr["Liczba mandatów"].sum() == MANDATY_SEJM
wyn_okr
Nr listy | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Nr okręgu | |||||||||||||||||
1 | 127370 | 90060 | 13003 | 15427 | 13886 | 37298 | 34229 | 25506 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2 | 72929 | 76424 | 7984 | 10075 | 7448 | 20517 | 20634 | 15544 | 2540.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3 | 163323 | 159582 | 22059 | 27341 | 13604 | 31932 | 45726 | 55756 | NaN | 2779.0 | NaN | NaN | NaN | 1269.0 | NaN | NaN | NaN |
4 | 113024 | 110948 | 14062 | 17002 | 22701 | 37583 | 29080 | 27334 | NaN | 2543.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
5 | 121703 | 93432 | 13227 | 14134 | 24476 | 39003 | 30159 | 23563 | NaN | 2041.0 | NaN | NaN | 772.0 | NaN | NaN | NaN | NaN |
6 | 232014 | 80892 | 13395 | 24376 | 37733 | 29172 | 45448 | 22158 | NaN | NaN | 599.0 | NaN | 580.0 | 1353.0 | NaN | NaN | NaN |
7 | 163122 | 41823 | 8116 | 14835 | 38689 | 24161 | 35567 | 12745 | NaN | NaN | NaN | NaN | 628.0 | NaN | NaN | NaN | NaN |
8 | 97877 | 97676 | 13825 | 17274 | 17743 | 34695 | 30284 | 34586 | NaN | 2258.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
9 | 107350 | 112333 | 16627 | 17163 | 9710 | 37615 | 25992 | 32274 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
10 | 134134 | 44173 | 8747 | 11160 | 21364 | 21352 | 28808 | 15983 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
11 | 147623 | 78314 | 13077 | 15171 | 29044 | 30256 | 32973 | 19681 | NaN | 1905.0 | NaN | NaN | 712.0 | 989.0 | NaN | NaN | NaN |
12 | 133213 | 55454 | 8840 | 12089 | 8226 | 14473 | 23551 | 15731 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
13 | 209607 | 133558 | 21053 | 34446 | 14644 | 34643 | 39379 | 52822 | NaN | NaN | NaN | NaN | NaN | 1372.0 | 1244.0 | NaN | NaN |
14 | 188010 | 43309 | 6627 | 13379 | 12929 | 7924 | 24318 | 11468 | NaN | 1606.0 | NaN | NaN | NaN | NaN | 891.0 | NaN | NaN |
15 | 151623 | 42887 | 7082 | 13766 | 23552 | 9933 | 28005 | 13233 | NaN | NaN | NaN | NaN | NaN | 906.0 | 654.0 | NaN | NaN |
16 | 131431 | 49353 | 10062 | 12214 | 31994 | 24440 | 25257 | 15451 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
17 | 130758 | 48332 | 7203 | 10770 | 25933 | 13258 | 23149 | 13233 | NaN | 2050.0 | NaN | NaN | NaN | NaN | 656.0 | NaN | NaN |
18 | 191733 | 50858 | 9964 | 16332 | 38396 | 16327 | 31378 | 17386 | 1045.0 | NaN | 582.0 | NaN | NaN | 1239.0 | NaN | NaN | NaN |
19 | 327342 | 301672 | 60663 | 67700 | 7882 | 93666 | 84937 | 146629 | NaN | NaN | 2760.0 | NaN | NaN | NaN | NaN | NaN | 1964.0 |
20 | 190355 | 123227 | 18895 | 23586 | 18666 | 27774 | 35418 | 49098 | NaN | 3597.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
21 | 93926 | 88731 | 10202 | 13346 | 12464 | 22841 | 42533 | 24152 | 1772.0 | NaN | NaN | NaN | NaN | 751.0 | NaN | 27530.0 | NaN |
22 | 174900 | 44971 | 7595 | 13984 | 23805 | 14893 | 29919 | 12981 | 2289.0 | 1528.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
23 | 284642 | 66516 | 11623 | 25163 | 23667 | 22409 | 47071 | 21106 | NaN | 2919.0 | NaN | NaN | NaN | 1363.0 | 778.0 | NaN | NaN |
24 | 197575 | 72894 | 11258 | 20297 | 35116 | 32015 | 39509 | 23361 | NaN | 2088.0 | NaN | NaN | 626.0 | NaN | 629.0 | NaN | NaN |
25 | 126466 | 148305 | 16992 | 21366 | 12904 | 28168 | 30536 | 39184 | NaN | 3210.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
26 | 145698 | 156147 | 18918 | 20677 | 15064 | 30973 | 37410 | 38344 | NaN | 3477.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
27 | 136330 | 79506 | 12557 | 15788 | 11533 | 22122 | 31570 | 27882 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
28 | 84773 | 49580 | 8625 | 10111 | 11799 | 26305 | 27521 | 15942 | NaN | 1378.0 | NaN | NaN | NaN | 600.0 | NaN | NaN | NaN |
29 | 89584 | 85130 | 12181 | 14949 | 7342 | 21163 | 35793 | 26122 | NaN | NaN | NaN | NaN | NaN | 1403.0 | NaN | NaN | NaN |
30 | 114799 | 70188 | 9669 | 13136 | 5197 | 17201 | 32794 | 18341 | NaN | NaN | NaN | 7928.0 | NaN | 710.0 | NaN | NaN | NaN |
31 | 135367 | 116658 | 16786 | 22803 | 4064 | 27837 | 41344 | 35591 | NaN | NaN | NaN | 10740.0 | NaN | NaN | NaN | NaN | NaN |
32 | 84410 | 72755 | 12652 | 13682 | 6687 | 39774 | 29161 | 25522 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
33 | 200652 | 80866 | 13106 | 19395 | 44574 | 36880 | 44115 | 23360 | 2344.0 | 1792.0 | NaN | NaN | 448.0 | 1158.0 | NaN | NaN | NaN |
34 | 63236 | 60359 | 7608 | 9480 | 13411 | 15407 | 16514 | 11668 | 1426.0 | 1253.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
35 | 80970 | 72055 | 9948 | 13584 | 22458 | 23312 | 23888 | 18141 | NaN | 1810.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
36 | 115668 | 89668 | 11497 | 15422 | 39874 | 32047 | 28996 | 25557 | 1973.0 | 1982.0 | NaN | NaN | 500.0 | NaN | NaN | NaN | NaN |
37 | 103781 | 56111 | 11032 | 11061 | 19027 | 32649 | 24486 | 19237 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
38 | 73665 | 83845 | 10591 | 11004 | 20712 | 24723 | 24359 | 18859 | NaN | 2515.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
39 | 97975 | 146141 | 18854 | 19548 | 7809 | 33080 | 24825 | 59387 | 2267.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
40 | 62032 | 65262 | 8466 | 9672 | 8972 | 24697 | 20428 | 17488 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
41 | 110697 | 121479 | 15678 | 20291 | 14776 | 32584 | 32030 | 32964 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
Wyniki w skali kraju i przekroczenie progu¶
kraj_liczbowo = wyn_okr.agg('sum').astype('int')
kraj_liczbowo.index = kraj_liczbowo.index
Surowa liczba głosów oddanych na poszczególne komitety wyborcze.
kraj_liczbowo
Nr listy 1 5711687 2 3661474 3 550349 4 722999 5 779875 6 1147102 7 1339094 8 1155370 9 15656 10 42731 11 3941 12 18668 13 4266 14 13113 15 4852 16 27530 17 1964 dtype: int64
suma_wszystkich_glosow = kraj_liczbowo.sum()
suma_wszystkich_glosow
15200671
W wyborach do Sejmu w 2015 oddano łącznie {{suma_wszystkich_glosow}} 15.2 mln ważnych głosów.
procenty = 100 * kraj_liczbowo / suma_wszystkich_glosow
kraj = pd.DataFrame({
"Skrót": def_kom["Skrót"],
"Liczba głosów": kraj_liczbowo,
"Procent ważnych głosów": procenty,
"Próg": def_kom["Próg"],
"Powyżej progu": procenty >= def_kom["Próg"],
})
Wyniki procentowe wszystkich komitetów¶
w skali całago kraju oraz informacja czy przekroczono próg wyborczy określony dla danego rodzaju komitetu.
kraj.round(2)
Skrót | Liczba głosów | Procent ważnych głosów | Próg | Powyżej progu | |
---|---|---|---|---|---|
Nr listy | |||||
1 | PiS | 5711687 | 37.58 | 5.0 | True |
2 | PO | 3661474 | 24.09 | 5.0 | True |
3 | Razem | 550349 | 3.62 | 5.0 | False |
4 | KORWiN | 722999 | 4.76 | 5.0 | False |
5 | PSL | 779875 | 5.13 | 5.0 | True |
6 | Lewica | 1147102 | 7.55 | 8.0 | False |
7 | Kukiz | 1339094 | 8.81 | 5.0 | True |
8 | Nowoczesna | 1155370 | 7.60 | 5.0 | True |
9 | Bezpartyjni | 15656 | 0.10 | 5.0 | False |
10 | Stonoga | 42731 | 0.28 | 5.0 | False |
11 | RSRP | 3941 | 0.03 | 5.0 | False |
12 | ZŚ | 18668 | 0.12 | 5.0 | False |
13 | Samoobrona | 4266 | 0.03 | 5.0 | False |
14 | Braun | 13113 | 0.09 | 5.0 | False |
15 | KNP | 4852 | 0.03 | 5.0 | False |
16 | MN | 27530 | 0.18 | 0.0 | True |
17 | OP | 1964 | 0.01 | 5.0 | False |
Komitety, które uzyskały wynik powyżej progu i będą uwzględniane w podziale mandatów.
kraj_pow = kraj[kraj["Powyżej progu"]].drop(columns=["Próg", "Powyżej progu"])
kom_pow_idx = kraj_pow.index
kraj_pow["Procent dzielonych głosów"] = 100 * kraj_pow["Liczba głosów"] / kraj_pow["Liczba głosów"].sum()
kraj_pow["Mandaty proporcjonalnie"] = round(MANDATY_SEJM * kraj_pow["Procent dzielonych głosów"] / 100, 1)
Dodaję kolumnę zawierającą wynik procentowy wśród komitetów, które przekroczyły próg. Teoretycznie procentowy podział mandatów powinien być zbliżony do tej liczby.
Gdyby podział w skali kraju był proporcjonalny, komitety powinny otrzymać liczbę mandatów obliczoną w ostatniej kolumnie.
kraj_pow.round(2)
Skrót | Liczba głosów | Procent ważnych głosów | Procent dzielonych głosów | Mandaty proporcjonalnie | |
---|---|---|---|---|---|
Nr listy | |||||
1 | PiS | 5711687 | 37.58 | 45.06 | 207.3 |
2 | PO | 3661474 | 24.09 | 28.89 | 132.9 |
5 | PSL | 779875 | 5.13 | 6.15 | 28.3 |
7 | Kukiz | 1339094 | 8.81 | 10.56 | 48.6 |
8 | Nowoczesna | 1155370 | 7.60 | 9.12 | 41.9 |
16 | MN | 27530 | 0.18 | 0.22 | 1.0 |
Faktyczny podział mandatów¶
Podział mandatów obliczony zgodnie z obowiązującymi zasadami: niezależnie w każdym okręgu.
Odrzucenie komitetów nieprzekraczających progu¶
W wynikach per okręg uwzględniam tylko te komitety, które przekroczyły próg.
wyn_okr_pow = wyn_okr[kom_pow_idx].copy()
wyn_okr_pow
Nr listy | 1 | 2 | 5 | 7 | 8 | 16 |
---|---|---|---|---|---|---|
Nr okręgu | ||||||
1 | 127370 | 90060 | 13886 | 34229 | 25506 | NaN |
2 | 72929 | 76424 | 7448 | 20634 | 15544 | NaN |
3 | 163323 | 159582 | 13604 | 45726 | 55756 | NaN |
4 | 113024 | 110948 | 22701 | 29080 | 27334 | NaN |
5 | 121703 | 93432 | 24476 | 30159 | 23563 | NaN |
6 | 232014 | 80892 | 37733 | 45448 | 22158 | NaN |
7 | 163122 | 41823 | 38689 | 35567 | 12745 | NaN |
8 | 97877 | 97676 | 17743 | 30284 | 34586 | NaN |
9 | 107350 | 112333 | 9710 | 25992 | 32274 | NaN |
10 | 134134 | 44173 | 21364 | 28808 | 15983 | NaN |
11 | 147623 | 78314 | 29044 | 32973 | 19681 | NaN |
12 | 133213 | 55454 | 8226 | 23551 | 15731 | NaN |
13 | 209607 | 133558 | 14644 | 39379 | 52822 | NaN |
14 | 188010 | 43309 | 12929 | 24318 | 11468 | NaN |
15 | 151623 | 42887 | 23552 | 28005 | 13233 | NaN |
16 | 131431 | 49353 | 31994 | 25257 | 15451 | NaN |
17 | 130758 | 48332 | 25933 | 23149 | 13233 | NaN |
18 | 191733 | 50858 | 38396 | 31378 | 17386 | NaN |
19 | 327342 | 301672 | 7882 | 84937 | 146629 | NaN |
20 | 190355 | 123227 | 18666 | 35418 | 49098 | NaN |
21 | 93926 | 88731 | 12464 | 42533 | 24152 | 27530.0 |
22 | 174900 | 44971 | 23805 | 29919 | 12981 | NaN |
23 | 284642 | 66516 | 23667 | 47071 | 21106 | NaN |
24 | 197575 | 72894 | 35116 | 39509 | 23361 | NaN |
25 | 126466 | 148305 | 12904 | 30536 | 39184 | NaN |
26 | 145698 | 156147 | 15064 | 37410 | 38344 | NaN |
27 | 136330 | 79506 | 11533 | 31570 | 27882 | NaN |
28 | 84773 | 49580 | 11799 | 27521 | 15942 | NaN |
29 | 89584 | 85130 | 7342 | 35793 | 26122 | NaN |
30 | 114799 | 70188 | 5197 | 32794 | 18341 | NaN |
31 | 135367 | 116658 | 4064 | 41344 | 35591 | NaN |
32 | 84410 | 72755 | 6687 | 29161 | 25522 | NaN |
33 | 200652 | 80866 | 44574 | 44115 | 23360 | NaN |
34 | 63236 | 60359 | 13411 | 16514 | 11668 | NaN |
35 | 80970 | 72055 | 22458 | 23888 | 18141 | NaN |
36 | 115668 | 89668 | 39874 | 28996 | 25557 | NaN |
37 | 103781 | 56111 | 19027 | 24486 | 19237 | NaN |
38 | 73665 | 83845 | 20712 | 24359 | 18859 | NaN |
39 | 97975 | 146141 | 7809 | 24825 | 59387 | NaN |
40 | 62032 | 65262 | 8972 | 20428 | 17488 | NaN |
41 | 110697 | 121479 | 14776 | 32030 | 32964 | NaN |
Metoda d'Honta¶
Definicja funkcji obliczającej liczbę mandatów zgodnie z metodą d'Honta.
def dhont_podzial_liczbowy(liczba_mandatow, wyniki):
ilorazy = [(nr, dzielnik, wynik/dzielnik)
for nr, wynik in enumerate(wyniki)
for dzielnik in range(1, liczba_mandatow+1)]
posortowane_ilorazy = sorted(ilorazy, key=(lambda t: t[2]), reverse=True)
mandaty = [0 for w in wyniki]
for (nr, _, _) in posortowane_ilorazy[:liczba_mandatow]:
mandaty[nr] += 1
return mandaty
Zastosowanie metody d'Honta w poszczególnych okręgach.
mandaty_surowe_okr = pd.DataFrame([
dhont_podzial_liczbowy(def_okr.loc[nr_okr, "Liczba mandatów"], row)
for nr_okr, row in wyn_okr_pow.iterrows()
],
index=wyn_okr_pow.index,
columns = wyn_okr_pow.columns)
# mandaty_surowe_okr
mandaty_okr = def_okr.copy()
for kom in kom_pow_idx:
mandaty_okr[kom] = mandaty_surowe_okr[kom]
mandaty_okr_prezentacja = mandaty_okr.rename(columns = map_kom_skrot)
Prawidzwe wyniki¶
Podział mandatów między komitety w poszcególnych okręgach
mandaty_okr_prezentacja
Okręg | Liczba mandatów | PiS | PO | PSL | Kukiz | Nowoczesna | MN | |
---|---|---|---|---|---|---|---|---|
Nr okręgu | ||||||||
1 | Legnica | 12 | 6 | 4 | 0 | 1 | 1 | 0 |
2 | Wałbrzych | 8 | 3 | 4 | 0 | 1 | 0 | 0 |
3 | Wrocław | 14 | 6 | 5 | 0 | 1 | 2 | 0 |
4 | Bydgoszcz | 12 | 5 | 4 | 1 | 1 | 1 | 0 |
5 | Toruń | 13 | 6 | 4 | 1 | 1 | 1 | 0 |
6 | Lublin | 15 | 10 | 3 | 1 | 1 | 0 | 0 |
7 | Chełm | 12 | 8 | 2 | 1 | 1 | 0 | 0 |
8 | Zielona Góra | 12 | 5 | 5 | 0 | 1 | 1 | 0 |
9 | Łódź | 10 | 4 | 4 | 0 | 1 | 1 | 0 |
10 | Piotrków Trybunalski | 9 | 6 | 2 | 0 | 1 | 0 | 0 |
11 | Sieradz | 12 | 7 | 3 | 1 | 1 | 0 | 0 |
12 | Kraków I (południe) | 8 | 5 | 2 | 0 | 1 | 0 | 0 |
13 | Kraków II (północ) | 14 | 7 | 5 | 0 | 1 | 1 | 0 |
14 | Nowy Sącz | 10 | 8 | 1 | 0 | 1 | 0 | 0 |
15 | Tarnów | 9 | 6 | 1 | 1 | 1 | 0 | 0 |
16 | Płock | 10 | 6 | 2 | 1 | 1 | 0 | 0 |
17 | Radom | 9 | 5 | 2 | 1 | 1 | 0 | 0 |
18 | Siedlce | 12 | 8 | 2 | 1 | 1 | 0 | 0 |
19 | Warszawa I (miasto) | 20 | 8 | 7 | 0 | 2 | 3 | 0 |
20 | Warszawa II (okręg) | 12 | 6 | 4 | 0 | 1 | 1 | 0 |
21 | Opole | 12 | 4 | 4 | 0 | 2 | 1 | 1 |
22 | Krosno | 11 | 7 | 2 | 1 | 1 | 0 | 0 |
23 | Rzeszów | 15 | 12 | 2 | 0 | 1 | 0 | 0 |
24 | Białystok | 14 | 8 | 3 | 1 | 1 | 1 | 0 |
25 | Gdańsk | 12 | 5 | 5 | 0 | 1 | 1 | 0 |
26 | Gdynia | 14 | 6 | 6 | 0 | 1 | 1 | 0 |
27 | Bielsko-Biała | 9 | 5 | 2 | 0 | 1 | 1 | 0 |
28 | Częstochowa | 7 | 4 | 2 | 0 | 1 | 0 | 0 |
29 | Gliwice | 9 | 4 | 3 | 0 | 1 | 1 | 0 |
30 | Rybnik | 9 | 5 | 3 | 0 | 1 | 0 | 0 |
31 | Katowice | 12 | 5 | 5 | 0 | 1 | 1 | 0 |
32 | Sosnowiec | 9 | 4 | 3 | 0 | 1 | 1 | 0 |
33 | Kielce | 16 | 9 | 3 | 2 | 1 | 1 | 0 |
34 | Elbląg | 8 | 4 | 3 | 0 | 1 | 0 | 0 |
35 | Olsztyn | 10 | 4 | 3 | 1 | 1 | 1 | 0 |
36 | Kalisz | 12 | 5 | 4 | 1 | 1 | 1 | 0 |
37 | Konin | 9 | 5 | 2 | 0 | 1 | 1 | 0 |
38 | Piła | 9 | 3 | 4 | 1 | 1 | 0 | 0 |
39 | Poznań | 10 | 3 | 5 | 0 | 0 | 2 | 0 |
40 | Koszalin | 8 | 3 | 3 | 0 | 1 | 1 | 0 |
41 | Szczecin | 12 | 5 | 5 | 0 | 1 | 1 | 0 |
Podsumowanie krajowe¶
mandaty_kraj = mandaty_okr.iloc[:, 2:].agg('sum')
mandaty_kraj
1 235 2 138 5 16 7 42 8 28 16 1 dtype: int64
mandaty_kraj_prezentacja = pd.DataFrame({
"Skrót": kraj_pow["Skrót"],
"Przyznanych mandatów": mandaty_kraj
})
mandaty_kraj_prezentacja
Skrót | Przyznanych mandatów | |
---|---|---|
1 | PiS | 235 |
2 | PO | 138 |
5 | PSL | 16 |
7 | Kukiz | 42 |
8 | Nowoczesna | 28 |
16 | MN | 1 |
mandaty_kraj.plot(kind='pie',
figsize=(8,6),
title='Faktyczny podział mandatów',
labels=[f'{komitet}: {mandaty}' for i, (komitet, mandaty) in mandaty_kraj_prezentacja.iterrows()])
<Axes: title={'center': 'Faktyczny podział mandatów'}>
assert mandaty_kraj.sum() == MANDATY_SEJM
Globalny podział mandatów¶
Gdyby podział liczby mandatów między komitety dokonywany był w skali kraju, liczba mandatów znacznie bliżej odpowiadałaby podziałowi proporcjonalnemu, a każdy głos wyborcy ważyłby tyle samo, niezależnie od okręgu, w którym głosuje.
Dokonam podziału mandatów metodą d'Honta na podstawie liczby głosów oddanej na poszczególne komitety w skali całego kraju.
mandaty_globalne = pd.Series(
dhont_podzial_liczbowy(MANDATY_SEJM, kraj_pow["Liczba głosów"]),
index = kom_pow_idx)
mandaty_globalne
Nr listy 1 208 2 133 5 28 7 48 8 42 16 1 dtype: int64
mandaty_globalne_prezentacja = pd.DataFrame({
"Skrót": kraj_pow["Skrót"],
"Przyznanych mandatów": mandaty_globalne
})
mandaty_globalne_prezentacja
Skrót | Przyznanych mandatów | |
---|---|---|
Nr listy | ||
1 | PiS | 208 |
2 | PO | 133 |
5 | PSL | 28 |
7 | Kukiz | 48 |
8 | Nowoczesna | 42 |
16 | MN | 1 |
mandaty_globalne.plot(kind='pie',
figsize=(8,6),
title='Potencjalny podział mandatów',
labels=[f'{komitet}: {mandaty}' for i, (komitet, mandaty) in mandaty_globalne_prezentacja.iterrows()])
<Axes: title={'center': 'Potencjalny podział mandatów'}>
assert mandaty_globalne.sum() == MANDATY_SEJM
Porównanie obu metod¶
Proporcjonaność¶
Powszechne jest publicystyczne stwierdzenie, że metoda d'Honta premiuje duże ugrupowania i koalicje, a jest nieopłacalna dla małych (w sensie osiągniętego wyniku) komitetów startujących samodzielnie. Moim zdaniem metoda d'Honta dąży do zachowania proporcjonalności, ale dopiero w dużej skali, a zaburzenia proporcjonalności pojawiają się ze względu na podział mandatów w zbyt małych okręgach (zwykle na korzyść dużych kosztem małych, ale zaraz zobaczymy, że jest to bardziej złożony problem).
tabela1 = kraj_pow.copy()
tabela1["Liczba mandatów"] = mandaty_kraj
tabela1["Procent mandatów"] = 100 * mandaty_kraj / MANDATY_SEJM
tabela1["Różnica % bezwzgl"] = tabela1["Procent mandatów"] - tabela1["Procent dzielonych głosów"]
tabela1["Różnica % wzgl"] = 100 * (tabela1["Procent mandatów"] - tabela1["Procent dzielonych głosów"]) / tabela1["Procent dzielonych głosów"]
tabela1["Różnica mandatów"] = tabela1["Liczba mandatów"] - tabela1["Mandaty proporcjonalnie"]
tabela1.sort_values("Liczba głosów", ascending=False)
Skrót | Liczba głosów | Procent ważnych głosów | Procent dzielonych głosów | Mandaty proporcjonalnie | Liczba mandatów | Procent mandatów | Różnica % bezwzgl | Różnica % wzgl | Różnica mandatów | |
---|---|---|---|---|---|---|---|---|---|---|
Nr listy | ||||||||||
1 | PiS | 5711687 | 37.575229 | 45.062513 | 207.3 | 235 | 51.086957 | 6.024444 | 13.369081 | 27.7 |
2 | PO | 3661474 | 24.087581 | 28.887300 | 132.9 | 138 | 30.000000 | 1.112700 | 3.851864 | 5.1 |
7 | Kukiz | 1339094 | 8.809440 | 10.564819 | 48.6 | 42 | 9.130435 | -1.434384 | -13.576990 | -6.6 |
8 | Nowoczesna | 1155370 | 7.600783 | 9.115324 | 41.9 | 28 | 6.086957 | -3.028367 | -33.222815 | -13.9 |
5 | PSL | 779875 | 5.130530 | 6.152845 | 28.3 | 16 | 3.478261 | -2.674585 | -43.469068 | -12.3 |
16 | MN | 27530 | 0.181110 | 0.217199 | 1.0 | 1 | 0.217391 | 0.000193 | 0.088678 | 0.0 |
Gdyby dzielić mandaty pomiędzy komitety w skali całego kraju, system ten byłby niemal idealnie proporcjonalny - czyli sprawiedliwy i zgodny z zasadami zapisanymi w konstytucji.
tabela2 = kraj_pow.copy()
tabela2["Liczba mandatów"] = mandaty_globalne
tabela2["Procent mandatów"] = 100 * mandaty_globalne / MANDATY_SEJM
tabela2["Różnica % bezwzgl"] = tabela2["Procent mandatów"] - tabela2["Procent dzielonych głosów"]
tabela2["Różnica % wzgl"] = 100 * (tabela2["Procent mandatów"] - tabela2["Procent dzielonych głosów"]) / tabela2["Procent dzielonych głosów"]
tabela2["Różnica mandatów"] = tabela2["Liczba mandatów"] - tabela2["Mandaty proporcjonalnie"]
tabela2.sort_values("Liczba głosów", ascending=False)
Skrót | Liczba głosów | Procent ważnych głosów | Procent dzielonych głosów | Mandaty proporcjonalnie | Liczba mandatów | Procent mandatów | Różnica % bezwzgl | Różnica % wzgl | Różnica mandatów | |
---|---|---|---|---|---|---|---|---|---|---|
Nr listy | ||||||||||
1 | PiS | 5711687 | 37.575229 | 45.062513 | 207.3 | 208 | 45.217391 | 0.154879 | 0.343697 | 0.7 |
2 | PO | 3661474 | 24.087581 | 28.887300 | 132.9 | 133 | 28.913043 | 0.025743 | 0.089115 | 0.1 |
7 | Kukiz | 1339094 | 8.809440 | 10.564819 | 48.6 | 48 | 10.434783 | -0.130037 | -1.230845 | -0.6 |
8 | Nowoczesna | 1155370 | 7.600783 | 9.115324 | 41.9 | 42 | 9.130435 | 0.015111 | 0.165778 | 0.1 |
5 | PSL | 779875 | 5.130530 | 6.152845 | 28.3 | 28 | 6.086957 | -0.065889 | -1.070868 | -0.3 |
16 | MN | 27530 | 0.181110 | 0.217199 | 1.0 | 1 | 0.217391 | 0.000193 | 0.088678 | 0.0 |
Waga głosu¶
Tradycyjny system dzielenia mandatów oparty o z góry ustaloną liczbę mandatów do podziału w okręgu jest niesprawiedliwy także pod względem wpływu pojedynczego głosu na ostateczne wyniki wyborów.
Wpływa na to kilka czynników:
- Liczba mandatów ustalona jest w oparciu o dane nt liczby mieszkańców pochodzące sprzed lat (np. obecnie w 2023 dane nie zostały uaktualnione, na niekorzyść Warszawy).
- Nie są uwzględnione nierejestrowane migracje oraz pobyt krótkoterminowy (praca, studia) - głównie w dużych miastach.
- Nie jest uwzględniony fakt, że do okręgu Warszawa doliczane są głosy z zagranicy.
- Liczba mandatów nie zależy od frekwencji. Tam, gdzie głosuje mniej wyborców, pojedyncze głosy ważą więcej.
W poniższym fragmencie pod uwagę biorę wszystkie głosy ważne; także te oddane na komitety, które nie przekroczyły progu.
Zacznijmy od obliczenia średniej liczby głosów przypadającej na jeden mandat w skali całego kraju.
stat_okr["Głosy ważne"].sum() / MANDATY_SEJM
33044.93695652174
Teraz zobaczymy tę liczbę w podziale na okręgi. Im mniejsza liczba, tym „ważniejszy” jest jeden głos wyborcy; im większa liczba, tym mniej ważny jest pojedynczy głos.
wagi_glosow = pd.concat([def_okr, stat_okr["Głosy ważne"]], axis=1)
wagi_glosow["Głosów na mandat"] = (wagi_glosow["Głosy ważne"] / wagi_glosow["Liczba mandatów"]).round().astype('int')
wagi_glosow.sort_values("Głosów na mandat")
Okręg | Liczba mandatów | Głosy ważne | Głosów na mandat | |
---|---|---|---|---|
Nr okręgu | ||||
34 | Elbląg | 8 | 200362 | 25045 |
35 | Olsztyn | 10 | 266166 | 26617 |
40 | Koszalin | 8 | 217017 | 27127 |
5 | Toruń | 13 | 362510 | 27885 |
21 | Opole | 12 | 338248 | 28187 |
7 | Chełm | 12 | 339686 | 28307 |
8 | Zielona Góra | 12 | 346218 | 28852 |
2 | Wałbrzych | 8 | 234095 | 29262 |
33 | Kielce | 16 | 468690 | 29293 |
22 | Krosno | 11 | 326865 | 29715 |
1 | Legnica | 12 | 356779 | 29732 |
16 | Płock | 10 | 300202 | 30020 |
38 | Piła | 9 | 270273 | 30030 |
36 | Kalisz | 12 | 363184 | 30265 |
17 | Radom | 9 | 275342 | 30594 |
11 | Sieradz | 12 | 369745 | 30812 |
37 | Konin | 9 | 277384 | 30820 |
14 | Nowy Sącz | 10 | 310461 | 31046 |
24 | Białystok | 14 | 435368 | 31098 |
4 | Bydgoszcz | 12 | 374277 | 31190 |
18 | Siedlce | 12 | 375240 | 31270 |
32 | Sosnowiec | 9 | 284643 | 31627 |
41 | Szczecin | 12 | 380499 | 31708 |
10 | Piotrków Trybunalski | 9 | 285721 | 31747 |
30 | Rybnik | 9 | 289963 | 32218 |
15 | Tarnów | 9 | 291641 | 32405 |
6 | Lublin | 15 | 487720 | 32515 |
29 | Gliwice | 9 | 293667 | 32630 |
26 | Gdynia | 14 | 466708 | 33336 |
28 | Częstochowa | 7 | 236634 | 33805 |
23 | Rzeszów | 15 | 507257 | 33817 |
12 | Kraków I (południe) | 8 | 271577 | 33947 |
31 | Katowice | 12 | 411190 | 34266 |
25 | Gdańsk | 12 | 427131 | 35594 |
9 | Łódź | 10 | 359064 | 35906 |
3 | Wrocław | 14 | 523371 | 37384 |
27 | Bielsko-Biała | 9 | 337288 | 37476 |
13 | Kraków II (północ) | 14 | 542768 | 38769 |
20 | Warszawa II (okręg) | 12 | 490616 | 40885 |
39 | Poznań | 10 | 409886 | 40989 |
19 | Warszawa I (miasto) | 20 | 1095215 | 54761 |
wagi_glosow["Głosów na mandat"].describe()
count 41.00 mean 32511.02 std 5045.57 min 25045.00 25% 29732.00 50% 31270.00 75% 33817.00 max 54761.00 Name: Głosów na mandat, dtype: float64
wagi_glosow["Głosów na mandat"].max() / wagi_glosow["Głosów na mandat"].min()
2.186504292273907
wykres = pd.Series(wagi_glosow["Głosów na mandat"])
wykres.index = wagi_glosow["Okręg"]
wykres.sort_values(inplace=True, ascending=False)
wykres.plot(kind='barh', figsize=(6, 16), grid=True)
<Axes: ylabel='Okręg'>
Jak widać, ekstremalnie poszkodowane są głosy oddane w stolicy. Główną przyczyną jest uwzględnianie głosów oddanych zagranicą właśnie w tym okręgu. Dodatkowo w Warszawie zwykle panuje większa od przeciętnej frekwencja, wielu jest dodatkowych wyborców jednorazowo dopisujących się do list lub głosujących z zaświadczeniem.
Poszkodowane są także niektóre inne duże miasta. Natomiast zyskują wyborcy z rejonów o tradycyjnie niskiej frekwencji: warmińsko-mazurskie, opolskie, lubuskie, środkowe pomorze. Bydgoszcz i Toruń są dla mnie akurat zaskoczeniem.
Sprawiedliwa liczba mandatów¶
Gdyby liczba mandatów dla danego okręgu była proporcjonalna do liczby faktycznie oddanych głosów, wartości byłyby zupełnie inne. Obliczmy je używając… metody d'Honta.
Jak już wiemy, w dużej skali metoda ta jest proporcjonalna. Możemy to potwierdzić ponownie obliczając współczynnik głosów na mandat dla hipotetycznej liczby mandatów - wychodzą wartości znacznie bliższe sobie (mniejsze odchylenie standardowe, radykalnie mniejszy stosunek między ekstremalnymi wartościami).
wagi_glosow["Sprawiedliwa liczba mandatów"] = dhont_podzial_liczbowy(MANDATY_SEJM, stat_okr["Głosy ważne"])
wagi_glosow["Sprawiedliwe głosy na mandat"] = (wagi_glosow["Głosy ważne"] / wagi_glosow["Sprawiedliwa liczba mandatów"]).round().astype('int')
wagi_glosow
Okręg | Liczba mandatów | Głosy ważne | Głosów na mandat | Sprawiedliwa liczba mandatów | Sprawiedliwe głosy na mandat | |
---|---|---|---|---|---|---|
Nr okręgu | ||||||
1 | Legnica | 12 | 356779 | 29732 | 11 | 32434 |
2 | Wałbrzych | 8 | 234095 | 29262 | 7 | 33442 |
3 | Wrocław | 14 | 523371 | 37384 | 16 | 32711 |
4 | Bydgoszcz | 12 | 374277 | 31190 | 11 | 34025 |
5 | Toruń | 13 | 362510 | 27885 | 11 | 32955 |
6 | Lublin | 15 | 487720 | 32515 | 15 | 32515 |
7 | Chełm | 12 | 339686 | 28307 | 10 | 33969 |
8 | Zielona Góra | 12 | 346218 | 28852 | 10 | 34622 |
9 | Łódź | 10 | 359064 | 35906 | 11 | 32642 |
10 | Piotrków Trybunalski | 9 | 285721 | 31747 | 9 | 31747 |
11 | Sieradz | 12 | 369745 | 30812 | 11 | 33613 |
12 | Kraków I (południe) | 8 | 271577 | 33947 | 8 | 33947 |
13 | Kraków II (północ) | 14 | 542768 | 38769 | 17 | 31928 |
14 | Nowy Sącz | 10 | 310461 | 31046 | 9 | 34496 |
15 | Tarnów | 9 | 291641 | 32405 | 9 | 32405 |
16 | Płock | 10 | 300202 | 30020 | 9 | 33356 |
17 | Radom | 9 | 275342 | 30594 | 8 | 34418 |
18 | Siedlce | 12 | 375240 | 31270 | 11 | 34113 |
19 | Warszawa I (miasto) | 20 | 1095215 | 54761 | 34 | 32212 |
20 | Warszawa II (okręg) | 12 | 490616 | 40885 | 15 | 32708 |
21 | Opole | 12 | 338248 | 28187 | 10 | 33825 |
22 | Krosno | 11 | 326865 | 29715 | 10 | 32686 |
23 | Rzeszów | 15 | 507257 | 33817 | 16 | 31704 |
24 | Białystok | 14 | 435368 | 31098 | 13 | 33490 |
25 | Gdańsk | 12 | 427131 | 35594 | 13 | 32856 |
26 | Gdynia | 14 | 466708 | 33336 | 14 | 33336 |
27 | Bielsko-Biała | 9 | 337288 | 37476 | 10 | 33729 |
28 | Częstochowa | 7 | 236634 | 33805 | 7 | 33805 |
29 | Gliwice | 9 | 293667 | 32630 | 9 | 32630 |
30 | Rybnik | 9 | 289963 | 32218 | 9 | 32218 |
31 | Katowice | 12 | 411190 | 34266 | 13 | 31630 |
32 | Sosnowiec | 9 | 284643 | 31627 | 9 | 31627 |
33 | Kielce | 16 | 468690 | 29293 | 14 | 33478 |
34 | Elbląg | 8 | 200362 | 25045 | 6 | 33394 |
35 | Olsztyn | 10 | 266166 | 26617 | 8 | 33271 |
36 | Kalisz | 12 | 363184 | 30265 | 11 | 33017 |
37 | Konin | 9 | 277384 | 30820 | 8 | 34673 |
38 | Piła | 9 | 270273 | 30030 | 8 | 33784 |
39 | Poznań | 10 | 409886 | 40989 | 12 | 34157 |
40 | Koszalin | 8 | 217017 | 27127 | 6 | 36170 |
41 | Szczecin | 12 | 380499 | 31708 | 12 | 31708 |
wagi_glosow["Sprawiedliwe głosy na mandat"].describe()
count 41.00 mean 33206.00 std 999.52 min 31627.00 25% 32515.00 50% 33336.00 75% 33825.00 max 36170.00 Name: Sprawiedliwe głosy na mandat, dtype: float64
wagi_glosow["Sprawiedliwe głosy na mandat"].max() / wagi_glosow["Sprawiedliwe głosy na mandat"].min()
1.1436430897650742
Wartość 34 dla Warszawy może być szokująca dla Polaków spoza stolicy (a może też działaczy partyjnych), ale właśnie taka liczba mandatów poselskich odpowiada liczbie oddanych głosów.