Skip to content

Instantly share code, notes, and snippets.

@endolith
Last active December 27, 2020 18:57
Show Gist options
  • Select an option

  • Save endolith/fa1d19767e5c2e9d4bd15e391ba79f91 to your computer and use it in GitHub Desktop.

Select an option

Save endolith/fa1d19767e5c2e9d4bd15e391ba79f91 to your computer and use it in GitHub Desktop.

Revisions

  1. endolith revised this gist Jun 10, 2017. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,10 @@
    See also:

    - 1D:
    - http://zesty.ca/voting/voteline/
    - 2D:
    - http://bolson.org/voting/sim_one_seat/www/spacegraph.html
    - http://zesty.ca/voting/sim/
    - http://rangevoting.org/IEVS/Pictures.html
    - 1D:
    - http://zesty.ca/voting/voteline/
    - ND:
    - https://github.com/electology/vse-sim
  2. endolith revised this gist Jun 10, 2017. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,8 @@
    See also:

    - 2D:
    - http://bolson.org/voting/sim_one_seat/www/spacegraph.html
    - http://zesty.ca/voting/sim/
    - http://rangevoting.org/IEVS/Pictures.html
    - http://bolson.org/voting/sim_one_seat/www/spacegraph.html
    - http://zesty.ca/voting/sim/
    - http://rangevoting.org/IEVS/Pictures.html
    - 1D:
    - http://zesty.ca/voting/voteline/
  3. endolith created this gist Dec 23, 2016.
    125 changes: 125 additions & 0 deletions election_simulator.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,125 @@
    # -*- coding: utf-8 -*-
    """
    Created on Sat Dec 17 11:33:35 2016
    """
    from __future__ import division, print_function
    from numpy.random import multivariate_normal
    import matplotlib.pyplot as plt
    from scipy.spatial.distance import cdist
    import numpy as np

    # Polarization between the two main factions
    pol = np.random.randn(1)[0]

    voters = np.concatenate((multivariate_normal(mean=(-pol, -pol),
    cov=((1, 0), (0, 1)), size=500),
    multivariate_normal(mean=(+pol, +pol),
    cov=((1, 0), (0, 1)), size=500)
    ))

    # Candidates distributed similarly to voters
    candidates = multivariate_normal(mean=(0, 0),
    cov=np.cov(voters, rowvar=False),
    size=9
    )

    plt.figure()
    plt.scatter(voters[:, 0], voters[:, 1], color='b', marker='.', alpha=0.2,
    label='voters')
    plt.scatter(candidates[:, 0], candidates[:, 1], color='r', marker='o',
    alpha=0.7, label='candidates')

    centroid = np.mean(voters, axis=0)
    plt.plot(centroid[0], centroid[1], color='k', marker='+', markersize=10,
    ls='none', label='centroid')

    dists = cdist(voters, candidates)


    def plurality(dists):
    """
    Honest plurality voting
    """
    # Voters choose the nearest candidate
    ballots = np.argmin(dists, axis=1)

    # Winner is the candidate with the most votes
    winner = np.argmax(np.bincount(ballots))
    return ballots, winner


    def approval_nearest(dists, n=3):
    """
    Honest approval voting of the nearest `n` candidates
    """
    # Voters choose the n nearest candidates
    ballots = np.argsort(dists, axis=1)[:, :n]

    # Winner is the candidate with the most votes
    winner = np.argmax(np.bincount(ballots.flat))
    return ballots, winner


    def approval_half(dists):
    """
    Honest approval voting of the nearest 50% of candidates
    """
    n_cands = dists.shape[1]
    return approval_nearest(dists, n=int(0.5 * n_cands))


    def score_normed(dists, levels=None):
    """
    Honest score voting, normalized to min and max for worst and best,
    optionally quantized to number of `levels`. All candidates are scored, so
    total vs average is irrelevant.
    """
    # Normalize from 0 to 1 for nearest to farthest
    normed = dists - np.amin(dists, axis=1)[:, np.newaxis]
    normed /= np.amax(normed, axis=1)[:, np.newaxis]

    # Farthest candidates get the lowest scores
    normed = 1 - normed

    # Optionally quantize to discrete scale
    if levels is not None:
    normed = np.around(normed * levels)

    # Total scores for each candidate
    scores = np.sum(normed, axis=0)

    # Winner is the candidate with the highest total/average score
    winner = np.argmax(scores)
    return ballots, winner


    def next_color():
    ax = plt.gca()
    while True:
    color = next(ax._get_lines.prop_cycler)['color']
    if color not in {'r', }: # , 'b'}:
    return color


    def next_size():
    s = 6
    while True:
    yield s
    s += 2

    sizes = next_size()


    for func in plurality, approval_nearest, approval_half, score_normed:
    name = func.__name__.replace('_', ' ')
    ballots, winner = func(dists)
    winner_loc = candidates[winner]
    plt.plot(winner_loc[0], winner_loc[1], marker='o', markersize=sizes.next(),
    ls='none', markeredgewidth=1, markeredgecolor=next_color(),
    color='none', # don't increment color cycle for invisible face
    markerfacecolor='none', label=name)

    plt.legend(loc='lower right', numpoints=1, fontsize='small')
    plt.axis('square')
    plt.grid(True)
    plt.tight_layout()
    8 changes: 8 additions & 0 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    See also:

    - 2D:
    - http://bolson.org/voting/sim_one_seat/www/spacegraph.html
    - http://zesty.ca/voting/sim/
    - http://rangevoting.org/IEVS/Pictures.html
    - 1D:
    - http://zesty.ca/voting/voteline/