123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- """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)
|