Spaces:
Running
Running
"""Trophic levels""" | |
import networkx as nx | |
from networkx.utils import not_implemented_for | |
__all__ = ["trophic_levels", "trophic_differences", "trophic_incoherence_parameter"] | |
def trophic_levels(G, weight="weight"): | |
r"""Compute the trophic levels of nodes. | |
The trophic level of a node $i$ is | |
.. math:: | |
s_i = 1 + \frac{1}{k^{in}_i} \sum_{j} a_{ij} s_j | |
where $k^{in}_i$ is the in-degree of i | |
.. math:: | |
k^{in}_i = \sum_{j} a_{ij} | |
and nodes with $k^{in}_i = 0$ have $s_i = 1$ by convention. | |
These are calculated using the method outlined in Levine [1]_. | |
Parameters | |
---------- | |
G : DiGraph | |
A directed networkx graph | |
Returns | |
------- | |
nodes : dict | |
Dictionary of nodes with trophic level as the value. | |
References | |
---------- | |
.. [1] Stephen Levine (1980) J. theor. Biol. 83, 195-207 | |
""" | |
import numpy as np | |
# find adjacency matrix | |
a = nx.adjacency_matrix(G, weight=weight).T.toarray() | |
# drop rows/columns where in-degree is zero | |
rowsum = np.sum(a, axis=1) | |
p = a[rowsum != 0][:, rowsum != 0] | |
# normalise so sum of in-degree weights is 1 along each row | |
p = p / rowsum[rowsum != 0][:, np.newaxis] | |
# calculate trophic levels | |
nn = p.shape[0] | |
i = np.eye(nn) | |
try: | |
n = np.linalg.inv(i - p) | |
except np.linalg.LinAlgError as err: | |
# LinAlgError is raised when there is a non-basal node | |
msg = ( | |
"Trophic levels are only defined for graphs where every " | |
+ "node has a path from a basal node (basal nodes are nodes " | |
+ "with no incoming edges)." | |
) | |
raise nx.NetworkXError(msg) from err | |
y = n.sum(axis=1) + 1 | |
levels = {} | |
# all nodes with in-degree zero have trophic level == 1 | |
zero_node_ids = (node_id for node_id, degree in G.in_degree if degree == 0) | |
for node_id in zero_node_ids: | |
levels[node_id] = 1 | |
# all other nodes have levels as calculated | |
nonzero_node_ids = (node_id for node_id, degree in G.in_degree if degree != 0) | |
for i, node_id in enumerate(nonzero_node_ids): | |
levels[node_id] = y[i] | |
return levels | |
def trophic_differences(G, weight="weight"): | |
r"""Compute the trophic differences of the edges of a directed graph. | |
The trophic difference $x_ij$ for each edge is defined in Johnson et al. | |
[1]_ as: | |
.. math:: | |
x_ij = s_j - s_i | |
Where $s_i$ is the trophic level of node $i$. | |
Parameters | |
---------- | |
G : DiGraph | |
A directed networkx graph | |
Returns | |
------- | |
diffs : dict | |
Dictionary of edges with trophic differences as the value. | |
References | |
---------- | |
.. [1] Samuel Johnson, Virginia Dominguez-Garcia, Luca Donetti, Miguel A. | |
Munoz (2014) PNAS "Trophic coherence determines food-web stability" | |
""" | |
levels = trophic_levels(G, weight=weight) | |
diffs = {} | |
for u, v in G.edges: | |
diffs[(u, v)] = levels[v] - levels[u] | |
return diffs | |
def trophic_incoherence_parameter(G, weight="weight", cannibalism=False): | |
r"""Compute the trophic incoherence parameter of a graph. | |
Trophic coherence is defined as the homogeneity of the distribution of | |
trophic distances: the more similar, the more coherent. This is measured by | |
the standard deviation of the trophic differences and referred to as the | |
trophic incoherence parameter $q$ by [1]. | |
Parameters | |
---------- | |
G : DiGraph | |
A directed networkx graph | |
cannibalism: Boolean | |
If set to False, self edges are not considered in the calculation | |
Returns | |
------- | |
trophic_incoherence_parameter : float | |
The trophic coherence of a graph | |
References | |
---------- | |
.. [1] Samuel Johnson, Virginia Dominguez-Garcia, Luca Donetti, Miguel A. | |
Munoz (2014) PNAS "Trophic coherence determines food-web stability" | |
""" | |
import numpy as np | |
if cannibalism: | |
diffs = trophic_differences(G, weight=weight) | |
else: | |
# If no cannibalism, remove self-edges | |
self_loops = list(nx.selfloop_edges(G)) | |
if self_loops: | |
# Make a copy so we do not change G's edges in memory | |
G_2 = G.copy() | |
G_2.remove_edges_from(self_loops) | |
else: | |
# Avoid copy otherwise | |
G_2 = G | |
diffs = trophic_differences(G_2, weight=weight) | |
return np.std(list(diffs.values())) | |