Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/algorithms/centrality/trophic.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

55 statements  

1"""Trophic levels""" 

2 

3import networkx as nx 

4from networkx.utils import not_implemented_for 

5 

6__all__ = ["trophic_levels", "trophic_differences", "trophic_incoherence_parameter"] 

7 

8 

9@not_implemented_for("undirected") 

10@nx._dispatchable(edge_attrs="weight") 

11def trophic_levels(G, weight="weight"): 

12 r"""Compute the trophic levels of nodes. 

13 

14 The trophic level of a node $i$ is 

15 

16 .. math:: 

17 

18 s_i = 1 + \frac{1}{k^{in}_i} \sum_{j} a_{ij} s_j 

19 

20 where $k^{in}_i$ is the in-degree of i 

21 

22 .. math:: 

23 

24 k^{in}_i = \sum_{j} a_{ij} 

25 

26 and nodes with $k^{in}_i = 0$ have $s_i = 1$ by convention. 

27 

28 These are calculated using the method outlined in Levine [1]_. 

29 

30 Parameters 

31 ---------- 

32 G : DiGraph 

33 A directed networkx graph 

34 

35 Returns 

36 ------- 

37 nodes : dict 

38 Dictionary of nodes with trophic level as the value. 

39 

40 References 

41 ---------- 

42 .. [1] Stephen Levine (1980) J. theor. Biol. 83, 195-207 

43 """ 

44 

45 basal_nodes = [n for n, deg in G.in_degree if deg == 0] 

46 if not basal_nodes: 

47 raise nx.NetworkXError( 

48 "This graph has no basal nodes (nodes with no incoming edges)." 

49 "Trophic levels are not defined without at least one basal node." 

50 ) 

51 

52 reachable_nodes = { 

53 node for layer in nx.bfs_layers(G, sources=basal_nodes) for node in layer 

54 } 

55 

56 if len(reachable_nodes) != len(G.nodes): 

57 raise nx.NetworkXError( 

58 "Trophic levels are only defined for graphs where every node has a path " 

59 "from a basal node (basal nodes are nodes with no incoming edges)." 

60 ) 

61 

62 import numpy as np 

63 

64 # find adjacency matrix 

65 a = nx.adjacency_matrix(G, weight=weight).T.toarray() 

66 

67 # drop rows/columns where in-degree is zero 

68 rowsum = np.sum(a, axis=1) 

69 p = a[rowsum != 0][:, rowsum != 0] 

70 # normalise so sum of in-degree weights is 1 along each row 

71 p = p / rowsum[rowsum != 0][:, np.newaxis] 

72 

73 # calculate trophic levels 

74 nn = p.shape[0] 

75 i = np.eye(nn) 

76 try: 

77 n = np.linalg.inv(i - p) 

78 except np.linalg.LinAlgError as err: 

79 # LinAlgError is raised when there is a non-basal node 

80 msg = ( 

81 "Trophic levels are only defined for graphs where every " 

82 + "node has a path from a basal node (basal nodes are nodes " 

83 + "with no incoming edges)." 

84 ) 

85 raise nx.NetworkXError(msg) from err 

86 y = n.sum(axis=1) + 1 

87 

88 levels = {} 

89 

90 # all nodes with in-degree zero have trophic level == 1 

91 zero_node_ids = (node_id for node_id, degree in G.in_degree if degree == 0) 

92 for node_id in zero_node_ids: 

93 levels[node_id] = 1 

94 

95 # all other nodes have levels as calculated 

96 nonzero_node_ids = (node_id for node_id, degree in G.in_degree if degree != 0) 

97 for i, node_id in enumerate(nonzero_node_ids): 

98 levels[node_id] = y.item(i) 

99 

100 return levels 

101 

102 

103@not_implemented_for("undirected") 

104@nx._dispatchable(edge_attrs="weight") 

105def trophic_differences(G, weight="weight"): 

106 r"""Compute the trophic differences of the edges of a directed graph. 

107 

108 The trophic difference $x_ij$ for each edge is defined in Johnson et al. 

109 [1]_ as: 

110 

111 .. math:: 

112 x_ij = s_j - s_i 

113 

114 Where $s_i$ is the trophic level of node $i$. 

115 

116 Parameters 

117 ---------- 

118 G : DiGraph 

119 A directed networkx graph 

120 

121 Returns 

122 ------- 

123 diffs : dict 

124 Dictionary of edges with trophic differences as the value. 

125 

126 References 

127 ---------- 

128 .. [1] Samuel Johnson, Virginia Dominguez-Garcia, Luca Donetti, Miguel A. 

129 Munoz (2014) PNAS "Trophic coherence determines food-web stability" 

130 """ 

131 levels = trophic_levels(G, weight=weight) 

132 diffs = {} 

133 for u, v in G.edges: 

134 diffs[(u, v)] = levels[v] - levels[u] 

135 return diffs 

136 

137 

138@not_implemented_for("undirected") 

139@nx._dispatchable(edge_attrs="weight") 

140def trophic_incoherence_parameter(G, weight="weight", cannibalism=False): 

141 r"""Compute the trophic incoherence parameter of a graph. 

142 

143 Trophic coherence is defined as the homogeneity of the distribution of 

144 trophic distances: the more similar, the more coherent. This is measured by 

145 the standard deviation of the trophic differences and referred to as the 

146 trophic incoherence parameter $q$ by [1]. 

147 

148 Parameters 

149 ---------- 

150 G : DiGraph 

151 A directed networkx graph 

152 

153 cannibalism: Boolean 

154 If set to False, self edges are not considered in the calculation 

155 

156 Returns 

157 ------- 

158 trophic_incoherence_parameter : float 

159 The trophic coherence of a graph 

160 

161 References 

162 ---------- 

163 .. [1] Samuel Johnson, Virginia Dominguez-Garcia, Luca Donetti, Miguel A. 

164 Munoz (2014) PNAS "Trophic coherence determines food-web stability" 

165 """ 

166 import numpy as np 

167 

168 if cannibalism: 

169 diffs = trophic_differences(G, weight=weight) 

170 else: 

171 # If no cannibalism, remove self-edges 

172 self_loops = list(nx.selfloop_edges(G)) 

173 if self_loops: 

174 # Make a copy so we do not change G's edges in memory 

175 G_2 = G.copy() 

176 G_2.remove_edges_from(self_loops) 

177 else: 

178 # Avoid copy otherwise 

179 G_2 = G 

180 diffs = trophic_differences(G_2, weight=weight) 

181 return float(np.std(list(diffs.values())))