PK!9ՖPP HISTORY.rstRelease History --------------- 0.1.2 (2019-07-11) ~~~~~~~~~~~~~~~~~~ * Bugfix Dhondt/Jefferson apportionment method 0.1.1 (2018-01-08) ~~~~~~~~~~~~~~~~~~ * Include license, history, readme in build. 0.1.0 (2018-01-08) ~~~~~~~~~~~~~~~~~~ * First release. * Various quotas, disproportionality measures, and apportionment functions PK!}KHhhvoting/__init__.py"""Module level accessible objects.""" from voting.__version__ import __version__ from voting.__version__ import __description__ from voting.__version__ import __url__ from voting.__version__ import __title__ from voting.__version__ import __author__ from voting.__version__ import __author_email__ from voting.__version__ import __license__ from voting.__version__ import __copyright__ from voting.__version__ import __docs_copyright__ __all__ = ( __version__, __description__, __url__, __title__, __author__, __author_email__, __license__, __copyright__, __docs_copyright__, ) PK!&uuvoting/__version__.py"""Version information.""" __title__ = "voting" __description__ = "Voting and election related functions." __url__ = "https://github.com/crflynn/voting" __version__ = "0.1.2" __author__ = "Christopher Flynn" __author_email__ = "crf204@gmail.com" __license__ = "MIT" __copyright__ = "Copyright 2018-2019 Christopher Flynn" __docs_copyright__ = "2018-2019 Christopher Flynn" PK! g  voting/apportionment.py"""Apportionment methods.""" from math import ceil from math import floor from math import modf from math import sqrt from operator import itemgetter def adams(votes, seats): """Apportion seats using the Adams method. :param list votes: a list of vote counts :param int seats: the number of seats to apportion """ divisor = 1.0 * sum(votes) / seats decs, lower = zip(*[modf(1.0 * v / divisor) for v in votes]) upper = [ceil(1.0 * v / divisor) for v in votes] surplus = int(sum(upper) - seats) if surplus > 0: # divisor diffs that would remove another seat to each group diffs = [1.0 * vote / max(floor(i + dec), 1) for i, dec, vote in zip(lower, decs, votes)] # argsort low to high divs = [i[0] for i in sorted(enumerate(diffs), key=itemgetter(1))] for k in range(surplus): upper[divs[k]] -= 1 return upper def dhondt(votes, seats): """Apportion seats using the D'Hondt method. Identical to the Jefferson method. :param list votes: a list of vote counts :param int seats: the number of seats to apportion """ return jefferson(votes, seats) def hagenbach_bischoff(votes, seats): """Apportion seats using the Hagenbach-Bischoff method. Identical to the Jefferson method. :param list votes: a list of vote counts :param int seats: the number of seats to apportion """ return jefferson(votes, seats) def hamilton(votes, seats): """Apportion seats using the Hamilton method. Known also as the Vinton method. :param list votes: a list of vote counts :param int seats: the number of seats to apportion """ divisor = 1.0 * sum(votes) / seats decs, lower = zip(*[modf(1.0 * v / divisor) for v in votes]) lower = [int(l) for l in lower] unallocated = int(seats - sum(lower)) if unallocated > 0: # argsort high to low leftovers = [i[0] for i in sorted(enumerate(decs), key=itemgetter(1))][::-1] for k in range(unallocated): lower[leftovers[k]] += 1 return lower def huntington_hill(votes, seats): """Apportion seats using the Huntington-Hill method. :param list votes: a list of vote counts :param int seats: the number of seats to apportion """ if seats < len(votes): raise ValueError("Must be at least one seat per group.") elif seats == len(votes): return [1] * len(votes) divisor = 1.0 * sum(votes) / seats decs, lower = zip(*[modf(1.0 * v / divisor) for v in votes]) quotients = [d + l for d, l in zip(decs, lower)] geo_means = [sqrt(ceil(q) * floor(q)) for q in quotients] assigned = [ceil(q) if q >= g else floor(q) for q, g in zip(quotients, geo_means)] unallocated = int(seats - sum(assigned)) if unallocated > 0: # divisors that would add another seat to each group geo_means_next = [sqrt(ceil(q + 1) * floor(q + 1)) for q in quotients] diffs = [ 1.0 * vote / max(gn, 1) if q >= g else 1.0 * vote / max(g, 1) for vote, q, g, gn in zip(votes, quotients, geo_means, geo_means_next) ] # argsort divs = [i[0] for i in sorted(enumerate(diffs), key=itemgetter(1))][::-1] for k in range(unallocated): assigned[divs[k]] += 1 elif unallocated < 0: unallocated = abs(unallocated) # divisors that would subtract a seat from each group geo_means_next = [sqrt(ceil(q - 1) * floor(q - 1)) for q in quotients] diffs = [ 1.0 * vote / max(gn, 1) if q < g else 1.0 * vote / max(g, 1) for vote, q, g, gn in zip(votes, quotients, geo_means, geo_means_next) ] # sort fix to prevent allocations to be zero to any one group diffs = [float("inf") if d < divisor else d for d in diffs] # argsort divs = [i[0] for i in sorted(enumerate(diffs), key=itemgetter(1))] for k in range(unallocated): assigned[divs[k]] -= 1 return assigned def jefferson(votes, seats): """Apportion seats using the Jefferson method. Known also as the D'Hondt method or Hagenbach-Bischoff method. :param list votes: a list of vote counts :param int seats: the number of seats to apportion """ allocated = [0] * len(votes) while sum(allocated) < seats: quotients = [1.0 * vote / (allocated[idx] + 1) for idx, vote in enumerate(votes)] idx_max = quotients.index(max(quotients)) allocated[idx_max] += 1 return allocated def sainte_lague(votes, seats): """Apportion seats using the Sainte-Lague method. Identical to the Webster method. :param list votes: a list of vote counts :param int seats: the number of seats to apportion """ return webster(votes, seats) def vinton(votes, seats): """Apportion seats using the Vinton method. Identical to the Hamilton method. :param list votes: a list of vote counts :param int seats: the number of seats to apportion """ return hamilton(votes, seats) def webster(votes, seats): """Apportion seats using the Webster method. Known also as the Sainte-Lague method. :param list votes: a list of vote counts :param int seats: the number of seats to apportion """ divisor = 1.0 * sum(votes) / seats decs, lower = zip(*[modf(1.0 * v / divisor) for v in votes]) lower = [int(l) for l in lower] assigned = [round(l + d) for l, d in zip(lower, decs)] unallocated = int(seats - sum(assigned)) if unallocated > 0: # divisors that would add another seat to each group diffs = [ 1.0 * vote / (i + 1.5) if dec >= 0.5 else 1.0 * vote / (i + 0.5) for i, dec, vote in zip(lower, decs, votes) ] # argsort divs = [i[0] for i in sorted(enumerate(diffs), key=itemgetter(1))][::-1] for k in range(unallocated): assigned[divs[k]] += 1 elif unallocated < 0: unallocated = abs(unallocated) # divisors that would subtract a seat from each group diffs = [ 1.0 * vote / (i + 0.5) if dec >= 0.5 else 1.0 * vote / (i - 0.5) for i, dec, vote in zip(lower, decs, votes) ] # argsort divs = [i[0] for i in sorted(enumerate(diffs), key=itemgetter(1))] for k in range(unallocated): assigned[divs[k]] -= 1 return assigned PK!I[ [ voting/diversity.py"""Measures of diversity.""" from math import exp from math import log from voting.util import normalize def berger_parker(groups): r"""Calculate the Berger-Parker index. .. math:: max(p_i) :param list group: a list of integers representing populations of groups """ groups = normalize(groups) return max(groups) def general(groups, q=1): r"""Calculate the general diversity index. .. math:: \left( \sum_{i=1}^n p_i^q \right) ^ {1/(1-q)} :param list groups: a list of integers representing populations of groups :param float q: weight value """ if q == 1: return exp(shannon(groups)) groups = normalize(groups) return sum([g ** q for g in groups]) ** (1.0 / (1 - q)) def gini_simpson(groups): r"""Calculate the Gini-Simpson index. .. math:: 1 - \sum_{i=1}^n p_i^2 :param list group: a list of integers representing populations of groups """ return 1 - simpson(groups) def golosov(groups): r"""Calculate the effective number of parties using Golosov. .. math:: \sum_{i=1}^n \frac{p_i}{p_i + p_1^2 - p_i^2} where :math:`p_1` is the largest proportion. :param list group: a list of integers representing populations of a group """ groups = normalize(groups) p1 = max(groups) return sum([g / (g + p1 ** 2 - g ** 2) for g in groups]) def inverse_simpson(groups): r"""Calculate the Inverse-Simpson index. .. math:: \frac{1}{\sum_{i=1}^n p_i^2} :param list group: a list of integers representing populations of groups """ return 1.0 / simpson(groups) def laakso_taagepera(groups): r"""Calculate the effective number of parties using Laakso-Taagepera. .. math:: \frac{1}{\sum_{i=1}^n p_i^2} :param list group: a list of integers representing populations of groups """ groups = normalize(groups) return 1.0 / sum([g ** 2 for g in groups]) def renyi(groups, q=0): r"""Calculate the Renyi entropy. .. math:: \frac{1}{1-q} \ln \left( \sum_{i=1}^n p_i ^ q \right) :param list groups: a list of integers representing populations of groups :param float q: weight value """ if q == 1: return shannon(groups) groups = normalize(groups) return 1.0 / (1 - q) * log(sum([g ** q for g in groups])) def shannon(groups): r"""Calculate the Shannon index. .. math:: \sum_{i=1}^n p_i \ln (p_i) :param list groups: a list of integers representing populations of groups """ groups = normalize(groups) return sum([g * log(g) for g in groups]) def simpson(groups): r"""Calculate the Simpson index. .. math:: \sum_{i=1}^n p_i^2 :param list groups: a list of integers representing populations of groups """ groups = normalize(groups) return sum([g ** 2 for g in groups]) PK!__mWWvoting/proportion.py"""Measures of disproportionality.""" from math import sqrt from voting.util import normalize def adjusted_loosemore_hanby(votes, seats, parties="votes"): r"""Calculate the adjusted Loosemore-Hanby index of disproportionality. .. math:: N \sum_{i=1}^n |v_i - s_i| where N is :math:`\sum_{i=1}^n v_i` if ``parties == 'votes'`` or :math:`\sum_{i=1}^n s_i` if ``parties == 'seats'``. :param list votes: a list of vote counts :param list seats: a list of seat counts :param str parties: ``votes`` or ``seats`` to use to calculate the effective number of parties """ votes = normalize(votes) seats = normalize(seats) return grofman(votes, seats, parties) def dhondt(votes, seats): r"""Calculate the D'Hondt index of disproportionality. .. math:: \max \frac{s_i}{v_i} :param list votes: a list of vote counts :param list seats: a list of seat counts """ votes = normalize(votes) seats = normalize(seats) return max([1.0 * s / v for v, s in zip(votes, seats)]) def gallagher(votes, seats): r"""Calculate the Gallagher index of disproportionality. .. math:: \sqrt{\frac{1}{2} \sum_{i=1}^n (v_i - s_i)^2} :param list votes: a list of vote counts :param list seats: a list of seat counts """ votes = normalize(votes) seats = normalize(seats) return sqrt(1.0 / 2) * least_square(votes, seats) def grofman(votes, seats, parties="votes"): r"""Calculate the Grofman index of disproportionality. .. math:: N \sum_{i=1}^n |v_i - s_i| where N is :math:`\sum_{i=1}^n v_i^2` if ``parties == 'votes'`` or :math:`\sum_{i=1}^n s_i^2` if ``parties == 'seats'``. :param list votes: a list of vote counts :param list seats: a list of seat counts :param str parties: ``votes`` or ``seats`` to use to calculate the effective number of parties """ votes = normalize(votes) seats = normalize(seats) if parties == "votes": n = sum([v ** 2 for v in votes]) elif parties == "seats": n = sum([s ** 2 for s in seats]) else: raise ValueError("Parties argument must be either votes or seats.") return n * sum([abs(v - s) for v, s in zip(votes, seats)]) def least_square(votes, seats): r"""Calculate the least squares index of disproportionality. .. math:: \sqrt{\sum_{i=1}^n (v_i - s_i)^2} :param list votes: a list of vote counts :param list seats: a list of seat counts """ votes = normalize(votes) seats = normalize(seats) return sqrt(sum([(v - s) ** 2 for v, s in zip(votes, seats)])) def lijphart(votes, seats): r"""Calculate the Lijphart index of disproportionality. .. math:: \max | v_i - s_i | :param list votes: a list of vote counts :param list seats: a list of seat counts """ votes = normalize(votes) seats = normalize(seats) return max([abs(v - s) for v, s in zip(votes, seats)]) def loosemore_hanby(votes, seats): r"""Calculate Loosemore-Hanby index of disproportionality. .. math:: \frac{1}{2} \sum_{i=1}^n |v_i - s_i| :param list votes: a list of vote counts :param list seats: a list of seat counts """ votes = normalize(votes) seats = normalize(seats) return 1.0 / 2 * sum([abs(v - s) for v, s in zip(votes, seats)]) def rae(votes, seats): r"""Calculate Rae's index of disproportionality. .. math:: \frac{1}{n} \sum_{i=1}^n |v_i - s_i| :param list votes: a list of vote counts :param list seats: a list of seat counts """ votes = normalize(votes) seats = normalize(seats) return 1.0 / len(votes) * sum([abs(v - s) for v, s in zip(votes, seats)]) def regression(votes, seats): r"""Calculate the regression index of disproportionality. .. math:: \frac{\sum_{i=1}^n v_i s_i}{\sum_{i=1}^n v_i^2} :param list votes: a list of vote counts :param list seats: a list of seat counts """ votes = normalize(votes) seats = normalize(seats) num = sum([v * s for v, s in zip(votes, seats)]) denom = sum([v ** 2 for v in votes]) return 1.0 * num / denom def rose(votes, seats): r"""Calculate the Rose index of proportionality. .. math:: 100 - \frac{1}{2} \sum_{i=1}^n |v_i - s_i| :param list votes: a list of vote counts :param list seats: a list of seat counts """ votes = normalize(votes) seats = normalize(seats) return 100 - loosemore_hanby(votes, seats) def sainte_lague(votes, seats): r"""Calculate the Sainte-Lague index of disproportionality. .. math:: \sum_{i=1}^n \frac{(v_i - s_i)^2}{v_i} :param list votes: a list of vote counts :param list seats: a list of seat counts """ votes = normalize(votes) seats = normalize(seats) return sum(1.0 / v * (v - s) ** 2 for v, s in zip(votes, seats)) PK!voting/quota.py"""Quota calculations.""" def droop(votes, seats): r"""Calculate the Droop quota. :math:`\frac{votes}{seats + 1} + 1` :param int votes: the number of votes :param int seats: the number of seats """ return int(1.0 * votes / (seats + 1) + 1) def hagenbach_bischoff(votes, seats): r"""Calculate the Hagenbach-Bischoff quota. :math:`\frac{votes}{seats + 1}` :param int votes: the number of votes :param int seats: the number of seats """ return 1.0 * votes / (seats + 1) def hare(votes, seats): r"""Calculate the Hare quota. :math:`\frac{votes}{seats}` :param int votes: the number of votes :param int seats: the number of seats """ return 1.0 * votes / seats def imperiali(votes, seats): r"""Calculate the Imperiali quota. :math:`\frac{votes}{seats + 2}` :param int votes: the number of votes :param int seats: the number of seats """ return 1.0 * votes / (seats + 2) PK!$"voting/util.py"""Utility functions.""" def normalize(values): """Normalize a list of values.""" total = sum(values) return [1.0 * value / total for value in values] PK! 77"voting-0.1.2.dist-info/LICENSE.txtMIT License Copyright (c) 2018-2019 Christopher Flynn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H|n-WYvoting-0.1.2.dist-info/WHEEL A н#Z;/" bFF]xzwK;<*mTֻ0*Ri.4Vm0[H, JPK!H\%/(voting-0.1.2.dist-info/METADATAVmo6_}ZX4j Âd[]dR -%&ɒZ;R27!5܀MޯlΎqz-&q1x KZJ{1X:cs+{%xd6,0W[}<W\cZT0 ["a^3Y6x>Y nU74aovq%OO8=XP/j-' łnsKϝ<!5\0F \9xn B|H kE*ln %w+˾$:Bz k}OUjqW.to2Qؔcڶ$cؓʔ3F Y~"J^G])0WYͬaf VKw"D3elމmLmrNuc dF]*a3,J qCj@-+i"5I6B*{YNGKPl_'u+Gőu{oIT coNC' G @ Q(xdir BNN f$@ T].X^Z. ~&08"\\q?szRa3,|et@+A3<"OGCt0~qzmTBY_\z#{j:Q]O6 W<"k?zs}YGA?PB Q|'_Y2jY氛O 31`?D& k`$ nF)| 0(8vG/_vYfH sO=tC4ŎQnr6KxU5NƓ_sFttxt'_Nh򂉼w͗ljLaJP֥6ިWW'Ŀ|:(Iyyhѩ[&bnNc mȹwy|?wOGtW6E{ؗ 1,bbR/d<8A7 oPK!Hoy-<vvoting-0.1.2.dist-info/RECORDurP}a:($,ӆB9p@;S"7o)vL eBss!-#5ShҚn{j9_ɣ@0 |Z{8^uDVA1݇\_.){0ԛ9(j "My?K)LybS8 cyi8SIH0<Ēk{]p:X;K+5 5v5 (bR?^NjuY{0Lg]˙ݠhwckݏF{摠E?o^eJkYU0b[HoIR[~>ةxV'v[}&)*;Cr@d7w[L5?Tz XzˋY悍n7Xr)iҊUH]$(Ȝ$ܷBW0}ԍ; ?3!E ֎K @"Cqte܍K)zؤ@|}E'*.uIzEKfJ&$_ɃJ[ɕyO)c3VAw:}l1I` ưPK!9ՖPP HISTORY.rstPK!}KHhhyvoting/__init__.pyPK!&uuvoting/__version__.pyPK! g  voting/apportionment.pyPK!I[ [ voting/diversity.pyPK!__mWW*voting/proportion.pyPK!>voting/quota.pyPK!$"Bvoting/util.pyPK! 77"Bvoting-0.1.2.dist-info/LICENSE.txtPK!H|n-WYVGvoting-0.1.2.dist-info/WHEELPK!H\%/(Gvoting-0.1.2.dist-info/METADATAPK!Hoy-<vSMvoting-0.1.2.dist-info/RECORDPK /O