Skip to content

Instantly share code, notes, and snippets.

@kespindler
Created November 20, 2017 04:23
Show Gist options
  • Save kespindler/a532ed990a616162da42a9b6bb1f18c2 to your computer and use it in GitHub Desktop.
Save kespindler/a532ed990a616162da42a9b6bb1f18c2 to your computer and use it in GitHub Desktop.
"""
ipython3 -i run.py
"""
import numpy as np
N_candidates = 5
def general_votes(candidate_R, candidate_D):
"""If candidate_R and candidate_D were the winners of the
R and D primaries, respectively, this function creates a matrix
to send votes from the original preferred candidate to the nearest
candidate on the spectrum.
:param candidate_R:
:param candidate_D:
:return:
"""
assert candidate_R < candidate_D, '%s, %s' % (candidate_R, candidate_D)
r = np.zeros((N_candidates, N_candidates))
midp = (candidate_R + candidate_D) // 2
r[0:midp, candidate_R] = 1
r[midp:N_candidates, candidate_D] = 1
if midp * 2 == candidate_R + candidate_D:
r[midp, candidate_R] = 0.5
r[midp, candidate_D] = 0.5
return r
def election_results(population, turnout, voting_blocs):
"""
Let B be number of voting blocs. Let C be the number of candidates.
:param population: percent of the population represented by each voting bloc. 1xB array which sums to 1.
:param turnout: percent of each voting bloc that turns out to vote. 1xB array with all entries in [0, 1].
:param voting_blocs: The likelihood that a person in a voting bloc votes for a candidate. BxC matrix, where each row sums to 1.
:return: results of an election on a per-bloc basis, a BxC matrix. result[i, j] is result for bloc i and candidate j.
"""
assert population.sum() == 1
assert (0 <= turnout <= 1).all()
assert (voting_blocs.sum(1) == 1).all()
voters = population * turnout
votes = voting_blocs.T * voters
return votes
def vote_distribution_normal(num_candidates, preferred_candidate, std_dev, samples=1000):
"""Create a voting bloc using a statistical distribution instead of static definition.
:param num_candidates: How many candidates are available.
:param preferred_candidate: Most preferred candidate for bloc. (Can be fractional).
:param std_dev: How diffused is the preference. A typical value might range from .5 (highly specific) to 2 (fairly diffuse)
:param samples: Samples from normal. This controls the variance in the ultimate distribution.
:return:
"""
bins = np.arange(-0.5, num_candidates + 0.5)
distribution = np.random.normal(preferred_candidate, std_dev, samples)
histogram, bins = np.histogram(distribution, bins, normed=True)
return histogram
"""
There are assumed to be 5 candidates in this race, ranging from a hard-line Republican (0)
to a centrist (2), to a hard-line Democrat (4).
We divide voters into 3 buckets of each Republicans (R) and Democrats (D): hard, moderate, and leaning,
for a total of 6 blocs of voters.
`voting_blocs` is then a 6x5 matrix, where each of 6 voting blocs have a percent
chance to vote for each of 5 candidates. Each 1x5 row in the matrix should sum to 1.
"""
hard_R = [.9, .1, 0, 0, 0]
mode_R = [.3, .5, .2, 0, 0]
lean_R = [0, .55, .45, .0, 0]
hard_D = list(reversed(hard_R))
mode_D = list(reversed(mode_R))
lean_D = list(reversed(lean_R))
voting_blocs = np.array([hard_R, mode_R, lean_R, lean_D, mode_D, hard_D])
"""
`population` represents what percent of the population falls into each of the
6 voting blocs. It is a 1x6 matrix which sums to 1.
`primary_turnout` is the turnout of each bloc, 1x6 matrix with each value in [0, 1].
"""
population = np.array([.05, .25, .23, .16, .27, .04])
primary_turnout = np.array([.90, .13, .20, .20, .13, .90])
"""
Now comes the fun part. By playing with the ratio of R voters
who vote in the D primary (tallies_D), we can flip the election
from going to the hardline Republican to the centrist Republican.
Vary `R_turnout_for_D_primary` to change the outcome. Flip occurs between 0.7 and 0.8.
"""
primary_votes = election_results(population, primary_turnout, voting_blocs)
blocs_R = primary_votes[:, :3]
blocs_D = primary_votes[:, 3:]
tallies_R = blocs_R.sum(1)
victor_R = tallies_R.argmax()
tallies_D = blocs_D.sum(1)
R_turnout_for_D_primary = 0.8
R_votes_in_D_primary = tallies_R.copy() * R_turnout_for_D_primary
R_votes_in_D_primary[:N_candidates//2] = 0
tallies_D = blocs_R.sum(1) * R_turnout_for_D_primary + tallies_D # open primary
victor_D = tallies_D.argmax()
# General
rebalancing = general_votes(victor_R, victor_D)
votes = population @ voting_blocs @ rebalancing
victor_overall = votes.argmax()
print(tallies_R, victor_R)
print(tallies_D, victor_D)
print(votes, victor_overall)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment