Spaces:
Running
Running
"""Functions for computing reaching centrality of a node or a graph.""" | |
import networkx as nx | |
from networkx.utils import pairwise | |
__all__ = ["global_reaching_centrality", "local_reaching_centrality"] | |
def _average_weight(G, path, weight=None): | |
"""Returns the average weight of an edge in a weighted path. | |
Parameters | |
---------- | |
G : graph | |
A networkx graph. | |
path: list | |
A list of vertices that define the path. | |
weight : None or string, optional (default=None) | |
If None, edge weights are ignored. Then the average weight of an edge | |
is assumed to be the multiplicative inverse of the length of the path. | |
Otherwise holds the name of the edge attribute used as weight. | |
""" | |
path_length = len(path) - 1 | |
if path_length <= 0: | |
return 0 | |
if weight is None: | |
return 1 / path_length | |
total_weight = sum(G.edges[i, j][weight] for i, j in pairwise(path)) | |
return total_weight / path_length | |
def global_reaching_centrality(G, weight=None, normalized=True): | |
"""Returns the global reaching centrality of a directed graph. | |
The *global reaching centrality* of a weighted directed graph is the | |
average over all nodes of the difference between the local reaching | |
centrality of the node and the greatest local reaching centrality of | |
any node in the graph [1]_. For more information on the local | |
reaching centrality, see :func:`local_reaching_centrality`. | |
Informally, the local reaching centrality is the proportion of the | |
graph that is reachable from the neighbors of the node. | |
Parameters | |
---------- | |
G : DiGraph | |
A networkx DiGraph. | |
weight : None or string, optional (default=None) | |
Attribute to use for edge weights. If ``None``, each edge weight | |
is assumed to be one. A higher weight implies a stronger | |
connection between nodes and a *shorter* path length. | |
normalized : bool, optional (default=True) | |
Whether to normalize the edge weights by the total sum of edge | |
weights. | |
Returns | |
------- | |
h : float | |
The global reaching centrality of the graph. | |
Examples | |
-------- | |
>>> G = nx.DiGraph() | |
>>> G.add_edge(1, 2) | |
>>> G.add_edge(1, 3) | |
>>> nx.global_reaching_centrality(G) | |
1.0 | |
>>> G.add_edge(3, 2) | |
>>> nx.global_reaching_centrality(G) | |
0.75 | |
See also | |
-------- | |
local_reaching_centrality | |
References | |
---------- | |
.. [1] Mones, Enys, Lilla Vicsek, and Tamás Vicsek. | |
"Hierarchy Measure for Complex Networks." | |
*PLoS ONE* 7.3 (2012): e33799. | |
https://doi.org/10.1371/journal.pone.0033799 | |
""" | |
if nx.is_negatively_weighted(G, weight=weight): | |
raise nx.NetworkXError("edge weights must be positive") | |
total_weight = G.size(weight=weight) | |
if total_weight <= 0: | |
raise nx.NetworkXError("Size of G must be positive") | |
# If provided, weights must be interpreted as connection strength | |
# (so higher weights are more likely to be chosen). However, the | |
# shortest path algorithms in NetworkX assume the provided "weight" | |
# is actually a distance (so edges with higher weight are less | |
# likely to be chosen). Therefore we need to invert the weights when | |
# computing shortest paths. | |
# | |
# If weight is None, we leave it as-is so that the shortest path | |
# algorithm can use a faster, unweighted algorithm. | |
if weight is not None: | |
def as_distance(u, v, d): | |
return total_weight / d.get(weight, 1) | |
shortest_paths = nx.shortest_path(G, weight=as_distance) | |
else: | |
shortest_paths = nx.shortest_path(G) | |
centrality = local_reaching_centrality | |
# TODO This can be trivially parallelized. | |
lrc = [ | |
centrality(G, node, paths=paths, weight=weight, normalized=normalized) | |
for node, paths in shortest_paths.items() | |
] | |
max_lrc = max(lrc) | |
return sum(max_lrc - c for c in lrc) / (len(G) - 1) | |
def local_reaching_centrality(G, v, paths=None, weight=None, normalized=True): | |
"""Returns the local reaching centrality of a node in a directed | |
graph. | |
The *local reaching centrality* of a node in a directed graph is the | |
proportion of other nodes reachable from that node [1]_. | |
Parameters | |
---------- | |
G : DiGraph | |
A NetworkX DiGraph. | |
v : node | |
A node in the directed graph `G`. | |
paths : dictionary (default=None) | |
If this is not `None` it must be a dictionary representation | |
of single-source shortest paths, as computed by, for example, | |
:func:`networkx.shortest_path` with source node `v`. Use this | |
keyword argument if you intend to invoke this function many | |
times but don't want the paths to be recomputed each time. | |
weight : None or string, optional (default=None) | |
Attribute to use for edge weights. If `None`, each edge weight | |
is assumed to be one. A higher weight implies a stronger | |
connection between nodes and a *shorter* path length. | |
normalized : bool, optional (default=True) | |
Whether to normalize the edge weights by the total sum of edge | |
weights. | |
Returns | |
------- | |
h : float | |
The local reaching centrality of the node ``v`` in the graph | |
``G``. | |
Examples | |
-------- | |
>>> G = nx.DiGraph() | |
>>> G.add_edges_from([(1, 2), (1, 3)]) | |
>>> nx.local_reaching_centrality(G, 3) | |
0.0 | |
>>> G.add_edge(3, 2) | |
>>> nx.local_reaching_centrality(G, 3) | |
0.5 | |
See also | |
-------- | |
global_reaching_centrality | |
References | |
---------- | |
.. [1] Mones, Enys, Lilla Vicsek, and Tamás Vicsek. | |
"Hierarchy Measure for Complex Networks." | |
*PLoS ONE* 7.3 (2012): e33799. | |
https://doi.org/10.1371/journal.pone.0033799 | |
""" | |
if paths is None: | |
if nx.is_negatively_weighted(G, weight=weight): | |
raise nx.NetworkXError("edge weights must be positive") | |
total_weight = G.size(weight=weight) | |
if total_weight <= 0: | |
raise nx.NetworkXError("Size of G must be positive") | |
if weight is not None: | |
# Interpret weights as lengths. | |
def as_distance(u, v, d): | |
return total_weight / d.get(weight, 1) | |
paths = nx.shortest_path(G, source=v, weight=as_distance) | |
else: | |
paths = nx.shortest_path(G, source=v) | |
# If the graph is unweighted, simply return the proportion of nodes | |
# reachable from the source node ``v``. | |
if weight is None and G.is_directed(): | |
return (len(paths) - 1) / (len(G) - 1) | |
if normalized and weight is not None: | |
norm = G.size(weight=weight) / G.size() | |
else: | |
norm = 1 | |
# TODO This can be trivially parallelized. | |
avgw = (_average_weight(G, path, weight=weight) for path in paths.values()) | |
sum_avg_weight = sum(avgw) / norm | |
return sum_avg_weight / (len(G) - 1) | |