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.

In [1]:
import pandas as pd
import numpy as np
In [2]:
#pd.set_option('display.float_format', '{:.2f}'.format)
In [3]:
path = 'dane/2015/robocze/'
In [4]:
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.

In [5]:
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)
In [6]:
def_kom
Out[6]:
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
In [7]:
map_kom_skrot = dict(def_kom["Skrót"])
In [8]:
def_okr
Out[8]:
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
In [9]:
map_okr = dict(def_okr["Okręg"])
In [10]:
assert def_okr["Liczba mandatów"].sum() == MANDATY_SEJM
In [11]:
wyn_okr
Out[11]:
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¶

In [12]:
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.

In [13]:
kraj_liczbowo
Out[13]:
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
In [14]:
suma_wszystkich_glosow = kraj_liczbowo.sum()
In [15]:
suma_wszystkich_glosow
Out[15]:
15200671

W wyborach do Sejmu w 2015 oddano łącznie {{suma_wszystkich_glosow}} 15.2 mln ważnych głosów.

In [16]:
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.

In [19]:
kraj.round(2)
Out[19]:
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.

In [20]:
kraj_pow = kraj[kraj["Powyżej progu"]].drop(columns=["Próg", "Powyżej progu"])
kom_pow_idx = kraj_pow.index
In [21]:
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.

In [22]:
kraj_pow.round(2)
Out[22]:
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.

In [23]:
wyn_okr_pow = wyn_okr[kom_pow_idx].copy()
In [24]:
wyn_okr_pow
Out[24]:
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.

In [25]:
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.

In [26]:
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)
In [27]:
# mandaty_surowe_okr
In [28]:
mandaty_okr = def_okr.copy()
for kom in kom_pow_idx:
    mandaty_okr[kom] = mandaty_surowe_okr[kom]
In [29]:
mandaty_okr_prezentacja = mandaty_okr.rename(columns = map_kom_skrot)

Prawidzwe wyniki¶

Podział mandatów między komitety w poszcególnych okręgach

In [30]:
mandaty_okr_prezentacja
Out[30]:
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¶

In [31]:
mandaty_kraj = mandaty_okr.iloc[:, 2:].agg('sum')
In [32]:
mandaty_kraj
Out[32]:
1     235
2     138
5      16
7      42
8      28
16      1
dtype: int64
In [33]:
mandaty_kraj_prezentacja = pd.DataFrame({
    "Skrót": kraj_pow["Skrót"],
    "Przyznanych mandatów": mandaty_kraj
})
In [34]:
mandaty_kraj_prezentacja
Out[34]:
Skrót Przyznanych mandatów
1 PiS 235
2 PO 138
5 PSL 16
7 Kukiz 42
8 Nowoczesna 28
16 MN 1
In [35]:
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()])
Out[35]:
<Axes: title={'center': 'Faktyczny podział mandatów'}>
No description has been provided for this image
In [36]:
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.

In [37]:
mandaty_globalne = pd.Series(
    dhont_podzial_liczbowy(MANDATY_SEJM, kraj_pow["Liczba głosów"]),
    index = kom_pow_idx)
In [38]:
mandaty_globalne
Out[38]:
Nr listy
1     208
2     133
5      28
7      48
8      42
16      1
dtype: int64
In [39]:
mandaty_globalne_prezentacja = pd.DataFrame({
    "Skrót": kraj_pow["Skrót"],
    "Przyznanych mandatów": mandaty_globalne
})
In [40]:
mandaty_globalne_prezentacja
Out[40]:
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
In [41]:
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()])
Out[41]:
<Axes: title={'center': 'Potencjalny podział mandatów'}>
No description has been provided for this image
In [42]:
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).

In [43]:
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"]
In [44]:
tabela1.sort_values("Liczba głosów", ascending=False)
Out[44]:
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.

In [45]:
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"]
In [46]:
tabela2.sort_values("Liczba głosów", ascending=False)
Out[46]:
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.

In [45]:
stat_okr["Głosy ważne"].sum() / MANDATY_SEJM
Out[45]:
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.

In [46]:
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')
In [47]:
wagi_glosow.sort_values("Głosów na mandat")
Out[47]:
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
In [48]:
wagi_glosow["Głosów na mandat"].describe()
Out[48]:
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
In [49]:
wagi_glosow["Głosów na mandat"].max() / wagi_glosow["Głosów na mandat"].min()
Out[49]:
2.186504292273907
In [50]:
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)
Out[50]:
<Axes: ylabel='Okręg'>
No description has been provided for this image

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).

In [51]:
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')
In [52]:
wagi_glosow
Out[52]:
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
In [53]:
wagi_glosow["Sprawiedliwe głosy na mandat"].describe()
Out[53]:
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
In [54]:
wagi_glosow["Sprawiedliwe głosy na mandat"].max() / wagi_glosow["Sprawiedliwe głosy na mandat"].min()
Out[54]:
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.