def test_join_can_handle_right_index(): graph = Graph([("01", "02"), ("02", "03"), ("03", "01")]) df = pandas.DataFrame({"16SenDVote": [20, 30, 50], "node": ["01", "02", "03"]}) graph.join(df, ["16SenDVote"], right_index="node") assert graph.nodes["01"]["16SenDVote"] == 20 assert graph.nodes["02"]["16SenDVote"] == 30 assert graph.nodes["03"]["16SenDVote"] == 50
def test_add_data_to_graph_can_handle_column_names_that_start_with_numbers(): graph = Graph([("01", "02"), ("02", "03"), ("03", "01")]) df = pandas.DataFrame({"16SenDVote": [20, 30, 50], "node": ["01", "02", "03"]}) df = df.set_index("node") graph.add_data(df, ["16SenDVote"]) assert graph.nodes["01"]["16SenDVote"] == 20 assert graph.nodes["02"]["16SenDVote"] == 30 assert graph.nodes["03"]["16SenDVote"] == 50
def test_make_graph_from_dataframe_gives_correct_graph(geodataframe): df = geodataframe.set_index("ID") graph = Graph.from_geodataframe(df) assert edge_set_equal( set(graph.edges), {("a", "b"), ("a", "c"), ("b", "d"), ("c", "d")} )
def test_from_file_and_then_to_json_with_geometries(shapefile, target_file): graph = Graph.from_file(shapefile) # Even the geometry column is copied to the graph assert all("geometry" in node_data for node_data in graph.nodes.values()) graph.to_json(target_file, include_geometries_as_geojson=True)
def twelve_by_twelve_with_pop(): xy_grid = networkx.grid_graph([12, 12]) nodes = {node: node[1] + 12 * node[0] for node in xy_grid} grid = networkx.relabel_nodes(xy_grid, nodes) for node in grid: grid.nodes[node]["pop"] = 1 return Graph.from_networkx(grid)
def test_identifies_boundary_nodes(geodataframe_with_boundary): df = geodataframe_with_boundary.set_index("ID") graph = Graph.from_geodataframe(df) for node in ("a", "b", "c", "e"): assert graph.nodes[node]["boundary_node"] assert not graph.nodes["d"]["boundary_node"]
def test_from_file_and_then_to_json_does_not_error(shapefile, target_file): graph = Graph.from_file(shapefile) # Even the geometry column is copied to the graph assert all("geometry" in node_data for node_data in graph.nodes.values()) graph.to_json(target_file)
def test_make_graph_works_with_queen_adjacency(geodataframe): df = geodataframe.set_index("ID") graph = Graph.from_geodataframe(df, adjacency="queen") assert edge_set_equal( set(graph.edges), {("a", "b"), ("a", "c"), ("b", "d"), ("c", "d"), ("a", "d"), ("b", "c")}, )
def discontiguous_partition_with_flips(): graph = nx.Graph() graph.add_nodes_from(range(4)) graph.add_edges_from([(0, 1), (1, 2), (2, 3)]) partition = Partition(Graph.from_networkx(graph), {0: 0, 1: 1, 2: 1, 3: 0}) # This flip will maintain discontiguity. return partition, {1: 0}
def test_can_pass_queen_or_rook_strings_to_control_adjacency(geodataframe): df = geodataframe.set_index("ID") graph = Graph.from_geodataframe(df, adjacency="queen") assert edge_set_equal( set(graph.edges), {("a", "b"), ("a", "c"), ("b", "d"), ("c", "d"), ("a", "d"), ("b", "c")}, )
def test_data_and_geometry(gdf_with_data): df = gdf_with_data graph = Graph.from_geodataframe(df, cols_to_add=["data", "data2"]) assert graph.geometry is df.geometry #graph.add_data(df[["data"]]) assert (graph.data["data"] == df["data"]).all() #graph.add_data(df[["data2"]]) assert list(graph.data.columns) == ["data", "data2"]
def test_computes_boundary_perims(geodataframe_with_boundary): df = geodataframe_with_boundary.set_index("ID") graph = Graph.from_geodataframe(df, reproject=False) expected = {"a": 5, "e": 5, "b": 1, "c": 1} for node, value in expected.items(): assert graph.nodes[node]["boundary_perim"] == value
def test_does_not_reproject_by_default(geodataframe): df = geodataframe.set_index("ID") graph = Graph.from_geodataframe(df) for node in ("a", "b", "c", "d"): assert graph.nodes[node]["area"] == 1.0 for edge in graph.edges: assert graph.edges[edge]["shared_perim"] == 1.0
def test_can_insist_on_not_reprojecting(geodataframe): df = geodataframe.set_index("ID") graph = Graph.from_geodataframe(df, reproject=False) for node in ("a", "b", "c", "d"): assert graph.nodes[node]["area"] == 1 for edge in graph.edges: assert graph.edges[edge]["shared_perim"] == 1
def example_geographic_partition(): graph = Graph.from_networkx(networkx.complete_graph(3)) assignment = {0: 1, 1: 1, 2: 2} for node in graph.nodes: graph.nodes[node]["boundary_node"] = False graph.nodes[node]["area"] = 1 for edge in graph.edges: graph.edges[edge]["shared_perim"] = 1 return GeographicPartition(graph, assignment, None, None, None)
def test_reproject(geodataframe): # I don't know what the areas and perimeters are in UTM for these made-up polygons, # but I'm pretty sure they're not 1. df = geodataframe.set_index("ID") graph = Graph.from_geodataframe(df, reproject=True) for node in ("a", "b", "c", "d"): assert graph.nodes[node]["area"] != 1 for edge in graph.edges: assert graph.edges[edge]["shared_perim"] != 1
def from_json_graph(cls, graph_path, assignment, updaters=None): """Creates a :class:`Partition` from a json file containing a serialized NetworkX `adjacency_data` object. Files of this kind for each state are available in the @gerrymandr/vtd-adjacency-graphs GitHub repository. :param graph_path: String filename for the json file :param assignment: String key for the node attribute giving a district assignment, or a dictionary mapping node IDs to district IDs. :param updaters: (optional) Dictionary of updater functions to attach to the partition, in addition to the default_updaters of `cls`. """ graph = Graph.from_json(graph_path) return cls(graph, assignment, updaters)
def test_bipartition_tree_returns_a_tree(graph_with_pop): ideal_pop = sum(graph_with_pop.nodes[node]["pop"] for node in graph_with_pop) / 2 tree = Graph.from_networkx(networkx.Graph( [(0, 1), (1, 2), (1, 4), (3, 4), (4, 5), (3, 6), (6, 7), (6, 8)] )) for node in tree: tree.nodes[node]["pop"] = graph_with_pop.nodes[node]["pop"] result = bipartition_tree( graph_with_pop, "pop", ideal_pop, 0.25, 10, tree, lambda x: 4 ) assert networkx.is_tree(tree.subgraph(result)) assert networkx.is_tree( tree.subgraph({node for node in tree if node not in result}) )
def test_Partition_can_update_stats(): graph = networkx.complete_graph(3) assignment = {0: 1, 1: 1, 2: 2} graph.nodes[0]["stat"] = 1 graph.nodes[1]["stat"] = 2 graph.nodes[2]["stat"] = 3 updaters = {"total_stat": Tally("stat", alias="total_stat")} partition = Partition(Graph.from_networkx(graph), assignment, updaters) assert partition["total_stat"][2] == 3 flip = {1: 2} new_partition = partition.flip(flip) assert new_partition["total_stat"][2] == 5
def test_find_balanced_cuts_contraction(): tree = Graph.from_networkx(networkx.Graph( [(0, 1), (1, 2), (1, 4), (3, 4), (4, 5), (3, 6), (6, 7), (6, 8)] )) # 0 - 1 - 2 # || # 3= 4 - 5 # || # 6- 7 # | # 8 populated_tree = PopulatedGraph( tree, {node: 1 for node in tree}, len(tree) / 2, 0.5 ) cuts = find_balanced_edge_cuts_contraction(populated_tree) edges = set(tuple(sorted(cut.edge)) for cut in cuts) assert edges == {(1, 4), (3, 4), (3, 6)}
def __init__( self, dimensions=None, with_diagonals=False, assignment=None, updaters=None, parent=None, flips=None, ): """ :param dimensions: tuple (m,n) of the desired dimensions of the grid. :param with_diagonals: (optional, defaults to False) whether to include diagonals as edges of the graph (i.e., whether to use 'queen' adjacency rather than 'rook' adjacency). :param assignment: (optional) dict matching nodes to their districts. If not provided, partitions the grid into 4 quarters of roughly equal size. :param updaters: (optional) dict matching names of attributes of the Partition to functions that compute their values. If not provided, the Grid configures the cut_edges updater for convenience. """ if dimensions: self.dimensions = dimensions graph = Graph.from_networkx( create_grid_graph(dimensions, with_diagonals)) if not assignment: thresholds = tuple(math.floor(n / 2) for n in self.dimensions) assignment = { node: color_quadrants(node, thresholds) for node in graph.nodes } if not updaters: updaters = dict() updaters.update(self.default_updaters) super().__init__(graph, assignment, updaters) elif parent: self.dimensions = parent.dimensions super().__init__(parent=parent, flips=flips) else: raise Exception("Not a good way to create a Partition")
def test_make_graph_from_dataframe_creates_graph(geodataframe): graph = Graph.from_geodataframe(geodataframe) assert isinstance(graph, Graph)
def test_can_ignore_errors_while_making_graph(shapefile): with patch("gerrychain.graph.geo.explain_validity") as explain: explain.return_value = "Invalid geometry" assert Graph.from_file(shapefile, ignore_errors=True)
def test_raises_geometry_error_if_invalid_geometry(shapefile): with patch("gerrychain.graph.geo.explain_validity") as explain: explain.return_value = "Invalid geometry" with pytest.raises(GeometryError): Graph.from_file(shapefile, ignore_errors=False)
def test_graph_raises_if_crs_is_missing_when_reprojecting(geodataframe): geodataframe.crs = None with pytest.raises(ValueError): Graph.from_geodataframe(geodataframe, reproject=True)
def test_graph_warns_for_islands(): graph = Graph() graph.add_node(0) with pytest.warns(Warning): graph.warn_for_islands()
def test_Partition_unlabelled_vertices_raises_keyerror(): graph = Graph.from_networkx(networkx.complete_graph(3)) assignment = {0: 1, 2: 2} with pytest.raises(KeyError): Partition(graph, assignment, {"cut_edges": cut_edges})
def test_graph_raises_if_crs_is_missing(geodataframe): del geodataframe.crs with pytest.raises(ValueError): Graph.from_geodataframe(geodataframe)
def test_from_file_adds_all_data_by_default(shapefile): graph = Graph.from_file(shapefile) assert all("data" in node_data for node_data in graph.nodes.values()) assert all("data2" in node_data for node_data in graph.nodes.values())
def test_make_graph_from_dataframe_preserves_df_index(geodataframe): df = geodataframe.set_index("ID") graph = Graph.from_geodataframe(df) assert set(graph.nodes) == {"a", "b", "c", "d"}