richclub.py 4.8 KB

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