123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- """Trophic levels"""
- import networkx as nx
- from networkx.utils import not_implemented_for
- __all__ = ["trophic_levels", "trophic_differences", "trophic_incoherence_parameter"]
- @not_implemented_for("undirected")
- 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
- @not_implemented_for("undirected")
- 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
- @not_implemented_for("undirected")
- 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()))
|