Source code for gerrychain.metrics.partisan

"""
The partisan metrics in this file are later used in the module
gerrychain.updaters.election.py. Thus, all of the election
results objects here are implicilty typed as ElectionResults,
but cannot be given an explicit type annotation due to problems
with circular imports.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy

if TYPE_CHECKING:
    from ..updaters.election import ElectionResults


[docs] def mean_median(election_results: ElectionResults) -> float: """Computes the Mean-Median score for the given ElectionResults. A positive value indicates an advantage for the first party listed in the Election's party_names_to_node_attribute_names dictionary. Args: election_results (ElectionResults): An ElectionResults object Returns: float: The Mean-Median score for the given ElectionResults """ first_party = election_results.election.parties[0] data = election_results.percents(first_party) return float(numpy.median(data) - numpy.mean(data))
def mean_thirdian(election_results: ElectionResults) -> float: """Computes the Mean-Median score for the given ElectionResults. A positive value indicates an advantage for the first party listed in the Election's party_names_to_node_attribute_names dictionary. The motivation for this score is that the minority party in many states struggles to win even a third of the seats. Args: election_results (ElectionResults): An ElectionResults object Returns: float: The Mean-Thirdian score for the given ElectionResults """ first_party = election_results.election.parties[0] data = election_results.percents(first_party) thirdian_index = round(len(data) / 3) thirdian = sorted(data)[thirdian_index] return float(thirdian - numpy.mean(data))
[docs] def efficiency_gap(election_results: ElectionResults) -> float: """Computes the efficiency gap for the given ElectionResults. A positive value indicates an advantage for the first party listed in the Election's party_names_to_node_attribute_names dictionary. Args: election_results (ElectionResults): An ElectionResults object Returns: float: The efficiency gap for the given ElectionResults """ party1, party2 = [election_results.counts(party) for party in election_results.election.parties] wasted_votes_by_part = map(wasted_votes, party1, party2) total_votes = election_results.total_votes() numerator = sum(waste2 - waste1 for waste1, waste2 in wasted_votes_by_part) return numerator / total_votes
[docs] def wasted_votes(party1_votes: float, party2_votes: float) -> tuple[float, float]: """Computes the wasted votes for each party in the given race. This function computes the wasted votes for each party in the given race. It returns a tuple of the wasted votes for each party. Args: party1_votes (float): the number of votes party1 received in the race party2_votes (float): the number of votes party2 received in the race Returns: tuple[float, float]: A tuple of the wasted votes for each party. """ total_votes = party1_votes + party2_votes if party1_votes > party2_votes: party1_waste = party1_votes - total_votes / 2 party2_waste = party2_votes else: party2_waste = party2_votes - total_votes / 2 party1_waste = party1_votes return party1_waste, party2_waste
[docs] def partisan_bias(election_results: ElectionResults) -> float: """Computes the partisan bias for the given ElectionResults. The partisan bias is defined as the number of districts with above-mean vote share by the first party divided by the total number of districts, minus 1/2. Args: election_results (ElectionResults): An ElectionResults object Returns: float: The partisan bias for the given ElectionResults """ first_party = election_results.election.parties[0] party_shares = numpy.array(election_results.percents(first_party)) mean_share = numpy.mean(party_shares) above_mean_districts = len(party_shares[party_shares > mean_share]) return (above_mean_districts / len(party_shares)) - 0.5
[docs] def partisan_gini(election_results: ElectionResults) -> float: """Computes the partisan Gini score for the given ElectionResults. The partisan Gini score is defined as the area between the seats-votes curve and its reflection about (.5, .5). For more information on the computation, see Definition 1 in: https://arxiv.org/pdf/2008.06930.pdf Args: election_results (ElectionResults): An ElectionResults object Returns: float: The partisan Gini score for the given ElectionResults """ # For two parties, the Gini score is symmetric--it does not vary by party. party = election_results.election.parties[0] overall_result = election_results.percent(party) race_results = sorted(election_results.percents(party), reverse=True) seats_votes = [overall_result - r + 0.5 for r in race_results] # Apply reflection of seats-votes curve about (.5, .5) reflected_sv = reversed([1 - s for s in seats_votes]) # Calculate the unscaled, unsigned area between the seats-votes curve # and its reflection. unscaled_area = sum(abs(s - r) for s, r in zip(seats_votes, reflected_sv)) # We divide by area by the number of seats to obtain a partisan Gini score # between 0 and 1. return unscaled_area / len(race_results)