From 0141e4764a8eb2a7a79ceccdb74909dcc8e4a1a5 Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Mon, 10 Oct 2022 20:39:04 -0400 Subject: [PATCH 1/8] move to pytest --- .github/workflows/build_docs.yml | 2 +- ci/39.yaml | 6 - requirements_tests.txt | 1 + ...est_classes.py => network_test_classes.py} | 306 +++++++++--------- spaghetti/tests/test_api_network.py | 11 +- spaghetti/tests/test_dev_network.py | 14 +- spaghetti/util.py | 2 +- 7 files changed, 165 insertions(+), 177 deletions(-) rename spaghetti/tests/{network_unittest_classes.py => network_test_classes.py} (77%) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index 5bcccb61..4e35b022 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -19,7 +19,7 @@ strategy: matrix: os: ['ubuntu-latest'] - environment-file: [ci/39.yaml] + environment-file: [ci/310.yaml] experimental: [false] defaults: run: diff --git a/ci/39.yaml b/ci/39.yaml index c89b4f08..cecb4650 100644 --- a/ci/39.yaml +++ b/ci/39.yaml @@ -21,9 +21,3 @@ dependencies: - pytest-xdist # optional - geopandas>=0.7.0 - # for docs build action (this env only) - - nbsphinx - - numpydoc - - sphinx - - sphinxcontrib-bibtex - - sphinx_bootstrap_theme diff --git a/requirements_tests.txt b/requirements_tests.txt index 31486399..2b4c8b52 100644 --- a/requirements_tests.txt +++ b/requirements_tests.txt @@ -3,5 +3,6 @@ pytest-cov pytest-timeout pytest-xdist codecov +coverage twine wheel \ No newline at end of file diff --git a/spaghetti/tests/network_unittest_classes.py b/spaghetti/tests/network_test_classes.py similarity index 77% rename from spaghetti/tests/network_unittest_classes.py rename to spaghetti/tests/network_test_classes.py index 145ebb8a..a18817ce 100644 --- a/spaghetti/tests/network_unittest_classes.py +++ b/spaghetti/tests/network_test_classes.py @@ -1,8 +1,9 @@ from libpysal import cg, examples, io from libpysal.common import RTOL, ATOL import numpy -import unittest import copy +import pytest +import warnings try: import geopandas @@ -81,8 +82,8 @@ # ------------------------------------------------------------------------------- -class TestNetwork(unittest.TestCase): - def setUp(self): +class TestNetwork: + def setup_method(self): # empirical network instantiated from shapefile self.ntw_shp = self.spaghetti.Network(in_data=STREETS, weightings=True) self.n_known_shp_arcs, self.n_known_shp_vertices = 303, 230 @@ -103,20 +104,17 @@ def setUp(self): # Pythagorean Triple self.triangle = self.spaghetti.Network(in_data=GOOD_TRIANGLE) - def tearDown(self): - pass - def test_network_data_read(self): # shp test against known - self.assertEqual(len(self.ntw_shp.arcs), self.n_known_shp_arcs) - self.assertEqual(len(self.ntw_shp.vertices), self.n_known_shp_vertices) + assert len(self.ntw_shp.arcs) == self.n_known_shp_arcs + assert len(self.ntw_shp.vertices) == self.n_known_shp_vertices arc_lengths = self.ntw_shp.arc_lengths.values() - self.assertAlmostEqual(sum(arc_lengths), 104414.0920159, places=5) + assert sum(arc_lengths) == pytest.approx(104414.0920159, 0.00001) - self.assertIn(0, self.ntw_shp.adjacencylist[1]) - self.assertIn(0, self.ntw_shp.adjacencylist[2]) - self.assertNotIn(0, self.ntw_shp.adjacencylist[3]) + assert 0 in self.ntw_shp.adjacencylist[1] + assert 0 in self.ntw_shp.adjacencylist[2] + assert 0 not in self.ntw_shp.adjacencylist[3] def test_network_from_libpysal_chains(self): known_components = self.ntw_shp.network_n_components @@ -127,8 +125,8 @@ def test_network_from_libpysal_chains(self): self.ntw_from_chains = self.spaghetti.Network(in_data=ntw_data) observed_components = self.ntw_from_chains.network_n_components observed_length = sum(self.ntw_from_chains.arc_lengths.values()) - self.assertEqual(observed_components, known_components) - self.assertAlmostEqual(observed_length, known_length, places=3) + assert observed_components == known_components + assert observed_length == pytest.approx(known_length, 0.001) def test_network_from_single_libpysal_chain(self): # network instantiated from a single libpysal.cg.Chain @@ -139,75 +137,75 @@ def test_network_from_single_libpysal_chain(self): self.ntw_chain_out.savenetwork(fname) self.ntw_chain_in = self.spaghetti.Network.loadnetwork(fname) observed_arcs = self.ntw_chain_in.arcs - self.assertEqual(observed_arcs, known_edges) + assert observed_arcs == known_edges def test_network_from_vertical_libpysal_chains(self): vert_up = cg.Chain([P0505, P052]) self.ntw_up_chain = self.spaghetti.Network(in_data=vert_up) - self.assertEqual(len(self.ntw_up_chain.arcs), len(vert_up.segments)) + assert len(self.ntw_up_chain.arcs) == len(vert_up.segments) vert_down = cg.Chain([P052, P0505]) self.ntw_down_chain = self.spaghetti.Network(in_data=vert_down) - self.assertEqual(len(self.ntw_down_chain.arcs), len(vert_down.segments)) + assert len(self.ntw_down_chain.arcs) == len(vert_down.segments) def test_network_failures(self): # try instantiating network with single point - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.spaghetti.Network(in_data=P11) # try instantiating network with list of single point - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.spaghetti.Network(in_data=[P11]) - @unittest.skipIf(GEOPANDAS_EXTINCT, "Missing Geopandas") + @pytest.mark.skipif(GEOPANDAS_EXTINCT, reason="Missing Geopandas") def test_network_from_geopandas(self): # network instantiated from geodataframe gdf = geopandas.read_file(STREETS) self.ntw_gdf = self.spaghetti.Network(in_data=gdf, w_components=True) # gdf test against known - self.assertEqual(len(self.ntw_gdf.arcs), self.n_known_shp_arcs) - self.assertEqual(len(self.ntw_gdf.vertices), self.n_known_shp_vertices) + assert len(self.ntw_gdf.arcs) == self.n_known_shp_arcs + assert len(self.ntw_gdf.vertices) == self.n_known_shp_vertices # shp against gdf - self.assertEqual(len(self.ntw_shp.arcs), len(self.ntw_gdf.arcs)) - self.assertEqual(len(self.ntw_shp.vertices), len(self.ntw_gdf.vertices)) + assert len(self.ntw_shp.arcs) == len(self.ntw_gdf.arcs) + assert len(self.ntw_shp.vertices) == len(self.ntw_gdf.vertices) def test_round_sig(self): # round to 2 significant digits test x_round2, y_round2 = 1200, 1900 self.ntw_shp.vertex_sig = 2 obs_xy_round2 = self.ntw_shp._round_sig((1215, 1865)) - self.assertEqual(obs_xy_round2, (x_round2, y_round2)) + assert obs_xy_round2 == (x_round2, y_round2) # round to no significant digits test x_roundNone, y_roundNone = 1215, 1865 self.ntw_shp.vertex_sig = None obs_xy_roundNone = self.ntw_shp._round_sig((1215, 1865)) - self.assertEqual(obs_xy_roundNone, (x_roundNone, y_roundNone)) + assert obs_xy_roundNone == (x_roundNone, y_roundNone) def test_vertex_atol(self): known_components = 1 ntw_triangle = self.spaghetti.Network(in_data=BAD_TRIANGLE, vertex_atol=2) observed_components = ntw_triangle.network_n_components - self.assertEqual(observed_components, known_components) + assert observed_components == known_components def test_contiguity_weights(self): known_network_histo = [(2, 35), (3, 89), (4, 105), (5, 61), (6, 13)] observed_network_histo = self.ntw_shp.w_network.histogram - self.assertEqual(known_network_histo, observed_network_histo) + assert known_network_histo == observed_network_histo known_graph_histo = [(2, 2), (3, 2), (4, 47), (5, 80), (6, 48)] observed_graph_histo = self.ntw_shp.w_graph.histogram - self.assertEqual(observed_graph_histo, known_graph_histo) + assert observed_graph_histo == known_graph_histo def test_components(self): known_network_arc = (225, 226) observed_network_arc = self.ntw_shp.network_component2arc[0][-1] - self.assertEqual(observed_network_arc, known_network_arc) + assert observed_network_arc == known_network_arc known_graph_edge = (207, 208) observed_graph_edge = self.ntw_shp.graph_component2edge[0][-1] - self.assertEqual(observed_graph_edge, known_graph_edge) + assert observed_graph_edge == known_graph_edge def test_connected_components(self): ## test warnings @@ -217,7 +215,7 @@ def test_connected_components(self): observed_connected = ntw.network_fully_connected # known values known_connected = False - self.assertEqual(observed_connected, known_connected) + assert observed_connected == known_connected # observed values observed_component_vertices = ntw.network_component_vertices @@ -226,7 +224,7 @@ def test_connected_components(self): 0: [0, 1, 2, 3, 4, 13], 1: [5, 6, 7, 8, 9, 10, 11, 12], } - self.assertEqual(observed_component_vertices, known_component_vertices) + assert observed_component_vertices == known_component_vertices # observed values observed_network_vtx = ntw.network_component_vertex_count @@ -234,14 +232,14 @@ def test_connected_components(self): # known values known_network_vtx = {0: 6, 1: 8} known_graph_vtx = {0: 3, 1: 8} - self.assertEqual(observed_network_vtx, known_network_vtx) - self.assertEqual(observed_graph_vtx, known_graph_vtx) + assert observed_network_vtx == known_network_vtx + assert observed_graph_vtx == known_graph_vtx # observed values observed_edge_lengths = ntw.edge_lengths[(0, 1)] # known values known_edge_lengths = 1.0 - self.assertEqual(observed_edge_lengths, known_edge_lengths) + assert observed_edge_lengths == known_edge_lengths # observed values observed_largest_net = ntw.network_largest_component @@ -249,72 +247,78 @@ def test_connected_components(self): # known values known_largest = 1 known_longest = 0 - self.assertEqual(observed_largest_net, known_largest) - self.assertEqual(observed_longest_graph, known_longest) + assert observed_largest_net == known_largest + assert observed_longest_graph == known_longest # observed values observed_lengths = ntw.network_component_lengths # known values known_lengths = {0: 6.0, 1: 1.914213562373095} - self.assertEqual(observed_lengths, known_lengths) + assert observed_lengths == known_lengths def test_distance_band_weights(self): w = self.ntw_shp.distancebandweights(threshold=500) - self.assertEqual(w.n, 230) - self.assertEqual( - w.histogram, - [(1, 22), (2, 58), (3, 63), (4, 40), (5, 36), (6, 3), (7, 5), (8, 3)], - ) + assert w.n == 230 + assert w.histogram == [ + (1, 22), + (2, 58), + (3, 63), + (4, 40), + (5, 36), + (6, 3), + (7, 5), + (8, 3), + ] def test_split_arcs_dist_200(self): n200 = self.ntw_shp.split_arcs(200.0) - self.assertEqual(len(n200.arcs), 688) + assert len(n200.arcs) == 688 def test_split_arcs_dist_1000(self): n1000 = self.ntw_shp.split_arcs(1000.0) - self.assertEqual(len(n1000.arcs), 303) + assert len(n1000.arcs) == 303 def test_split_arcs_dist_ntw_from_lattice_ring_2(self): n_2 = self.ntw_from_lattice_ring.split_arcs(0.2) known_neighbors = [(1, 17), (1, 25), (1, 26), (18, 19)] observed_neighbors = n_2.w_network.neighbors[1, 18] - self.assertEqual(observed_neighbors, known_neighbors) + assert observed_neighbors == known_neighbors def test_split_arcs_dist_ntw_from_lattice_ring_3(self): n_3 = self.ntw_from_lattice_ring.split_arcs(0.3) known_neighbors = [(1, 16), (1, 22), (1, 23), (17, 18)] observed_neighbors = n_3.w_network.neighbors[1, 17] - self.assertEqual(observed_neighbors, known_neighbors) + assert observed_neighbors == known_neighbors def test_split_arcs_dist_ntw_from_lattice_ring_5(self): n_5 = self.ntw_from_lattice_ring.split_arcs(0.5) known_neighbors = [(1, 14), (1, 16), (1, 17), (2, 15)] observed_neighbors = n_5.w_network.neighbors[1, 15] - self.assertEqual(observed_neighbors, known_neighbors) + assert observed_neighbors == known_neighbors def test_split_arcs_count_2(self): n200 = self.ntw_shp.split_arcs(2, split_by="count") - self.assertEqual(len(n200.arcs), 606) + assert len(n200.arcs) == 606 def test_split_arcs_count_1(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.ntw_shp.split_arcs(1, split_by="count") def test_split_arcs_count_half(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.ntw_shp.split_arcs(0.5, split_by="count") def test_split_arcs_count_1_and_half(self): - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.ntw_shp.split_arcs(1.99, split_by="count") def test_split_arcs_misspell(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.ntw_shp.split_arcs(3, split_by="MasterP") def test_enum_links_vertex(self): coincident = self.ntw_shp.enum_links_vertex(24) - self.assertIn((24, 48), coincident) + assert (24, 48) in coincident def test_shortest_paths(self): # symmetric point pattern @@ -323,7 +327,7 @@ def test_shortest_paths(self): _, tree = self.ntw_shp.allneighbordistances(schools, gen_tree=True) observed_paths = self.ntw_shp.shortest_paths(tree, schools) observed_vertices = len(observed_paths[0][1].vertices) - self.assertEqual(observed_vertices, known_vertices) + assert observed_vertices == known_vertices # asymmetric point pattern bounds, h, v = (0, 0, 3, 3), 2, 2 @@ -343,29 +347,38 @@ def test_shortest_paths(self): # known values known_vertices1 = [(1.0, 0.5), (1.0, 0.6)] known_vertices2 = 4 - self.assertEqual(observed_vertices1, observed_vertices1) - self.assertEqual(observed_vertices2, known_vertices2) + assert observed_vertices1 == observed_vertices1 + assert observed_vertices2 == known_vertices2 # test error - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): lattice = self.spaghetti.regular_lattice((0, 0, 4, 4), 4) ntw = self.spaghetti.Network(in_data=lattice) paths = ntw.shortest_paths([], synth_obs) + @pytest.mark.filterwarnings("ignore:There is a least one point pattern") + @pytest.mark.filterwarnings("ignore:Either one or both") def test_extract_component(self): ntw = copy.deepcopy(self.ntw_from_lattice_ring) s2s, tree = ntw.allneighbordistances("points", gen_tree=True) # test longest component - longest = self.spaghetti.extract_component(ntw, ntw.network_longest_component) + with pytest.warns(UserWarning, match="There is a least one point pattern"): + longest = self.spaghetti.extract_component( + ntw, ntw.network_longest_component + ) # observed values observed_napts = longest.non_articulation_points # known values known_napts = [2, 4, 13] - self.assertEqual(observed_napts, known_napts) + assert observed_napts == known_napts # test largest component - largest = self.spaghetti.extract_component(ntw, ntw.network_largest_component) + with pytest.warns(UserWarning, match="Either one or both"): + largest = self.spaghetti.extract_component( + ntw, ntw.network_largest_component + ) + # observed values observed_arcs, observed_edges = largest.arcs, largest.edges # known values @@ -380,10 +393,10 @@ def test_extract_component(self): (11, 12), ] known_edges = known_arcs - self.assertEqual(observed_arcs, known_arcs) - self.assertEqual(observed_arcs, known_edges) - self.assertEqual(observed_edges, known_arcs) - self.assertEqual(observed_edges, known_edges) + assert observed_arcs == known_arcs + assert observed_arcs == known_edges + assert observed_edges == known_arcs + assert observed_edges == known_edges def test_spanning_tree(self): # minimum @@ -392,7 +405,7 @@ def test_spanning_tree(self): self.triangle, method="sort", maximum=False, silence_warnings=True ) observed_len = sum(mst.arc_lengths.values()) - self.assertEqual(observed_len, known_len) + assert observed_len == known_len # maximum known_len = 9.0 @@ -400,13 +413,13 @@ def test_spanning_tree(self): self.triangle, method="sort", maximum=True, silence_warnings=True ) observed_len = sum(mst.arc_lengths.values()) - self.assertEqual(observed_len, known_len) + assert observed_len == known_len # method error - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.spaghetti.spanning_tree(self.triangle, method="tors") - @unittest.skipIf(GEOPANDAS_EXTINCT, "Missing Geopandas") + @pytest.mark.skipif(GEOPANDAS_EXTINCT, reason="Missing Geopandas") def test_element_as_gdf(self): # extract both vertices and arcs vertices, arcs = self.spaghetti.element_as_gdf( @@ -416,20 +429,20 @@ def test_element_as_gdf(self): known_vertex_wkt = "POINT (728368.04762 877125.89535)" observed_vertex = vertices.loc[(vertices["id"] == 0), "geometry"].squeeze() observed_vertex_wkt = observed_vertex.wkt - self.assertEqual(observed_vertex_wkt, known_vertex_wkt) + assert observed_vertex_wkt == known_vertex_wkt # test arcs known_arc_wkt = ( "LINESTRING (728368.04762 877125.89535, 728368.13931 877023.27186)" ) observed_arc = arcs.loc[(arcs["id"] == (0, 1)), "geometry"].squeeze() observed_arc_wkt = observed_arc.wkt - self.assertEqual(observed_arc_wkt, known_arc_wkt) + assert observed_arc_wkt == known_arc_wkt # extract only arcs arcs = self.spaghetti.element_as_gdf(self.ntw_shp, arcs=True) observed_arc = arcs.loc[(arcs["id"] == (0, 1)), "geometry"].squeeze() observed_arc_wkt = observed_arc.wkt - self.assertEqual(observed_arc_wkt, known_arc_wkt) + assert observed_arc_wkt == known_arc_wkt # extract symmetric routes known_length, bounds, h, v = 2.6, (0, 0, 3, 3), 2, 2 @@ -441,7 +454,7 @@ def test_element_as_gdf(self): paths = ntw.shortest_paths(tree, synth_obs) paths_gdf = self.spaghetti.element_as_gdf(ntw, routes=paths) observed_length = paths_gdf.loc[0, "geometry"].length - self.assertEqual(observed_length, known_length) + assert observed_length == known_length # extract asymmetric routes known_origins, bounds, h, v = 2, (0, 0, 3, 3), 2, 2 @@ -455,7 +468,7 @@ def test_element_as_gdf(self): paths = ntw.shortest_paths(tree, points1, pp_dest=points2) paths_gdf = self.spaghetti.element_as_gdf(ntw, routes=paths) observed_origins = paths_gdf["O"].nunique() - self.assertEqual(observed_origins, known_origins) + assert observed_origins == known_origins def test_regular_lattice(self): # 4x4 regular lattice with the exterior @@ -463,14 +476,14 @@ def test_regular_lattice(self): bounds = (0, 0, 3, 3) lattice = self.spaghetti.regular_lattice(bounds, 2, nv=2, exterior=True) observed = lattice[0].vertices - self.assertEqual(observed, known) + assert observed == known # 5x5 regular lattice without the exterior known = [P33, P34] bounds = (0, 0, 4, 4) lattice = self.spaghetti.regular_lattice(bounds, 3, exterior=False) observed = lattice[-1].vertices - self.assertEqual(observed, known) + assert observed == known # 7x9 regular lattice from shapefile bounds known_vertices = [ @@ -481,20 +494,20 @@ def test_regular_lattice(self): lattice = self.spaghetti.regular_lattice(shp.bbox, 5, nv=7, exterior=True) observed_vertices = lattice[0].vertices for observed, known in zip(observed_vertices, known_vertices): - self.assertEqual((observed[0], observed[1]), known) + assert (observed[0], observed[1]) == known # test for Type Error - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.spaghetti.regular_lattice(bounds, [[4]]) # test for Runtime Error - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): self.spaghetti.regular_lattice((0, 0, 1), 1) # ------------------------------------------------------------------------------- -class TestNetworkPointPattern(unittest.TestCase): - def setUp(self): +class TestNetworkPointPattern: + def setup_method(self): self.ntw = self.spaghetti.Network(in_data=STREETS) self.obs = [schools, crimes] self.OBS = [SCHOOLS, CRIMES] @@ -506,9 +519,6 @@ def setUp(self): self.known_pp1_npoints = 8 - def tearDown(self): - pass - def test_pp_from_libpysal_points(self): # known cpp = self.ntw.pointpatterns[crimes] @@ -521,7 +531,7 @@ def test_pp_from_libpysal_points(self): self.ntw.snapobservations(point_data, cg_crimes, attribute=True) observed = self.ntw.pointpatterns[cg_crimes] observed_snapped = set(observed.snapped_coordinates.values()) - self.assertEqual(observed_snapped, known_snapped) + assert observed_snapped == known_snapped def test_pp_from_single_libpysal_point(self): # network instantiated from a single libpysal.cg.Chain @@ -529,7 +539,7 @@ def test_pp_from_single_libpysal_point(self): self.ntw_from_chain = self.spaghetti.Network(in_data=P11P22_CHAIN) self.ntw_from_chain.snapobservations(P00, synth_obs) snap_dist = self.ntw_from_chain.pointpatterns[synth_obs].dist_snapped[0] - self.assertAlmostEqual(snap_dist, known_dist, places=10) + assert snap_dist == pytest.approx(known_dist, 0.0000000001) # network instantiated from a single vertical (up) libpysal.cg.Chain chain = cg.Chain([P11, P12]) @@ -537,7 +547,7 @@ def test_pp_from_single_libpysal_point(self): self.ntw_from_chain = self.spaghetti.Network(in_data=chain) self.ntw_from_chain.snapobservations(cg.Point((0, 1.5)), synth_obs) snap_dist = self.ntw_from_chain.pointpatterns[synth_obs].dist_snapped[0] - self.assertEqual(snap_dist, known_dist) + assert snap_dist == known_dist # network instantiated from a single vertical (down) libpysal.cg.Chain chain = cg.Chain([cg.Point((5, 5)), cg.Point((5, 4))]) @@ -545,19 +555,19 @@ def test_pp_from_single_libpysal_point(self): self.ntw_from_chain = self.spaghetti.Network(in_data=chain) self.ntw_from_chain.snapobservations(cg.Point((6.5, 4.5)), synth_obs) snap_dist = self.ntw_from_chain.pointpatterns[synth_obs].dist_snapped[0] - self.assertEqual(snap_dist, known_dist) + assert snap_dist == known_dist def test_pp_failures(self): # network instantiated from a single libpysal.cg.Chain self.ntw_from_chain = self.spaghetti.Network(in_data=P11P22_CHAIN) # try snapping chain - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.ntw_from_chain.snapobservations(P11P22_CHAIN, "chain") # try snapping list of chain - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.ntw_from_chain.snapobservations([P11P22_CHAIN], "chain") - @unittest.skipIf(GEOPANDAS_EXTINCT, "Missing Geopandas") + @pytest.mark.skipif(GEOPANDAS_EXTINCT, reason="Missing Geopandas") def test_pp_from_geopandas(self): idxs = ["gdf_%s" % pp for pp in self.idxs] iterator = zip(self.obs, self.OBS, idxs) @@ -569,30 +579,30 @@ def test_pp_from_geopandas(self): self.ntw.snapobservations(OBS, obs, **kwargs) setattr(self, idx, self.ntw.pointpatterns[obs]) - self.assertEqual(self.pp1.npoints, self.gdf_pp1.npoints) - self.assertEqual(self.pp2.npoints, self.gdf_pp2.npoints) + assert self.pp1.npoints == self.gdf_pp1.npoints + assert self.pp2.npoints == self.gdf_pp2.npoints def test_add_point_pattern(self): - self.assertEqual(self.pp1.npoints, self.known_pp1_npoints) - self.assertIn("properties", self.pp1.points[0]) - self.assertIn([1], self.pp1.points[0]["properties"]) + assert self.pp1.npoints == self.known_pp1_npoints + assert "properties" in self.pp1.points[0] + assert [1] in self.pp1.points[0]["properties"] def test_count_per_link_network(self): counts = self.ntw.count_per_link(self.pp1.obs_to_arc, graph=False) meancounts = sum(counts.values()) / float(len(counts.keys())) - self.assertAlmostEqual(meancounts, 1.0, places=5) + assert meancounts == pytest.approx(1.0, 0.00001) def test_count_per_edge_graph(self): counts = self.ntw.count_per_link(self.pp1.obs_to_arc, graph=True) meancounts = sum(counts.values()) / float(len(counts.keys())) - self.assertAlmostEqual(meancounts, 1.0, places=5) + assert meancounts == pytest.approx(1.0, 0.00001) def test_simulate_uniform_observations(self): sim = self.ntw.simulate_observations(self.known_pp1_npoints) - self.assertEqual(self.known_pp1_npoints, sim.npoints) + assert self.known_pp1_npoints == sim.npoints def test_simulate_unsupported_distribution_observations(self): - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): self.ntw.simulate_observations(1, distribution="mrofinu") def test_all_neighbor_distances(self): @@ -600,29 +610,29 @@ def test_all_neighbor_distances(self): known_mtx_val = 17682.436988 known_tree_val = (173, 64) - self.assertAlmostEqual(numpy.nansum(matrix1[0]), known_mtx_val, places=4) - self.assertEqual(tree[(6, 7)], known_tree_val) + assert numpy.nansum(matrix1[0]) == pytest.approx(known_mtx_val, 0.0001) + assert tree[(6, 7)] == known_tree_val del self.ntw.distance_matrix del self.ntw.network_trees matrix2 = self.ntw.allneighbordistances(schools, fill_diagonal=0.0) observed = matrix2.diagonal() known = numpy.zeros(matrix2.shape[0]) - self.assertEqual(observed.all(), known.all()) + assert observed.all() == known.all() del self.ntw.distance_matrix del self.ntw.network_trees matrix3 = self.ntw.allneighbordistances(schools, snap_dist=True) known_mtx_val = 3218.2597894 observed_mtx_val = matrix3 - self.assertAlmostEqual(observed_mtx_val[0, 1], known_mtx_val, places=4) + assert observed_mtx_val[0, 1] == pytest.approx(known_mtx_val, 0.0001) del self.ntw.distance_matrix del self.ntw.network_trees matrix4 = self.ntw.allneighbordistances(schools, fill_diagonal=0.0) observed = matrix4.diagonal() known = numpy.zeros(matrix4.shape[0]) - self.assertEqual(observed.all(), known.all()) + assert observed.all() == known.all() del self.ntw.distance_matrix del self.ntw.network_trees @@ -630,8 +640,8 @@ def test_all_neighbor_distances(self): known_mtx_val = 1484112.694526529 known_tree_val = (-0.1, -0.1) - self.assertAlmostEqual(numpy.nansum(matrix5[0]), known_mtx_val, places=4) - self.assertEqual(tree[(18, 19)], known_tree_val) + assert numpy.nansum(matrix5[0]) == pytest.approx(known_mtx_val, 0.0001) + assert tree[(18, 19)] == known_tree_val del self.ntw.distance_matrix del self.ntw.network_trees @@ -644,15 +654,15 @@ def test_all_neighbor_distances_multiproccessing(self): observed = matrix1.diagonal() known = numpy.zeros(matrix1.shape[0]) - self.assertEqual(observed.all(), known.all()) - self.assertAlmostEqual(numpy.nansum(matrix1[0]), known_mtx1_val, places=4) - self.assertEqual(tree[(6, 7)], known_tree_val) + assert observed.all() == known.all() + assert numpy.nansum(matrix1[0]) == pytest.approx(known_mtx1_val, 0.0001) + assert tree[(6, 7)] == known_tree_val del self.ntw.distance_matrix del self.ntw.network_trees matrix2 = self.ntw.allneighbordistances(schools, n_processes=2) known_mtx2_val = 17682.436988 - self.assertAlmostEqual(numpy.nansum(matrix2[0]), known_mtx2_val, places=4) + assert numpy.nansum(matrix2[0]) == pytest.approx(known_mtx2_val, 0.0001) del self.ntw.distance_matrix del self.ntw.network_trees @@ -662,14 +672,14 @@ def test_all_neighbor_distances_multiproccessing(self): known_mtx3_val = 17682.436988 known_tree_val = (173, 64) - self.assertAlmostEqual(numpy.nansum(matrix3[0]), known_mtx3_val, places=4) - self.assertEqual(tree[(6, 7)], known_tree_val) + assert numpy.nansum(matrix3[0]) == pytest.approx(known_mtx3_val, 0.0001) + assert tree[(6, 7)] == known_tree_val del self.ntw.distance_matrix del self.ntw.network_trees def test_nearest_neighbor_distances(self): # general test - with self.assertRaises(KeyError): + with pytest.raises(KeyError): self.ntw.nearestneighbordistances("i_should_not_exist") nnd1 = self.ntw.nearestneighbordistances(schools) nnd2 = self.ntw.nearestneighbordistances(schools, destpattern=schools) @@ -682,25 +692,25 @@ def test_nearest_neighbor_distances(self): # nearest neighbor keeping zero test known_zero = ([19], 0.0)[0] nn_c = self.ntw.nearestneighbordistances(crimes, keep_zero_dist=True) - self.assertEqual(nn_c[18][0], known_zero) + assert nn_c[18][0] == known_zero del self.ntw.distance_matrix del self.ntw.network_trees # nearest neighbor omitting zero test known_nonzero = ([11], 165.33982412719126)[1] nn_c = self.ntw.nearestneighbordistances(crimes, keep_zero_dist=False) - self.assertAlmostEqual(nn_c[18][1], known_nonzero, places=4) + assert nn_c[18][1] == pytest.approx(known_nonzero, 0.0001) del self.ntw.distance_matrix del self.ntw.network_trees # nearest neighbor with snap distance known_neigh = ([3], 402.5219673922477)[1] nn_c = self.ntw.nearestneighbordistances(crimes, snap_dist=True) - self.assertAlmostEqual(nn_c[0][1], known_neigh, places=4) + assert nn_c[0][1] == pytest.approx(known_neigh, 0.0001) del self.ntw.distance_matrix del self.ntw.network_trees - @unittest.skipIf(GEOPANDAS_EXTINCT, "Missing Geopandas") + @pytest.mark.skipif(GEOPANDAS_EXTINCT, reason="Missing Geopandas") def test_element_as_gdf(self): obs = self.spaghetti.element_as_gdf(self.ntw, pp_name=schools) snap_obs = self.spaghetti.element_as_gdf( @@ -711,15 +721,15 @@ def test_element_as_gdf(self): observed_point = obs.loc[(obs["id"] == 0), "geometry"].squeeze() snap_point = snap_obs.loc[(snap_obs["id"] == 0), "geometry"].squeeze() observed_dist = observed_point.distance(snap_point) - self.assertAlmostEqual(observed_dist, known_dist, places=8) + assert observed_dist == pytest.approx(known_dist, 0.00000001) - with self.assertRaises(KeyError): + with pytest.raises(KeyError): self.spaghetti.element_as_gdf(self.ntw, pp_name="i_should_not_exist") # ------------------------------------------------------------------------------- -class TestNetworkAnalysis(unittest.TestCase): - def setUp(self): +class TestNetworkAnalysis: + def setup_method(self): # synthetic test data bounds, h, v = (0, 0, 3, 3), 2, 2 lattice = self.spaghetti.regular_lattice(bounds, h, nv=v, exterior=True) @@ -748,9 +758,6 @@ def setUp(self): self.ntw_shp = self.spaghetti.Network(in_data=STREETS) self.ntw_shp.snapobservations(CRIMES, crimes, attribute=True) - def tearDown(self): - pass - def test_global_auto_k_uniform(self): known_lowerenvelope = numpy.array( [ @@ -773,11 +780,11 @@ def test_global_auto_k_uniform(self): nsteps=self.test_steps, distribution="uniform", ) - self.assertEqual(obtained.lowerenvelope.shape[0], self.test_steps) + assert obtained.lowerenvelope.shape[0] == self.test_steps numpy.testing.assert_allclose(obtained.lowerenvelope, known_lowerenvelope) def test_global_auto_k_unsupported_distribution(self): - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): self.ntw.GlobalAutoK( self.ntw.pointpatterns[self.mids], permutations=self.test_permutations, @@ -791,7 +798,7 @@ def test_moran_network(self): numpy.testing.assert_allclose( observed_moran.I, known_moran_I, rtol=RTOL, atol=ATOL ) - self.assertEqual(observed_y[:5], known_y) + assert observed_y[:5] == known_y def test_moran_graph(self): known_moran_I, known_y = 0.004777863137379377, [1, 0.0, 0.0, 3, 1] @@ -799,24 +806,21 @@ def test_moran_graph(self): numpy.testing.assert_allclose( observed_moran.I, known_moran_I, rtol=RTOL, atol=ATOL ) - self.assertEqual(observed_y[:5], known_y) + assert observed_y[:5] == known_y # ------------------------------------------------------------------------------- -class TestNetworkUtils(unittest.TestCase): - def setUp(self): +class TestNetworkUtils: + def setup_method(self): self.ntw = self.spaghetti.Network(in_data=STREETS) self.P00, self.P01 = P00, P01 self.P10, self.P11 = P10, P11 - def tearDown(self): - pass - def test_compute_length(self): point1, point2 = (0, 0), (1, 1) known_length = 1.4142135623730951 observed_length = self.util.compute_length(point1, point2) - self.assertAlmostEqual(observed_length, known_length, places=4) + assert observed_length == pytest.approx(known_length, 0.0001) def test_get_neighbor_distances(self): known_neighs = {1: 102.62353453439829, 2: 660.000001049743} @@ -824,28 +828,28 @@ def test_get_neighbor_distances(self): self.ntw, 0, self.ntw.arc_lengths ) for k in known_neighs.keys(): - self.assertAlmostEqual(observed_neighs[k], known_neighs[k], places=4) + assert observed_neighs[k] == pytest.approx(known_neighs[k], 0.0001) def test_generate_tree(self): known_path = [23, 22, 20, 19, 170, 2, 0] distance, pred = self.util.dijkstra(self.ntw, 0) tree = self.util.generatetree(pred) - self.assertEqual(tree[3], known_path) + assert tree[3] == known_path def test_dijkstra(self): distance, pred = self.util.dijkstra(self.ntw, 0) - self.assertAlmostEqual(distance[196], 5505.668247, places=4) - self.assertEqual(pred[196], 133) + assert distance[196] == pytest.approx(5505.668247, 0.0001) + assert pred[196] == 133 def test_dijkstra_mp(self): distance, pred = self.util.dijkstra_mp((self.ntw, 0)) - self.assertAlmostEqual(distance[196], 5505.668247, places=4) - self.assertEqual(pred[196], 133) + assert distance[196] == pytest.approx(5505.668247, 0.0001) + assert pred[196] == 133 def test_chain_constr(self): known_len = 1.4142135623730951 chain = self.util.chain_constr({0: (0.0, 0.0), 1: (1.0, 1.0)}, [(0, 1)])[0] - self.assertAlmostEqual(chain.len, known_len, places=10) + assert chain.len == pytest.approx(known_len, 0.00000000001) def test_build_chains(self): # 1x1 cross (regular lattice without the exterior) @@ -857,8 +861,8 @@ def test_build_chains(self): _hl = self.util.build_chains(space_h, space_v, exterior, bounds, h=True) v_observed = _vl[0].vertices h_observed = _hl[0].vertices - self.assertEqual(v_observed, v_known) - self.assertEqual(h_observed, h_known) + assert v_observed == v_known + assert h_observed == h_known # 3x3 cross (regular lattice with the exterior) v_known = [self.P00, self.P01] @@ -868,21 +872,21 @@ def test_build_chains(self): _hl = self.util.build_chains(space_h, space_v, exterior, bounds, h=True) v_observed = _vl[0].vertices h_observed = _hl[0].vertices - self.assertEqual(v_observed, v_known) - self.assertEqual(h_observed, h_known) + assert v_observed == v_known + assert h_observed == h_known # this test is causing CI to stall in some cases # see https://github.com/pysal/spaghetti/issues/666 def test_squared_distance_point_link(self): point, link = (1, 1), ((0, 0), (2, 0)) sqrd_nearp = self.util.squared_distance_point_link(point, link) - self.assertEqual(sqrd_nearp[0], 1.0) - self.assertEqual(sqrd_nearp[1].all(), numpy.array([1.0, 0.0]).all()) + assert sqrd_nearp[0] == 1.0 + assert sqrd_nearp[1].all() == numpy.array([1.0, 0.0]).all() def test_snap_points_to_links(self): points = {0: P11} links = [cg.shapes.Chain([P00, cg.shapes.Point((2, 0))])] snapped = self.util.snap_points_to_links(points, links) known_coords = [xy._Point__loc for xy in snapped[0][0]] - self.assertEqual(known_coords, [(0.0, 0.0), (2.0, 0.0)]) - self.assertEqual(snapped[0][1].all(), numpy.array([1.0, 0.0]).all()) + assert known_coords == [(0.0, 0.0), (2.0, 0.0)] + assert snapped[0][1].all() == numpy.array([1.0, 0.0]).all() diff --git a/spaghetti/tests/test_api_network.py b/spaghetti/tests/test_api_network.py index 6a95309d..68b124ff 100644 --- a/spaghetti/tests/test_api_network.py +++ b/spaghetti/tests/test_api_network.py @@ -1,11 +1,9 @@ """ Testing for the spaghetti api import structure. """ -import unittest - -from .network_unittest_classes import TestNetwork -from .network_unittest_classes import TestNetworkPointPattern -from .network_unittest_classes import TestNetworkAnalysis +from .network_test_classes import TestNetwork +from .network_test_classes import TestNetworkPointPattern +from .network_test_classes import TestNetworkAnalysis # api import structure import spaghetti @@ -21,6 +19,3 @@ # run tests on spaghetti.analysis TestNetworkAnalysis.spaghetti = spaghetti TestNetworkAnalysis() - -if __name__ == "__main__": - unittest.main() diff --git a/spaghetti/tests/test_dev_network.py b/spaghetti/tests/test_dev_network.py index 83fa4942..9d8cbe91 100644 --- a/spaghetti/tests/test_dev_network.py +++ b/spaghetti/tests/test_dev_network.py @@ -1,13 +1,10 @@ """ Testing for the spaghetti development import structure. """ -import unittest - -from .network_unittest_classes import TestNetwork -from .network_unittest_classes import TestNetworkPointPattern -from .network_unittest_classes import TestNetworkAnalysis - -from .network_unittest_classes import TestNetworkUtils +from .network_test_classes import TestNetwork +from .network_test_classes import TestNetworkPointPattern +from .network_test_classes import TestNetworkAnalysis +from .network_test_classes import TestNetworkUtils # dev import structure from .. import network as spaghetti @@ -29,6 +26,3 @@ TestNetworkUtils.spaghetti = spaghetti TestNetworkUtils.util = util TestNetworkUtils() - -if __name__ == "__main__": - unittest.main() diff --git a/spaghetti/util.py b/spaghetti/util.py index 52b24f94..bd5eba71 100644 --- a/spaghetti/util.py +++ b/spaghetti/util.py @@ -244,7 +244,7 @@ def dijkstra(ntw, v0, initial_dist=numpy.inf): unvisited.add(v1) # cast preceding vertices list as an array of integers - pred = numpy.array(pred, dtype=numpy.int) + pred = numpy.array(pred, dtype=int) return distance, pred From 3c3dbcf3344246a2030ea66248e456a27df35914 Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Mon, 10 Oct 2022 20:44:55 -0400 Subject: [PATCH 2/8] run isort --- docs/conf.py | 12 +++++++----- setup.py | 2 ++ spaghetti/__init__.py | 13 +++++++++---- spaghetti/network.py | 19 +++++++++++-------- spaghetti/tests/network_test_classes.py | 9 +++++---- spaghetti/tests/test_api_network.py | 10 ++++++---- spaghetti/tests/test_dev_network.py | 11 ++++++----- spaghetti/util.py | 5 ++--- 8 files changed, 48 insertions(+), 33 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 63a38677..e903f1fb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,9 @@ # directories to sys.path here. If the directory is relative to the documentation root, # use os.path.abspath to make it absolute, like shown here. -import sys, os +import os +import sys + import sphinx_bootstrap_theme # import your package to obtain the version info to display on the docs website @@ -189,8 +191,8 @@ ( master_doc, "%s.tex" % project, - u"%s Documentation" % project, - u"pysal developers", + "%s Documentation" % project, + "pysal developers", "manual", ) ] @@ -200,7 +202,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "%s" % project, u"%s Documentation" % project, [author], 1)] +man_pages = [(master_doc, "%s" % project, "%s Documentation" % project, [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -212,7 +214,7 @@ ( master_doc, "%s" % project, - u"%s Documentation" % project, + "%s Documentation" % project, author, "%s" % project, "SPAtial GrapHs: nETworks, Topology, & Inference", diff --git a/setup.py b/setup.py index 4e37a026..8eeecff7 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,7 @@ from distutils.command.build_py import build_py + from setuptools import setup + import versioneer package = "spaghetti" diff --git a/spaghetti/__init__.py b/spaghetti/__init__.py index bf0b3193..bc5c1902 100644 --- a/spaghetti/__init__.py +++ b/spaghetti/__init__.py @@ -2,10 +2,15 @@ # `spaghetti` --- Spatial Graphs: Networks, Topology, & Inference """ -from .network import Network, PointPattern, SimulatedPointPattern -from .network import extract_component, spanning_tree -from .network import element_as_gdf, regular_lattice - from . import _version +from .network import ( + Network, + PointPattern, + SimulatedPointPattern, + element_as_gdf, + extract_component, + regular_lattice, + spanning_tree, +) __version__ = _version.get_versions()["version"] diff --git a/spaghetti/network.py b/spaghetti/network.py index 4d098b2b..0c67fc34 100644 --- a/spaghetti/network.py +++ b/spaghetti/network.py @@ -1,15 +1,18 @@ -from collections import defaultdict, OrderedDict +import copy +import os +import pickle +import warnings +from collections import OrderedDict, defaultdict from itertools import islice -import copy, os, pickle, warnings import esda import numpy - -from .analysis import GlobalAutoK -from . import util from libpysal import cg, examples, weights from libpysal.common import requires +from . import util +from .analysis import GlobalAutoK + try: from libpysal import open except ImportError: @@ -32,7 +35,7 @@ "requiring network and point pattern input as ``libpysal.cg`` " "geometries should prepare for this simply by converting " "to ``shapely`` geometries." - ) +) warnings.warn(f"{dep_msg}", FutureWarning) @@ -1409,12 +1412,12 @@ def _newpoint_coords(self, arc, distance): # if the horizontal direction is negative from # vertex 1 to vertex 2 on the euclidean plane if x1 > x2: - x0 = x1 - distance / numpy.sqrt(1 + m ** 2) + x0 = x1 - distance / numpy.sqrt(1 + m**2) # if the horizontal direction is positive from # vertex 1 to vertex 2 on the euclidean plane elif x1 < x2: - x0 = x1 + distance / numpy.sqrt(1 + m ** 2) + x0 = x1 + distance / numpy.sqrt(1 + m**2) # calculate the (y) coordinate y0 = m * (x0 - x1) + y1 diff --git a/spaghetti/tests/network_test_classes.py b/spaghetti/tests/network_test_classes.py index a18817ce..0ffc311c 100644 --- a/spaghetti/tests/network_test_classes.py +++ b/spaghetti/tests/network_test_classes.py @@ -1,10 +1,11 @@ -from libpysal import cg, examples, io -from libpysal.common import RTOL, ATOL -import numpy import copy -import pytest import warnings +import numpy +import pytest +from libpysal import cg, examples, io +from libpysal.common import ATOL, RTOL + try: import geopandas diff --git a/spaghetti/tests/test_api_network.py b/spaghetti/tests/test_api_network.py index 68b124ff..3fbcaee5 100644 --- a/spaghetti/tests/test_api_network.py +++ b/spaghetti/tests/test_api_network.py @@ -1,13 +1,15 @@ """ Testing for the spaghetti api import structure. """ -from .network_test_classes import TestNetwork -from .network_test_classes import TestNetworkPointPattern -from .network_test_classes import TestNetworkAnalysis - # api import structure import spaghetti +from .network_test_classes import ( + TestNetwork, + TestNetworkAnalysis, + TestNetworkPointPattern, +) + # run tests on spaghetti.network.Network TestNetwork.spaghetti = spaghetti TestNetwork() diff --git a/spaghetti/tests/test_dev_network.py b/spaghetti/tests/test_dev_network.py index 9d8cbe91..5f09a1d0 100644 --- a/spaghetti/tests/test_dev_network.py +++ b/spaghetti/tests/test_dev_network.py @@ -1,14 +1,15 @@ """ Testing for the spaghetti development import structure. """ -from .network_test_classes import TestNetwork -from .network_test_classes import TestNetworkPointPattern -from .network_test_classes import TestNetworkAnalysis -from .network_test_classes import TestNetworkUtils - # dev import structure from .. import network as spaghetti from .. import util +from .network_test_classes import ( + TestNetwork, + TestNetworkAnalysis, + TestNetworkPointPattern, + TestNetworkUtils, +) # run tests on spaghetti.network.Network TestNetwork.spaghetti = spaghetti diff --git a/spaghetti/util.py b/spaghetti/util.py index bd5eba71..d5ea64e4 100644 --- a/spaghetti/util.py +++ b/spaghetti/util.py @@ -1,14 +1,13 @@ from warnings import warn +import numpy from libpysal import cg from libpysal.common import requires -import numpy from rtree import Rtree - try: import geopandas - from shapely.geometry import Point, LineString + from shapely.geometry import LineString, Point except ImportError: msg = "geopandas/shapely not available. Some functionality will be disabled." warn(msg) From 94e2fc9c317a46bd32294dec8178b0a4d9e5ceb2 Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Mon, 10 Oct 2022 20:49:22 -0400 Subject: [PATCH 3/8] autmate linting and code sorting --- .pre-commit-config.yaml | 22 ++++++++++++++++------ setup.cfg | 5 +++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 344f35dc..8445812b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,16 @@ -repos: -- repo: https://github.com/psf/black - rev: 22.6.0 - hooks: - - id: black - language_version: python3 +repos: +- repo: https://github.com/pycqa/isort + rev: 5.10.1 + hooks: + - id: isort + language_version: python3 +- repo: https://github.com/pycqa/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + language_version: python3 +- repo: https://github.com/psf/black + rev: 22.6.0 + hooks: + - id: black + language_version: python3 diff --git a/setup.cfg b/setup.cfg index f9db0bdb..248a15fc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,3 +5,8 @@ versionfile_source = spaghetti/_version.py versionfile_build = spaghetti/_version.py tag_prefix = v parentdir_prefix = spaghetti- + +[flake8] +# Black enforces 88 characters line length +max_line_length = 88 +exclude = *__init__.py, docs/conf.py, versioneer.py, spaghetti/_version.py From 07fdda9b82bd18a3901f2d0c2e2b98e496230a87 Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Mon, 10 Oct 2022 21:18:41 -0400 Subject: [PATCH 4/8] some clean ups --- spaghetti/network.py | 52 ++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/spaghetti/network.py b/spaghetti/network.py index 0c67fc34..8a002331 100644 --- a/spaghetti/network.py +++ b/spaghetti/network.py @@ -7,7 +7,7 @@ import esda import numpy -from libpysal import cg, examples, weights +from libpysal import cg, weights from libpysal.common import requires from . import util @@ -48,7 +48,7 @@ class Network: Parameters ---------- - in_data : {str, iterable (list, tuple, numpy.ndarray), libpysal.cg.Chain, geopandas.GeoDataFrame} + in_data : {str, iterable, libpysal.cg.Chain, geopandas.GeoDataFrame} The input geographic data. Either (1) a path to a shapefile (str); (2) an iterable containing ``libpysal.cg.Chain`` objects; (3) a single ``libpysal.cg.Chain``; or @@ -191,7 +191,7 @@ class Network: network structure will generally have (1) more vertices and links than may expected, and, (2) many degree-2 vertices, which differs from a truly graph-theoretic object. This is demonstrated in the - `Caveats Tutorial `_. + `Caveats Tutorial `_. See :cite:`Cliff1981`, :cite:`Tansel1983a`, :cite:`AhujaRavindraK`, :cite:`Labbe1995`, @@ -317,7 +317,7 @@ def __init__( as_graph = False network_weightings = False - if weightings == True: + if weightings: # set network arc weights to length if weights are # desired, but no other input in given weightings = self.arc_lengths @@ -440,7 +440,7 @@ def identify_components(self, w, graph=False): c2l_ = component2link[cpl] arclens_ = self.arc_lengths.items() component_lengths[cpl] = sum([v for k, v in arclens_ if k in c2l_]) - component_vertices[cpl] = list(set([v for l in c2l_ for v in l])) + component_vertices[cpl] = list(set([v for link in c2l_ for v in link])) component_vertex_count[cpl] = len(component_vertices[cpl]) # longest and largest components @@ -1014,7 +1014,7 @@ def distancebandweights(self, threshold, n_processes=1, gen_tree=False): >>> ntw = spaghetti.Network(in_data=streets_file) Create a contiguity-based ``W`` object based on network distance, ``500`` - `US feet in this case `_. + US feet in this case. >>> w = ntw.distancebandweights(threshold=500) @@ -2293,7 +2293,7 @@ def split_arcs(self, split_param, split_by="distance", w_components=True): >>> ntw = spaghetti.Network(examples.get_path("streets.shp")) Split the network into a segments of 200 distance units in length - (`US feet in this case `_.). + (US feet in this case). This will include "remainder" segments unless the network is comprised of arcs with lengths exactly divisible by ``distance``. @@ -2322,6 +2322,12 @@ def split_arcs(self, split_param, split_by="distance", w_components=True): """ + def int_coord(c): + """convert coordinates for integers if possible + e.g., (1.0, 0.5) --> (1, 0.5) + """ + return int(c) if (type(c) == float and c.is_integer()) else c + # catch invalid split types split_by = split_by.lower() valid_split_types = ["distance", "count"] @@ -2342,10 +2348,6 @@ def split_arcs(self, split_param, split_by="distance", w_components=True): msg += f"Currently 'split_param' is set to {split_param}." raise TypeError(msg) - # convert coordinates for integers if possible - # e.g., (1.0, 0.5) --> (1, 0.5) - int_coord = lambda c: int(c) if (type(c) == float and c.is_integer()) else c - # create new shell network instance split_network = Network() @@ -2490,7 +2492,8 @@ def GlobalAutoK( upperbound=None, ): r"""Compute a global auto :math:`K`-function based on a network constrained - cost matrix through `Monte Carlo simulation `_ + cost matrix through + `Monte Carlo simulation `_ according to the formulation adapted from :cite:`doi:10.1002/9780470549094.ch5`. See the **Notes** section for further description. @@ -2528,11 +2531,12 @@ def GlobalAutoK( \displaystyle K(r)=\frac{\sum^n_{i=1} \#[\hat{A} \in D(a_i, r)]}{n\lambda}, - where $n$ is the set cardinality of :math:`A`, :math:`\hat{A}` is the subset of - observations in :math:`A` that are within :math:`D` units of distance from :math:`a_i` - (each single observation in :math:`A`), and :math:`r` is the range of distance - values over which the :math:`K`-function is calculated. The :math:`\lambda` term - is the intensity of observations along the network, calculated as: + where $n$ is the set cardinality of :math:`A`, :math:`\hat{A}` is the + subset of observations in :math:`A` that are within :math:`D` units of + distance from :math:`a_i` (each single observation in :math:`A`), and :math:`r` + is the range of distance values over which the :math:`K`-function is + calculated. The :math:`\lambda` term is the intensity of observations + along the network, calculated as: .. math:: @@ -2544,7 +2548,8 @@ def GlobalAutoK( distance buffers :math:`D \in r`. The :math:`K`-function improves upon nearest-neighbor distance measures through the analysis of all neighbor distances. For an explanation on how to interpret the results of the - :math:`K`-function see the `Network Spatial Dependence tutorial `_. + :math:`K`-function see the Network Spatial Dependence tutorial + `here `_. For original implementation see :cite:`Ripley1976` and :cite:`Ripley1977`. @@ -2601,8 +2606,8 @@ def Moran(self, pp_name, permutations=999, graph=False): while considering both attribute values and spatial relationships. A value of closer to +1 indicates absolute clustering while a value of closer to -1 indicates absolute dispersion. Complete - spatial randomness takes the value of 0. See the - `esda documentation `_ + spatial randomness takes the value of 0. See the ``esda`` + `documentation `_ for in-depth descriptions and tutorials. Parameters @@ -2876,8 +2881,7 @@ def _reassign(attr, cid): warnings.warn(msg) for attr in [dm, nt]: if hasattr(net, attr): - _attr = getattr(net, attr) - del _attr + delattr(net, attr) # make initial copy of the network cnet = copy.deepcopy(net) @@ -3215,8 +3219,8 @@ def element_as_gdf( def regular_lattice(bounds, nh, nv=None, exterior=False): - """Generate a regular lattice of line segments - (`libpysal.cg.Chain objects `_). + """Generate a regular lattice of line segments (``libpysal.cg.Chain`` + `objects `_). Parameters ---------- From e129a96820a1e9945460f4645113693885360b5d Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Mon, 10 Oct 2022 21:19:52 -0400 Subject: [PATCH 5/8] some clean ups [2] --- spaghetti/analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spaghetti/analysis.py b/spaghetti/analysis.py index c5869be7..908a89ed 100644 --- a/spaghetti/analysis.py +++ b/spaghetti/analysis.py @@ -80,7 +80,7 @@ def validatedistribution(self): """Ensure the statistical distribution is supported.""" valid_distributions = ["uniform"] - if not self.distribution in valid_distributions: + if self.distribution not in valid_distributions: msg = "%s distribution not currently supported." % self.distribution raise RuntimeError(msg) From 04c7a956836b4e66494d61ff2e3d45f4d78a02d9 Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Mon, 10 Oct 2022 21:21:26 -0400 Subject: [PATCH 6/8] some clean ups [3] --- spaghetti/util.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spaghetti/util.py b/spaghetti/util.py index d5ea64e4..fd26e31d 100644 --- a/spaghetti/util.py +++ b/spaghetti/util.py @@ -43,7 +43,7 @@ def compute_length(v0, v1): return euc_dist -def get_neighbor_distances(ntw, v0, l): +def get_neighbor_distances(ntw, v0, link): """Get distances to the nearest vertex neighbors along connecting arcs. @@ -53,7 +53,7 @@ def get_neighbor_distances(ntw, v0, l): A spaghetti network object. v0 : int The vertex ID. - l : dict + link : dict The key is a tuple (start vertex, end vertex); value is ``float``. Cost per arc to travel, e.g. distance. @@ -85,9 +85,9 @@ def get_neighbor_distances(ntw, v0, l): # set distance from vertex1 to vertex2 (link length) if arc[0] != v0: - neighbors[arc[0]] = l[arc] + neighbors[arc[0]] = link[arc] else: - neighbors[arc[1]] = l[arc] + neighbors[arc[1]] = link[arc] return neighbors From 8f8e5f4941e7e94e165e06a0d80b3e6f18e50954 Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Mon, 10 Oct 2022 21:26:39 -0400 Subject: [PATCH 7/8] trigger CI --- spaghetti/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spaghetti/util.py b/spaghetti/util.py index fd26e31d..dbbfc4fd 100644 --- a/spaghetti/util.py +++ b/spaghetti/util.py @@ -98,7 +98,7 @@ def generatetree(pred): Parameters ---------- pred : list - List of preceding vertices for traversal route. + List of preceding vertices for route traversal. Returns ------- From 227b3c3032cd75b0a1603ab2b767723298bc8ad3 Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Mon, 10 Oct 2022 21:55:18 -0400 Subject: [PATCH 8/8] update test --- spaghetti/tests/network_test_classes.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/spaghetti/tests/network_test_classes.py b/spaghetti/tests/network_test_classes.py index 0ffc311c..17185046 100644 --- a/spaghetti/tests/network_test_classes.py +++ b/spaghetti/tests/network_test_classes.py @@ -1,5 +1,4 @@ import copy -import warnings import numpy import pytest @@ -209,7 +208,6 @@ def test_components(self): assert observed_graph_edge == known_graph_edge def test_connected_components(self): - ## test warnings ntw = copy.deepcopy(self.ntw_from_lattice_ring) # observed values @@ -346,19 +344,22 @@ def test_shortest_paths(self): observed_vertices1 = observed_paths[2][1].vertices observed_vertices2 = len(observed_paths[3][1].vertices) # known values - known_vertices1 = [(1.0, 0.5), (1.0, 0.6)] + known_vertices1 = [ + cg.Point((0.5, 1.0)), + cg.Point((1.0, 1.0)), + cg.Point((1.0, 0.6)), + ] known_vertices2 = 4 - assert observed_vertices1 == observed_vertices1 + assert observed_vertices1 == known_vertices1 assert observed_vertices2 == known_vertices2 # test error with pytest.raises(AttributeError): lattice = self.spaghetti.regular_lattice((0, 0, 4, 4), 4) ntw = self.spaghetti.Network(in_data=lattice) - paths = ntw.shortest_paths([], synth_obs) + ntw.shortest_paths([], synth_obs) @pytest.mark.filterwarnings("ignore:There is a least one point pattern") - @pytest.mark.filterwarnings("ignore:Either one or both") def test_extract_component(self): ntw = copy.deepcopy(self.ntw_from_lattice_ring) s2s, tree = ntw.allneighbordistances("points", gen_tree=True) @@ -375,10 +376,7 @@ def test_extract_component(self): assert observed_napts == known_napts # test largest component - with pytest.warns(UserWarning, match="Either one or both"): - largest = self.spaghetti.extract_component( - ntw, ntw.network_largest_component - ) + largest = self.spaghetti.extract_component(ntw, ntw.network_largest_component) # observed values observed_arcs, observed_edges = largest.arcs, largest.edges @@ -751,7 +749,6 @@ def setup_method(self): midpoints.append(mid) self.mids = "mids" self.ntw.snapobservations(midpoints, self.mids) - npts = self.ntw.pointpatterns[self.mids].npoints self.test_permutations = 99 self.test_steps = 10