richclub.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. """Functions for computing rich-club coefficients."""
  2. from itertools import accumulate
  3. import networkx as nx
  4. from networkx.utils import not_implemented_for
  5. __all__ = ["rich_club_coefficient"]
  6. @not_implemented_for("directed")
  7. @not_implemented_for("multigraph")
  8. def rich_club_coefficient(G, normalized=True, Q=100, seed=None):
  9. r"""Returns the rich-club coefficient of the graph `G`.
  10. For each degree *k*, the *rich-club coefficient* is the ratio of the
  11. number of actual to the number of potential edges for nodes with
  12. degree greater than *k*:
  13. .. math::
  14. \phi(k) = \frac{2 E_k}{N_k (N_k - 1)}
  15. where `N_k` is the number of nodes with degree larger than *k*, and
  16. `E_k` is the number of edges among those nodes.
  17. Parameters
  18. ----------
  19. G : NetworkX graph
  20. Undirected graph with neither parallel edges nor self-loops.
  21. normalized : bool (optional)
  22. Normalize using randomized network as in [1]_
  23. Q : float (optional, default=100)
  24. If `normalized` is True, perform `Q * m` double-edge
  25. swaps, where `m` is the number of edges in `G`, to use as a
  26. null-model for normalization.
  27. seed : integer, random_state, or None (default)
  28. Indicator of random number generation state.
  29. See :ref:`Randomness<randomness>`.
  30. Returns
  31. -------
  32. rc : dictionary
  33. A dictionary, keyed by degree, with rich-club coefficient values.
  34. Examples
  35. --------
  36. >>> G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (4, 5)])
  37. >>> rc = nx.rich_club_coefficient(G, normalized=False, seed=42)
  38. >>> rc[0]
  39. 0.4
  40. Notes
  41. -----
  42. The rich club definition and algorithm are found in [1]_. This
  43. algorithm ignores any edge weights and is not defined for directed
  44. graphs or graphs with parallel edges or self loops.
  45. Estimates for appropriate values of `Q` are found in [2]_.
  46. References
  47. ----------
  48. .. [1] Julian J. McAuley, Luciano da Fontoura Costa,
  49. and Tibério S. Caetano,
  50. "The rich-club phenomenon across complex network hierarchies",
  51. Applied Physics Letters Vol 91 Issue 8, August 2007.
  52. https://arxiv.org/abs/physics/0701290
  53. .. [2] R. Milo, N. Kashtan, S. Itzkovitz, M. E. J. Newman, U. Alon,
  54. "Uniform generation of random graphs with arbitrary degree
  55. sequences", 2006. https://arxiv.org/abs/cond-mat/0312028
  56. """
  57. if nx.number_of_selfloops(G) > 0:
  58. raise Exception(
  59. "rich_club_coefficient is not implemented for " "graphs with self loops."
  60. )
  61. rc = _compute_rc(G)
  62. if normalized:
  63. # make R a copy of G, randomize with Q*|E| double edge swaps
  64. # and use rich_club coefficient of R to normalize
  65. R = G.copy()
  66. E = R.number_of_edges()
  67. nx.double_edge_swap(R, Q * E, max_tries=Q * E * 10, seed=seed)
  68. rcran = _compute_rc(R)
  69. rc = {k: v / rcran[k] for k, v in rc.items()}
  70. return rc
  71. def _compute_rc(G):
  72. """Returns the rich-club coefficient for each degree in the graph
  73. `G`.
  74. `G` is an undirected graph without multiedges.
  75. Returns a dictionary mapping degree to rich-club coefficient for
  76. that degree.
  77. """
  78. deghist = nx.degree_histogram(G)
  79. total = sum(deghist)
  80. # Compute the number of nodes with degree greater than `k`, for each
  81. # degree `k` (omitting the last entry, which is zero).
  82. nks = (total - cs for cs in accumulate(deghist) if total - cs > 1)
  83. # Create a sorted list of pairs of edge endpoint degrees.
  84. #
  85. # The list is sorted in reverse order so that we can pop from the
  86. # right side of the list later, instead of popping from the left
  87. # side of the list, which would have a linear time cost.
  88. edge_degrees = sorted((sorted(map(G.degree, e)) for e in G.edges()), reverse=True)
  89. ek = G.number_of_edges()
  90. k1, k2 = edge_degrees.pop()
  91. rc = {}
  92. for d, nk in enumerate(nks):
  93. while k1 <= d:
  94. if len(edge_degrees) == 0:
  95. ek = 0
  96. break
  97. k1, k2 = edge_degrees.pop()
  98. ek -= 1
  99. rc[d] = 2 * ek / (nk * (nk - 1))
  100. return rc