Skip to content

Instantly share code, notes, and snippets.

@michalskop
Last active July 29, 2019 21:36
Show Gist options
  • Save michalskop/fcc68091ab8ae8daaf57729050c18182 to your computer and use it in GitHub Desktop.
Save michalskop/fcc68091ab8ae8daaf57729050c18182 to your computer and use it in GitHub Desktop.
Pollster 2018
# estimation of number of seats in CZ parliament based on pollsters' potentials
# !!! NOTE: HTML in /home/michal/dev/dark_corners/flow/test4/
import copy
import csv
import json
import numpy
import operator
code = "cvvm201803"
inputfile = code + ".csv"
# inputfile = "party_sociologu201706_kdustan.csv"
outputfile = "stats_" + code + ".json"
n = 550
runs = 10000
# D'Hondt
def dhondt(parties, seats):
line = []
for k in parties:
for j in range(1, seats + 1):
line.append({
'party_code': k,
'value': parties[k] / j
})
line_sorted = sorted(line, key=lambda x: x['value'], reverse=True)
nof_seats = {}
for k in parties:
nof_seats[k] = 0
for j in range(0, seats):
nof_seats[line_sorted[j]['party_code']] += 1
return nof_seats
trial_seats = {}
# current data / polls
current_poll = {}
with open(inputfile) as fin:
dr = csv.DictReader(fin)
for row in dr:
current_poll[row['party_code']] = row
# gains 2017
gains_prev = {}
with open("psp2017_results_selected.csv") as fin:
dr = csv.DictReader(fin)
for row in dr:
gains_prev[row['party_code']] = row
# votes 2017
votes_prev = {}
with open("psp2017_selected.csv") as fin:
dr = csv.DictReader(fin)
for row in dr:
try:
votes_prev[row['party_code']]
except Exception:
votes_prev[row['party_code']] = {}
votes_prev[row['party_code']][row['region_code']] = row
# votes 2017 totals
votes_totals_prev = {}
for k in votes_prev:
s = 0
for j in votes_prev[k]:
s += int(votes_prev[k][j]['votes'])
votes_totals_prev[k] = s
# seats in regions
regions_seats = {}
with open("psp2017_seats.csv") as fin:
dr = csv.DictReader(fin)
for row in dr:
regions_seats[row['region_code']] = row
# calculate seats from poll
def calculate_seats(current):
# calculated no of votes
calc_nof_votes = {}
for k in current:
party_code_prev = current[k]['party_code_2017']
if float(current[k]['gain']) >= float(current[k]['needs']):
calc_nof_votes[k] = float(current[k]['gain']) / float(gains_prev[party_code_prev]['gain']) * votes_totals_prev[party_code_prev]
else:
calc_nof_votes[k] = 0
# calculated no of votes by regions
calc_nof_votes_regions = {}
for k in calc_nof_votes:
party_code_prev = current[k]['party_code_2017']
for j in regions_seats:
try:
calc_nof_votes_regions[j]
except Exception:
calc_nof_votes_regions[j] = {}
calc_nof_votes_regions[j][k] = calc_nof_votes[k] / votes_totals_prev[party_code_prev] * int(votes_prev[party_code_prev][j]['votes'])
# calculate total seats
calc_seats = {}
for k in current:
calc_seats[k] = 0
for j in regions_seats:
calculated = dhondt(calc_nof_votes_regions[j], int(regions_seats[j]['seats']))
# if j == 'pa':
# print(j, calculated['pirati'])
for k in current:
calc_seats[k] += calculated[k]
return calc_seats
for i in range(0, runs):
# randomize sample
current = copy.deepcopy(current_poll)
for k in current_poll:
current[k]['gain'] = numpy.random.binomial(n, float(current[k]['gain'])) / n
calc_seats = calculate_seats(current)
for k in calc_seats:
try:
trial_seats[k]
except Exception:
trial_seats[k] = []
trial_seats[k].append(calc_seats[k])
seats = calculate_seats(current_poll)
stats = []
for k in trial_seats:
try:
difference = seats[k] - int(gains_prev[current_poll[k]['party_code_2017']]['seats'])
except Exception:
difference = seats[k]
row = {
'party_code': k,
'median': sorted(trial_seats[k])[round(runs * 0.5)],
'lo': sorted(trial_seats[k])[round(runs * 0.05)],
'hi': sorted(trial_seats[k])[round(runs * 0.95)],
'seats': seats[k],
'difference': difference,
'name': current_poll[k]['name'],
'color': current_poll[k]['color'],
'gain': current_poll[k]['gain']
}
print(row)
stats.append(row)
stats.sort(key=operator.itemgetter("gain"), reverse=True)
stats.sort(key=operator.itemgetter("hi"), reverse=True)
stats.sort(key=lambda x: x['seats'], reverse=True)
with open(outputfile, "w") as fout:
json.dump(stats[0:9], fout)
def majority_probability(seats, majority=101):
over = 0
for i in range(0, runs):
s = 0
for k in seats:
s += seats[k][i]
if s >= majority:
over += 1
return over / runs
# coalitions
coalitions = []
potentials = []
# single party
for k in trial_seats:
potentials.append({
'trial_seats': {k: trial_seats[k]},
'seats': seats[k],
'party_code': k
})
# two parties
for k in trial_seats:
for m in trial_seats:
if k < m:
potentials.append({
'trial_seats': {k: trial_seats[k], m: trial_seats[m]},
'seats': seats[k] + seats[m],
'party_code': '+'.join([k, m])
})
# multiple parties
with open("coalitions.csv") as fin:
dr = csv.DictReader(fin)
for row in dr:
keys = row['party_code'].split('+')
ts = {}
ss = 0
for key in keys:
ts[key] = trial_seats[key]
ss += seats[key]
potentials.append({
'trial_seats': ts,
'seats': ss,
'party_code': row['party_code']
})
# try potentials
for p in potentials:
mp = majority_probability(p['trial_seats'])
if mp > 0:
item = {
'party_code': p['party_code'],
'seats': p['seats'],
'majority_probability': mp
}
coalitions.append(item)
coalitions.sort(key=lambda x: x['majority_probability'], reverse=True)
<!doctype html>
<html lang="cs">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<title>Koalice</title>
<style>
.party {
float: left;
width: 10px;
height: 20px;
border-radius: 2px;
display: block;
line-height: 20px;
text-align: center;
color: #333;
font-weight: bold;
margin-right: 7px;
}
.party-name {
width: 60px;
}
.plus {
float: left:
}
.bg-ano {
background-color: #261060
}
.bg-cssd {
background-color: #F07D00
}
.bg-kscm {
background-color: #8c0000
}
.bg-spd {
background-color: #ea2329
}
.bg-ods {
background-color: #004494
}
.bg-pirati {
background-color: #000000
}
.bg-kdu-csl {
background-color: #e6ac21
}
.bg-top-09 {
background-color: #723769
}
.bg-stan {
background-color: #5d8c00
}
.row {
padding-top: 2em;
}
.bold {
font-weight: bold;
color: #444;
}
</style>
</head>
<body>
<div class="container">
<h2>Kdo by měl dostatek mandátů na vládu? <small><small><span class="badge badge-pill badge-secondary">1.4.2018</span> <span class="badge badge-primary">Experimentální</span></small></small></h2>
Jakou by měly <em>některé zvažované koalice</em> šanci získat 101 nebo více mandátů, čímž by získaly možnost podpory většinové vlády.
</div>
<div class="container bold">
<div class="row">
<div class="col">
<span class="party bg-ano"></span>
<span class="party">+</span>
<span class="party bg-kscm"></span>
<span class="party">+</span>
<span class="party bg-cssd"></span>
<span class="party">~</span>
131 mandátů<br />
šance na majoritu > 99 %
</div>
<div class="col">
<span class="party bg-ano"></span>
<span class="party">+</span>
<span class="party bg-kscm"></span>
<span class="party">+</span>
<span class="party bg-spd"></span>
<span class="party">~</span>
120<br />
šance ~ 98 %
</div>
<div class="col">
<span class="party bg-ano"></span>
<span class="party">+</span>
<span class="party bg-ods"></span>
<span class="party">~</span>
113<br />
šance ~ 86 %
</div>
<div class="col">
<span class="party bg-ano"></span>
<span class="party">+</span>
<span class="party bg-pirati"></span>
<span class="party">~</span>
110<br />
šance ~ 85 %
</div>
</div>
<div class="row">
<div class="col">
<span class="party bg-ano"></span>
<span class="party">+</span>
<span class="party bg-kscm"></span>
<span class="party">~</span>
108<br />
šance ~ 72 %
</div>
<div class="col">
<span class="party bg-ano"></span>
<span class="party">+</span>
<span class="party bg-cssd"></span>
<span class="party">~</span>
106<br />
šance ~ 67 %
</div>
<div class="col">
<span class="party bg-ods"></span>
<span class="party">+</span>
<span class="party bg-pirati"></span>
<span class="party">+</span>
<span class="party bg-kdu-csl"></span>
<span class="party">+</span>
<span class="party bg-top-09"></span>
<span class="party">+</span>
<span class="party bg-stan"></span>
<span class="party">+</span>
<span class="party bg-cssd"></span>
<span class="party">~</span>
80<br />
šance < 2 %
</div>
<div class="col">
<span class="party bg-ano"></span>
<span class="party">~</span>
83<br />
šance < 1 %
</div>
</div>
<div class="row legenda">
<div class="col">
Legenda:<br />
<span class="party bg-ano"></span>
<span class="party party-name">ANO</span>
<span class="party bg-ods"></span>
<span class="party party-name">ODS</span>
<span class="party bg-pirati"></span>
<span class="party party-name">Piráti</span>
<span class="party bg-kscm"></span>
<span class="party party-name">KSČM</span>
<span class="party bg-cssd"></span>
<span class="party party-name">ČSSD</span>
<span class="party bg-spd"></span>
<span class="party party-name">SPD</span>
<span class="party bg-kdu-csl"></span>
<span class="party party-name">KDU-ČSL</span>
<span class="party bg-top-09"></span>
<span class="party party-name">TOP 09</span>
<span class="party bg-stan"></span>
<span class="party party-name">STAN</span>
</div>
</div>
</div>
<div class="container">
<div class="card mt-4">
<div class="card-body bg-light small">
<p>Výpočet zisku mandátů: Jako základ je brán <strong>volební model CVVM z 15.3.2018</strong>. Přepočet na mandáty je po krajích s tím, že je zjednodušeně uvažováno stejné rozdělení voličů každé strany mezi kraji jako v volbách 2017. Také celkové počty mandátů pro každý kraj jsou zjednodušeně dle roku 2017.</p>
<p>Odhad počtu mandátů: Provedl jsem 10 000 simulací, kdy hodnoty volebního modelu byly náhodně upraveny se započítáním (zjednodušeně pouze) statistické chyby modelu. Jako další zjednodušení byly hodnoty pro jednotlivé strany uvažovány nezávislé.</p>
</div>
</div>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>
party_code gain needs party_code_2017 name color
ano 0.305 0.05 ano ANO #261060
cssd 0.11 0.05 cssd ČSSD #F07D00
kscm 0.11 0.05 kscm KSČM #8c0000
ods 0.125 0.05 ods ODS #004494
top-09 0.045 0.05 top-09 TOP 09 #723769
kdu-csl 0.045 0.05 kdu-csl KDU-ČSL #e6ac21
spd 0.065 0.05 spd SPD #ea2329
pirati 0.125 0.05 pirati Piráti #000000
zeleni 0.01 0.05 zeleni Zelení #06b15d
svobodni 0.01 0.05 svobodni Svobodní #009982
stan 0.045 0.05 stan STAN #5d8c00
party value date weight
Zelení 0.0146 2017-10-21 5
Svobodní 0.0156 2017-10-21 5
STAN 0.0518 2017-10-21 5
TOP 09 0.0531 2017-10-21 5
KDU-ČSL 0.058 2017-10-21 5
ČSSD 0.0727 2017-10-21 5
KSČM 0.0776 2017-10-21 5
SPD 0.1064 2017-10-21 5
Piráti 0.1079 2017-10-21 5
ODS 0.1132 2017-10-21 5
ANO 0.2964 2017-10-21 5
Svobodní 0 2017-12-17 2
Zelení 0 2017-12-17 2
ANO 0.355 2017-12-17 2
ČSSD 0.1 2017-12-17 2
KSČM 0.075 2017-12-17 2
ODS 0.115 2017-12-17 2
TOP 09 0.045 2017-12-17 2
KDU-ČSL 0.065 2017-12-17 2
Piráti 0.115 2017-12-17 2
SPD 0.065 2017-12-17 2
STAN 0.04 2017-12-17 2
ANO 0.305 2018-01-24 2
ČSSD 0.125 2018-01-24 2
KSČM 0.08 2018-01-24 2
ODS 0.12 2018-01-24 2
TOP 09 0.04 2018-01-24 2
KDU-ČSL 0.05 2018-01-24 2
Piráti 0.125 2018-01-24 2
SPD 0.075 2018-01-24 2
STAN 0.03 2018-01-24 2
Svobodní 0.015 2018-01-24 2
Zelení 0.015 2018-01-24 2
ANO 0.335 2018-02-15 2
ČSSD 0.12 2018-02-15 2
KSČM 0.1 2018-02-15 2
ODS 0.105 2018-02-15 2
TOP 09 0.035 2018-02-15 2
KDU-ČSL 0.035 2018-02-15 2
Piráti 0.13 2018-02-15 2
SPD 0.065 2018-02-15 2
STAN 0.03 2018-02-15 2
Svobodní 0.01 2018-02-15 2
Zelení 0.01 2018-02-15 2
ANO 0.305 2018-03-15 2
ČSSD 0.11 2018-03-15 2
KSČM 0.11 2018-03-15 2
ODS 0.125 2018-03-15 2
TOP 09 0.045 2018-03-15 2
KDU-ČSL 0.045 2018-03-15 2
Piráti 0.125 2018-03-15 2
SPD 0.065 2018-03-15 2
STAN 0.045 2018-03-15 2
Svobodní 0.01 2018-03-15 2
Zelení 0.01 2018-03-15 2
<html>
<head>
<meta charset="utf-8" />
<script src="./plotly-1.35.2.min.js"></script>
<script src="./plotly-locale-cs.js"></script>
</head>
<body>
<div id="tester" style="width:900px;height:800px;"></div>
Pozn: intervaly jsou jen hrubý odhad na ukázku
<script>
var hex2rgba = (str, a) => str.replace('#','').split('').reduce((r,c,i,{length: l},j,n)=>(j=parseInt(i*3/l),n=parseInt(c,16),r[j]=(l==3?n:r[j])*16+n,r),[0,0,0,a||1]);
var electionData = [
{name:"Zelení", value: 0.0146, color:"#06b15d"},
{name:"Svobodní", value: 0.0156, color:"#009982"},
{name:"STAN", value: 0.0518, color:"#5d8c00"},
{name:"TOP 09", value: 0.0531, color:"#723769"},
{name:"KDU-ČSL", value: 0.0580, color:"#e6ac21"},
{name:"ČSSD", value: 0.0727, color:"#F07D00"},
{name:"KSČM", value: 0.0776, color:"#8c0000"},
{name:"SPD", value: 0.1064, color:"#ea2329"},
{name:"Piráti", value: 0.1079, color:"#000000"},
{name:"ODS", value: 0.1132, color:"#004494"},
{name:"ANO", value: 0.2964, color:"#261060"}
];
var electionDate = '2017-10-21';
var dates = ['2017-12-17', '2018-01-24', '2018-02-15', '2018-03-15'];
var parties = [
{name:"Zelení", data:[0.015, 'nan', 0.1, 0.1], color:"#06b15d"},
{name:"Svobodní", data:[null, 0.015, 0.01, 0.01], color:"#009982"},
{name:"TOP 09", data:[0.045, 0.04, 0.035, 0.045], color:"#723769"},
{name:"STAN", data:[0.04, 0.03, 0.03, 0.045], color:"#5d8c00"},
{name:"KDU-ČSL", data:[0.065, 0.05, 0.035, 0.045], color:"#e6ac21"},
{name:"SPD", data:[0.065, 0.075, 0.065, 0.065], color:"#ea2329"},
{name:"KSČM", data:[0.075, 0.08, 0.1, 0.11], color:"#8c0000"},
{name:"ČSSD", data:[0.1, 0.125, 0.12, 0.11], color:"#F07D00"},
{name:"Piráti", data:[0.115, 0.125, 0.13, 0.125], color:"#000000"},
{name:"ODS", data:[0.115, 0.12, 0.105, 0.125], color:"#004494"},
{name:"ANO", data:[0.355, 0.305, 0.335, 0.305], color:"#261060"}
];
var currentEstimates = [
{name:"Zelení", data:[0.1], color:"#06b15d"},
{name:"Svobodní", data:[0.01], color:"#009982"},
{name:"TOP 09", data:[0.045], color:"#723769"},
{name:"STAN", data:[0.045], color:"#5d8c00"},
{name:"KDU-ČSL", data:[0.045], color:"#e6ac21"},
{name:"SPD", data:[0.065], color:"#ea2329"},
{name:"KSČM", data:[0.11], color:"#8c0000"},
{name:"ČSSD", data:[0.11], color:"#F07D00"},
{name:"Piráti", data:[0.125], color:"#000000"},
{name:"ODS", data:[0.125], color:"#004494"},
{name:"ANO", data:[0.305], color:"#261060"}
]
var elections = electionData.map( obj => {
var d = {
type: 'scatter',
mode: 'markers',
x: [electionDate],
y: [obj.value * 100],
name: "Volby: " + obj.name,
legendgroup: obj.name,
showlegend: false,
marker: {
size: 20,
color: "rgba(" + hex2rgba(obj.color, 1).join(',') + ")",
// border: {
// color: //"rgba(" + hex2rgba(obj.color).join(',') + ")",
// arearatio: 1
// }
}
}
return d
})
var data = parties.map(obj => {
var d = {
mode: 'markers+lines',
type: 'scatter',
// connectgaps: false,
line: {
color: "rgba(" + hex2rgba(obj.color).join(',') + ")",
shape: "spline"
},
textposition: 'top right',
textfont : {
family:'Arial',
size: 30,
color: "rgba(" + hex2rgba(obj.color).join(',') + ")"
}
};
d['legendgroup'] = obj.name;
d['name'] = obj.name;
d['x'] = dates;
d['y'] = obj.data.map( v => v * 100);
d['text'] = ['a','b','c','d'];
return d;
});
var currents = currentEstimates.map(obj => {
var d = {
mode: 'text',
type: 'scatter',
// connectgaps: false,
// line: {
// color: "rgba(" + hex2rgba(obj.color).join(',') + ")",
// shape: "spline"
// },
textposition: 'middle right',
textfont : {
family:'Arial',
size: 16,
// weight: 600,
color: "rgba(" + hex2rgba(obj.color).join(',') + ")"
}
};
d['legendgroup'] = obj.name;
d['name'] = obj.name;
d['x'] = [dates[dates.length -1]];
d['y'] = [obj.data[0] * 100];
d['text'] = " " + Math.round(obj.data[0] * 100) + "%";
return d;
});
var upper = parties.map(obj => {
var d = {
mode: 'lines',
type: 'scatter',
line: {
color: "rgba(" + hex2rgba(obj.color).join(',') + ")",
shape: "spline",
width: 0.01
},
showlegend: false,
hoverinfo: 'skip',
fill: 'tonexty'
};
d['legendgroup'] = obj.name;
d['name'] = obj.name;
d['x'] = dates;
d['y'] = obj.data.map( v => v * 1.15 * 100);
d['fillcolor'] = "rgba(" + hex2rgba(obj.color, 0.15).join(',') + ")";
return d;
});
var lower = parties.map(obj => {
var d = {
mode: 'lines',
type: 'scatter',
line: {
color: "rgba(" + hex2rgba(obj.color).join(',') + ")",
shape: "spline",
width: 0.01
},
showlegend: false,
hoverinfo: 'skip'
};
d['legendgroup'] = obj.name;
d['name'] = obj.name;
d['x'] = dates;
d['y'] = obj.data.map( v => v * 0.85 * 100);
return d;
});
var limit = [{
x: [electionDate, data[0]['x'][data[0]['x'].length - 1]],
y: [5, 5],
name: '5% hranice',
mode: 'lines',
// showlegend: false,
hoverinfo: 'skip',
fill: 'tozeroy',
line: {
color: "red",
dash: 'dot',
width: 3
}
}]
var bounds = [];
for (var i = 0; i < upper.length; i++) {
bounds.push(lower[i]);
bounds.push(upper[i]);
}
data = bounds.concat(data);
data = elections.concat(data);
data = limit.concat(data);
data = currents.concat(data);
var layout = {
xaxis: {
type: 'date',
title: 'Datum průzkumu'
},
yaxis: {
title: 'Volební model',
ticksuffix: '%',
showticksuffix: 'all' // or 'first' or 'all' (the default)
},
title:'Volební modely CVVM',
showlegend: true,
legend: {
traceorder: 'reversed'
},
annotations: [
{
xref: 'paper',
yref: 'paper',
x: 0.0,
y: 1.05,
xanchor: 'left',
yanchor: 'top',
text: 'Cool chart',
showarrow: true
}
]
};
// for (var i = 0; i < parties.length; i++) {
// var result = {
// xref: 'paper',
// x: 0.95,
// y: parties[i]['data'][3] * 100,
// xanchor: 'left',
// yanchor: 'middle',
// text: Math.round(parties[i]['data'][3] * 100) + "%",
// showarrow: false,
// legendgroup: parties[i]['name'], // does not work
// font: {
// color: parties[i]['color']
// }
// }
// layout.annotations.push(result)
// }
var config = {
displaylogo: false,
staticPlot: false,
locale: 'cs'
}
TESTER = document.getElementById('tester');
Plotly.plot('tester', data, layout, config);
</script>
</body>
</html>
This file has been truncated, but you can view the full file.
/**
* plotly.js v1.35.2
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
* Licensed under the MIT license
*/
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment