load.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. """Load centrality."""
  2. from operator import itemgetter
  3. import networkx as nx
  4. __all__ = ["load_centrality", "edge_load_centrality"]
  5. @nx._dispatchable(edge_attrs="weight")
  6. def newman_betweenness_centrality(G, v=None, cutoff=None, normalized=True, weight=None):
  7. """Compute load centrality for nodes.
  8. The load centrality of a node is the fraction of all shortest
  9. paths that pass through that node.
  10. Parameters
  11. ----------
  12. G : graph
  13. A networkx graph.
  14. normalized : bool, optional (default=True)
  15. If True the betweenness values are normalized by b=b/(n-1)(n-2) where
  16. n is the number of nodes in G.
  17. weight : None or string, optional (default=None)
  18. If None, edge weights are ignored.
  19. Otherwise holds the name of the edge attribute used as weight.
  20. The weight of an edge is treated as the length or distance between the two sides.
  21. cutoff : bool, optional (default=None)
  22. If specified, only consider paths of length <= cutoff.
  23. Returns
  24. -------
  25. nodes : dictionary
  26. Dictionary of nodes with centrality as the value.
  27. See Also
  28. --------
  29. betweenness_centrality
  30. Notes
  31. -----
  32. Load centrality is slightly different than betweenness. It was originally
  33. introduced by [2]_. For this load algorithm see [1]_.
  34. References
  35. ----------
  36. .. [1] Mark E. J. Newman:
  37. Scientific collaboration networks. II.
  38. Shortest paths, weighted networks, and centrality.
  39. Physical Review E 64, 016132, 2001.
  40. http://journals.aps.org/pre/abstract/10.1103/PhysRevE.64.016132
  41. .. [2] Kwang-Il Goh, Byungnam Kahng and Doochul Kim
  42. Universal behavior of Load Distribution in Scale-Free Networks.
  43. Physical Review Letters 87(27):1–4, 2001.
  44. https://doi.org/10.1103/PhysRevLett.87.278701
  45. """
  46. if v is not None: # only one node
  47. betweenness = 0.0
  48. for source in G:
  49. ubetween = _node_betweenness(G, source, cutoff, False, weight)
  50. betweenness += ubetween[v] if v in ubetween else 0
  51. if normalized:
  52. order = G.order()
  53. if order <= 2:
  54. return betweenness # no normalization b=0 for all nodes
  55. betweenness *= 1.0 / ((order - 1) * (order - 2))
  56. else:
  57. betweenness = {}.fromkeys(G, 0.0)
  58. for source in betweenness:
  59. ubetween = _node_betweenness(G, source, cutoff, False, weight)
  60. for vk in ubetween:
  61. betweenness[vk] += ubetween[vk]
  62. if normalized:
  63. order = G.order()
  64. if order <= 2:
  65. return betweenness # no normalization b=0 for all nodes
  66. scale = 1.0 / ((order - 1) * (order - 2))
  67. for v in betweenness:
  68. betweenness[v] *= scale
  69. return betweenness # all nodes
  70. def _node_betweenness(G, source, cutoff=False, normalized=True, weight=None):
  71. """Node betweenness_centrality helper:
  72. See betweenness_centrality for what you probably want.
  73. This actually computes "load" and not betweenness.
  74. See https://networkx.lanl.gov/ticket/103
  75. This calculates the load of each node for paths from a single source.
  76. (The fraction of number of shortests paths from source that go
  77. through each node.)
  78. To get the load for a node you need to do all-pairs shortest paths.
  79. If weight is not None then use Dijkstra for finding shortest paths.
  80. """
  81. # get the predecessor and path length data
  82. if weight is None:
  83. (pred, length) = nx.predecessor(G, source, cutoff=cutoff, return_seen=True)
  84. else:
  85. (pred, length) = nx.dijkstra_predecessor_and_distance(G, source, cutoff, weight)
  86. # order the nodes by path length
  87. onodes = [(l, vert) for (vert, l) in length.items()]
  88. onodes.sort()
  89. onodes[:] = [vert for (l, vert) in onodes if l > 0]
  90. # initialize betweenness
  91. between = {}.fromkeys(length, 1.0)
  92. while onodes:
  93. v = onodes.pop()
  94. if v in pred:
  95. num_paths = len(pred[v]) # Discount betweenness if more than
  96. for x in pred[v]: # one shortest path.
  97. if x == source: # stop if hit source because all remaining v
  98. break # also have pred[v]==[source]
  99. between[x] += between[v] / num_paths
  100. # remove source
  101. for v in between:
  102. between[v] -= 1
  103. # rescale to be between 0 and 1
  104. if normalized:
  105. l = len(between)
  106. if l > 2:
  107. # scale by 1/the number of possible paths
  108. scale = 1 / ((l - 1) * (l - 2))
  109. for v in between:
  110. between[v] *= scale
  111. return between
  112. load_centrality = newman_betweenness_centrality
  113. @nx._dispatchable
  114. def edge_load_centrality(G, cutoff=False):
  115. """Compute edge load.
  116. WARNING: This concept of edge load has not been analysed
  117. or discussed outside of NetworkX that we know of.
  118. It is based loosely on load_centrality in the sense that
  119. it counts the number of shortest paths which cross each edge.
  120. This function is for demonstration and testing purposes.
  121. Parameters
  122. ----------
  123. G : graph
  124. A networkx graph
  125. cutoff : bool, optional (default=False)
  126. If specified, only consider paths of length <= cutoff.
  127. Returns
  128. -------
  129. A dict keyed by edge 2-tuple to the number of shortest paths
  130. which use that edge. Where more than one path is shortest
  131. the count is divided equally among paths.
  132. """
  133. betweenness = {}
  134. for u, v in G.edges():
  135. betweenness[(u, v)] = 0.0
  136. betweenness[(v, u)] = 0.0
  137. for source in G:
  138. ubetween = _edge_betweenness(G, source, cutoff=cutoff)
  139. for e, ubetweenv in ubetween.items():
  140. betweenness[e] += ubetweenv # cumulative total
  141. return betweenness
  142. def _edge_betweenness(G, source, nodes=None, cutoff=False):
  143. """Edge betweenness helper."""
  144. # get the predecessor data
  145. (pred, length) = nx.predecessor(G, source, cutoff=cutoff, return_seen=True)
  146. # order the nodes by path length
  147. onodes = [n for n, d in sorted(length.items(), key=itemgetter(1))]
  148. # initialize betweenness, doesn't account for any edge weights
  149. between = {}
  150. for u, v in G.edges(nodes):
  151. between[(u, v)] = 1.0
  152. between[(v, u)] = 1.0
  153. while onodes: # work through all paths
  154. v = onodes.pop()
  155. if v in pred:
  156. # Discount betweenness if more than one shortest path.
  157. num_paths = len(pred[v])
  158. for w in pred[v]:
  159. if w in pred:
  160. # Discount betweenness, mult path
  161. num_paths = len(pred[w])
  162. for x in pred[w]:
  163. between[(w, x)] += between[(v, w)] / num_paths
  164. between[(x, w)] += between[(w, v)] / num_paths
  165. return between