block by michalskop 9d2cb8950f551a33eea51d925f945dce

CZ: voters changes

Full Screen

Voters changes in Czech elections 2013 → 2017

Estimates of numbers of people changing parties between Czech elections 2013-2017 based on ecological inference model.

Individual parties:

Example (i: 1-8)

index.html?i=3

Overview

See overview chart

Analysis flow

  1. download data for 2017 (to /json): downloader.py
  2. extract data (list_2017.csv): scraper.py
  3. join 2013 and 2017, select random polling station only, to be able to run R calculation (data_filtered.csv and data_filtered_random.csv): join_data.py
  4. analysis - ecological inference (matn_filtered.csv, matp_filtered.csv): ei_psp.py calling ei.r
  5. manually recalculate numbers to real total matn_filtered_adjusted.csv
  6. manually reorder parties to have better matrix matn_filtered_adjusted_reordered.csv
  7. manually add labels/party names matn_chart.csv
  8. manually prepare lists of parties list_2013_filtered.csv and list_2017_filtered.csv
  9. overview chart: bubbles_psp.html
  10. calculate errors of model presnost.ods
  11. individual party charts (with ?i=1 parameter, i: 1-8) index.html and index_cs.html
  12. generate pictures (PNGs): generate.py

CC-BY Michal Škop

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
    <script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <style>
        .mcontainer {
            padding-left: 10px;
            padding-right: 10px;
        }
        .header {
            padding-left: 10px;
            padding-right: 10px;
        }
        .h3 {
            position: relative;
            bottom: 20px;
        }
        .note {
            padding: 10px;
        }
        .logo {
            width: 64px;
            height: 64px;
            border-radius: 50%;
            margin: 10px;
            border: 1px solid #eee;
        }
        #chart {
            border: 1px solid #eee;
        }
        .hithit {
            font-size: 1.5em;
            width: 100vw;
            /*background-color: #fed201;*/
            padding: 10px;
            height: 100vh;
            color: white;
        }
    </style>
        <div class="header">
            <h1 id="h1" class="text-center"><span id="h1pic"></span> <span id="h1name"></span></h1>
            <h3 class="text-center h3">Gains and Losts of Voters in Czech Elections 2013 → 2017</h3>
            <div class="row">
                <div class="col-xs-4 col-xs-offset-1 text-center">
                    <strong>Came to <span class="shortname"></span></strong>
                </div>
                <div class="col-xs-4 col-xs-offset-2 text-center">
                    <strong>Left from <span class="shortname"></span></strong>
                </div>
            </div>
        </div>
        <div id="chart"></div>
        <div class="note xbg-info">
            <i class="fa fa-info-circle"></i> Estimated by Ecological Inference. Only streams with more than 25,000 people shown.<br />
            CC-BY Michal Škop, KohoVolit.eu, VolebníKalkulačka.cz
        </div>
    <script>
        var formatNumber = d3.format(",.0f");

        d3.csv("list_2017_filtered.csv", function (er,d2017) {
            d3.csv("list_2013_filtered.csv", function(err,d2013) {
                d3.csv("matn_chart.csv", function(errr,source) {

                    var d13to17 = {};
                    var party2color = {};
                    d2013.forEach(function(d) {
                        d13to17[d['party']] = {}
                        party2color[d['party']] = d['color']
                    });
                    d2017.forEach(function(d) {
                        party2color[d['party']] = d['color']
                    });
                    var d17to13 = {};
                    source.forEach(function(d) {
                        d17to13[d['name']] = {}
                        for (k in d) {
                            if (k != 'name') {
                                d17to13[d['name']][k] = parseInt(d[k])
                                d13to17[k][d['name']] = parseInt(d[k])
                            }
                        }
                    })



                    var names = source['name'];
                    var i = QueryString.i;
                    if (i === undefined) {
                        i = 1;
                    }
                    var party = d2017[i]['party']
                    var prev_party = d2017[i]['previous']
                    var data = source[i];

                    d3.select("#h1name")
                        .html(d2017[i]['party_name']);
                    d3.select("#h1pic")
                        .html("<img class='logo' src='./" + d2017[i]['party'] + ".png' />");
                    d3.selectAll(".shortname")
                        .html(d2017[i]['party'])

                    var sumout = 0
                    for (k in d13to17[prev_party]) {
                        if (k != prev_party) {
                            sumout += d13to17[prev_party][k]
                        }
                    }

                    if (d2017[i]['votes'] > 1000000) {
                        var h = 700;
                    } else {
                        var h = 400;
                    }

                    var margin = {top: 10, right: 10, bottom: 10, left: 10},
                        width = 930 - margin.left - margin.right,
                        height = h - margin.top - margin.bottom;
                    var svg = d3.select("#chart").append("svg")
                        .attr("width", width + margin.left + margin.right)
                        .attr("height", height + margin.top + margin.bottom)
                      .append("g")
                        .attr("transform",
                              "translate(" + margin.left + "," + margin.top + ")");

                    maxsum = Math.max(sumout, d2017[i]['votes'])

                    var x = d3.scale.linear()
              		             .domain([0, 100])
              		             .range([0, width])
              		var y = d3.scale.linear()
              		             .domain([0, maxsum * 1.1])
              		             .range([0, height]);

                    // basic rectangle
                    var offset = (maxsum - d2017[i]['votes']) / 2;
                    svg.append("rect")
                        .attr("width", x(20))
                        .attr("height", y(d2017[i]['votes']))
                        .attr("x", x(40))
                        .attr("y", y(offset))
                        .attr("fill", d2017[i]['color']);

                    svg.append("text")
                        .attr("x", x(50))
                        .attr("y", function() {
                            return y(d2017[i]['votes']/2 + offset);
                        })
                        .attr("dy", 0)
                        .attr("text-anchor", "middle")
                        .attr("fill", "white")
                        .attr("font-size", "40px")
                        .text(party)

                    svg.append("text")
                        .attr("x", x(50))
                        .attr("y", function() {
                            return y(d2017[i]['votes']/2 * 1.3 + offset);
                        })
                        .attr("dy", 0)
                        .attr("text-anchor", "middle")
                        .attr("fill", "white")
                        .attr("font-size", "20px")
                        .text(formatNumber(d2017[i]['votes']))

                    // prepare out and in rectangles
                    var outs = []
                    for (k in d13to17[prev_party]) {
                        if ((d13to17[prev_party][k] > 25000) && (party != k)) {
                            outs.push({
                                'name': k,
                                'value': d13to17[prev_party][k],
                                'color': party2color[k]
                            })
                        }
                    }
                    outs.sort(function(a, b) {
                        return b.value - a.value;
                    });
                    var ins = []
                    for (k in d13to17) {
                        if ((d13to17[k][party] > 25000) && (prev_party != k)) {
                            ins.push({
                                'name': k,
                                'value': d13to17[k][party],
                                'color': party2color[k]
                            })
                        }
                    }
                    ins.sort(function(a, b) {
                        return b.value - a.value;
                    });

                    s = 0
                    outs.forEach(function (out,k) {
                        s += y(out['value'])
                        s += 15
                    })
                    s = s - 15
                    var offset = (y(maxsum) - s) / 2;
                    outs.forEach(function (out, k) {
                        outs[k]['y0'] = offset;
                        outs[k]['y1'] = offset + y(out['value'])
                        outs[k]['half'] = (outs[k]['y1'] + outs[k]['y0']) / 2
                        offset = outs[k]['y1'] + 15;
                        outs[k]['arrow'] = [
                            {"x": x(76.98), "y": outs[k]['y0']}, {"x": x(79), "y": outs[k]['half']}, {"x": x(76.98), "y": outs[k]['y1']}, {"x": x(76.98), "y": outs[k]['y0']}
                        ]
                    });

                    s = 0
                    ins.forEach(function (out,k) {
                        s += y(out['value'])
                        s += 15
                    })
                    s = s - 15
                    var offset = (y(maxsum) - s) / 2;
                    ins.forEach(function (inn, k) {
                        ins[k]['y0'] = offset;
                        ins[k]['y1'] = offset + y(inn['value'])
                        ins[k]['half'] = (ins[k]['y1'] + ins[k]['y0']) / 2
                        offset = ins[k]['y1'] + 15;
                        ins[k]['arrow'] = [
                            {"x": x(34.95), "y": ins[k]['y0']}, {"x": x(37), "y": ins[k]['half']}, {"x": x(34.95), "y": ins[k]['y1']},
                             {"x": x(34.95), "y": ins[k]['y0']}
                        ]
                    });


                    var arrowFunction = d3.svg.area()
                                .x(function(d) { return d.x; })
                                .y1(function(d) { return d.y; });

                    // arrows out
                    var rectouts = svg.selectAll(".rectout")
                       .data(outs)
                           .enter()
                       .append("rect")
                            .attr("width", x(14))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(63))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", d2017[i]['color']);

                    var arrowouts = svg.selectAll(".arrowout")
                       .data(outs)
                           .enter()
                        .append("path")
                            .attr("d", function(d) {
                                return arrowFunction(d['arrow']);
                            })
                            .attr("fill", d2017[i]['color']);

                    svg.selectAll(".textout")
                       .data(outs)
                           .enter()
                        .append("text")
                            .attr("x", x(70))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4.5), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4.5), 18)
                            })
                            .text(function(d) {
                                return formatNumber(my_round(d['value']))
                            })

                    //arrows in
                    svg.selectAll(".rectin")
                       .data(ins)
                           .enter()
                       .append("rect")
                            .attr("width", x(14))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(21))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".arrowin")
                       .data(ins)
                           .enter()
                        .append("path")
                            .attr("d", function(d) {
                                return arrowFunction(d['arrow']);
                            })
                            .attr("fill", function(d) {
                                return d['color']
                            });

                    svg.selectAll(".textin")
                       .data(ins)
                           .enter()
                        .append("text")
                            .attr("x", x(28))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4), 20)
                            })
                            .text(function(d) {
                                return formatNumber(my_round(d['value']))
                            })

                    // outs
                    svg.selectAll(".rectend")
                       .data(outs)
                           .enter()
                       .append("rect")
                            .attr("width", x(16))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(84))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".textout")
                       .data(outs)
                           .enter()
                        .append("text")
                            .attr("x", x(92))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4), 20)
                            })
                            .text(function(d) {
                                return d['name']
                            })
                    // ins
                    svg.selectAll(".rectstart")
                       .data(ins)
                           .enter()
                       .append("rect")
                            .attr("width", x(16))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(0))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".textstart")
                       .data(ins)
                           .enter()
                        .append("text")
                            .attr("x", x(8))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4), 20)
                            })
                            .text(function(d) {
                                return d['name']
                            })



                    var pause = 0;

                })
            })
        })

        function my_round(n) {
            if (n < 100000) {
                return Math.round(n/10000)*10000;
            }
            return Math.round(n/20000)*20000;
        }
        var QueryString = function () {
        // This function is anonymous, is executed immediately and
        // the return value is assigned to QueryString!
          var query_string = {};
          var query = window.location.search.substring(1);
          var vars = query.split("&");
          for (var i=0;i<vars.length;i++) {
            var pair = vars[i].split("=");
                // If first entry with this name
            if (typeof query_string[pair[0]] === "undefined") {
              query_string[pair[0]] = decodeURIComponent(pair[1]);
                // If second entry with this name
            } else if (typeof query_string[pair[0]] === "string") {
              var arr = [ query_string[pair[0]],decodeURIComponent(pair[1]) ];
              query_string[pair[0]] = arr;
                // If third or later entry with this name
            } else {
              query_string[pair[0]].push(decodeURIComponent(pair[1]));
            }
          }
          return query_string;
        }();

    </script>
</body>
</html>

ano.svg

<svg width="930" height="700">
    <g transform="translate(10,10)">
        <rect width="182" height="618.1818181818181" x="364" y="0" fill="#261060"></rect>
        <text x="455" y="309.09090909090907" dy="0" text-anchor="middle" fill="white" font-size="40px">ANO</text>
        <text x="455" y="401.8181818181818" dy="0" text-anchor="middle" fill="white" font-size="20px">1,500,113</text>
        <rect width="127.4" height="59.28988501048073" x="573.3" y="188.1298752766685" fill="#261060"></rect>
        <rect width="127.4" height="29.209363197669404" x="573.3" y="262.41976028714924" fill="#261060"></rect>
        <rect width="127.4" height="26.03585681393821" x="573.3" y="306.62912348481865" fill="#261060"></rect>
        <rect width="127.4" height="21.323605742912832" x="573.3" y="347.66498029875686" fill="#261060"></rect>
        <rect width="127.4" height="20.603272129257164" x="573.3" y="383.9885860416697" fill="#261060"></rect>
        <rect width="127.4" height="10.460084734222733" x="573.3" y="419.59185817092686" fill="#261060"></rect>
        <path d="M700.518,188.1298752766685L718.9,217.7748177819089L700.518,247.41976028714924L700.518,188.1298752766685L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <path d="M700.518,262.41976028714924L718.9,277.02444188598395L700.518,291.62912348481865L700.518,262.41976028714924L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <path d="M700.518,306.62912348481865L718.9,319.6470518917878L700.518,332.66498029875686L700.518,306.62912348481865L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <path d="M700.518,347.66498029875686L718.9,358.3267831702133L700.518,368.9885860416697L700.518,347.66498029875686L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <path d="M700.518,383.9885860416697L718.9,394.2902221062983L700.518,404.59185817092686L700.518,383.9885860416697L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <path d="M700.518,419.59185817092686L718.9,424.8219005380382L700.518,430.0519429051496L700.518,419.59185817092686L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <text x="637" y="224.44148444857555" text-anchor="middle" fill="white" font-size="18">140,000</text>
        <text x="637" y="283.69110855265063" text-anchor="middle" fill="white" font-size="18">70,000</text>
        <text x="637" y="326.31371855845447" text-anchor="middle" fill="white" font-size="18">60,000</text>
        <text x="637" y="364.99344983687996" text-anchor="middle" fill="white" font-size="18">50,000</text>
        <text x="637" y="400.956888772965" text-anchor="middle" fill="white" font-size="18">50,000</text>
        <text x="637" y="431.4885672047049" text-anchor="middle" fill="white" font-size="18">30,000</text>
        <rect width="127.4" height="150.15947586493937" x="191.1" y="61.77709551941027" fill="#f1953e"></rect>
        <rect width="127.4" height="84.93301989431944" x="191.1" y="226.93657138434963" fill="#888"></rect>
        <rect width="127.4" height="82.25402171218252" x="191.1" y="326.8695912786691" fill="#8c0000"></rect>
        <rect width="127.4" height="36.106928429573486" x="191.1" y="424.1236129908516" fill="#f51941"></rect>
        <rect width="127.4" height="21.8873450927303" x="191.1" y="475.2305414204251" fill="#888"></rect>
        <rect width="127.4" height="15.993219419894558" x="191.1" y="512.1178865131553" fill="#004494"></rect>
        <rect width="127.4" height="13.29361672935795" x="191.1" y="543.1111059330499" fill="#e6ac21"></rect>
        <path d="M318.045,61.77709551941027L336.7,136.85683345187994L318.045,211.93657138434963L318.045,61.77709551941027L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#f1953e"></path>
        <path d="M318.045,226.93657138434963L336.7,269.4030813315094L318.045,311.8695912786691L318.045,226.93657138434963L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#888"></path>
        <path d="M318.045,326.8695912786691L336.7,367.99660213476034L318.045,409.1236129908516L318.045,326.8695912786691L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#8c0000"></path>
        <path d="M318.045,424.1236129908516L336.7,442.17707720563834L318.045,460.2305414204251L318.045,424.1236129908516L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#f51941"></path>
        <path d="M318.045,475.2305414204251L336.7,486.17421396679026L318.045,497.1178865131554L318.045,475.2305414204251L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#888"></path>
        <path d="M318.045,512.1178865131553L336.7,520.1144962231026L318.045,528.1111059330499L318.045,512.1178865131553L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#004494"></path>
        <path d="M318.045,543.1111059330499L336.7,549.7579142977288L318.045,556.4047226624078L318.045,543.1111059330499L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#e6ac21"></path>
        <text x="254.8" y="149.37012310729156" text-anchor="middle" fill="white" font-size="37.53986896623484">360,000</text>
        <text x="254.8" y="276.48083298936933" text-anchor="middle" fill="white" font-size="21.233254973579868">200,000</text>
        <text x="254.8" y="374.8511039441089" text-anchor="middle" fill="white" font-size="20.563505428045634">200,000</text>
        <text x="254.8" y="448.843743872305" text-anchor="middle" fill="white" font-size="20">90,000</text>
        <text x="254.8" y="492.84088063345695" text-anchor="middle" fill="white" font-size="20">50,000</text>
        <text x="254.8" y="526.7811628897692" text-anchor="middle" fill="white" font-size="20">40,000</text>
        <text x="254.8" y="556.4245809643954" text-anchor="middle" fill="white" font-size="20">30,000</text>
        <rect width="145.6" height="59.28988501048073" x="764.4" y="188.1298752766685" fill="#888"></rect>
        <rect width="145.6" height="29.209363197669404" x="764.4" y="262.41976028714924" fill="#888"></rect>
        <rect width="145.6" height="26.03585681393821" x="764.4" y="306.62912348481865" fill="#000000"></rect>
        <rect width="145.6" height="21.323605742912832" x="764.4" y="347.66498029875686" fill="#0066a5"></rect>
        <rect width="145.6" height="20.603272129257164" x="764.4" y="383.9885860416697" fill="#004494"></rect>
        <rect width="145.6" height="10.460084734222733" x="764.4" y="419.59185817092686" fill="#5d8c00"></rect>
        <text x="837.2" y="224.44148444857555" text-anchor="middle" fill="white" font-size="20">nevoliči</text>
        <text x="837.2" y="283.69110855265063" text-anchor="middle" fill="white" font-size="20">neparlamentní</text>
        <text x="837.2" y="326.31371855845447" text-anchor="middle" fill="white" font-size="20">Piráti</text>
        <text x="837.2" y="364.99344983687996" text-anchor="middle" fill="white" font-size="20">SPD</text>
        <text x="837.2" y="400.956888772965" text-anchor="middle" fill="white" font-size="20">ODS</text>
        <text x="837.2" y="431.4885672047049" text-anchor="middle" fill="white" font-size="20">STAN</text>
        <rect width="145.6" height="150.15947586493937" x="0" y="61.77709551941027" fill="#f1953e"></rect>
        <rect width="145.6" height="84.93301989431944" x="0" y="226.93657138434963" fill="#888"></rect>
        <rect width="145.6" height="82.25402171218252" x="0" y="326.8695912786691" fill="#8c0000"></rect>
        <rect width="145.6" height="36.106928429573486" x="0" y="424.1236129908516" fill="#f51941"></rect>
        <rect width="145.6" height="21.8873450927303" x="0" y="475.2305414204251" fill="#888"></rect>
        <rect width="145.6" height="15.993219419894558" x="0" y="512.1178865131553" fill="#004494"></rect>
        <rect width="145.6" height="13.29361672935795" x="0" y="543.1111059330499" fill="#e6ac21"></rect>
        <text x="72.8" y="149.37012310729156" text-anchor="middle" fill="white" font-size="37.53986896623484">ČSSD</text>
        <text x="72.8" y="276.48083298936933" text-anchor="middle" fill="white" font-size="21.233254973579868">nevoliči</text>
        <text x="72.8" y="374.8511039441089" text-anchor="middle" fill="white" font-size="20.563505428045634">KSČM</text>
        <text x="72.8" y="448.843743872305" text-anchor="middle" fill="white" font-size="20">Úsvit</text>
        <text x="72.8" y="492.84088063345695" text-anchor="middle" fill="white" font-size="20">neparlamentní</text>
        <text x="72.8" y="526.7811628897692" text-anchor="middle" fill="white" font-size="20">ODS</text>
        <text x="72.8" y="556.4245809643954" text-anchor="middle" fill="white" font-size="20">KDU-ČSL</text>
    </g>
</svg>

bubbles_psp.html

cssd.svg

d3.tips.js

downloader.py

ei.r

ei_psp.py

generate.py

index_cs.html

index_cs_print.html

join_data.py

list_2013_filtered.csv

list_2017_filtered.csv

matn_chart.csv

matn_filtered.csv

matn_filtered_adjusted.csv

matn_filtered_adjusted_reordered.csv

matp_filtered.csv

scraper.py