Analiza 2019¶
Prezentacja faktycznego i hipotetycznego podziału mandatów w wyborach do Sejmu 2019.
© 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/2019/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_2019
.
def_kom = pd.read_csv(path + 'def_kom_2019.csv', sep=';', index_col=0)
def_okr = pd.read_csv(path + 'def_okr_2019.csv', sep=';', index_col=0)
wyn_okr = pd.read_csv(path + 'kom_okr_2019.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_2019.csv', sep=';', index_col=0)
def_kom
Nazwa pełna | Nazwa | Skrót | Próg | |
---|---|---|---|---|
Nr listy | ||||
1 | KOMITET WYBORCZY POLSKIE STRONNICTWO LUDOWE | POLSKIE STRONNICTWO LUDOWE | PSL | 5.0 |
2 | KOMITET WYBORCZY PRAWO I SPRAWIEDLIWOŚĆ | PRAWO I SPRAWIEDLIWOŚĆ | PiS | 5.0 |
3 | KOMITET WYBORCZY SOJUSZ LEWICY DEMOKRATYCZNEJ | SOJUSZ LEWICY DEMOKRATYCZNEJ | Lewica | 5.0 |
4 | KOMITET WYBORCZY KONFEDERACJA WOLNOŚĆ I NIEPOD... | KONFEDERACJA WOLNOŚĆ I NIEPODLEGŁOŚĆ | Konfederacja | 5.0 |
5 | KOALICYJNY KOMITET WYBORCZY KOALICJA OBYWATELS... | KOALICJA OBYWATELSKA PO .N IPL ZIELONI | KO | 8.0 |
6 | KOMITET WYBORCZY PRAWICA | PRAWICA | KW Prawica | 5.0 |
7 | KOMITET WYBORCZY AKCJA ZAWIEDZIONYCH EMERYTÓW ... | AKCJA ZAWIEDZIONYCH EMERYTÓW RENCISTÓW | AZER | 5.0 |
8 | KOMITET WYBORCZY WYBORCÓW KOALICJA BEZPARTYJNI... | KOALICJA BEZPARTYJNI I SAMORZĄDOWCY | BS | 5.0 |
9 | KOMITET WYBORCZY SKUTECZNI PIOTRA LIROYA-MARCA | SKUTECZNI PIOTRA LIROYA-MARCA | Liroy | 5.0 |
10 | KOMITET WYBORCZY WYBORCÓW MNIEJSZOŚĆ NIEMIECKA | MNIEJSZOŚĆ NIEMIECKA | MN | 0.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 | 8 |
13 | Kraków | 14 |
14 | Nowy Sącz | 10 |
15 | Tarnów | 9 |
16 | Płock | 10 |
17 | Radom | 9 |
18 | Siedlce | 12 |
19 | Warszawa | 20 |
20 | Warszawa | 12 |
21 | Opole | 12 |
22 | Krosno | 11 |
23 | Rzeszów | 15 |
24 | Białystok | 14 |
25 | Gdańsk | 12 |
26 | Słupsk | 14 |
27 | Bielsko-Biała | 9 |
28 | Częstochowa | 7 |
29 | Katowice | 9 |
30 | Bielsko-Biała | 9 |
31 | Katowice | 12 |
32 | Katowice | 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 |
---|---|---|---|---|---|---|---|---|---|---|
Numer okręgu | ||||||||||
1 | 31006 | 183364 | 71061 | 25319 | 108191 | NaN | NaN | 13495.0 | NaN | NaN |
2 | 20528 | 114728 | 34957 | 15346 | 90812 | NaN | NaN | 6631.0 | NaN | NaN |
3 | 42269 | 226915 | 100843 | 48775 | 214629 | NaN | NaN | 21024.0 | NaN | NaN |
4 | 41497 | 167550 | 69763 | 32406 | 142844 | NaN | NaN | 5922.0 | NaN | NaN |
5 | 49225 | 182648 | 67076 | 28625 | 119526 | NaN | NaN | 5230.0 | NaN | NaN |
6 | 51474 | 313284 | 44152 | 40012 | 109185 | NaN | NaN | 7490.0 | NaN | NaN |
7 | 47604 | 238802 | 27404 | 23439 | 59401 | NaN | NaN | 4668.0 | NaN | NaN |
8 | 50943 | 150188 | 68341 | 31490 | 136955 | NaN | NaN | NaN | NaN | NaN |
9 | 18828 | 136731 | 83524 | 27627 | 148830 | NaN | NaN | NaN | NaN | NaN |
10 | 36151 | 194658 | 37930 | 23427 | 54160 | NaN | NaN | NaN | NaN | NaN |
11 | 47373 | 229245 | 55116 | 27054 | 94268 | NaN | NaN | 7183.0 | NaN | NaN |
12 | 24996 | 169106 | 26909 | 22334 | 72869 | NaN | NaN | NaN | NaN | NaN |
13 | 47219 | 256847 | 84457 | 51855 | 197930 | 1765.0 | NaN | 9214.0 | NaN | NaN |
14 | 27203 | 243583 | 22483 | 25747 | 51183 | NaN | NaN | NaN | NaN | NaN |
15 | 46333 | 206845 | 20618 | 24695 | 48597 | NaN | NaN | NaN | NaN | NaN |
16 | 56227 | 194371 | 32448 | 19405 | 62429 | NaN | NaN | 5681.0 | NaN | NaN |
17 | 34185 | 193709 | 24884 | 19724 | 57449 | NaN | NaN | 2555.0 | 2503.0 | NaN |
18 | 54085 | 270641 | 29235 | 29390 | 63124 | NaN | 1412.0 | 5019.0 | NaN | NaN |
19 | 65683 | 379880 | 251434 | 103843 | 581077 | NaN | NaN | NaN | NaN | NaN |
20 | 51484 | 244823 | 78348 | 39675 | 171286 | NaN | NaN | 13111.0 | NaN | NaN |
21 | 41901 | 152999 | 47699 | 23176 | 108570 | NaN | NaN | NaN | NaN | 32094.0 |
22 | 30655 | 247488 | 23577 | 26615 | 62246 | NaN | NaN | NaN | NaN | NaN |
23 | 45868 | 367268 | 38817 | 48600 | 84703 | NaN | NaN | NaN | 3530.0 | NaN |
24 | 48566 | 270888 | 47342 | 36207 | 109527 | NaN | 1775.0 | 4001.0 | 2272.0 | NaN |
25 | 31203 | 169753 | 71236 | 38153 | 218484 | NaN | NaN | NaN | NaN | NaN |
26 | 46132 | 211582 | 72436 | 42364 | 208208 | NaN | NaN | NaN | NaN | NaN |
27 | 27752 | 182027 | 44701 | 28900 | 105876 | NaN | NaN | NaN | NaN | NaN |
28 | 24704 | 125990 | 44354 | 17278 | 64374 | NaN | NaN | 7817.0 | NaN | NaN |
29 | 20408 | 128579 | 45583 | 26114 | 111078 | NaN | NaN | 8885.0 | NaN | NaN |
30 | 18816 | 161160 | 32300 | 23939 | 92493 | NaN | NaN | 5128.0 | NaN | NaN |
31 | 20512 | 184030 | 55992 | 34416 | 174683 | NaN | NaN | NaN | NaN | NaN |
32 | 16263 | 124553 | 73466 | 21650 | 99499 | NaN | NaN | NaN | NaN | NaN |
33 | 56289 | 314455 | 56699 | 33895 | 94880 | NaN | NaN | 5400.0 | 8273.0 | NaN |
34 | 27319 | 102478 | 29196 | 14187 | 71320 | NaN | NaN | 6319.0 | NaN | NaN |
35 | 43758 | 128760 | 45912 | 23134 | 87780 | NaN | NaN | NaN | 2340.0 | NaN |
36 | 58759 | 195053 | 61674 | 30177 | 113489 | NaN | NaN | NaN | NaN | NaN |
37 | 34620 | 166965 | 53090 | 23810 | 72295 | NaN | 2261.0 | NaN | NaN | NaN |
38 | 48371 | 124392 | 46355 | 23123 | 106810 | NaN | NaN | NaN | NaN | NaN |
39 | 31875 | 130319 | 84835 | 34024 | 233474 | NaN | NaN | NaN | NaN | NaN |
40 | 25632 | 100078 | 41943 | 16259 | 87799 | NaN | NaN | NaN | NaN | NaN |
41 | 34807 | 165200 | 71756 | 30744 | 168022 | 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 1578523 2 8051935 3 2319946 4 1256953 5 5060355 6 1765 7 5448 8 144773 9 18918 10 32094 dtype: int64
suma_wszystkich_glosow = kraj_liczbowo.sum()
suma_wszystkich_glosow
18470710
W wyborach do Sejmu w 2019 oddano łącznie {{suma_wszystkich_glosow}} 18.5 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 | PSL | 1578523 | 8.55 | 5.0 | True |
2 | PiS | 8051935 | 43.59 | 5.0 | True |
3 | Lewica | 2319946 | 12.56 | 5.0 | True |
4 | Konfederacja | 1256953 | 6.81 | 5.0 | True |
5 | KO | 5060355 | 27.40 | 8.0 | True |
6 | KW Prawica | 1765 | 0.01 | 5.0 | False |
7 | AZER | 5448 | 0.03 | 5.0 | False |
8 | BS | 144773 | 0.78 | 5.0 | False |
9 | Liroy | 18918 | 0.10 | 5.0 | False |
10 | MN | 32094 | 0.17 | 0.0 | True |
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).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 | |
---|---|---|---|---|---|
Nr listy | |||||
2 | PiS | 8051935 | 43.59 | 44.00 | 202.4 |
5 | KO | 5060355 | 27.40 | 27.65 | 127.2 |
3 | Lewica | 2319946 | 12.56 | 12.68 | 58.3 |
1 | PSL | 1578523 | 8.55 | 8.63 | 39.7 |
4 | Konfederacja | 1256953 | 6.81 | 6.87 | 31.6 |
10 | MN | 32094 | 0.17 | 0.18 | 0.8 |
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 | 3 | 4 | 5 | 10 |
---|---|---|---|---|---|---|
Numer okręgu | ||||||
1 | 31006 | 183364 | 71061 | 25319 | 108191 | NaN |
2 | 20528 | 114728 | 34957 | 15346 | 90812 | NaN |
3 | 42269 | 226915 | 100843 | 48775 | 214629 | NaN |
4 | 41497 | 167550 | 69763 | 32406 | 142844 | NaN |
5 | 49225 | 182648 | 67076 | 28625 | 119526 | NaN |
6 | 51474 | 313284 | 44152 | 40012 | 109185 | NaN |
7 | 47604 | 238802 | 27404 | 23439 | 59401 | NaN |
8 | 50943 | 150188 | 68341 | 31490 | 136955 | NaN |
9 | 18828 | 136731 | 83524 | 27627 | 148830 | NaN |
10 | 36151 | 194658 | 37930 | 23427 | 54160 | NaN |
11 | 47373 | 229245 | 55116 | 27054 | 94268 | NaN |
12 | 24996 | 169106 | 26909 | 22334 | 72869 | NaN |
13 | 47219 | 256847 | 84457 | 51855 | 197930 | NaN |
14 | 27203 | 243583 | 22483 | 25747 | 51183 | NaN |
15 | 46333 | 206845 | 20618 | 24695 | 48597 | NaN |
16 | 56227 | 194371 | 32448 | 19405 | 62429 | NaN |
17 | 34185 | 193709 | 24884 | 19724 | 57449 | NaN |
18 | 54085 | 270641 | 29235 | 29390 | 63124 | NaN |
19 | 65683 | 379880 | 251434 | 103843 | 581077 | NaN |
20 | 51484 | 244823 | 78348 | 39675 | 171286 | NaN |
21 | 41901 | 152999 | 47699 | 23176 | 108570 | 32094.0 |
22 | 30655 | 247488 | 23577 | 26615 | 62246 | NaN |
23 | 45868 | 367268 | 38817 | 48600 | 84703 | NaN |
24 | 48566 | 270888 | 47342 | 36207 | 109527 | NaN |
25 | 31203 | 169753 | 71236 | 38153 | 218484 | NaN |
26 | 46132 | 211582 | 72436 | 42364 | 208208 | NaN |
27 | 27752 | 182027 | 44701 | 28900 | 105876 | NaN |
28 | 24704 | 125990 | 44354 | 17278 | 64374 | NaN |
29 | 20408 | 128579 | 45583 | 26114 | 111078 | NaN |
30 | 18816 | 161160 | 32300 | 23939 | 92493 | NaN |
31 | 20512 | 184030 | 55992 | 34416 | 174683 | NaN |
32 | 16263 | 124553 | 73466 | 21650 | 99499 | NaN |
33 | 56289 | 314455 | 56699 | 33895 | 94880 | NaN |
34 | 27319 | 102478 | 29196 | 14187 | 71320 | NaN |
35 | 43758 | 128760 | 45912 | 23134 | 87780 | NaN |
36 | 58759 | 195053 | 61674 | 30177 | 113489 | NaN |
37 | 34620 | 166965 | 53090 | 23810 | 72295 | NaN |
38 | 48371 | 124392 | 46355 | 23123 | 106810 | NaN |
39 | 31875 | 130319 | 84835 | 34024 | 233474 | NaN |
40 | 25632 | 100078 | 41943 | 16259 | 87799 | NaN |
41 | 34807 | 165200 | 71756 | 30744 | 168022 | 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 | PSL | PiS | Lewica | Konfederacja | KO | MN | |
---|---|---|---|---|---|---|---|---|
Nr okręgu | ||||||||
1 | Legnica | 12 | 1 | 6 | 2 | 0 | 3 | 0 |
2 | Wałbrzych | 8 | 0 | 4 | 1 | 0 | 3 | 0 |
3 | Wrocław | 14 | 1 | 5 | 2 | 1 | 5 | 0 |
4 | Bydgoszcz | 12 | 1 | 5 | 2 | 0 | 4 | 0 |
5 | Toruń | 13 | 1 | 6 | 2 | 0 | 4 | 0 |
6 | Lublin | 15 | 1 | 9 | 1 | 1 | 3 | 0 |
7 | Chełm | 12 | 1 | 8 | 1 | 0 | 2 | 0 |
8 | Zielona Góra | 12 | 1 | 4 | 2 | 1 | 4 | 0 |
9 | Łódź | 10 | 0 | 4 | 2 | 0 | 4 | 0 |
10 | Piotrków Trybunalski | 9 | 1 | 6 | 1 | 0 | 1 | 0 |
11 | Sieradz | 12 | 1 | 7 | 1 | 0 | 3 | 0 |
12 | Kraków | 8 | 0 | 6 | 0 | 0 | 2 | 0 |
13 | Kraków | 14 | 1 | 6 | 2 | 1 | 4 | 0 |
14 | Nowy Sącz | 10 | 1 | 8 | 0 | 0 | 1 | 0 |
15 | Tarnów | 9 | 1 | 7 | 0 | 0 | 1 | 0 |
16 | Płock | 10 | 1 | 6 | 1 | 0 | 2 | 0 |
17 | Radom | 9 | 1 | 6 | 0 | 0 | 2 | 0 |
18 | Siedlce | 12 | 1 | 9 | 0 | 0 | 2 | 0 |
19 | Warszawa | 20 | 1 | 6 | 3 | 1 | 9 | 0 |
20 | Warszawa | 12 | 1 | 6 | 1 | 0 | 4 | 0 |
21 | Opole | 12 | 1 | 5 | 1 | 0 | 4 | 1 |
22 | Krosno | 11 | 1 | 8 | 0 | 0 | 2 | 0 |
23 | Rzeszów | 15 | 1 | 10 | 1 | 1 | 2 | 0 |
24 | Białystok | 14 | 1 | 8 | 1 | 1 | 3 | 0 |
25 | Gdańsk | 12 | 0 | 4 | 1 | 1 | 6 | 0 |
26 | Słupsk | 14 | 1 | 5 | 2 | 1 | 5 | 0 |
27 | Bielsko-Biała | 9 | 0 | 5 | 1 | 0 | 3 | 0 |
28 | Częstochowa | 7 | 0 | 4 | 1 | 0 | 2 | 0 |
29 | Katowice | 9 | 0 | 4 | 1 | 0 | 4 | 0 |
30 | Bielsko-Biała | 9 | 0 | 5 | 1 | 0 | 3 | 0 |
31 | Katowice | 12 | 0 | 5 | 1 | 1 | 5 | 0 |
32 | Katowice | 9 | 0 | 4 | 2 | 0 | 3 | 0 |
33 | Kielce | 16 | 1 | 10 | 1 | 1 | 3 | 0 |
34 | Elbląg | 8 | 1 | 4 | 1 | 0 | 2 | 0 |
35 | Olsztyn | 10 | 1 | 5 | 1 | 0 | 3 | 0 |
36 | Kalisz | 12 | 1 | 6 | 2 | 0 | 3 | 0 |
37 | Konin | 9 | 1 | 5 | 1 | 0 | 2 | 0 |
38 | Piła | 9 | 1 | 4 | 1 | 0 | 3 | 0 |
39 | Poznań | 10 | 0 | 3 | 2 | 0 | 5 | 0 |
40 | Koszalin | 8 | 1 | 3 | 1 | 0 | 3 | 0 |
41 | Szczecin | 12 | 1 | 4 | 2 | 0 | 5 | 0 |
Podsumowanie krajowe¶
mandaty_kraj = mandaty_okr.iloc[:, 2:].agg('sum')
mandaty_kraj
1 30 2 235 3 49 4 11 5 134 10 1 dtype: int64
mandaty_kraj_prezentacja = pd.DataFrame({
"Skrót": kraj_pow["Skrót"],
"Przyznanych mandatów": mandaty_kraj
}).sort_values("Przyznanych mandatów", ascending=False)
mandaty_kraj_prezentacja
Skrót | Przyznanych mandatów | |
---|---|---|
2 | PiS | 235 |
5 | KO | 134 |
3 | Lewica | 49 |
1 | PSL | 30 |
4 | Konfederacja | 11 |
10 | MN | 1 |
mandaty_kraj_prezentacja.plot(kind='pie',
figsize=(8,6),
title='Faktyczny podział mandatów',
y="Przyznanych mandatów",
labels=[f'{komitet}: {mandaty}' for i, (komitet, mandaty) in mandaty_kraj_prezentacja.iterrows()])
<Axes: title={'center': 'Faktyczny podział mandatów'}, ylabel='Przyznanych 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 39 2 204 3 58 4 31 5 128 10 0 dtype: int64
mandaty_globalne_prezentacja = pd.DataFrame({
"Skrót": kraj_pow["Skrót"],
"Przyznanych mandatów": mandaty_globalne
}).sort_values("Przyznanych mandatów", ascending=False)
mandaty_globalne_prezentacja
Skrót | Przyznanych mandatów | |
---|---|---|
Nr listy | ||
2 | PiS | 204 |
5 | KO | 128 |
3 | Lewica | 58 |
1 | PSL | 39 |
4 | Konfederacja | 31 |
10 | MN | 0 |
mandaty_globalne_prezentacja.plot(kind='pie',
figsize=(8,6),
title='Potencjalny podział mandatów',
y="Przyznanych mandatów",
labels=[f'{komitet}: {mandaty}' for i, (komitet, mandaty) in mandaty_globalne_prezentacja.iterrows()])
<Axes: title={'center': 'Potencjalny podział mandatów'}, ylabel='Przyznanych 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 | ||||||||||
2 | PiS | 8051935 | 43.592991 | 44.000111 | 202.4 | 235 | 51.086957 | 7.086845 | 16.106426 | 32.6 |
5 | KO | 5060355 | 27.396646 | 27.652506 | 127.2 | 134 | 29.130435 | 1.477929 | 5.344646 | 6.8 |
3 | Lewica | 2319946 | 12.560134 | 12.677435 | 58.3 | 49 | 10.652174 | -2.025261 | -15.975322 | -9.3 |
1 | PSL | 1578523 | 8.546087 | 8.625900 | 39.7 | 30 | 6.521739 | -2.104161 | -24.393524 | -9.7 |
4 | Konfederacja | 1256953 | 6.805115 | 6.868668 | 31.6 | 11 | 2.391304 | -4.477364 | -65.185329 | -20.6 |
10 | MN | 32094 | 0.173756 | 0.175379 | 0.8 | 1 | 0.217391 | 0.042012 | 23.955216 | 0.2 |
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 | ||||||||||
2 | PiS | 8051935 | 43.592991 | 44.000111 | 202.4 | 204 | 44.347826 | 0.347715 | 0.790259 | 1.6 |
5 | KO | 5060355 | 27.396646 | 27.652506 | 127.2 | 128 | 27.826087 | 0.173581 | 0.627721 | 0.8 |
3 | Lewica | 2319946 | 12.560134 | 12.677435 | 58.3 | 58 | 12.608696 | -0.068739 | -0.542218 | -0.3 |
1 | PSL | 1578523 | 8.546087 | 8.625900 | 39.7 | 39 | 8.478261 | -0.147639 | -1.711582 | -0.7 |
4 | Konfederacja | 1256953 | 6.805115 | 6.868668 | 31.6 | 31 | 6.739130 | -0.129538 | -1.885926 | -0.6 |
10 | MN | 32094 | 0.173756 | 0.175379 | 0.8 | 0 | 0.000000 | -0.175379 | -100.000000 | -0.8 |
Waga głosu¶
W poniższym fragmencie pod uwagę biorę wszystkie głosy ważne; także te oddane na komitety, które nie przekroczyły progu.
kol = 'Liczba głosów ważnych oddanych łącznie na wszystkie listy kandydatów'
Średnia liczba głosów na mandat
srednia = stat_okr[kol].sum() / MANDATY_SEJM
srednia
40153.717391304344
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[kol]], axis=1)
wagi_glosow["Głosów na mandat"] = (wagi_glosow[kol] / wagi_glosow["Liczba mandatów"]).round().astype('int')
wagi_glosow.sort_values("Głosów na mandat")
Okręg | Liczba mandatów | Liczba głosów ważnych oddanych łącznie na wszystkie listy kandydatów | Głosów na mandat | |
---|---|---|---|---|
34 | Elbląg | 8 | 250819 | 31352 |
35 | Olsztyn | 10 | 331684 | 33168 |
7 | Chełm | 12 | 401318 | 33443 |
21 | Opole | 12 | 406439 | 33870 |
40 | Koszalin | 8 | 271711 | 33964 |
5 | Toruń | 13 | 452330 | 34795 |
2 | Wałbrzych | 8 | 283002 | 35375 |
22 | Krosno | 11 | 390581 | 35507 |
33 | Kielce | 16 | 569891 | 35618 |
1 | Legnica | 12 | 432436 | 36036 |
8 | Zielona Góra | 12 | 437917 | 36493 |
14 | Nowy Sącz | 10 | 370199 | 37020 |
16 | Płock | 10 | 370561 | 37056 |
30 | Bielsko-Biała | 9 | 333836 | 37093 |
24 | Białystok | 14 | 520578 | 37184 |
17 | Radom | 9 | 335009 | 37223 |
32 | Katowice | 9 | 335431 | 37270 |
6 | Lublin | 15 | 565597 | 37706 |
18 | Siedlce | 12 | 452906 | 37742 |
29 | Katowice | 9 | 340647 | 37850 |
36 | Kalisz | 12 | 459152 | 38263 |
4 | Bydgoszcz | 12 | 459982 | 38332 |
11 | Sieradz | 12 | 460239 | 38353 |
10 | Piotrków Trybunalski | 9 | 346326 | 38481 |
15 | Tarnów | 9 | 347088 | 38565 |
38 | Piła | 9 | 349051 | 38783 |
31 | Katowice | 12 | 469633 | 39136 |
41 | Szczecin | 12 | 470529 | 39211 |
37 | Konin | 9 | 353041 | 39227 |
23 | Rzeszów | 15 | 588786 | 39252 |
12 | Kraków | 8 | 316214 | 39527 |
28 | Częstochowa | 7 | 284517 | 40645 |
26 | Słupsk | 14 | 580722 | 41480 |
9 | Łódź | 10 | 415540 | 41554 |
27 | Bielsko-Biała | 9 | 389256 | 43251 |
25 | Gdańsk | 12 | 528829 | 44069 |
13 | Kraków | 14 | 649287 | 46378 |
3 | Wrocław | 14 | 654455 | 46747 |
20 | Warszawa | 12 | 598727 | 49894 |
39 | Poznań | 10 | 514527 | 51453 |
19 | Warszawa | 20 | 1381917 | 69096 |
wagi_glosow["Głosów na mandat"].describe()
count 41.000000 mean 39450.292683 std 6358.334138 min 31352.000000 25% 36493.000000 50% 38263.000000 75% 39527.000000 max 69096.000000 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.203878540443991
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'>
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[kol])
wagi_glosow["Sprawiedliwe głosy na mandat"] = (wagi_glosow[kol] / wagi_glosow["Sprawiedliwa liczba mandatów"]).round().astype('int')
wagi_glosow
Okręg | Liczba mandatów | Liczba głosów ważnych oddanych łącznie na wszystkie listy kandydatów | Głosów na mandat | Sprawiedliwa liczba mandatów | Sprawiedliwe głosy na mandat | |
---|---|---|---|---|---|---|
1 | Legnica | 12 | 432436 | 36036 | 11 | 39312 |
2 | Wałbrzych | 8 | 283002 | 35375 | 7 | 40429 |
3 | Wrocław | 14 | 654455 | 46747 | 17 | 38497 |
4 | Bydgoszcz | 12 | 459982 | 38332 | 11 | 41817 |
5 | Toruń | 13 | 452330 | 34795 | 11 | 41121 |
6 | Lublin | 15 | 565597 | 37706 | 14 | 40400 |
7 | Chełm | 12 | 401318 | 33443 | 10 | 40132 |
8 | Zielona Góra | 12 | 437917 | 36493 | 11 | 39811 |
9 | Łódź | 10 | 415540 | 41554 | 10 | 41554 |
10 | Piotrków Trybunalski | 9 | 346326 | 38481 | 9 | 38481 |
11 | Sieradz | 12 | 460239 | 38353 | 11 | 41840 |
12 | Kraków | 8 | 316214 | 39527 | 8 | 39527 |
13 | Kraków | 14 | 649287 | 46378 | 16 | 40580 |
14 | Nowy Sącz | 10 | 370199 | 37020 | 9 | 41133 |
15 | Tarnów | 9 | 347088 | 38565 | 9 | 38565 |
16 | Płock | 10 | 370561 | 37056 | 9 | 41173 |
17 | Radom | 9 | 335009 | 37223 | 8 | 41876 |
18 | Siedlce | 12 | 452906 | 37742 | 11 | 41173 |
19 | Warszawa | 20 | 1381917 | 69096 | 35 | 39483 |
20 | Warszawa | 12 | 598727 | 49894 | 15 | 39915 |
21 | Opole | 12 | 406439 | 33870 | 10 | 40644 |
22 | Krosno | 11 | 390581 | 35507 | 10 | 39058 |
23 | Rzeszów | 15 | 588786 | 39252 | 15 | 39252 |
24 | Białystok | 14 | 520578 | 37184 | 13 | 40044 |
25 | Gdańsk | 12 | 528829 | 44069 | 13 | 40679 |
26 | Słupsk | 14 | 580722 | 41480 | 15 | 38715 |
27 | Bielsko-Biała | 9 | 389256 | 43251 | 10 | 38926 |
28 | Częstochowa | 7 | 284517 | 40645 | 7 | 40645 |
29 | Katowice | 9 | 340647 | 37850 | 8 | 42581 |
30 | Bielsko-Biała | 9 | 333836 | 37093 | 8 | 41730 |
31 | Katowice | 12 | 469633 | 39136 | 12 | 39136 |
32 | Katowice | 9 | 335431 | 37270 | 8 | 41929 |
33 | Kielce | 16 | 569891 | 35618 | 14 | 40706 |
34 | Elbląg | 8 | 250819 | 31352 | 6 | 41803 |
35 | Olsztyn | 10 | 331684 | 33168 | 8 | 41460 |
36 | Kalisz | 12 | 459152 | 38263 | 11 | 41741 |
37 | Konin | 9 | 353041 | 39227 | 9 | 39227 |
38 | Piła | 9 | 349051 | 38783 | 9 | 38783 |
39 | Poznań | 10 | 514527 | 51453 | 13 | 39579 |
40 | Koszalin | 8 | 271711 | 33964 | 7 | 38816 |
41 | Szczecin | 12 | 470529 | 39211 | 12 | 39211 |
wagi_glosow["Sprawiedliwe głosy na mandat"].describe()
count 41.000000 mean 40280.097561 std 1182.023304 min 38481.000000 25% 39227.000000 50% 40400.000000 75% 41173.000000 max 42581.000000 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.106546087679634
Wartość 35 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.