Use Markov chain Monte Carlo to analyze districting plans and gerrymanders
—
Create and manipulate district partitions representing assignments of geographic units to electoral districts. Partitions track updatable attributes and support efficient manipulation operations.
Create partitions with district assignments and updatable attributes that are automatically computed and cached.
class Partition:
def __init__(
self,
graph: Graph,
assignment: Union[Dict[NodeId, DistrictId], str],
updaters: Dict[str, UpdaterFunction] = None
) -> None:
"""
Create a new partition of a graph into districts.
Parameters:
- graph (Graph): The underlying graph
- assignment (Union[Dict, str]): District assignments as dict or column name
- updaters (Dict[str, UpdaterFunction], optional): Functions to compute attributes
Returns:
None
"""
def __getitem__(self, key: str) -> Any:
"""
Access partition attributes computed by updaters.
Parameters:
- key (str): Name of the attribute
Returns:
Any: The computed attribute value
"""
def flip(self, flips: Dict[NodeId, DistrictId]) -> "Partition":
"""
Create a new partition with specified node assignments changed.
Parameters:
- flips (Dict[NodeId, DistrictId]): Node ID to new district ID mappings
Returns:
Partition: New partition with flipped assignments
"""
def crosses_parts(self, edge: Tuple[NodeId, NodeId]) -> bool:
"""
Check if an edge crosses district boundaries.
Parameters:
- edge (Tuple[NodeId, NodeId]): Edge as (node1, node2) tuple
Returns:
bool: True if edge crosses districts, False otherwise
"""Usage example:
from gerrychain import Partition, Graph
from gerrychain.updaters import Tally, cut_edges
# Create basic partition
graph = Graph.from_file("precincts.shp")
partition = Partition(
graph,
assignment="district", # Use 'district' column from shapefile
updaters={
"population": Tally("population"),
"cut_edges": cut_edges
}
)
# Access attributes
total_pop = sum(partition["population"].values())
num_cut_edges = len(partition["cut_edges"])
# Create new partition with flipped assignments
new_partition = partition.flip({node_id: new_district})Create partitions with geographic data and spatial capabilities, extending basic partitions with coordinate reference system support and file I/O.
class GeographicPartition(Partition):
@classmethod
def from_file(
cls,
filename: str,
district_column: str,
adjacency: str = "rook",
updaters: Dict[str, UpdaterFunction] = None,
**kwargs
) -> "GeographicPartition":
"""
Create a GeographicPartition from a shapefile or GeoJSON.
Parameters:
- filename (str): Path to geographic data file
- district_column (str): Column containing district assignments
- adjacency (str): Adjacency type - "rook" or "queen"
- updaters (Dict[str, UpdaterFunction], optional): Attribute updaters
- **kwargs: Additional arguments passed to Graph.from_file
Returns:
GeographicPartition: New geographic partition
"""
@classmethod
def from_districtr_file(
cls,
graph: Graph,
assignment_file: str,
updaters: Dict[str, UpdaterFunction] = None
) -> "GeographicPartition":
"""
Create a GeographicPartition from a Districtr assignment file.
Parameters:
- graph (Graph): The underlying graph
- assignment_file (str): Path to Districtr JSON assignment file
- updaters (Dict[str, UpdaterFunction], optional): Attribute updaters
Returns:
GeographicPartition: New geographic partition from Districtr data
"""Usage example:
from gerrychain import GeographicPartition
from gerrychain.updaters import Tally, cut_edges
# From shapefile
partition = GeographicPartition.from_file(
"precincts.shp",
district_column="district",
adjacency="queen",
updaters={
"population": Tally("population"),
"cut_edges": cut_edges
}
)
# From Districtr file
graph = Graph.from_file("precincts.shp")
partition = GeographicPartition.from_districtr_file(
graph,
"assignment.json",
updaters={"population": Tally("population")}
)Manipulate and query district assignments efficiently.
def get_assignment(
nodes: Iterable[NodeId],
assignment: Union[Dict[NodeId, DistrictId], str]
) -> Dict[NodeId, DistrictId]:
"""
Extract assignment mapping for specified nodes.
Parameters:
- nodes (Iterable[NodeId]): Nodes to get assignments for
- assignment (Union[Dict, str]): Assignment dict or column name
Returns:
Dict[NodeId, DistrictId]: Assignment mapping for specified nodes
"""Create views of partition subgraphs for efficient district-level analysis.
class SubgraphView:
def __init__(self, partition: Partition, districts: List[DistrictId]) -> None:
"""
Create a view of partition subgraph for specified districts.
Parameters:
- partition (Partition): The partition
- districts (List[DistrictId]): Districts to include in view
Returns:
None
"""Common patterns for working with partitions in analysis workflows:
# Multi-step partition modification
partition = GeographicPartition.from_file("precincts.shp", "district")
# Chain multiple flips
step1 = partition.flip({node1: district1})
step2 = step1.flip({node2: district2})
final = step2.flip({node3: district3})
# Batch operations for efficiency
flips = {node1: district1, node2: district2, node3: district3}
result = partition.flip(flips)
# Check boundary crossings
boundary_edges = [
edge for edge in graph.edges()
if partition.crosses_parts(edge)
]
# Access computed attributes
districts = list(partition.parts.keys())
district_populations = {
dist: partition["population"][dist]
for dist in districts
}
# Parent tracking for Markov chains
current_partition = initial_partition
for step in range(1000):
proposed = propose_flip(current_partition)
if is_valid(proposed) and accept(proposed):
current_partition = proposed
# proposed.parent points to current_partitionAssignment = Dict[NodeId, DistrictId] # Node to district mapping
NodeId = Union[int, str] # Graph node identifier
DistrictId = int # District identifier
UpdaterFunction = Callable[[Partition], Any] # Updater function typeInstall with Tessl CLI
npx tessl i tessl/pypi-gerrychain