or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

acceptance-functions.mdconstraints.mddata-aggregation.mdevaluation-metrics.mdgraph-operations.mdindex.mdmarkov-chain-analysis.mdoptimization.mdpartition-management.mdproposal-algorithms.md
tile.json

tessl/pypi-gerrychain

Use Markov chain Monte Carlo to analyze districting plans and gerrymanders

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/gerrychain@0.3.x

To install, run

npx @tessl/cli install tessl/pypi-gerrychain@0.3.0

index.mddocs/

GerryChain

A Python library for building ensembles of districting plans using Markov chain Monte Carlo methods. GerryChain enables quantitative analysis of electoral districts and gerrymandering by generating large collections of sample districting plans for comparison against initial plans, developed by the Metric Geometry and Gerrymandering Group.

Package Information

  • Package Name: gerrychain
  • Language: Python
  • Installation: pip install gerrychain
  • Optional Dependencies: pip install gerrychain[geo] (for shapely and geopandas support)

Core Imports

from gerrychain import MarkovChain, Graph, Partition, GeographicPartition, Election

Constraint functions:

from gerrychain.constraints import contiguous, within_percent_of_ideal_population

Proposal functions:

from gerrychain.proposals import recom

Acceptance and metrics:

from gerrychain.accept import always_accept
from gerrychain.metrics import mean_median, polsby_popper

Basic Usage

from functools import partial
from gerrychain import MarkovChain, Graph, GeographicPartition, Election
from gerrychain.constraints import contiguous, within_percent_of_ideal_population
from gerrychain.proposals import recom
from gerrychain.accept import always_accept
from gerrychain.updaters import Tally, cut_edges
from gerrychain.metrics import mean_median

# Load geographic data into a graph
graph = Graph.from_file("precincts.shp")

# Create initial partition with election data
partition = GeographicPartition(
    graph,
    assignment="district",
    updaters={
        "population": Tally("TOTPOP"),
        "cut_edges": cut_edges,
        "SEN18": Election("SEN18", ["SEN18D", "SEN18R"])
    }
)

# Calculate ideal population for ReCom
ideal_population = sum(partition["population"].values()) / len(partition)

# Configure ReCom proposal with parameters
my_proposal = partial(
    recom,
    pop_col="TOTPOP",
    pop_target=ideal_population,
    epsilon=0.05,
    node_repeats=2
)

# Set up constraints
constraints = [
    contiguous,
    within_percent_of_ideal_population(partition, 0.05)
]

# Create and run Markov chain
chain = MarkovChain(
    proposal=my_proposal,
    constraints=constraints,
    accept=always_accept,
    initial_state=partition,
    total_steps=1000
)

# Analyze the chain
scores = []
for state in chain:
    # Compute partisan metrics
    score = mean_median(state["SEN18"])
    scores.append(score)
    
    # Print progress every 100 steps
    if len(scores) % 100 == 0:
        print(f"Step {len(scores)}: Mean-Median = {score:.3f}")

Architecture

GerryChain follows a modular, functional design with four core components:

  • Graph: NetworkX-based geographic graphs representing geographic units (precincts, blocks)
  • Partition: District assignments with updatable attributes (population, elections, metrics)
  • MarkovChain: Iterator that proposes new partitions based on constraints and acceptance criteria
  • Updaters: Functions that compute partition attributes (elections, tallies, cut edges, metrics)

The modular design allows mixing and matching proposals, constraints, acceptance functions, and metrics for custom redistricting analysis workflows.

Capabilities

Graph Creation and Management

Create and manipulate geographic graphs from shapefiles, GeoDataFrames, and adjacency data. Supports spatial operations, data joining, and node/edge querying.

class Graph:
    @classmethod
    def from_file(cls, filename: str, adjacency: str = "rook") -> "Graph": ...
    @classmethod  
    def from_geodataframe(cls, dataframe, adjacency: str = "rook") -> "Graph": ...
    @classmethod
    def from_networkx(cls, graph) -> "Graph": ...
    @classmethod
    def from_json(cls, json_file: str) -> "Graph": ...
    def join(self, other_dataframe, columns: List[str] = None) -> None: ...
    def lookup(self, key: str, target_column: str) -> Dict: ...
    def to_json(self, json_file: str, *, include_geometries_as_geojson: bool = False) -> None: ...
    def issue_warnings(self) -> None: ...

Graph Operations

Partition Management

Create and manipulate district partitions with automatic attribute updates. Supports both basic partitions and geographic partitions with spatial data.

class Partition:
    def __init__(self, graph: Graph, assignment: Dict, updaters: Dict = None) -> None: ...
    @classmethod
    def from_random_assignment(
        cls, 
        graph: Graph, 
        n_parts: int, 
        epsilon: float, 
        pop_col: str, 
        **kwargs
    ) -> "Partition": ...
    def flip(self, flips: Dict) -> "Partition": ...
    def crosses_parts(self, edge: Tuple) -> bool: ...

class GeographicPartition(Partition):
    @classmethod
    def from_file(cls, filename: str, **kwargs) -> "GeographicPartition": ...
    @classmethod
    def from_districtr_file(
        cls, 
        graph: Graph, 
        districtr_file: str, 
        updaters: Dict = None
    ) -> "GeographicPartition": ...

Partition Management

Markov Chain Analysis

Core MCMC functionality for generating ensembles of districting plans. The MarkovChain class orchestrates proposal generation, constraint validation, and acceptance decisions.

class MarkovChain:
    def __init__(
        self, 
        proposal: Callable, 
        constraints: Union[Callable, List[Callable]], 
        accept: Callable,
        initial_state: Partition,
        total_steps: int
    ) -> None: ...
    def with_progress_bar(self): ...

Markov Chain Analysis

Constraint Validation

Ensure proposed partitions meet districting requirements including contiguity, population balance, and administrative unit preservation.

class Validator:
    def __init__(self, constraints: List[Callable]) -> None: ...
    def __call__(self, partition: Partition) -> bool: ...

def contiguous(partition: Partition) -> bool: ...
def within_percent_of_ideal_population(
    partition: Partition, 
    percent: float
) -> Callable[[Partition], bool]: ...
def no_vanishing_districts(partition: Partition) -> bool: ...

Constraints

Proposal Algorithms

Generate new districting plans through various algorithms including ReCom (recombination), random flips, and tree-based partitioning.

def recom(partition: Partition) -> Partition: ...
def propose_random_flip(partition: Partition) -> Partition: ...
def propose_chunk_flip(partition: Partition, k: int = 3) -> Partition: ...

Proposal Algorithms

Data Aggregation and Tracking

Compute and track partition attributes including election results, demographic data, and structural properties like cut edges.

class Election:
    def __init__(self, name: str, columns: List[str], alias: str = None) -> None: ...
    def percents(self, alias: str) -> Dict[str, float]: ...
    @property
    def counts(self) -> Dict[str, int]: ...

class Tally:
    def __init__(self, columns: Union[str, List[str]], alias: str = None) -> None: ...

def cut_edges(partition: Partition) -> Set[Tuple]: ...

Data Aggregation

Evaluation Metrics

Analyze districting plans using partisan metrics (efficiency gap, mean-median), compactness measures (Polsby-Popper, Schwartzberg), and demographic analysis.

def mean_median(election_results: Dict) -> float: ...
def efficiency_gap(election_results: Dict) -> float: ...
def partisan_bias(election_results: Dict) -> float: ...
def polsby_popper(partition: Partition) -> Dict[int, float]: ...
def schwartzberg(partition: Partition) -> Dict[int, float]: ...

Evaluation Metrics

Optimization Algorithms

Single-metric optimization and specialized analysis including Gingles analysis for Voting Rights Act compliance.

class SingleMetricOptimizer:
    def __init__(
        self,
        proposal: Callable,
        constraints: List[Callable], 
        accept: Callable,
        initial_state: Partition,
        maximize: bool = True
    ) -> None: ...
    def optimize(self) -> Tuple[Partition, float]: ...

class Gingles:
    def __init__(
        self,
        proposal: Callable,
        constraints: List[Callable],
        accept: Callable, 
        initial_state: Partition,
        minority_column: str,
        threshold: float = 0.5
    ) -> None: ...

Optimization

Acceptance Functions

Control which proposed partitions are accepted in the Markov chain, including basic acceptance and Metropolis-Hastings rules.

def always_accept(partition: Partition) -> bool: ...
def cut_edge_accept(partition: Partition) -> bool: ...

Acceptance Functions

Types

Core type definitions used throughout the API:

# Type aliases for common patterns
Assignment = Dict[int, int]  # Node ID -> District ID mapping
NodeId = Union[int, str]     # Graph node identifier
DistrictId = int             # District identifier
UpdaterFunction = Callable[[Partition], Any]  # Updater function type
ConstraintFunction = Callable[[Partition], bool]  # Constraint function type
ProposalFunction = Callable[[Partition], Partition]  # Proposal function type
AcceptanceFunction = Callable[[Partition], bool]  # Acceptance function type