\n",
" \n",
"\n",
"This document walks you through the most common ways that you might work with a\n",
"GerryChain `Partition` object."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aef6c5e1",
"metadata": {},
"outputs": [],
"source": [
"from gerrychain import Partition, Graph\n",
"from gerrychain.updaters import cut_edges"
]
},
{
"cell_type": "markdown",
"id": "07b12df2",
"metadata": {},
"source": [
"We'll use our\n",
"[Pennsylvania VTD json](https://github.com/mggg/GerryChain/tree/main/docs/_static/PA_VTDs.json)\n",
"to create the graph we'll use in these examples."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06a70989",
"metadata": {},
"outputs": [],
"source": [
"graph = Graph.from_json(\"./PA_VTDs.json\")"
]
},
{
"cell_type": "markdown",
"id": "b1d443d5",
"metadata": {},
"source": [
"## Creating a Partition\n",
"\n",
"There are a couple of ways in which we could make a partition. The first way is to\n",
"just make a random assignment with a population balance of $\\varepsilon$"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "153ce2da",
"metadata": {},
"outputs": [],
"source": [
"partition = Partition.from_random_assignment(\n",
" graph=graph, n_parts=2, epsilon=0.01, pop_col=\"TOT_POP\", rng=2024\n",
")"
]
},
{
"cell_type": "markdown",
"id": "6b2a3a36",
"metadata": {},
"source": [
"However, in this example we will create a partition based on the \"2011_PLA_1\" plan\n",
"that already exists in the file:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "52a02f9f",
"metadata": {},
"outputs": [],
"source": [
"partition = Partition(graph, \"2011_PLA_1\", {\"cut_edges\": cut_edges})"
]
},
{
"cell_type": "markdown",
"id": "c666228e",
"metadata": {},
"source": [
"The `Partition` class takes three arguments to create a Partition:\n",
"\n",
"- A **graph**.\n",
"- An **assignment of nodes to districts**. This can be the string name of a\n",
" node attribute (shapefile column) that holds each node's district\n",
" assignment, or a dictionary mapping each node ID to its assigned district\n",
" ID.\n",
"- A dictionary of **updaters**.\n",
"\n",
"This creates a partition of the `graph` object we created above from the\n",
"Pennsylvania shapefile. The partition is defined by the `\"2011_PLA_1\"` column\n",
"from our shapefile's attribute table."
]
},
{
"cell_type": "markdown",
"id": "09b8d577",
"metadata": {},
"source": [
"## `partition.graph`: The Underlying Graph\n",
"\n",
"`partition.graph` is a\n",
"[gerrychain.Graph](../api/graphs.rst)\n",
"object. In previous releases it was a subclass of a NetworkX Graph object, but the current\n",
"release instead embeds either a NetworkX Graph object or a RustworkX PyGraph object. While\n",
"NetworkX has many convenient associated functions, for instance to build a graph or to plot\n",
"a graph, RustworkX is much more efficient at graph manipulation, so the current release uses\n",
"NetworkX to build graphs but it converts the graph to be a RustworkX when a Partition object\n",
"is created to make the number crunching faster.\n",
"\n",
"If when building your graph (before creating a Partition object), you wish to use\n",
"NetworkX functions, you can get the embedded NetworkX Graph by calling `graph.get_nx_graph()`.\n",
"This will return the embedded NetworkX graph, and you can use NetworkX functions directly\n",
"on it and any changes (like adding nodes or attribute values) will be reflected in the\n",
"embedding GerryChain Graph object.\n",
"\n",
"However, after creating a Partition object, the embedded graph is converted to a RustworkX\n",
"PyGraph object, and it is \"frozen\", meaning no changes to the structure of the graph are\n",
"permitted (nodes and edges)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "598d008c",
"metadata": {},
"outputs": [],
"source": [
"partition.graph"
]
},
{
"cell_type": "markdown",
"id": "cb6b48e1",
"metadata": {},
"source": [
"Now we have a graph of Pennsylvania's VTDs, with all of the data from our\n",
"shapefile's attribute table attached to the graph as *node attributes*. We can\n",
"see the data that a node has like this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c9f800ae",
"metadata": {},
"outputs": [],
"source": [
"node_id = 0\n",
"partition.graph.node_data(node_id)"
]
},
{
"cell_type": "markdown",
"id": "7905b1bb",
"metadata": {},
"source": [
"It is worth noting the last attribute value, `__networkx_node__`. Recall that\n",
"the Partition object's graph object is a RustworkX PyGraph object that was\n",
"created by converting the contents of a NetworkX Graph object. This attribute\n",
"retains the corresponding NetworkX node_id. This will be useful later on..."
]
},
{
"cell_type": "markdown",
"id": "f82107aa",
"metadata": {},
"source": [
"## `partition.assignment`: Assign Nodes to Parts\n",
"\n",
"`partition.assignment` gives you a mapping from node IDs to part IDs (\"part\" is\n",
"our generic word for \"district\"). It is a custom data structure but you can use\n",
"it just like a dictionary. So the code:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "016e4fd0",
"metadata": {},
"outputs": [],
"source": [
"import itertools\n",
"\n",
"# RustworkX node_ids are sequential integers starting at 0\n",
"first_ten_node_ids = range(10)\n",
"for node_id in first_ten_node_ids:\n",
" print(partition.assignment[node_id])"
]
},
{
"cell_type": "markdown",
"id": "89b762fa",
"metadata": {},
"source": [
"## `partition.parts`: The Nodes in Each Part\n",
"\n",
"`partition.parts` gives you a mapping from each part ID to the set of nodes that\n",
"belong to that part. This is the \"opposite\" mapping of `assignment`.\n",
"\n",
"As an example, let's print out the number of nodes in each part:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aa93d6c1",
"metadata": {},
"outputs": [],
"source": [
"for part in partition.parts:\n",
" number_of_nodes = len(partition.parts[part])\n",
" print(f\"Part {part} has {number_of_nodes} nodes\")"
]
},
{
"cell_type": "markdown",
"id": "5622fb95",
"metadata": {},
"source": [
"## `partition.subgraphs`: The Subgraphs of Each Part\n",
"\n",
"For each part of our partition, we can look at the _subgraph_ that it defines.\n",
"That is, we can look at the graph made up of all the nodes in a certain part and\n",
"all the edges between those nodes.\n",
"\n",
"`partition.subgraphs` gives us a mapping (like a dictionary) from part IDs to\n",
"RX-backed `FrozenGraph` objects. Nodes, edges, and node attributes use the same\n",
"GerryChain graph interface as the main graph."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f211d87e",
"metadata": {},
"outputs": [],
"source": [
"for part, subgraph in partition.subgraphs.items():\n",
" number_of_edges = len(subgraph.edges)\n",
" print(f\"Part {part} has {number_of_edges} edges\")"
]
},
{
"cell_type": "markdown",
"id": "a3cc3b5a",
"metadata": {},
"source": [
"## Outputs of Updaters\n",
"\n",
"The other main way we can extract information from `partition` is through the\n",
"updaters that we configured when we created it. We gave `partition` just one\n",
"updater, `cut_edges`. This is the set of edges that go between nodes that are in\n",
"_different_ parts of the partition.\n",
"\n",
"\"Cut edge\" is the standard term in the redistricting literature, but if it does not read\n",
"intuitively, think of it as a *boundary edge*. An edge is cut precisely when its two endpoints\n",
"land in different districts, so the cut edges are exactly the edges that trace the boundaries\n",
"between districts. The name comes from the idea that cutting them is what would separate the\n",
"plan into its individual pieces. A plan with fewer cut edges has shorter district boundaries,\n",
"which is why this count is often used as a rough measure of compactness.\n",
"\n",
"Updaters for our partition are an attribute of the partition, so we can\n",
"access them with:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a5ccb4be",
"metadata": {},
"outputs": [],
"source": [
"len(partition[\"cut_edges\"])"
]
},
{
"cell_type": "markdown",
"id": "2cfcce39",
"metadata": {},
"source": [
"So if we wanted to print out the proportion of cut edges present within our graph,\n",
"we might write:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b306ff7f",
"metadata": {},
"outputs": [],
"source": [
"proportion_of_cut_edges = len(partition[\"cut_edges\"]) / len(partition.graph.edge_indices)\n",
"print(\"Proportion of edges that are cut:\")\n",
"print(proportion_of_cut_edges)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}