centrality.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. """Functions for computing communities based on centrality notions."""
  2. import networkx as nx
  3. __all__ = ["girvan_newman"]
  4. def girvan_newman(G, most_valuable_edge=None):
  5. """Finds communities in a graph using the Girvan–Newman method.
  6. Parameters
  7. ----------
  8. G : NetworkX graph
  9. most_valuable_edge : function
  10. Function that takes a graph as input and outputs an edge. The
  11. edge returned by this function will be recomputed and removed at
  12. each iteration of the algorithm.
  13. If not specified, the edge with the highest
  14. :func:`networkx.edge_betweenness_centrality` will be used.
  15. Returns
  16. -------
  17. iterator
  18. Iterator over tuples of sets of nodes in `G`. Each set of node
  19. is a community, each tuple is a sequence of communities at a
  20. particular level of the algorithm.
  21. Examples
  22. --------
  23. To get the first pair of communities::
  24. >>> G = nx.path_graph(10)
  25. >>> comp = girvan_newman(G)
  26. >>> tuple(sorted(c) for c in next(comp))
  27. ([0, 1, 2, 3, 4], [5, 6, 7, 8, 9])
  28. To get only the first *k* tuples of communities, use
  29. :func:`itertools.islice`::
  30. >>> import itertools
  31. >>> G = nx.path_graph(8)
  32. >>> k = 2
  33. >>> comp = girvan_newman(G)
  34. >>> for communities in itertools.islice(comp, k):
  35. ... print(tuple(sorted(c) for c in communities))
  36. ...
  37. ([0, 1, 2, 3], [4, 5, 6, 7])
  38. ([0, 1], [2, 3], [4, 5, 6, 7])
  39. To stop getting tuples of communities once the number of communities
  40. is greater than *k*, use :func:`itertools.takewhile`::
  41. >>> import itertools
  42. >>> G = nx.path_graph(8)
  43. >>> k = 4
  44. >>> comp = girvan_newman(G)
  45. >>> limited = itertools.takewhile(lambda c: len(c) <= k, comp)
  46. >>> for communities in limited:
  47. ... print(tuple(sorted(c) for c in communities))
  48. ...
  49. ([0, 1, 2, 3], [4, 5, 6, 7])
  50. ([0, 1], [2, 3], [4, 5, 6, 7])
  51. ([0, 1], [2, 3], [4, 5], [6, 7])
  52. To just choose an edge to remove based on the weight::
  53. >>> from operator import itemgetter
  54. >>> G = nx.path_graph(10)
  55. >>> edges = G.edges()
  56. >>> nx.set_edge_attributes(G, {(u, v): v for u, v in edges}, "weight")
  57. >>> def heaviest(G):
  58. ... u, v, w = max(G.edges(data="weight"), key=itemgetter(2))
  59. ... return (u, v)
  60. ...
  61. >>> comp = girvan_newman(G, most_valuable_edge=heaviest)
  62. >>> tuple(sorted(c) for c in next(comp))
  63. ([0, 1, 2, 3, 4, 5, 6, 7, 8], [9])
  64. To utilize edge weights when choosing an edge with, for example, the
  65. highest betweenness centrality::
  66. >>> from networkx import edge_betweenness_centrality as betweenness
  67. >>> def most_central_edge(G):
  68. ... centrality = betweenness(G, weight="weight")
  69. ... return max(centrality, key=centrality.get)
  70. ...
  71. >>> G = nx.path_graph(10)
  72. >>> comp = girvan_newman(G, most_valuable_edge=most_central_edge)
  73. >>> tuple(sorted(c) for c in next(comp))
  74. ([0, 1, 2, 3, 4], [5, 6, 7, 8, 9])
  75. To specify a different ranking algorithm for edges, use the
  76. `most_valuable_edge` keyword argument::
  77. >>> from networkx import edge_betweenness_centrality
  78. >>> from random import random
  79. >>> def most_central_edge(G):
  80. ... centrality = edge_betweenness_centrality(G)
  81. ... max_cent = max(centrality.values())
  82. ... # Scale the centrality values so they are between 0 and 1,
  83. ... # and add some random noise.
  84. ... centrality = {e: c / max_cent for e, c in centrality.items()}
  85. ... # Add some random noise.
  86. ... centrality = {e: c + random() for e, c in centrality.items()}
  87. ... return max(centrality, key=centrality.get)
  88. ...
  89. >>> G = nx.path_graph(10)
  90. >>> comp = girvan_newman(G, most_valuable_edge=most_central_edge)
  91. Notes
  92. -----
  93. The Girvan–Newman algorithm detects communities by progressively
  94. removing edges from the original graph. The algorithm removes the
  95. "most valuable" edge, traditionally the edge with the highest
  96. betweenness centrality, at each step. As the graph breaks down into
  97. pieces, the tightly knit community structure is exposed and the
  98. result can be depicted as a dendrogram.
  99. """
  100. # If the graph is already empty, simply return its connected
  101. # components.
  102. if G.number_of_edges() == 0:
  103. yield tuple(nx.connected_components(G))
  104. return
  105. # If no function is provided for computing the most valuable edge,
  106. # use the edge betweenness centrality.
  107. if most_valuable_edge is None:
  108. def most_valuable_edge(G):
  109. """Returns the edge with the highest betweenness centrality
  110. in the graph `G`.
  111. """
  112. # We have guaranteed that the graph is non-empty, so this
  113. # dictionary will never be empty.
  114. betweenness = nx.edge_betweenness_centrality(G)
  115. return max(betweenness, key=betweenness.get)
  116. # The copy of G here must include the edge weight data.
  117. g = G.copy().to_undirected()
  118. # Self-loops must be removed because their removal has no effect on
  119. # the connected components of the graph.
  120. g.remove_edges_from(nx.selfloop_edges(g))
  121. while g.number_of_edges() > 0:
  122. yield _without_most_central_edges(g, most_valuable_edge)
  123. def _without_most_central_edges(G, most_valuable_edge):
  124. """Returns the connected components of the graph that results from
  125. repeatedly removing the most "valuable" edge in the graph.
  126. `G` must be a non-empty graph. This function modifies the graph `G`
  127. in-place; that is, it removes edges on the graph `G`.
  128. `most_valuable_edge` is a function that takes the graph `G` as input
  129. (or a subgraph with one or more edges of `G` removed) and returns an
  130. edge. That edge will be removed and this process will be repeated
  131. until the number of connected components in the graph increases.
  132. """
  133. original_num_components = nx.number_connected_components(G)
  134. num_new_components = original_num_components
  135. while num_new_components <= original_num_components:
  136. edge = most_valuable_edge(G)
  137. G.remove_edge(*edge)
  138. new_components = tuple(nx.connected_components(G))
  139. num_new_components = len(new_components)
  140. return new_components