#!/usr/bin/env python
# -*- coding: utf-8 -*-
# connectivity.py
# definitons of connectivity characters
import math
import networkx as nx
import numpy as np
from tqdm import tqdm
__all__ = [
"node_degree",
"meshedness",
"mean_node_dist",
"cds_length",
"mean_node_degree",
"proportion",
"cyclomatic",
"edge_node_ratio",
"gamma",
"clustering",
"local_closeness_centrality",
"closeness_centrality",
"betweenness_centrality",
"local_betweenness_centrality",
"local_straightness_centrality",
"straightness_centrality",
"subgraph",
"mean_nodes",
]
[docs]def node_degree(graph, name="degree"):
"""
Calculates node degree for each node.
Wrapper around `networkx.degree()`.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
name : str(default 'degree')
calculated attribute name
Returns
-------
Graph
networkx.Graph
Examples
--------
>>> network_graph = mm.node_degree(network_graph)
"""
netx = graph.copy()
degree = dict(nx.degree(netx))
nx.set_node_attributes(netx, degree, name)
return netx
def _meshedness(graph):
"""
Calculates meshedness of a graph.
"""
e = graph.number_of_edges()
v = graph.number_of_nodes()
return (e - v + 1) / (2 * v - 5)
[docs]def meshedness(graph, radius=5, name="meshedness", distance=None):
"""
Calculates meshedness for subgraph around each node.
Subgraph is generated around each node within set radius. If distance=None,
radius will define topological distance, otherwise it uses values in distance
attribute.
.. math::
\\alpha=\\frac{e-v+1}{2 v-5}
where :math:`e` is the number of edges in subgraph and :math:`v` is the number of nodes in subgraph.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius: int
number of topological steps defining the extent of subgraph
name : str, optional
calculated attribute name
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.
Returns
-------
Graph
networkx.Graph
References
----------
Feliciotti A (2018) RESILIENCE AND URBAN DESIGN:A SYSTEMS APPROACH TO THE STUDY OF RESILIENCE IN URBAN FORM.
LEARNING FROM THE CASE OF GORBALS. Glasgow.
Examples
--------
>>> network_graph = mm.meshedness(network_graph, radius=800, 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] = _meshedness(
sub
) # save value calulated for subgraph to node
return netx
[docs]def mean_node_dist(graph, name="meanlen", length="mm_len"):
"""
Calculates mean distance to neighbouring nodes.
Mean of values in `length` attribute.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
name : str, optional
calculated attribute name
length : str, optional
name of attribute of segment length (geographical)
Returns
-------
Graph
networkx.Graph
Examples
--------
>>> network_graph = mm.mean_node_dist(network_graph)
"""
netx = graph.copy()
for n, nbrs in tqdm(netx.adj.items(), total=len(netx)):
lengths = []
for nbr, keydict in nbrs.items():
for key, eattr in keydict.items():
lengths.append(eattr[length])
netx.nodes[n][name] = np.mean(lengths)
return netx
def _cds_length(graph, mode, length):
"""
Calculates cul-de-sac length in a graph.
"""
lens = []
for u, v, k, cds in graph.edges.data("cdsbool", keys=True):
if cds:
lens.append(graph[u][v][k][length])
if mode == "sum":
return sum(lens)
if mode == "mean":
return np.mean(lens)
raise ValueError("Mode {} is not supported. Use 'sum' or 'mean'.".format(mode))
[docs]def cds_length(
graph,
radius=5,
mode="sum",
name="cds_len",
degree="degree",
length="mm_len",
distance=None,
):
"""
Calculates length of cul-de-sacs for subgraph around each node.
Subgraph is generated around each node within set radius. If distance=None,
radius will define topological distance, otherwise it uses values in distance
attribute.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius : int
number of topological steps defining the extent of subgraph
mode : str (defualt 'sum')
if 'sum', calculate total length, if 'mean' calculate mean length
name : str, optional
calculated attribute name
degree : str
name of attribute of node degree (:py:func:`momepy.node_degree`)
length : str, optional
name of attribute of segment length (geographical)
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.
Returns
-------
Graph
networkx.Graph
Examples
--------
>>> network_graph = mm.cds_length(network_graph, radius=9, mode='mean')
"""
# node degree needed beforehand
netx = graph.copy()
for u, v, k in netx.edges(keys=True):
if netx.nodes[u][degree] == 1 or netx.nodes[v][degree] == 1:
netx[u][v][k]["cdsbool"] = True
else:
netx[u][v][k]["cdsbool"] = False
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] = _cds_length(
sub, mode=mode, length=length
) # save value calulated for subgraph to node
return netx
def _mean_node_degree(graph, degree):
"""
Calculates mean node degree in a graph.
"""
return np.mean(list(dict(graph.nodes(degree)).values()))
[docs]def mean_node_degree(graph, radius=5, name="mean_nd", degree="degree", distance=None):
"""
Calculates mean node degree for subgraph around each node.
Subgraph is generated around each node within set radius. If distance=None,
radius will define topological distance, otherwise it uses values in distance
attribute.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius: int
radius defining the extent of subgraph
name : str, optional
calculated attribute name
degree : str
name of attribute of node degree (:py:func:`momepy.node_degree`)
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.
Returns
-------
Graph
networkx.Graph
Examples
--------
>>> network_graph = mm.mean_node_degree(network_graph, radius=3)
"""
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] = _mean_node_degree(sub, degree=degree)
return netx
def _proportion(graph, degree):
"""
Calculates the proportion of intersection types in a graph.
"""
import collections
values = list(dict(graph.nodes(degree)).values())
counts = collections.Counter(values)
return counts
[docs]def proportion(
graph, radius=5, three=None, four=None, dead=None, degree="degree", distance=None
):
"""
Calculates the proportion of intersection types for subgraph around each node.
Subgraph is generated around each node within set radius. If distance=None,
radius will define topological distance, otherwise it uses values in distance
attribute.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius: int
number of topological steps defining the extent of subgraph
three : str, optional
attribute name for 3-way intersections proportion
four : str, optional
attribute name for 4-way intersections proportion
dead : str, optional
attribute name for deadends proportion
degree : str
name of attribute of node degree (:py:func:`momepy.node_degree`)
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.
Returns
-------
Graph
networkx.Graph
Examples
--------
>>> network_graph = mm.proportion(network_graph, three='threeway', four='fourway', dead='deadends')
"""
if not three and not four and not dead:
raise ValueError(
"Nothing to calculate. Define names for at least one proportion to be calculated: three, four, dead."
)
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
counts = _proportion(sub, degree=degree)
if three:
netx.nodes[n][three] = counts[3] / len(sub)
if four:
netx.nodes[n][four] = counts[4] / len(sub)
if dead:
netx.nodes[n][dead] = counts[1] / len(sub)
return netx
def _cyclomatic(graph):
"""
Calculates the cyclomatic complexity of a graph.
"""
e = graph.number_of_edges()
v = graph.number_of_nodes()
return e - v + 1
[docs]def cyclomatic(graph, radius=5, name="cyclomatic", distance=None):
"""
Calculates cyclomatic complexity for subgraph around each node.
Subgraph is generated around each node within set radius. If distance=None,
radius will define topological distance, otherwise it uses values in distance
attribute.
.. math::
\\alpha=e-v+1
where :math:`e` is the number of edges in subgraph and :math:`v` is the number of nodes in subgraph.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius: int
number of topological steps defining the extent of subgraph
name : str, optional
calculated attribute name
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.
Returns
-------
Graph
networkx.Graph
References
----------
Bourdic L, Salat S and Nowacki C (2012) Assessing cities: a new system of cross-scale spatial indicators.
Building Research & Information 40(5): 592–605.
Examples
--------
>>> network_graph = mm.cyclomatic(network_graph, radius=3)
"""
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] = _cyclomatic(
sub
) # save value calulated for subgraph to node
return netx
def _edge_node_ratio(graph):
"""
Calculates edge / node ratio of a graph.
"""
e = graph.number_of_edges()
v = graph.number_of_nodes()
return e / v
[docs]def edge_node_ratio(graph, radius=5, name="edge_node_ratio", distance=None):
"""
Calculates edge / node ratio for subgraph around each node.
Subgraph is generated around each node within set radius. If distance=None,
radius will define topological distance, otherwise it uses values in distance
attribute.
.. math::
\\alpha=e/v
where :math:`e` is the number of edges in subgraph and :math:`v` is the number of nodes in subgraph.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius: int
number of topological steps defining the extent of subgraph
name : str, optional
calculated attribute name
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.
Returns
-------
Graph
networkx.Graph
References
----------
Dibble J, Prelorendjos A, Romice O, et al. (2017) On the origin of spaces: Morphometric foundations of urban form evolution.
Environment and Planning B: Urban Analytics and City Science 46(4): 707–730.
Examples
--------
>>> network_graph = mm.edge_node_ratio(network_graph, radius=3)
"""
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] = _edge_node_ratio(
sub
) # save value calulated for subgraph to node
return netx
def _gamma(graph):
"""
Calculates gamma index of a graph.
"""
e = graph.number_of_edges()
v = graph.number_of_nodes()
if v == 2:
return np.nan
return e / (3 * (v - 2)) # save value calulated for subgraph to node
[docs]def gamma(graph, radius=5, name="gamma", distance=None):
"""
Calculates connectivity gamma index for subgraph around each node.
Subgraph is generated around each node within set radius. If distance=None,
radius will define topological distance, otherwise it uses values in distance
attribute.
.. math::
\\alpha=\\frac{e}{3(v-2)}
where :math:`e` is the number of edges in subgraph and :math:`v` is the number of nodes in subgraph.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius: int
number of topological steps defining the extent of subgraph
name : str, optional
calculated attribute name
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.
Returns
-------
Graph
networkx.Graph
References
----------
Dibble J, Prelorendjos A, Romice O, et al. (2017) On the origin of spaces: Morphometric foundations of urban form evolution.
Environment and Planning B: Urban Analytics and City Science 46(4): 707–730.
Examples
--------
>>> network_graph = mm.gamma(network_graph, radius=3)
"""
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] = _gamma(sub)
return netx
[docs]def clustering(graph, name="cluster"):
"""
Calculates the squares clustering coefficient for nodes.
Wrapper around ``networkx.square_clustering``.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
name : str, optional
calculated attribute name
Returns
-------
Graph
networkx.Graph
Examples
--------
>>> network_graph = mm.clustering(network_graph)
"""
netx = graph.copy()
vals = nx.square_clustering(netx)
nx.set_node_attributes(netx, vals, name)
return netx
def _closeness_centrality(G, u=None, length=None, wf_improved=True, len_graph=None):
r"""Compute closeness centrality for nodes. Slight adaptation of networkx
`closeness_centrality` to allow normalisation for local closeness.
Adapted script used in networkx.
Closeness centrality [1]_ of a node `u` is the reciprocal of the
average shortest path distance to `u` over all `n-1` reachable nodes.
.. math::
C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)},
where `d(v, u)` is the shortest-path distance between `v` and `u`,
and `n` is the number of nodes that can reach `u`. Notice that the
closeness distance function computes the incoming distance to `u`
for directed graphs. To use outward distance, act on `G.reverse()`.
Notice that higher values of closeness indicate higher centrality.
Wasserman and Faust propose an improved formula for graphs with
more than one connected component. The result is "a ratio of the
fraction of actors in the group who are reachable, to the average
distance" from the reachable actors [2]_. You might think this
scale factor is inverted but it is not. As is, nodes from small
components receive a smaller closeness value. Letting `N` denote
the number of nodes in the graph,
.. math::
C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)},
Parameters
----------
G : graph
A NetworkX graph
u : node, optional
Return only the value for node u
distance : edge attribute key, optional (default=None)
Use the specified edge attribute as the edge distance in shortest
path calculations
len_graph : int
length of complete graph
Returns
-------
nodes : dictionary
Dictionary of nodes with closeness centrality as the value.
References
----------
.. [1] Linton C. Freeman: Centrality in networks: I.
Conceptual clarification. Social Networks 1:215-239, 1979.
http://leonidzhukov.ru/hse/2013/socialnetworks/papers/freeman79-centrality.pdf
.. [2] pg. 201 of Wasserman, S. and Faust, K.,
Social Network Analysis: Methods and Applications, 1994,
Cambridge University Press.
"""
if length is not None:
import functools
# use Dijkstra's algorithm with specified attribute as edge weight
path_length = functools.partial(
nx.single_source_dijkstra_path_length, weight=length
)
else:
path_length = nx.single_source_shortest_path_length
nodes = [u]
closeness_centrality = {}
for n in nodes:
sp = dict(path_length(G, n))
totsp = sum(sp.values())
if totsp > 0.0 and len(G) > 1:
closeness_centrality[n] = (len(sp) - 1.0) / totsp
# normalize to number of nodes-1 in connected part
s = (len(sp) - 1.0) / (len_graph - 1)
closeness_centrality[n] *= s
else:
closeness_centrality[n] = 0.0
return closeness_centrality[u]
[docs]def local_closeness_centrality(
graph, radius=5, name="closeness", distance=None, weight=None
):
"""
Calculates local closeness for each node based on the defined distance.
Subgraph is generated around each node within set radius. If distance=None,
radius will define topological distance, otherwise it uses values in distance
attribute. Based on networkx.closeness_centrality.
Local closeness centrality of a node `u` is the reciprocal of the
average shortest path distance to `u` over all `n-1` nodes within subgraph.
.. math::
C(u) = \\frac{n - 1}{\\sum_{v=1}^{n-1} d(v, u)},
where `d(v, u)` is the shortest-path distance between `v` and `u`,
and `n` is the number of nodes that can reach `u`.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius: int
number of topological steps defining the extent of subgraph
name : str, optional
calculated attribute name
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.
weight : str, optional
Use the specified edge attribute as the edge distance in shortest
path calculations in closeness centrality algorithm
Returns
-------
Graph
networkx.Graph
References
----------
Porta S, Crucitti P and Latora V (2006) The network analysis of urban streets: A primal approach.
Environment and Planning B: Planning and Design 33(5): 705–725.
Examples
--------
>>> 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
)
return netx
[docs]def closeness_centrality(graph, name="closeness", weight="mm_len", **kwargs):
"""
Calculates the closeness centrality for nodes.
Wrapper around ``networkx.closeness_centrality``.
Closeness centrality of a node `u` is the reciprocal of the
average shortest path distance to `u` over all `n-1` nodes within reachable nodes.
.. math::
C(u) = \\frac{n - 1}{\\sum_{v=1}^{n-1} d(v, u)},
where `d(v, u)` is the shortest-path distance between `v` and `u`,
and `n` is the number of nodes that can reach `u`.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
name : str, optional
calculated attribute name
weight : str (default 'mm_len')
attribute holding the weight of edge (e.g. length, angle)
**kwargs
kwargs for ``networkx.closeness_centrality``
Returns
-------
Graph
networkx.Graph
Examples
--------
>>> network_graph = mm.closeness_centrality(network_graph)
"""
netx = graph.copy()
vals = nx.closeness_centrality(netx, distance=weight, **kwargs)
nx.set_node_attributes(netx, vals, name)
return netx
[docs]def betweenness_centrality(
graph, name="betweenness", mode="nodes", weight="mm_len", endpoints=True, **kwargs
):
"""
Calculates the shortest-path betweenness centrality for nodes.
Wrapper around ``networkx.betweenness_centrality`` or ``networkx.edge_betweenness_centrality``.
Betweenness centrality of a node `v` is the sum of the
fraction of all-pairs shortest paths that pass through `v`
.. math::
c_B(v) =\\sum_{s,t \\in V} \\frac{\\sigma(s, t|v)}{\\sigma(s, t)}
where `V` is the set of nodes, :math:`\\sigma(s, t)` is the number of
shortest :math:`(s, t)`-paths, and :math:`\\sigma(s, t|v)` is the number of
those paths passing through some node `v` other than `s, t`.
If `s = t`, :math:`\\sigma(s, t) = 1`, and if `v` in `{s, t}``,
:math:`\\sigma(s, t|v) = 0`.
Betweenness centrality of an edge `e` is the sum of the
fraction of all-pairs shortest paths that pass through `e`
.. math::
c_B(e) =\\sum_{s,t \\in V} \\frac{\\sigma(s, t|e)}{\\sigma(s, t)}
where `V` is the set of nodes, :math:`\\sigma(s, t)` is the number of
shortest :math:`(s, t)`-paths, and :math:`\\sigma(s, t|e)` is the number of
those paths passing through edge `e`.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
name : str, optional
calculated attribute name
mode : str, default 'nodes'
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)
**kwargs
kwargs for ``networkx.betweenness_centrality`` or ``networkx.edge_betweenness_centrality``
Returns
-------
Graph
networkx.Graph
References
----------
Porta S, Crucitti P and Latora V (2006) The network analysis of urban streets: A primal approach.
Environment and Planning B: Planning and Design 33(5): 705–725.
Examples
--------
>>> network_graph = mm.betweenness_centrality(network_graph)
Note
----
In case of angular betweenness, implementation is based on "Tasos Implementation".
"""
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):
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)
if mode == "nodes":
vals = nx.betweenness_centrality(
G, weight=weight, endpoints=endpoints, **kwargs
)
nx.set_node_attributes(netx, vals, name)
elif mode == "edges":
vals = nx.edge_betweenness_centrality(G, weight=weight, **kwargs)
for u, v, k in netx.edges(keys=True):
try:
val = vals[u, v]
except KeyError:
val = vals[v, u]
netx[u][v][k][name] = val
else:
raise ValueError(
"Mode {} is not supported. Use 'nodes' or 'edges'.".format(mode)
)
return netx
[docs]def local_betweenness_centrality(
graph,
radius=5,
name="betweenness",
distance=None,
weight=None,
normalized=False,
**kwargs
):
"""
Calculates the shortest-path betweenness centrality for nodes within subgraph.
Subgraph is generated around each node within set radius. If distance=None,
radius will define topological distance, otherwise it uses values in distance
attribute. Based on ``networkx.betweenness_centrality``.
Betweenness centrality of a node `v` is the sum of the
fraction of all-pairs shortest paths that pass through `v`
.. math::
c_B(v) =\\sum_{s,t \\in V} \\frac{\\sigma(s, t|v)}{\\sigma(s, t)}
where `V` is the set of nodes, :math:`\\sigma(s, t)` is the number of
shortest :math:`(s, t)`-paths, and :math:`\\sigma(s, t|v)` is the number of
those paths passing through some node `v` other than `s, t`.
If `s = t`, :math:`\\sigma(s, t) = 1`, and if `v` in `{s, t}``,
:math:`\\sigma(s, t|v) = 0`.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius: int
number of topological steps defining the extent of subgraph
name : str, optional
calculated attribute name
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.
weight : str, optional
Use the specified edge attribute as the edge distance in shortest
path calculations in closeness centrality algorithm
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_subset``
Returns
-------
Graph
networkx.Graph
Examples
--------
>>> 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
def _euclidean(n, m):
"""helper for straightness"""
return math.sqrt((n[0] - m[0]) ** 2 + (n[1] - m[1]) ** 2)
def _straightness_centrality(G, weight, normalized=True):
"""
Calculates straightness centrality.
"""
straightness_centrality = {}
for n in G.nodes():
straightness = 0
sp = nx.single_source_dijkstra_path_length(G, n, weight=weight)
if len(sp) > 0 and len(G) > 1:
for target in sp:
if n != target:
network_dist = sp[target]
euclidean_dist = _euclidean(n, target)
straightness = straightness + (euclidean_dist / network_dist)
straightness_centrality[n] = straightness * (1.0 / (len(G) - 1.0))
# normalize to number of nodes-1 in connected part
if normalized:
if len(sp) > 1:
s = (len(G) - 1.0) / (len(sp) - 1.0)
straightness_centrality[n] *= s
else:
straightness_centrality[n] = 0
else:
straightness_centrality[n] = 0.0
return straightness_centrality
[docs]def straightness_centrality(
graph, weight="mm_len", normalized=True, name="straightness"
):
"""
Calculates the straightness centrality for nodes.
.. math::
C_{S}(i)=\\frac{1}{n-1} \\sum_{j \\in V, j \\neq i} \\frac{d_{i j}^{E u}}{d_{i j}}
where :math:`\\mathrm{d}^{\\mathrm{E} \\mathrm{u}}_{\\mathrm{ij}}` is the Euclidean distance
between nodes `i` and `j` along a straight line.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
weight : str (default 'mm_len')
attribute holding length of edge
normalized : bool
normalize to number of nodes-1 in connected part
name : str, optional
calculated attribute name
Returns
-------
Graph
networkx.Graph
References
----------
Porta S, Crucitti P and Latora V (2006) The network analysis of urban streets: A primal approach.
Environment and Planning B: Planning and Design 33(5): 705–725.
Examples
--------
>>> network_graph = mm.straightness_centrality(network_graph)
"""
netx = graph.copy()
vals = _straightness_centrality(netx, weight=weight, normalized=normalized)
nx.set_node_attributes(netx, vals, name)
return netx
[docs]def local_straightness_centrality(
graph, radius=5, name="straightness", distance=None, weight="mm_len"
):
"""
Calculates local straightness for each node based on the defined distance.
Subgraph is generated around each node within set radius. If distance=None,
radius will define topological distance, otherwise it uses values in distance
attribute.
.. math::
C_{S}(i)=\\frac{1}{n-1} \\sum_{j \\in V, j \\neq i} \\frac{d_{i j}^{E u}}{d_{i j}}
where :math:`\\mathrm{d}^{\\mathrm{E} \\mathrm{u}}_{\\mathrm{ij}}` is the Euclidean distance
between nodes `i` and `j` along a straight line.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius: int
number of topological steps defining the extent of subgraph
name : str, optional
calculated attribute name
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.
weight : str, optional
Use the specified edge attribute as the edge distance in shortest
path calculations in closeness centrality algorithm
Returns
-------
Graph
networkx.Graph
Examples
--------
>>> 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
[docs]def subgraph(
graph,
radius=5,
distance=None,
meshedness=True,
cds_length=True,
mode="sum",
degree="degree",
length="mm_len",
mean_node_degree=True,
proportion={3: True, 4: True, 0: True},
cyclomatic=True,
edge_node_ratio=True,
gamma=True,
local_closeness=True,
closeness_weight=None,
):
"""
Calculates all subgraph-based characters.
Generating subgraph might be a time consuming activity. If we want to use the same
subgraph for more characters, ``subgraph`` allows this by generating subgraph and
then analysing it using selected options.
Parameters
----------
graph : networkx.Graph
Graph representing street network.
Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
radius: int
radius defining the extent of subgraph
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.
meshedness : bool, default True
Calculate meshedness (True/False)
cds_length : bool, default True
Calculate cul-de-sac length (True/False)
mode : str (defualt 'sum')
if 'sum', calculate total cds_length, if 'mean' calculate mean cds_length
degree : str
name of attribute of node degree (:py:func:`momepy.node_degree`)
length : str, default `mm_len`
name of attribute of segment length (geographical)
mean_node_degree : bool, default True
Calculate mean node degree (True/False)
proportion : dict, default {3: True, 4: True, 0: True}
Calculate proportion {3: True/False, 4: True/False, 0: True/False}
cyclomatic : bool, default True
Calculate cyclomatic complexity (True/False)
edge_node_ratio : bool, default True
Calculate edge node ratio (True/False)
gamma : bool, default True
Calculate gamma index (True/False)
local_closeness : bool, default True
Calculate local closeness centrality (True/False)
closeness_weight : str, optional
Use the specified edge attribute as the edge distance in shortest
path calculations in closeness centrality algorithm
Returns
-------
Graph
networkx.Graph
Examples
--------
>>> network_graph = mm.subgraph(network_graph)
"""
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
if meshedness:
netx.nodes[n]["meshedness"] = _meshedness(sub)
if cds_length:
for u, v, k in netx.edges(keys=True):
if netx.nodes[u][degree] == 1 or netx.nodes[v][degree] == 1:
netx[u][v][k]["cdsbool"] = True
else:
netx[u][v][k]["cdsbool"] = False
netx.nodes[n]["cds_length"] = _cds_length(sub, mode=mode, length=length)
if mean_node_degree:
netx.nodes[n]["mean_node_degree"] = _mean_node_degree(sub, degree=degree)
if proportion:
counts = _proportion(sub, degree=degree)
if proportion[3]:
netx.nodes[n]["proportion_3"] = counts[3] / len(sub)
if proportion[4]:
netx.nodes[n]["proportion_4"] = counts[4] / len(sub)
if proportion[0]:
netx.nodes[n]["proportion_0"] = counts[1] / len(sub)
if cyclomatic:
netx.nodes[n]["cyclomatic"] = _cyclomatic(sub)
if edge_node_ratio:
netx.nodes[n]["edge_node_ratio"] = _edge_node_ratio(sub)
if gamma:
netx.nodes[n]["gamma"] = _gamma(sub)
if local_closeness:
lengraph = len(netx)
netx.nodes[n]["local_closeness"] = _closeness_centrality(
sub, n, length=closeness_weight, len_graph=lengraph
)
return netx
[docs]def mean_nodes(G, attr):
"""
Calculates mean value of nodes attr for each edge.
"""
for u, v, k in tqdm(G.edges(keys=True), total=G.number_of_edges()):
mean = (G.nodes[u][attr] + G.nodes[v][attr]) / 2
G[u][v][k][attr] = mean