Repository: martinfleis/momepy
Pull request #179 (2020-06-20 20:11:22)

REF: limit duplications in nx_to_gdf


Patch:
diff --git a/docs/api.rst b/docs/api.rst
index 41b53a5..0ffd8fd 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -103,9 +103,6 @@ graph
    cyclomatic
    edge_node_ratio
    gamma
-   local_betweenness_centrality
-   local_closeness_centrality
-   local_straightness_centrality
    mean_node_degree
    mean_node_dist
    mean_nodes
diff --git a/docs/user_guide/graph/centrality.ipynb b/docs/user_guide/graph/centrality.ipynb
index 3157796..4a0173b 100644
--- a/docs/user_guide/graph/centrality.ipynb
+++ b/docs/user_guide/graph/centrality.ipynb
@@ -115,7 +115,7 @@
     "Closeness centrality could be simplified as average distance to every other node from each node. As such, it can be measured on the whole network (Global Closeness Centrality) or wihtin certain reach only (Local Closeness Centrality). \n",
     "\n",
     "#### Local closeness\n",
-    "To measure `local_closeness_centrality` we need to specify radius (how far we should go from each node). We can use topological distance (e.g. 5 steps, then radius=5) or metric distance (e.g. 400 metres) - then radius=400 and distance= lenght of each segment saved as a parameter of each edge. By default, momepy saves length as `mm_len`. \n",
+    "To measure local `closeness_centrality` we need to specify radius (how far we should go from each node). We can use topological distance (e.g. 5 steps, then radius=5) or metric distance (e.g. 400 metres) - then radius=400 and distance= lenght of each segment saved as a parameter of each edge. By default, momepy saves length as `mm_len`. \n",
     "\n",
     "Weight parameter is used for centrality calculation. Again, we can use metric weight (using the same attribute as above) or no weight (weight=None) at all. Or any other attribute we wish."
    ]
@@ -134,7 +134,7 @@
     }
    ],
    "source": [
-    "primal = momepy.local_closeness_centrality(primal, radius=400, name='closeness400', distance='mm_len', weight='mm_len')"
+    "primal = momepy.closeness_centrality(primal, radius=400, name='closeness400', distance='mm_len', weight='mm_len')"
    ]
   },
   {
@@ -560,20 +560,20 @@
    "source": [
     "### Topological vs metric distances\n",
     "\n",
-    "Centrality can be measure topologically (ignoring physical lenght of street segments) or metrically. Moreover, in the case of local centrality, local subgraph can be also defined topologically and metrically. In the end, you have four options how to measure centralilty, illustrated on `local_betweenness_centrality`:\n",
+    "Centrality can be measure topologically (ignoring physical lenght of street segments) or metrically. Moreover, in the case of local centrality, local subgraph can be also defined topologically and metrically. In the end, you have four options how to measure centralilty, illustrated on `betweenness_centrality`:\n",
     "\n",
     "```py\n",
     "# topologically defined subgraph (5 steps) and topologically measured shortest path\n",
-    "local_betweenness_centrality(graph, radius=5, distance=None, weight=None)\n",
+    "betweenness_centrality(graph, radius=5, distance=None, weight=None)\n",
     "\n",
     "# topologically defined subgraph (5 steps) and metrically measured shortest path\n",
-    "local_betweenness_centrality(graph, radius=5, distance=None, weight='edge_length')\n",
+    "betweenness_centrality(graph, radius=5, distance=None, weight='edge_length')\n",
     "\n",
     "# metrically defined subgraph (800 meters) and topologically measured shortest path\n",
-    "local_betweenness_centrality(graph, radius=800, distance='edge_length', weight=None)\n",
+    "betweenness_centrality(graph, radius=800, distance='edge_length', weight=None)\n",
     "\n",
     "# metrically defined subgraph (800 meters) and metrically measured shortest path\n",
-    "local_betweenness_centrality(graph, radius=800, distance='edge_length', weight='edge_length')\n",
+    "betweenness_centrality(graph, radius=800, distance='edge_length', weight='edge_length')\n",
     "```"
    ]
   },
@@ -814,4 +814,4 @@
  },
  "nbformat": 4,
  "nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/momepy/graph.py b/momepy/graph.py
index 2ad491a..497449d 100644
--- a/momepy/graph.py
+++ b/momepy/graph.py
@@ -4,6 +4,7 @@
 # connectivity.py
 # definitions of connectivity characters
 import math
+import warnings
 
 import networkx as nx
 import numpy as np
@@ -94,7 +95,7 @@ def meshedness(graph, radius=5, name="meshedness", distance=None):
         Graph representing street network.
         Ideally generated from GeoDataFrame using :func:`momepy.gdf_to_nx`
     radius: int, optional
-        number of topological steps defining the extent of subgraph
+        Include all neighbors of distance <= radius from n
     name : str, optional
         calculated attribute name
     distance : str, optional
@@ -206,7 +207,7 @@ def cds_length(
         Graph representing street network.
         Ideally generated from GeoDataFrame using :func:`momepy.gdf_to_nx`
     radius : int
-        number of topological steps defining the extent of subgraph
+        Include all neighbors of distance <= radius from n
     mode : str (default 'sum')
         if ``'sum'``, calculate total length, if ``'mean'`` calculate mean length
     name : str, optional
@@ -341,7 +342,7 @@ def proportion(
         Graph representing street network.
         Ideally generated from GeoDataFrame using :func:`momepy.gdf_to_nx`
     radius: int
-        number of topological steps defining the extent of subgraph
+        Include all neighbors of distance <= radius from n
     three : str, optional
         attribute name for 3-way intersections proportion
     four : str, optional
@@ -430,7 +431,7 @@ def cyclomatic(graph, radius=5, name="cyclomatic", distance=None):
         Graph representing street network.
         Ideally generated from GeoDataFrame using :func:`momepy.gdf_to_nx`
     radius: int
-        number of topological steps defining the extent of subgraph
+        Include all neighbors of distance <= radius from n
     name : str, optional
         calculated attribute name
     distance : str, optional
@@ -496,7 +497,7 @@ def edge_node_ratio(graph, radius=5, name="edge_node_ratio", distance=None):
         Graph representing street network.
         Ideally generated from GeoDataFrame using :func:`momepy.gdf_to_nx`
     radius: int
-        number of topological steps defining the extent of subgraph
+        Include all neighbors of distance <= radius from n
     name : str, optional
         calculated attribute name
     distance : str, optional
@@ -564,7 +565,7 @@ def gamma(graph, radius=5, name="gamma", distance=None):
         Graph representing street network.
         Ideally generated from GeoDataFrame using :func:`momepy.gdf_to_nx`
     radius: int
-        number of topological steps defining the extent of subgraph
+        Include all neighbors of distance <= radius from n
     name : str, optional
         calculated attribute name
     distance : str, optional
@@ -746,7 +747,7 @@ def local_closeness_centrality(
         Graph representing street network.
         Ideally generated from GeoDataFrame using :func:`momepy.gdf_to_nx`
     radius: int
-        number of topological steps defining the extent of subgraph
+        Include all neighbors of distance <= radius from n
     name : str, optional
         calculated attribute name
     distance : str, optional
@@ -767,20 +768,20 @@ def local_closeness_centrality(
     >>> network_graph = mm.local_closeness_centrality(network_graph, radius=400, distance='edge_length')
 
     """
-    netx = graph.copy()
-    lengraph = len(netx)
-    for n in tqdm(netx, total=len(netx)):
-        sub = nx.ego_graph(
-            netx, n, radius=radius, distance=distance
-        )  # define subgraph of steps=radius
-        netx.nodes[n][name] = _closeness_centrality(
-            sub, n, length=weight, len_graph=lengraph
-        )
+    warnings.warn(
+        "local_closeness_centrality() is deprecated and will be removed in momepy 0.4.0. "
+        "Use closeness_centrality() instead.",
+        FutureWarning,
+    )
 
-    return netx
+    return closeness_centrality(
+        graph=graph, radius=radius, name=name, distance=distance, weight=weight
+    )
 
 
-def closeness_centrality(graph, name="closeness", weight="mm_len", **kwargs):
+def closeness_centrality(
+    graph, name="closeness", weight="mm_len", radius=None, distance=None, **kwargs
+):
     """
     Calculates the closeness centrality for nodes.
 
@@ -806,6 +807,12 @@ def closeness_centrality(graph, name="closeness", weight="mm_len", **kwargs):
         calculated attribute name
     weight : str (default 'mm_len')
         attribute holding the weight of edge (e.g. length, angle)
+    radius: int
+        Include all neighbors of distance <= radius from n
+    distance : str, optional
+        Use specified edge data key as distance.
+        For example, setting ``distance=’weight’`` will use the edge ``weight`` to
+        measure the distance from the node n during ego_graph generation.
     **kwargs
         kwargs for ``networkx.closeness_centrality``
 
@@ -820,14 +827,32 @@ def closeness_centrality(graph, name="closeness", weight="mm_len", **kwargs):
     """
     netx = graph.copy()
 
-    vals = nx.closeness_centrality(netx, distance=weight, **kwargs)
-    nx.set_node_attributes(netx, vals, name)
+    if radius:
+        lengraph = len(netx)
+        for n in tqdm(netx, total=len(netx)):
+            sub = nx.ego_graph(
+                netx, n, radius=radius, distance=distance
+            )  # define subgraph of steps=radius
+            netx.nodes[n][name] = _closeness_centrality(
+                sub, n, length=weight, len_graph=lengraph
+            )
+    else:
+        vals = nx.closeness_centrality(netx, distance=weight, **kwargs)
+        nx.set_node_attributes(netx, vals, name)
 
     return netx
 
 
 def betweenness_centrality(
-    graph, name="betweenness", mode="nodes", weight="mm_len", endpoints=True, **kwargs
+    graph,
+    name="betweenness",
+    mode="nodes",
+    weight="mm_len",
+    endpoints=True,
+    radius=None,
+    distance=None,
+    normalized=False,
+    **kwargs
 ):
     """
     Calculates the shortest-path betweenness centrality for nodes.
@@ -871,6 +896,15 @@ def betweenness_centrality(
         mode of betweenness calculation. 'node' for node-based, 'edges' for edge-based
     weight : str (default 'mm_len')
         attribute holding the weight of edge (e.g. length, angle)
+    radius: int
+        Include all neighbors of distance <= radius from n
+    distance : str, optional
+        Use specified edge data key as distance.
+        For example, setting ``distance=’weight’`` will use the edge ``weight`` to
+        measure the distance from the node n during ego_graph generation.
+    normalized : bool, optional
+        If True the betweenness values are normalized by `2/((n-1)(n-2))`,
+        where n is the number of nodes in subgraph.
     **kwargs
         kwargs for ``networkx.betweenness_centrality`` or ``networkx.edge_betweenness_centrality``
 
@@ -898,7 +932,16 @@ def betweenness_centrality(
         else:
             G.add_edge(u, v, **data)
 
-    if mode == "nodes":
+    if radius:
+        for n in tqdm(G, total=len(G)):
+            sub = nx.ego_graph(
+                G, n, radius=radius, distance=distance
+            )  # define subgraph of steps=radius
+            netx.nodes[n][name] = nx.betweenness_centrality(
+                sub, weight=weight, normalized=normalized, **kwargs
+            )[n]
+
+    elif mode == "nodes":
         vals = nx.betweenness_centrality(
             G, weight=weight, endpoints=endpoints, **kwargs
         )
@@ -955,7 +998,7 @@ def local_betweenness_centrality(
         Graph representing street network.
         Ideally generated from GeoDataFrame using :func:`momepy.gdf_to_nx`
     radius: int
-        number of topological steps defining the extent of subgraph
+        Include all neighbors of distance <= radius from n
     name : str, optional
         calculated attribute name
     distance : str, optional
@@ -981,26 +1024,21 @@ def local_betweenness_centrality(
     >>> network_graph = mm.local_betweenness_centrality(network_graph, radius=800, distance='edge_length')
 
     """
-    netx = graph.copy()
-
-    # has to be Graph not MultiGraph as MG is not supported by networkx2.4
-    G = nx.Graph()
-    for u, v, k, data in netx.edges(data=True, keys=True):
-        if G.has_edge(u, v) and weight is not None:
-            if G[u][v][weight] > netx[u][v][k][weight]:
-                nx.set_edge_attributes(G, {(u, v): data})
-        else:
-            G.add_edge(u, v, **data)
-
-    for n in tqdm(G, total=len(G)):
-        sub = nx.ego_graph(
-            G, n, radius=radius, distance=distance
-        )  # define subgraph of steps=radius
-        netx.nodes[n][name] = nx.betweenness_centrality(
-            sub, weight=weight, normalized=normalized, **kwargs
-        )[n]
-
-    return netx
+    warnings.warn(
+        "local_betweenness_centrality() is deprecated and will be removed in momepy 0.4.0. "
+        "Use betweenness_centrality() instead.",
+        FutureWarning,
+    )
+
+    return betweenness_centrality(
+        graph,
+        radius=radius,
+        name=name,
+        distance=distance,
+        weight=weight,
+        normalized=normalized,
+        **kwargs
+    )
 
 
 def _euclidean(n, m):
@@ -1038,7 +1076,12 @@ def _straightness_centrality(G, weight, normalized=True):
 
 
 def straightness_centrality(
-    graph, weight="mm_len", normalized=True, name="straightness"
+    graph,
+    weight="mm_len",
+    normalized=True,
+    name="straightness",
+    radius=None,
+    distance=None,
 ):
     """
     Calculates the straightness centrality for nodes.
@@ -1059,9 +1102,16 @@ def straightness_centrality(
     weight : str (default 'mm_len')
         attribute holding length of edge
     normalized : bool
-        normalize to number of nodes-1 in connected part
+        normalize to number of nodes-1 in connected part (for local straightness
+        is recommended to set to normalized False)
     name : str, optional
         calculated attribute name
+    radius: int
+        Include all neighbors of distance <= radius from n
+    distance : str, optional
+        Use specified edge data key as distance.
+        For example, setting ``distance=’weight’`` will use the edge ``weight`` to
+        measure the distance from the node n during ego_graph generation.
 
     Returns
     -------
@@ -1074,8 +1124,17 @@ def straightness_centrality(
     """
     netx = graph.copy()
 
-    vals = _straightness_centrality(netx, weight=weight, normalized=normalized)
-    nx.set_node_attributes(netx, vals, name)
+    if radius:
+        for n in tqdm(netx, total=len(netx)):
+            sub = nx.ego_graph(
+                netx, n, radius=radius, distance=distance
+            )  # define subgraph of steps=radius
+            netx.nodes[n][name] = _straightness_centrality(
+                sub, weight=weight, normalized=normalized
+            )[n]
+    else:
+        vals = _straightness_centrality(netx, weight=weight, normalized=normalized)
+        nx.set_node_attributes(netx, vals, name)
 
     return netx
 
@@ -1103,7 +1162,7 @@ def local_straightness_centrality(
         Graph representing street network.
         Ideally generated from GeoDataFrame using :func:`momepy.gdf_to_nx`
     radius: int
-        number of topological steps defining the extent of subgraph
+        Include all neighbors of distance <= radius from n
     name : str, optional
         calculated attribute name
     distance : str, optional
@@ -1125,16 +1184,15 @@ def local_straightness_centrality(
     >>> network_graph = mm.local_straightness_centrality(network_graph, radius=400, distance='edge_length')
 
     """
-    netx = graph.copy()
-    for n in tqdm(netx, total=len(netx)):
-        sub = nx.ego_graph(
-            netx, n, radius=radius, distance=distance
-        )  # define subgraph of steps=radius
-        netx.nodes[n][name] = _straightness_centrality(
-            sub, weight=weight, normalized=False
-        )[n]
-
-    return netx
+    warnings.warn(
+        "local_straightness_centrality() is deprecated and will be removed in momepy 0.4.0. "
+        "Use straightness_centrality() instead.",
+        FutureWarning,
+    )
+
+    return straightness_centrality(
+        graph=graph, radius=radius, name=name, distance=distance, weight=weight
+    )
 
 
 def subgraph(
diff --git a/momepy/utils.py b/momepy/utils.py
index fdbc797..b070f48 100644
--- a/momepy/utils.py
+++ b/momepy/utils.py
@@ -252,30 +252,23 @@ def nx_to_gdf(net, points=True, lines=True, spatial_weights=False, nodeID="nodeI
 
     """
     # generate nodes and edges geodataframes from graph
+    primal = None
     if "approach" in net.graph.keys():
         if net.graph["approach"] == "primal":
-            nid = 1
-            for n in net:
-                net.nodes[n][nodeID] = nid
-                nid += 1
-            return _primal_to_gdf(
-                net,
-                points=points,
-                lines=lines,
-                spatial_weights=spatial_weights,
-                nodeID=nodeID,
-            )
-        if net.graph["approach"] == "dual":
+            primal = True
+        elif net.graph["approach"] == "dual":
             return _dual_to_gdf(net)
-        raise ValueError(
-            "Approach {} is not supported. Use 'primal' or 'dual'.".format(
-                net.graph["approach"]
+        else:
+            raise ValueError(
+                "Approach {} is not supported. Use 'primal' or 'dual'.".format(
+                    net.graph["approach"]
+                )
             )
-        )
 
-    import warnings
+    if not primal:
+        import warnings
 
-    warnings.warn("Approach is not set. Defaulting to 'primal'.")
+        warnings.warn("Approach is not set. Defaulting to 'primal'.")
 
     nid = 1
     for n in net:
@@ -352,8 +345,8 @@ def preprocess(buildings, size=30, compactness=True, islands=True):
     blg = buildings.copy()
     blg = blg.explode()
     blg.reset_index(drop=True, inplace=True)
-    for l in range(0, 2):
-        print("Loop", l + 1, "out of 2.")
+    for loop in range(0, 2):
+        print("Loop", loop + 1, "out of 2.")
         blg.reset_index(inplace=True, drop=True)
         blg["mm_uid"] = range(len(blg))
         sw = libpysal.weights.contiguity.Rook.from_dataframe(blg, silence_warnings=True)


Test patch:
diff --git a/tests/test_graph.py b/tests/test_graph.py
index b78b39d..67a54dd 100644
--- a/tests/test_graph.py
+++ b/tests/test_graph.py
@@ -85,18 +85,21 @@ class TestGraph:
         assert mm.gamma(self.network, radius=None) == 0.43209876543209874
 
     def test_local_closeness_centrality(self):
-        net = mm.local_closeness_centrality(self.network)
-        check = 0.27557319223985893
-        assert net.nodes[(1603650.450422848, 6464368.600601688)]["closeness"] == check
-        net2 = mm.local_closeness_centrality(self.network, weight="mm_len")
-        check2 = 0.0015544070362478774
-        assert net2.nodes[(1603650.450422848, 6464368.600601688)]["closeness"] == check2
+        with pytest.warns(FutureWarning):
+            mm.local_closeness_centrality(self.network)
 
     def test_closeness_centrality(self):
         net = mm.closeness_centrality(self.network, weight="mm_len")
         check = 0.0016066095164175716
         assert net.nodes[(1603650.450422848, 6464368.600601688)]["closeness"] == check
 
+        net = mm.closeness_centrality(self.network, radius=5, weight=None)
+        check = 0.27557319223985893
+        assert net.nodes[(1603650.450422848, 6464368.600601688)]["closeness"] == check
+        net2 = mm.closeness_centrality(self.network, weight="mm_len", radius=5)
+        check2 = 0.0015544070362478774
+        assert net2.nodes[(1603650.450422848, 6464368.600601688)]["closeness"] == check2
+
     def test_betweenness_centrality(self):
         net = mm.betweenness_centrality(self.network)
         net2 = mm.betweenness_centrality(self.network, mode="edges")
@@ -120,18 +123,21 @@ class TestGraph:
             == ang_b
         )
 
-    def test_local_betweenness_centrality(self):
-        net = mm.local_betweenness_centrality(self.network)
+        net = mm.betweenness_centrality(self.network, radius=5, weight=None)
         check = 53.74999999999999
         assert net.nodes[(1603650.450422848, 6464368.600601688)]["betweenness"] == check
-        net2 = mm.local_betweenness_centrality(
-            self.network, weight="mm_len", normalized=True
+        net2 = mm.betweenness_centrality(
+            self.network, radius=5, weight="mm_len", normalized=True
         )
         check2 = 0.21333333333333335
         assert (
             net2.nodes[(1603650.450422848, 6464368.600601688)]["betweenness"] == check2
         )
 
+    def test_local_betweenness_centrality(self):
+        with pytest.warns(FutureWarning):
+            mm.local_betweenness_centrality(self.network)
+
     def test_straightness_centrality(self):
         net = mm.straightness_centrality(self.network)
         net2 = mm.straightness_centrality(
@@ -150,13 +156,16 @@ class TestGraph:
             net3.nodes[(1603650.450422848, 6464368.600601688)]["straightness"] == check
         )
 
-    def test_local_straightness_centrality(self):
-        net = mm.local_straightness_centrality(self.network)
+        net = mm.straightness_centrality(self.network, radius=5, normalized=False)
         check = 0.8614261420539604
         assert (
             net.nodes[(1603650.450422848, 6464368.600601688)]["straightness"] == check
         )
 
+    def test_local_straightness_centrality(self):
+        with pytest.warns(FutureWarning):
+            mm.local_straightness_centrality(self.network)
+
     def test_mean_nodes(self):
         net = mm.straightness_centrality(self.network)
         mm.mean_nodes(net, "straightness")