### view env/lib/python3.9/site-packages/networkx/algorithms/centrality/trophic.py @ 0:4f3585e2f14bdraftdefaulttip

author shellac Mon, 22 Mar 2021 18:12:50 +0000
line wrap: on
line source

"""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 _.

Parameters
----------
G : DiGraph
A directed networkx graph

Returns
-------
nodes : dict
Dictionary of nodes with trophic level as the vale.

References
----------
..  Stephen Levine (1980) J. theor. Biol. 83, 195-207
"""
try:
import numpy as np
except ImportError as e:
raise ImportError("trophic_levels() requires NumPy: http://numpy.org/") from e

# 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
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.
_ 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
----------
..  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 .

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
----------
..  Samuel Johnson, Virginia Dominguez-Garcia, Luca Donetti, Miguel A.
Munoz (2014) PNAS "Trophic coherence determines food-web stability"
"""
try:
import numpy as np
except ImportError as e:
raise ImportError(
"trophic_incoherence_parameter() requires NumPy: " "http://scipy.org/"
) from e

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()))