trophic.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. """Trophic levels"""
  2. import networkx as nx
  3. from networkx.utils import not_implemented_for
  4. __all__ = ["trophic_levels", "trophic_differences", "trophic_incoherence_parameter"]
  5. @not_implemented_for("undirected")
  6. def trophic_levels(G, weight="weight"):
  7. r"""Compute the trophic levels of nodes.
  8. The trophic level of a node $i$ is
  9. .. math::
  10. s_i = 1 + \frac{1}{k^{in}_i} \sum_{j} a_{ij} s_j
  11. where $k^{in}_i$ is the in-degree of i
  12. .. math::
  13. k^{in}_i = \sum_{j} a_{ij}
  14. and nodes with $k^{in}_i = 0$ have $s_i = 1$ by convention.
  15. These are calculated using the method outlined in Levine [1]_.
  16. Parameters
  17. ----------
  18. G : DiGraph
  19. A directed networkx graph
  20. Returns
  21. -------
  22. nodes : dict
  23. Dictionary of nodes with trophic level as the value.
  24. References
  25. ----------
  26. .. [1] Stephen Levine (1980) J. theor. Biol. 83, 195-207
  27. """
  28. import numpy as np
  29. # find adjacency matrix
  30. a = nx.adjacency_matrix(G, weight=weight).T.toarray()
  31. # drop rows/columns where in-degree is zero
  32. rowsum = np.sum(a, axis=1)
  33. p = a[rowsum != 0][:, rowsum != 0]
  34. # normalise so sum of in-degree weights is 1 along each row
  35. p = p / rowsum[rowsum != 0][:, np.newaxis]
  36. # calculate trophic levels
  37. nn = p.shape[0]
  38. i = np.eye(nn)
  39. try:
  40. n = np.linalg.inv(i - p)
  41. except np.linalg.LinAlgError as err:
  42. # LinAlgError is raised when there is a non-basal node
  43. msg = (
  44. "Trophic levels are only defined for graphs where every "
  45. + "node has a path from a basal node (basal nodes are nodes "
  46. + "with no incoming edges)."
  47. )
  48. raise nx.NetworkXError(msg) from err
  49. y = n.sum(axis=1) + 1
  50. levels = {}
  51. # all nodes with in-degree zero have trophic level == 1
  52. zero_node_ids = (node_id for node_id, degree in G.in_degree if degree == 0)
  53. for node_id in zero_node_ids:
  54. levels[node_id] = 1
  55. # all other nodes have levels as calculated
  56. nonzero_node_ids = (node_id for node_id, degree in G.in_degree if degree != 0)
  57. for i, node_id in enumerate(nonzero_node_ids):
  58. levels[node_id] = y[i]
  59. return levels
  60. @not_implemented_for("undirected")
  61. def trophic_differences(G, weight="weight"):
  62. r"""Compute the trophic differences of the edges of a directed graph.
  63. The trophic difference $x_ij$ for each edge is defined in Johnson et al.
  64. [1]_ as:
  65. .. math::
  66. x_ij = s_j - s_i
  67. Where $s_i$ is the trophic level of node $i$.
  68. Parameters
  69. ----------
  70. G : DiGraph
  71. A directed networkx graph
  72. Returns
  73. -------
  74. diffs : dict
  75. Dictionary of edges with trophic differences as the value.
  76. References
  77. ----------
  78. .. [1] Samuel Johnson, Virginia Dominguez-Garcia, Luca Donetti, Miguel A.
  79. Munoz (2014) PNAS "Trophic coherence determines food-web stability"
  80. """
  81. levels = trophic_levels(G, weight=weight)
  82. diffs = {}
  83. for u, v in G.edges:
  84. diffs[(u, v)] = levels[v] - levels[u]
  85. return diffs
  86. @not_implemented_for("undirected")
  87. def trophic_incoherence_parameter(G, weight="weight", cannibalism=False):
  88. r"""Compute the trophic incoherence parameter of a graph.
  89. Trophic coherence is defined as the homogeneity of the distribution of
  90. trophic distances: the more similar, the more coherent. This is measured by
  91. the standard deviation of the trophic differences and referred to as the
  92. trophic incoherence parameter $q$ by [1].
  93. Parameters
  94. ----------
  95. G : DiGraph
  96. A directed networkx graph
  97. cannibalism: Boolean
  98. If set to False, self edges are not considered in the calculation
  99. Returns
  100. -------
  101. trophic_incoherence_parameter : float
  102. The trophic coherence of a graph
  103. References
  104. ----------
  105. .. [1] Samuel Johnson, Virginia Dominguez-Garcia, Luca Donetti, Miguel A.
  106. Munoz (2014) PNAS "Trophic coherence determines food-web stability"
  107. """
  108. import numpy as np
  109. if cannibalism:
  110. diffs = trophic_differences(G, weight=weight)
  111. else:
  112. # If no cannibalism, remove self-edges
  113. self_loops = list(nx.selfloop_edges(G))
  114. if self_loops:
  115. # Make a copy so we do not change G's edges in memory
  116. G_2 = G.copy()
  117. G_2.remove_edges_from(self_loops)
  118. else:
  119. # Avoid copy otherwise
  120. G_2 = G
  121. diffs = trophic_differences(G_2, weight=weight)
  122. return np.std(list(diffs.values()))