# This file is part of pyunicorn.
# Copyright (C) 2008--2024 Jonathan F. Donges and pyunicorn authors
# URL: <https://www.pik-potsdam.de/members/donges/software-2/software>
# License: BSD (3-clause)
#
# Please acknowledge and cite the use of this software and its authors
# when results are used in publications or published elsewhere.
#
# You can use the following reference:
# J.F. Donges, J. Heitzig, B. Beronov, M. Wiedermann, J. Runge, Q.-Y. Feng,
# L. Tupikina, V. Stolbova, R.V. Donner, N. Marwan, H.A. Dijkstra,
# and J. Kurths, "Unified functional network and nonlinear time series analysis
# for complex systems science: The pyunicorn package"
"""
Provides classes for analyzing spatially embedded complex networks, handling
multivariate data and generating time series surrogates.
"""
import numpy as np
from numpy import random
from ._ext.types import to_cy, ADJ, NODE, DWEIGHT, DFIELD
from ._ext.numerics import _randomlySetCrossLinks, _randomlyRewireCrossLinks, \
_cross_transitivity, _nsi_cross_transitivity, _cross_local_clustering, \
_nsi_cross_local_clustering
from .network import Network, NetworkError
[docs]
class InteractingNetworks(Network):
"""
Encapsulates an ensemble of interacting networks.
Provides measures to analyze the interaction topology of different pairs of
subnetworks (groups of vertices).
So far, most methods only give meaningful results for undirected networks!
The idea of interacting networks and measures for their analysis are
described in [Donges2011a]_.
Consistently node-weighted measures for interacting network topologies are
derived, described and applied in [Wiedermann2011]_.
"""
#
# Definitions of internal methods
#
[docs]
def __init__(self, adjacency, directed=False, node_weights=None,
silence_level=0):
"""
Initialize an instance of InteractingNetworks.
:type adjacency: square numpy array or list [node,node] of 0s and 1s
:arg adjacency: Adjacency matrix of the new network. Entry [i,j]
indicates whether node i links to node j. Its diagonal must be
zero. Must be symmetric if directed=False.
:arg bool directed: Indicates whether the network shall be considered
as directed. If False, adjacency must be symmetric.
:type node_weights: 1d numpy array or list [node] of floats >= 0
:arg node_weights: Optional array or list of node weights to be used
for node splitting invariant network measures. Entry [i] is the
weight of node i. (Default: list of ones)
:arg int silence_level: The inverse level of verbosity of the object.
"""
Network.__init__(self, adjacency=adjacency, directed=directed,
node_weights=node_weights,
silence_level=silence_level)
[docs]
def __str__(self):
"""
Return a string representation of InteractingNetworks object.
"""
return 'InteractingNetworks:\n' + Network.__str__(self)
#
# Graph generation methods
#
[docs]
@staticmethod
def SmallTestNetwork():
"""
Return a 6-node undirected test network.
The network looks like this::
3 - 1
| | \\
5 - 0 - 4 - 2
:rtype: InteractingNetworks instance
:return: an InteractingNetworks instance for testing purposes.
"""
nw = InteractingNetworks(adjacency=[[0, 0, 0, 1, 1, 1],
[0, 0, 1, 1, 1, 0],
[0, 1, 0, 0, 1, 0],
[1, 1, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0]],
directed=False,
node_weights=[0.6, 0.8, 1.0, 1.2, 1.4, 1.6],
silence_level=2)
link_weights = np.array([[0, 0, 0, 1.3, 2.5, 1.1],
[0, 0, 2.3, 2.9, 2.7, 0],
[0, 2.3, 0, 0, 1.5, 0],
[1.3, 2.9, 0, 0, 0, 0],
[2.5, 2.7, 1.5, 0, 0, 0],
[1.1, 0, 0, 0, 0, 0]])
nw.set_link_attribute("link_weights", link_weights)
return nw
[docs]
@staticmethod
def SmallDirectedTestNetwork():
"""
Return a 6-node directed test network with node and edge weights.
The node weights are [1.5, 1.7, 1.9, 2.1, 2.3, 2.5],
a typical node weight for corrected n.s.i. measures would be 2.0.
:rtype: InteractingNetworks instance
"""
nw = InteractingNetworks(adjacency=[[0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[1, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0]],
directed=True,
node_weights=[0.6, 0.8, 1.0, 1.2, 1.4, 1.6],
silence_level=1)
nw.set_link_attribute("link_weights", np.array([[0, 1.3, 0, 2.5, 0, 0],
[0, 0, 1.9, 0, 1.0, 0],
[0, 0, 0, 0, 0, 0],
[0, 3.0, 0, 0, 0, 0],
[2.1, 0, 2.7, 0, 0, 0],
[1.5, 0, 0, 0, 0, 0]]))
return nw
[docs]
@staticmethod
def RandomlySetCrossLinks(network, node_list1, node_list2,
cross_link_density=None,
number_cross_links=None):
"""
Creates a set of random cross links between the considered
interacting subnetworks. The number of cross links to be set
can be chosen either explicitly or via a predefined cross link density.
By not choosing any of either, a null model is created under
preservation of the cross link density of the initial network.
Implementation:
Determines the number of cross links to be set.
Creates an empty cross adjacency matrix.
Randomly picks the coordinates of an entry and sets it to one.
Repeats the procedure until the desired cross link density
is reached.
:type network: InteractingNetworks instance
:arg network: The base network for setting random cross links.
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:rtype: :class:`InteractingNetworks`
:return: The initial InteractingNetworks with random cross links
"""
# store node lists as arrays
nodes1 = np.array(node_list1, dtype=NODE)
nodes2 = np.array(node_list2, dtype=NODE)
# retrieve number of nodes
N1, N2 = len(nodes1), len(nodes2)
# retrieve cross adjacency matrix
cross_A = network.cross_adjacency(nodes1, nodes2).astype(ADJ)
# determine number of cross links
if cross_link_density is not None:
number_cross_links = int(cross_link_density * (N1 * N2))
print("Setting number of cross links according to "
"chosen link density.")
elif cross_link_density is None and number_cross_links is None:
number_cross_links = int(cross_A.sum())
print("Creating a null model for the given interacting networks.")
# else: take the explicitly chosen number of cross links
if number_cross_links > (N1 * N2):
print("The number of cross links exceeds maximum.")
print("Setting link density of initial interacting network.")
number_cross_links = int(cross_A.sum())
# retrieve adjacency matrix of the full interacting network
A_new = network.adjacency.astype(ADJ)
# create new empty cross adjacency matrix
cross_A_new = np.zeros((N1, N2), dtype=ADJ)
_randomlySetCrossLinks(A_new, cross_A_new, number_cross_links,
nodes1, nodes2, N1, N2)
return InteractingNetworks(adjacency=A_new,
directed=network.directed,
node_weights=network.node_weights,
silence_level=network.silence_level)
[docs]
@staticmethod
def RandomlySetCrossLinks_sparse(network, node_list1, node_list2,
cross_link_density=None,
number_cross_links=None):
"""
Creates a set of random cross links between the considered
interacting subnetworks. The number of cross links to be set
can be chosen either explicitly or via a predefined cross link density.
By not choosing any of either, a null model is created under
preservation of the cross link density of the initial network.
Implementation:
Determines the number of cross links to be set.
Creates an empty cross adjacency matrix.
Randomly picks the coordinates of an entry and sets it to one.
Repeats the procedure until the desired cross link density
is reached.
:type network: InteractingNetworks instance
:arg network: The base network for setting random cross links.
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:rtype: :class:`InteractingNetworks`
:return: The initial InteractingNetworks with random cross links
"""
# store node lists as arrays
nodes1 = np.array(node_list1, dtype=int)
nodes2 = np.array(node_list2, dtype=int)
# retrieve number of nodes
N1, N2 = len(nodes1), len(nodes2)
# retrieve cross adjacency matrix
cross_A = network.cross_adjacency_sparse(nodes1, nodes2).astype(int)
# determine number of cross links
if cross_link_density is not None:
number_cross_links = int(cross_link_density * (N1 * N2))
print("Setting number of cross links according to chosen \
link density.")
elif cross_link_density is None and number_cross_links is None:
number_cross_links = int(sum(cross_A.values()))
print("Creating a null model for the given interacting networks.")
# else: take the explicitly chosen number of cross links
if number_cross_links > (N1 * N2):
print("The number of cross links exceeds maximum.")
print("Setting link density of initial interacting network.")
number_cross_links = int(sum(cross_A.values()))
# retrieve adjacency matrix of the full interacting network
A_new = network.sp_A.astype(int)
# create new empty cross adjacency matrix
cross_A_new = np.zeros((N1, N2))
n_1, n_2 = 0, 0
node1, node2 = 0, 0
for i in range(number_cross_links):
while True:
n_1 = int(random.random() * N1)
n_2 = int(random.random() * N2)
if not (cross_A_new[n_1, n_2] == 1):
break
cross_A_new[n_1, n_2] = 1
for i in range(N1):
for j in range(N2):
node1 = int(nodes1[i])
node2 = int(nodes2[j])
A_new[node1, node2] = cross_A_new[i, j]
A_new[node2, node1] = cross_A_new[i, j]
return InteractingNetworks(adjacency=A_new,
directed=network.directed,
node_weights=network.node_weights,
silence_level=network.silence_level)
[docs]
@staticmethod
def RandomlyRewireCrossLinks(network, node_list1, node_list2, swaps):
"""
Randomize the cross links between two subnetworks under preservation of
cross degree centrality of both subnetworks.
Chooses randomly two cross links and swaps their ending points in
subnetwork 2.
Implementation:
Stores the coordinates of the "1"-entries of the cross adjacency matrix
in a tuple. Chooses randomly two entries of the tuple (ergo two cross
links) allowing for the constraints that
(1) the chosen links have distinct starting points in subnetwork 1 and
distinct ending points in subnetwork 2
(2) there do not exist intermediate links such that starting point of
link 1 is connected to ending point of link 2 and vice versa.
[In case two links have the same starting point or / and the same
ending point, condition (2) is never satisfied. Therefore only
condition (2) is implemented.]
Swaps the ending points of the links in subnetwork 2 and overwrites the
coordinates of the initial links in the tuple. The number of
permutation procedures is determined by the "swaps" argument and the
initial number of cross links. Creates a new adjacency matrix out of
the altered tuple of coordinates.
**Example** (Degree and cross degree sequences should be the same after
rewiring):
>>> net = InteractingNetworks.SmallTestNetwork()
>>> print("Degree:", net.degree())
Degree: [3 3 2 2 3 1]
>>> print("Cross degree:", net.cross_degree(
... node_list1=[0,3,5], node_list2=[1,2,4]))
Cross degree: [1 1 0]
>>> rewired_net = net.RandomlyRewireCrossLinks(
... network=net, node_list1=[0,3,5],
... node_list2=[1,2,4], swaps=10.)
>>> print("Degree:", rewired_net.degree())
Degree: [3 3 2 2 3 1]
>>> print("Cross degree:", rewired_net.cross_degree(
... node_list1=[0,3,5], node_list2=[1,2,4]))
Cross degree: [1 1 0]
:type network: :class:`InteractingNetworks` instance
:arg network: The base network for rewiring cross links.
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:arg float internal: Gives the fraction number_swaps /
number_cross_links.
:rtype: :class:`InteractingNetworks`
:return: The initial InteractingNetworks with swapped cross links
"""
# retrieve cross adjacency matrix of the considered interacting
# network
cross_A = network.cross_adjacency(node_list1, node_list2).astype(ADJ)
# determine number of cross links
number_cross_links = cross_A.sum()
# Store node lists as arrays
nodes1 = np.array(node_list1, dtype=NODE)
nodes2 = np.array(node_list2, dtype=NODE)
# retrieve adjacency matrix of the full interacting network
A_new = network.adjacency.astype(ADJ)
# determine number of cross link permutations that will be performed
number_swaps = NODE(swaps * number_cross_links)
# Create list of cross links
cross_links = np.array(cross_A.nonzero(), dtype=NODE).transpose()
_randomlyRewireCrossLinks(A_new, cross_A, cross_links, nodes1, nodes2,
number_cross_links, number_swaps)
return InteractingNetworks(adjacency=A_new,
directed=network.directed,
node_weights=network.node_weights,
silence_level=network.silence_level)
#
# Define methods for handling the interacting networks
#
[docs]
def subnetwork(self, node_list):
"""
Return the subnetwork induced by a subset of nodes as a Network object.
This can be used to conveniently analyze the subnetwork separately,
e.g., for calculation network measures solely this subnetwork.
:arg [int] node_list: list of node indices describing the subnetwork
:rtype: Network
:return: the subnetwork induced by the nodes in node_list.
"""
return Network(adjacency=self.internal_adjacency(node_list),
directed=self.directed,
node_weights=self.node_weights[node_list],
silence_level=self.silence_level)
[docs]
def internal_adjacency(self, node_list):
"""
Return the adjacency matrix of a subnetwork induced by a subset of
nodes.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
internal_adjacency([0,3,5])
array([[0, 1, 1], [1, 0, 0], [1, 0, 0]], dtype=int8)
>>> InteractingNetworks.SmallTestNetwork().\
internal_adjacency([1,2,4])
array([[0, 1, 1], [1, 0, 1], [1, 1, 0]], dtype=int8)
:arg [int] node_list: list of node indices describing the subnetwork
:rtype: 2D array [node index, node index]
:return: the subnetwork's adjacency matrix.
"""
# Create igraph Graph object describing the subgraph
subgraph = self.graph.subgraph(node_list)
# Get adjacency matrix
return np.array(subgraph.get_adjacency(type=2).data).astype(np.int8)
[docs]
def cross_adjacency(self, node_list1, node_list2):
"""
Return cross adjacency matrix describing the interaction of two
subnetworks.
The cross adjacency matrix entry :math:`CA_{ij} = 1` describes that
node i in the first subnetwork is linked to node j in the second
subnetwork. Vice versa, :math:`CA_{ji} = 1` indicates that node j in
the first subnetwork is linked to node i in the second subnetwork.
.. note::
The Cross adjacency matrix is NEITHER square NOR symmetric in
general!
**Examples:**
>>> r(InteractingNetworks.SmallTestNetwork().\
cross_adjacency([1,2,4], [0,3,5]))
array([[0, 1, 0], [0, 0, 0], [1, 0, 0]])
>>> r(InteractingNetworks.SmallTestNetwork().\
cross_adjacency([1,2,3,4], [0,5]))
array([[0, 0], [0, 0], [1, 0], [1, 0]])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:rtype: 2D array [node index_1, node index_2]
:return: the cross adjacency matrix.
"""
return self.adjacency[node_list1, :][:, node_list2]
[docs]
def cross_adjacency_sparse(self, node_list1, node_list2):
"""
Return cross adjacency matrix describing the interaction of two
subnetworks.
The cross adjacency matrix entry M{CA_ij = 1} describes that node i
in the first subnetwork is linked to node j in the second subnetwork.
Vice versa, M{CA_ji = 1} indicates that node j in the first subnetwork
is linked to node i in the second subnetwork.
.. note::
The Cross adjacency matrix is NEITHER square NOR symmetric in
general!
Examples:
>>> print(InteractingNetworks.SmallTestNetwork().\
cross_adjacency_sparse([1,2,4], [0,3,5]))
[[0 1 0] [0 0 0] [1 0 0]]
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:rtype: 2D array [node index_1, node index_2]
:return: the cross adjacency matrix.
"""
return self.sp_A[node_list1, :][:, node_list2].A
[docs]
def internal_link_attribute(self, attribute_name, node_list):
"""
Return a like attribute matrix of a subnetwork induced by a subset of
nodes.
**Example:**
>>> net = InteractingNetworks.SmallTestNetwork()
>>> r(net.internal_link_attribute("link_weights", [1,2,3]))
array([[ 0. , 2.3, 2.9],
[ 2.3, 0. , 0. ],
[ 2.9, 0. , 0. ]])
:arg str attribute_name: _name of link attribute to be used
:arg [int] node_list: list of node indices describing the subnetwork
:rtype: square numpy array [node_index, node_index]
:return: link weights submatrix
"""
weights = np.zeros((len(node_list), len(node_list)))
subgraph = self.graph.subgraph(node_list)
if self.directed:
for e in subgraph.es:
weights[e.tuple] = e[attribute_name]
# Symmetrize if subgraph is undirected
else:
for e in subgraph.es:
weights[e.tuple] = e[attribute_name]
weights[e.tuple[1], e.tuple[0]] = e[attribute_name]
return weights
[docs]
def cross_link_attribute(self, attribute_name, node_list1, node_list2):
"""
Return a cross link weights matrix describing the interaction of two
subnetworks.
The cross link weights matrix entry :math:`CW_{ij} = w` describes that
node i in the first subnetwork is linked to node j in the second
subnetwork with weights :math:`w`.
.. note::
The cross link weights matrix is NEITHER square NOR symmetric in
general!
Example:
>>> net = InteractingNetworks.SmallTestNetwork()
>>> r(net.cross_link_attribute("link_weights", [1,2,3], [0,4]))
array([[ 0. , 2.7],
[ 0. , 1.5],
[ 1.3, 0. ]])
:arg str attribute_name: _name of link attribute to be used
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:rtype: 2D array [node index_1, node index_2]
:return: the cross adjacency matrix.
"""
W = self.link_attribute(attribute_name)
return W[node_list1, :][:, node_list2]
[docs]
def internal_path_lengths(self, node_list, link_attribute=None):
"""
Return internal path length matrix of an induced subnetwork.
Contains the paths length between all pairs of nodes within the
subnetwork. However, the paths themselves will generally contain nodes
from the full network. To avoid this and only consider paths lying
within the subnetwork, do the following:
>>> InteractingNetworks.SmallTestNetwork().\
subnetwork([0,3,5]).path_lengths()
array([[ 0., 1., 1.], [ 1., 0., 2.], [ 1., 2., 0.]])
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
internal_path_lengths([0,3,5], None)
array([[ 0., 1., 1.], [ 1., 0., 2.], [ 1., 2., 0.]])
>>> InteractingNetworks.SmallTestNetwork().\
internal_path_lengths([1,2,4], None)
array([[ 0., 1., 1.], [ 1., 0., 1.], [ 1., 1., 0.]])
:arg [int] node_list: list of node indices describing the subnetwork
:arg str link_attribute: Optional name of the link attribute to be used
as the links' length. If None, links have length 1. (Default: None)
:rtype: 2D array [node index, node index]
:return: the internal path length matrix of an induced subnetwork.
"""
return self.\
path_lengths(link_attribute)[node_list, :][:, node_list]
[docs]
def cross_path_lengths(self, node_list1, node_list2, link_attribute=None):
"""
Return cross path length matrix for a pair of subnetworks.
Contains the path length between nodes from different subnetworks. The
paths may generally contain nodes from the full network.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
cross_path_lengths([0,3,5], [1,2,4], None)
array([[ 2., 2., 1.], [ 1., 2., 2.], [ 3., 3., 2.]])
>>> InteractingNetworks.SmallTestNetwork().\
cross_path_lengths([0,5], [1,2,3,4], None)
array([[ 2., 2., 1., 1.], [ 3., 3., 2., 2.]])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:arg str link_attribute: Optional name of the link attribute to be used
as the links' length. If None, links have length 1. (Default: None)
:rtype: 2D array [index1, index2]
:return: the cross path length matrix for a pair of subnetworks.
"""
return self.path_lengths(link_attribute)[node_list1, :][:, node_list2]
#
# Define scalar statistics for interacting networks
#
[docs]
def number_cross_links(self, node_list1, node_list2):
"""
Return the number of links connecting the two subnetworks.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
number_cross_links([0,3,5], [1,2,4])
2
>>> InteractingNetworks.SmallTestNetwork().\
number_cross_links([0,5], [1,2,3,4])
2
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:return int: the number of links between nodes from different
subnetworks.
"""
if self.directed:
raise NetworkError("Not implemented yet...")
return self.cross_adjacency(node_list1, node_list2).sum()
[docs]
def total_cross_degree(self, node_list1, node_list2):
"""
Return the total cross degree of the two subnetworks.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
toal_cross_degree([0,3,5], [1,2,4])
1.0
>>> InteractingNetworks.SmallTestNetwork().\
total_cross_degree([0,5], [1,2,3,4]).round(4)
0.6667
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:return int: the total cross degree.
"""
return np.mean(self.cross_degree(node_list1, node_list2))
[docs]
def number_internal_links(self, node_list):
"""
Return the number of links within an induced subnetwork.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
number_internal_links([0,3,5])
2
>>> InteractingNetworks.SmallTestNetwork().\
number_internal_links([1,2,4])
3
:arg [int] node_list: list of node indices describing the subnetwork
:return int: the number of links within a given subnetwork.
"""
n_links = self.internal_adjacency(node_list).sum()
if self.directed:
return n_links
else:
return n_links // 2
[docs]
def cross_degree_density(self, node_list1, node_list2):
"""
Return the density of degrees, i.e., the cross degree sequence of the
first subnetwork normalized to the number of nodes in the second
subnetwork
**Example:**
>>> InteractingNetworks.SmallTestNetwork().\
cross_degree_density([0,3,5], [1,2,4])
array([0.33333333, 0.33333333, 0. ])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:rtype: 1D array [node index]
:return: the cross degree sequence.
"""
N2 = len(node_list2)
return self.cross_degree(node_list1, node_list2) / N2
[docs]
def cross_link_density(self, node_list1, node_list2):
"""
Return the density of links between two subnetworks.
**Examples:**
>>> r(InteractingNetworks.SmallTestNetwork().\
cross_link_density([0,3,5], [1,2,4]))
0.2222
>>> InteractingNetworks.SmallTestNetwork().\
cross_link_density([0,5], [1,2,3,4])
0.25
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:return float: the density of links between two subnetworks.
"""
N1, N2 = len(node_list1), len(node_list2)
if self.directed:
raise NetworkError("Not implemented yet...")
n_cl = self.number_cross_links(node_list1, node_list2)
return float(n_cl) / (N1 * N2)
[docs]
def internal_link_density(self, node_list):
"""
Return the density of links within an induced subnetwork.
**Examples:**
>>> r(InteractingNetworks.SmallTestNetwork().\
internal_link_density([0,3,5]))
0.6667
>>> r(InteractingNetworks.SmallTestNetwork().\
internal_link_density([1,2,3,4]))
0.6667
:arg [int] node_list: list of node indices describing the subnetwork
:return float: the density of links within a subnetwork.
"""
N = len(node_list)
n_links = InteractingNetworks.number_internal_links(self, node_list)
if self.directed:
return float(n_links) / (N * (N - 1))
else:
return 2 * float(n_links) / (N * (N - 1))
[docs]
def internal_global_clustering(self, node_list):
"""
Return internal global clustering coefficients for an induced
subnetwork.
Internal global clustering coefficients are calculated as mean values
from the local clustering sequence of the whole network. This
implies that triangles spanning different subnetworks will generally
contribute to the internal clustering coefficient.
To avoid this and consider only triangles lying within the subnetwork:
>>> r(InteractingNetworks.SmallTestNetwork().\
subnetwork([0,3,5]).global_clustering())
0.0
**Examples:**
>>> r(InteractingNetworks.SmallTestNetwork().\
internal_global_clustering([0,3,5]))
0.0
>>> r(InteractingNetworks.SmallTestNetwork().\
internal_global_clustering([1,2,4]))
0.5556
:arg [int] node_list: list of node indices describing the subnetwork
:return float: the internal global clustering coefficient for a
subnetwork.
"""
clustering = self.local_clustering()
internal_clustering = clustering[node_list].mean()
return internal_clustering
[docs]
def cross_global_clustering(self, node_list1, node_list2):
"""
Return global cross clustering for a pair of subnetworks.
The global cross clustering coefficient C_v gives the average
probability, that two randomly drawn neighbors in subnetwork 2 of node
v in subnetwork 1 are also neighbors and vice versa. It counts
triangles having one vertex in subnetwork 1 and two vertices in
subnetwork 2 and vice versa.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
cross_global_clustering([0,3,5], [1,2,4])
0.0
>>> InteractingNetworks.SmallTestNetwork().\
cross_global_clustering([2], [1,3,4])
1.0
>>> InteractingNetworks.SmallTestNetwork().\
cross_global_clustering([3,4], [1,2])
0.5
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:return float: the cross global clustering coefficient for a pair of
subnetworks.
"""
# Get cross local clustering sequences
cc = InteractingNetworks.cross_local_clustering(self,
node_list1, node_list2)
return cc.mean()
[docs]
def cross_global_clustering_sparse(self, node_list1, node_list2):
"""
Return global cross clustering for a pair of subnetworks.
The global cross clustering coefficient C_v gives the average
probability, that two randomly drawn neighbors in subnetwork 2 of node
v in subnetwork 1 are also neighbors and vice versa. It counts
triangles having one vertex in subnetwork 1 and two vertices in
subnetwork 2 and vice versa.
Examples:
>>> InteractingNetworks.SmallTestNetwork().\
cross_global_clustering_sparse([0,3,5], [1,2,4])
0.0
>>> InteractingNetworks.SmallTestNetwork().\
cross_global_clustering_sparse([2], [1,3,4])
1.0
>>> InteractingNetworks.SmallTestNetwork().\
cross_global_clustering_sparse([3,4], [1,2])
0.5
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:return float: the cross global clustering coefficient for a pair of
subnetworks.
"""
# Get cross local clustering sequences
cc = self.cross_local_clustering_sparse(node_list1, node_list2)
return cc.mean()
[docs]
def cross_transitivity(self, node_list1, node_list2):
"""
Return cross transitivity for a pair of subnetworks.
The cross transitivity is the probability, that two randomly drawn
neighbors in subnetwork 2 of node v in subnetwork 1 are also neighbors.
It counts triangles having one vertex in subnetwork 1 and two vertices
in subnetwork 2. Cross transitivity tends to weight low cross degree
vertices less strongly when compared to the global cross clustering
coefficient (see [Newman2003]_).
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
cross_transitivity([0,3,5], [1,2,4])
0.0
>>> InteractingNetworks.SmallTestNetwork().\
cross_transitivity([2], [1,3,4])
1.0
>>> InteractingNetworks.SmallTestNetwork().\
cross_transitivity([3,4], [1,2])
1.0
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:return float: the cross transitivity for a pair of subnetworks.
"""
return _cross_transitivity(
to_cy(self.adjacency, ADJ),
np.array(node_list1, dtype=NODE), np.array(node_list2, dtype=NODE))
[docs]
def cross_transitivity_sparse(self, node_list1, node_list2):
"""
Return cross transitivity for a pair of subnetworks.
The cross transitivity is the probability, that two randomly drawn
neighbors in subnetwork 2 of node v in subnetwork 1 are also
neighbors. It counts triangles having one vertex in
subnetwork 1 and two vertices in subnetwork 2. Cross
transitivity tends to weight low cross degree vertices less strongly
when compared to the global cross clustering coefficient (see Newman,
SIAM Review, 2003).
Examples:
>>> InteractingNetworks.SmallTestNetwork().\
cross_transitivity_sparse([0,3,5], [1,2,4])
0.0
>>> InteractingNetworks.SmallTestNetwork().\
cross_transitivity_sparse([3,4], [1,2])
1.0
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:return float: the cross transitivity for a pair of subnetworks.
"""
cross_degree = self.cross_degree(node_list1, node_list2)
# Get sparse adjacency matrix
A = self.sp_A[node_list1+node_list2, :][:, node_list1+node_list2]
# Get subnetwork sizes
N1, N2 = len(node_list1), len(node_list2)
# Initialize
cross_transitivity = 0.0
# Set counter
counter_triangles = 0.0
counter_triples = 0.0
# Calculate cross transitivity from subnetwork 1 to subnetwork 2
# Loop over nodes in subnetwork 1
for i in range(N1):
node1 = i
if cross_degree[i] > 1:
# Loop over unique pairs of nodes in subnetwork 2
for j in range(N1, N1+N2):
node2 = j
for k in range(N1, j):
node3 = k
if A[node1, node2] == 1 and A[node1, node3] == 1:
counter_triples += 1
if A[node2, node3] == 1:
counter_triangles += 1
if counter_triples:
cross_transitivity = counter_triangles / counter_triples
return cross_transitivity
[docs]
@staticmethod
def _calculate_general_average_path_length(path_lengths, internal=False):
"""
Calculate general average path length for interacting networks.
:type path_lengths: 2D array [index, index]
:arg path_lengths: The path length matrix.
:arg bool internal: Indicates, whether internal or cross average path
length shall be calculated.
:return float: the general average path length.
"""
# Get shape of path lengths array for normalization
(N, M) = path_lengths.shape
# Identify unconnected pairs and save in binary array isinf
unconnected_pairs = np.isinf(path_lengths)
# Count the number of unconnected pairs
n_unconnected_pairs = unconnected_pairs.sum()
# Set infinite entries corresponding to unconnected pairs to zero
path_lengths[unconnected_pairs] = 0
# Take average of shortest geographical path length matrix optionally
# excluding the diagonal, since it is always zero, and all unconnected
# pairs. The diagonal should never contain infinities, so that should
# not be a problem.
if internal:
norm = float((N - 1) * M - n_unconnected_pairs)
else:
norm = float(N * M - n_unconnected_pairs)
average_path_length = path_lengths.sum() / norm
# Reverse changes to path_lengths
path_lengths[unconnected_pairs] = np.inf
return average_path_length
[docs]
def cross_average_path_length(self, node_list1, node_list2,
link_attribute=None):
"""
Return cross average path length.
Return the average (weighted) shortest path length between two induced
subnetworks.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
cross_average_path_length([0,3,5], [1,2,4], None)
2.0
>>> InteractingNetworks.SmallTestNetwork().\
cross_average_path_length([0,5], [1,2,3,4], None)
2.0
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:arg str link_attribute: Optional name of the link attribute to be used
as the links' length. If None, links have length 1. (Default: None)
:return float: the cross average path length between a pair of
subnetworks.
"""
path_lengths = InteractingNetworks.cross_path_lengths(
self, node_list1, node_list2, link_attribute)
return self._calculate_general_average_path_length(
path_lengths, internal=False)
[docs]
def internal_average_path_length(self, node_list, link_attribute=None):
"""
Return internal average path length for an induced subnetwork.
Return the average (weighted) shortest path length between all pairs
of nodes within a subnetwork separately for which a path exists. Paths
between nodes from different subnetworks are not included in the
average!
However, even if the end points lie within the same layer, the paths
themselves will generally contain nodes from the whole network. To
avoid this and only consider paths lying within the subnetwork, do the
following:
>>> r(InteractingNetworks.SmallTestNetwork().\
subnetwork([0,3,5]).average_path_length(None))
1.3333
**Examples:**
>>> r(InteractingNetworks.SmallTestNetwork().\
internal_average_path_length([0,3,5], None))
1.3333
>>> r(InteractingNetworks.SmallTestNetwork().\
internal_average_path_length([1,2,4], None))
1.0
:arg [int] node_list: list of node indices describing the subnetwork
:arg str link_attribute: Optional name of the link attribute to be used
as the links' length. If None, links have length 1. (Default: None)
:return float: the internal average path length.
"""
path_lengths = self.internal_path_lengths(node_list, link_attribute)
return self._calculate_general_average_path_length(path_lengths,
internal=True)
[docs]
def average_cross_closeness(self, node_list1, node_list2,
link_attribute=None):
"""
Return the average cross closeness.
**Example:**
>>> r(InteractingNetworks.SmallTestNetwork().\
average_cross_closeness([0,5], [1,2,3,4]))
1.7143
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:arg str link_attribute: Optional name of the link attribute to be used
as the links' length. If None, links have length 1. (Default: None)
:return float: the average cross closeness.
"""
return np.mean(self.cross_closeness(node_list1, node_list2,
link_attribute))
[docs]
def global_efficiency(self, node_list1, node_list2, link_attribute=None):
"""
Return the global efficiency.
**Example:**
>>> r(InteractingNetworks.SmallTestNetwork().\
global_efficiency([0,5], [1,2,3,4]))
1.7143
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:arg str link_attribute: Optional name of the link attribute to be used
as the links' length. If None, links have length 1. (Default: None)
:return float: the global efficiency.
"""
local_efficiency = self.local_efficiency(node_list1, node_list2,
link_attribute)
return 1/np.mean(local_efficiency)
#
# Define local measures for interacting networks
#
[docs]
def cross_degree(self, node_list1, node_list2, link_attribute=None):
"""
Return the cross degree sequence for one subnetwork with respect to a
second subnetwork.
Gives the number of links from a specific node in the first subnetwork
projecting to the second subnetwork. If a link attribute is specified,
return the associated strength
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
cross_degree([0,3,5], [1,2,4])
array([1, 1, 0])
>>> InteractingNetworks.SmallTestNetwork().\
cross_degree([1,2,4], [0,3,5])
array([1, 0, 1])
>>> InteractingNetworks.SmallTestNetwork().\
cross_degree([1,2,3,4], [0,5])
array([0, 0, 1, 1])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:arg str key: link attribute key (optional)
:rtype: 1D array [node index]
:return: the cross degree sequence.
"""
if self.directed:
return (self.cross_indegree(node_list1, node_list2,
link_attribute)
+ self.cross_outdegree(node_list1, node_list2,
link_attribute))
else:
return self.cross_outdegree(node_list1, node_list2,
link_attribute)
[docs]
def cross_indegree(self, node_list1, node_list2, link_attribute=None):
"""
Return the cross indegree sequence for the first given subnetwork with
respect to the second given subnetwork
Gives the number of links from nodes in subnetwork two to a specific
node from subnetwork one. If a link attribute is specified,
return the associated in strength.
**Example:**
>>> net = InteractingNetworks.SmallDirectedTestNetwork()
>>> net.cross_indegree([1, 2], [0, 3, 4])
array([2, 1])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:arg str key: link attribute key (optional)
:rtype: 1D array [node index]
:return: the cross in degree sequence.
"""
if link_attribute is None:
return np.sum(self.cross_adjacency(node_list2, node_list1), axis=0)
else:
return np.sum(self.cross_link_attribute(link_attribute, node_list2,
node_list1), axis=0)
[docs]
def cross_outdegree(self, node_list1, node_list2, link_attribute=None):
"""
Return the cross outdegree sequence for the first given subnetwork with
respect to the second given subnetwork
Gives the number of links from a specific node in subnetwork one to
nodes in subnetwork two. If a link attribute is specified,
return the associated out strength.
**Example:**
>>> net = InteractingNetworks.SmallDirectedTestNetwork()
>>> net.cross_outdegree([1, 2], [0, 3, 4])
array([1, 0])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:arg str key: link attribute key (optional)
:rtype: 1D array [node index]
:return: the cross out degree sequence.
"""
if link_attribute is None:
return np.sum(self.cross_adjacency(node_list1, node_list2), axis=1)
else:
return np.sum(self.cross_link_attribute(link_attribute, node_list1,
node_list2), axis=1)
[docs]
def internal_degree(self, node_list, link_attribute=None):
"""
Return the internal degree sequence of one induced subnetwork.
Gives the number of links from a specific node to other nodes within
the same induced subnetwork. If a link attribute is specified,
return the associated strength.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().internal_degree([0,3,5])
array([2, 1, 1])
>>> InteractingNetworks.SmallTestNetwork().internal_degree([1,2,4])
array([2, 2, 2])
:arg [int] node_list: list of node indices describing the subnetwork
:arg str key: link attribute key (optional)
:rtype: 1D array [node index]
:return: the internal degree sequence.
"""
if self.directed:
return (self.internal_indegree(node_list, link_attribute)
+ self.internal_outdegree(node_list, link_attribute))
else:
return self.internal_outdegree(node_list, link_attribute)
[docs]
def internal_indegree(self, node_list, link_attribute=None):
"""
Return the internal indegree sequence of one induced subnetwork.
Gives the number of links from other nodes to a specific node within
the same induced subnetwork. If a link attribute is specified,
return the associated in strength.
**Example:**
>>> net = InteractingNetworks.SmallDirectedTestNetwork()
>>> net.internal_indegree([0, 1, 3])
array([0, 2, 1])
:arg [int] node_list: list of node indices describing the subnetwork
:arg str key: link attribute key (optional)
:rtype: 1D array [node index]
:return: the internal in degree sequence.
"""
if link_attribute is None:
return np.sum(self.internal_adjacency(node_list), axis=0)
else:
return np.sum(self.internal_link_attribute(link_attribute,
node_list), axis=0)
[docs]
def internal_outdegree(self, node_list, link_attribute=None):
"""
Return the internal outdegree sequence of one induced subnetwork.
Gives the number of links from a specific node to other nodes within
the same induced subnetwork. If a link attribute is specified,
return the associated out strength.
**Example:**
>>> net = InteractingNetworks.SmallDirectedTestNetwork()
>>> net.internal_outdegree([0, 1, 3])
array([2, 0, 1])
:arg [int] node_list: list of node indices describing the subnetwork
:arg str key: link attribute key (optional)
:rtype: 1D array [node index]
:return: the internal out degree sequence.
"""
if link_attribute is None:
return np.sum(self.internal_adjacency(node_list), axis=1)
else:
return np.sum(self.internal_link_attribute(link_attribute,
node_list), axis=1)
[docs]
def cross_local_clustering(self, node_list1, node_list2):
"""
Return local cross clustering for a pair of subnetworks.
The local cross clustering coefficient C_v gives the probability, that
two randomly drawn neighbors in subnetwork 1 of node v in subnetwork 1
are also neighbors. It counts triangles having one vertex in
subnetwork 1 and two vertices in subnetwork 2.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
cross_local_clustering([0,3,5], [1,2,4])
array([ 0., 0., 0.])
>>> InteractingNetworks.SmallTestNetwork().\
cross_local_clustering([2], [1,3,4])
array([ 1.])
>>> InteractingNetworks.SmallTestNetwork().\
cross_local_clustering([3,4], [1,2])
array([ 0., 1.])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:rtype: 1D array [node index]
:return: the cross local clustering coefficient.
"""
nodes1 = np.array(node_list1, dtype=NODE)
nodes2 = np.array(node_list2, dtype=NODE)
# Get cross degree sequence
cross_degree = InteractingNetworks.cross_degree(self, nodes1, nodes2)
# Prepare normalization factor
norm = cross_degree * (cross_degree - 1) / 2.
# Initialize
cross_clustering = np.zeros_like(nodes1, dtype=DFIELD)
_cross_local_clustering(to_cy(self.adjacency, ADJ),
to_cy(norm, DFIELD),
nodes1, nodes2, cross_clustering)
return cross_clustering
[docs]
def cross_local_clustering_sparse(self, node_list1, node_list2):
"""
Return local cross clustering for a pair of subnetworks.
The local cross clustering coefficient C_v gives the probability, that
two randomly drawn neighbors in subnetwork 1 of node v in subnetwork 1
are also neighbors. It counts triangles having one vertex in
subnetwork 1 and two vertices in subnetwork 2.
Examples:
>>> InteractingNetworks.SmallTestNetwork().\
cross_local_clustering_sparse([0,3,5], [1,2,4])
array([ 0., 0., 0.])
>>> InteractingNetworks.SmallTestNetwork().\
cross_local_clustering_sparse([2], [1,3,4])
array([ 1.])
>>> InteractingNetworks.SmallTestNetwork().\
cross_local_clustering_sparse([3,4], [1,2])
array([ 0., 1.])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:rtype: 1D array [node index]
:return: the cross local clustering coefficient.
"""
# Get cross degree sequence
cross_degree = self.cross_degree(node_list1, node_list2)
# Get full adjacency matrix
A = self.sp_A[node_list1+node_list2, :][:, node_list1+node_list2]
# Get layer sizes
N1, N2 = len(node_list1), len(node_list2)
# Convert node lists to Numpy arrays
nodes1, nodes2 = np.array(node_list1), np.array(node_list2)
# Initialize
cross_clustering = np.zeros(N1)
# Prepare normalization factor
norm = cross_degree * (cross_degree - 1) / 2
# Calculate cross clustering from subnetwork 1 to subnetwork 2
counter = 0
for node1 in range(N1):
if not norm[node1] == 0:
# Reset counter
counter = 0
# Loop over unique pairs of nodes in subnetwork 2
for node2 in range(N1, N1+N2):
for node3 in range(N1, node2):
if (A[node1, node2] == 1 and A[node2, node3] == 1
and A[node3, node1] == 1):
counter += 1
cross_clustering[node1] = counter / norm[node1]
return cross_clustering
[docs]
def _calculate_general_closeness(self, path_lengths, internal=True):
"""
Calculate general closeness sequence for interacting networks.
:type path_lengths: 2D array [node,node] of floats
:arg path_lengths: Path lengths to use
:arg bool internal: Indicates, whether internal or cross closeness
shall be calculated.
:rtype: 1D array [index]
:return: the general closeness sequence.
"""
# Get shape of path lengths array
(N, M) = path_lengths.shape
# Set total number of nodes to be considered for calculation
if internal:
n_nodes = N
norm = M - 1
else:
n_nodes = self.N # All nodes of the whole network here!
norm = M
# Closeness has the length of the first dimension of path lengths
closeness = np.zeros(N)
# Identify unconnected pairs and save in binary array isinf
unconnected_pairs = np.isinf(path_lengths)
# Set infinite entries corresponding to unconnected pairs to maximum
# possible path length.
path_lengths[unconnected_pairs] = n_nodes - 1
# Some nodes have a distance of zero to all their
# neighbors. These nodes get zero closeness centrality.
path_length_sum = path_lengths.sum(axis=1)
# M entries have been summed over, so we also have to normalize by M
closeness[path_length_sum != 0] = \
norm / path_length_sum[path_length_sum != 0]
# Reverse changes to path_lengths
path_lengths[unconnected_pairs] = np.inf
return closeness
[docs]
def cross_closeness(self, node_list1, node_list2, link_attribute=None):
"""
Return cross closeness sequence for a pair of induced subnetworks.
Gives the inverse average geodesic distance from a node in subnetwork 1
to all nodes in subnetwork 2.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
cross_closeness([0,3,5], [1,2,4], None)
array([ 0.6 , 0.6 , 0.375])
>>> InteractingNetworks.SmallTestNetwork().\
cross_closeness([0,5], [1,2,3,4], None)
array([ 0.66666667, 0.4 ])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:arg str link_attribute: Optional name of the link attribute to be used
as the links' length. If None, links have length 1. (Default: None)
:rtype: 1D arrays [index]
:return: the cross closeness sequence.
"""
path_lengths = InteractingNetworks.cross_path_lengths(
self, node_list1, node_list2, link_attribute)
return self._calculate_general_closeness(path_lengths, internal=False)
[docs]
def internal_closeness(self, node_list, link_attribute=None):
"""
Return internal closeness sequence for an induced subnetwork.
Gives the inverse average geodesic distance from a node to all other
nodes in the same induced subnetwork.
However, the included paths will generally contain nodes from the whole
network. To avoid this, do the following:
>>> r(InteractingNetworks.SmallTestNetwork().\
subnetwork([0,3,5]).closeness(None))
array([ 1. , 0.6667, 0.6667])
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
internal_closeness([0,3,5], None)
array([ 1. , 0.66666667, 0.66666667])
>>> InteractingNetworks.SmallTestNetwork().\
internal_closeness([1,2,4], None)
array([ 1., 1., 1.])
:arg [int] node_list: list of node indices describing the subnetwork
:arg str link_attribute: Optional name of the link attribute to be used
as the links' length. If None, links have length 1. (Default: None)
:rtype: 1D array [index]
:return: the internal closeness sequence.
"""
path_lengths = self.internal_path_lengths(node_list, link_attribute)
return self._calculate_general_closeness(path_lengths, internal=True)
[docs]
def cross_betweenness(self, node_list1, node_list2):
"""
Return the cross betweenness sequence for the whole network with
respect to a pair of subnetworks.
Gives the normalized number of shortest paths only between nodes from
**two** subnetworks, in which a node :math:`i` is contained. This is
equivalent to the inter-regional / inter-group betweenness with respect
to subnetwork 1 and subnetwork 2.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
cross_betweenness([2], [3,5])
array([ 1., 1., 0., 0., 1., 0.])
>>> InteractingNetworks.SmallTestNetwork().\
cross_betweenness(range(0,6), range(0,6))
array([ 9., 3., 0., 2., 6., 0.])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:rtype: 1D arrays [node index]
:return: the cross betweenness sequence for the whole network with
respect to two subnetworks.
"""
return self.interregional_betweenness(sources=node_list1,
targets=node_list2)
[docs]
def internal_betweenness(self, node_list):
"""
Return the internal betweenness sequence for an induced subnetwork.
Gives the normalized number of shortest paths only between nodes from
subnetwork 1, in which a node :math:`i` from the whole network is
contained. This is equivalent to the inter-regional / inter-group
betweenness with respect to subnetwork 1 and subnetwork 1.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
internal_betweenness(range(0,6))
array([ 9., 3., 0., 2., 6., 0.])
:arg [int] node_list: list of node indices describing the subnetwork
:rtype: 1D array [node index]
:return: the internal betweenness sequence for layer 1.
"""
return self.interregional_betweenness(sources=node_list,
targets=node_list)
[docs]
def local_efficiency(self, node_list1, node_list2, link_attribute=None):
"""
Return the local efficiency sequence for an induced subnetwork.
**Example:**
>>> InteractingNetworks.SmallTestNetwork().\
local_efficiency([0,5], [1,2,3,4])
array([0.75 , 0.41666667])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:arg str link_attribute: Optional name of the link attribute to be used
as the links' length. If None, links have length 1. (Default: None)
:rtype: 1D arrays [index]
:return: the local efficiency sequence.
"""
path_lengths = self.cross_path_lengths(node_list1, node_list2,
link_attribute)
return np.mean(1/path_lengths, axis=1)
[docs]
def nsi_cross_degree(self, node_list1, node_list2):
"""
Return the n.s.i. cross-degree for a pair of induced subnetworks.
Gives an estimation about the quota of the whole domain of interest of
the subnetwork 2 any node in the subnetwork 1 is connected to.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
nsi_cross_degree([0,1,2],[3,4,5])
array([ 4.2, 2.6, 1.4])
>>> InteractingNetworks.SmallTestNetwork().\
nsi_cross_degree([0,2,5],[1,4])
array([ 1.4, 2.2, 0. ])
:arg [int] node_list1: list of node indices describing the subnetwork 1
:arg [int] node_list2: list of node indices describing the subnetwork 2
:rtype: 1D array [node index]
:return: the n.s.i. cross-degree for layer 1.
"""
cross_A = (self.adjacency
+ np.eye(self.N))[node_list1, :][:, node_list2]
return (cross_A * self.node_weights[node_list2]).sum(axis=1)
[docs]
def nsi_cross_mean_degree(self, node_list1, node_list2):
"""
Return the n.s.i. cross-mean degree for a pair of induced subnetworks.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
nsi_cross_mean_degree([0,1,2],[3,4,5])
2.5
>>> r(InteractingNetworks.SmallTestNetwork().\
nsi_cross_mean_degree([0,2,5],[1,4]))
0.95
:arg [int] node_list1: list of node indices describing the subnetwork 1
:arg [int] node_list2: list of node indices describing the subnetwork 2
:return float: the n.s.i. cross-mean degree for layer 1.
"""
nsi_cross = self.nsi_cross_degree(node_list1, node_list2)
node_weights = self.node_weights[node_list1]
W_i = sum(node_weights)
return sum(nsi_cross * node_weights) / W_i
[docs]
def nsi_internal_degree(self, node_list):
"""
Return the n.s.i. internal degree sequence of one induced subnetwork.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
nsi_internal_degree([0,3,5])
array([ 3.4, 1.8, 2.2])
>>> InteractingNetworks.SmallTestNetwork().\
nsi_internal_degree([0,1,3,5])
array([ 3.4, 2. , 2.6, 2.2])
:arg [int] node_list: list of node indices describing the subnetwork
:rtype: 1D array [node index]
:return: the n.s.i. internal degree sequence
"""
return self.nsi_cross_degree(node_list, node_list)
[docs]
def nsi_cross_local_clustering(self, node_list1, node_list2):
"""
Return the n.s.i. cross-local clustering coefficient for a pair of
induced subnetworks.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
nsi_cross_local_clustering([0,1,2],[3,4,5])
array([ 0.33786848, 0.50295858, 1. ])
>>> InteractingNetworks.SmallTestNetwork().\
nsi_cross_local_clustering([0,2,5],[1,4])
array([ 1., 1., 0.])
:arg [int] node_list1: list of node indices describing the subnetwork 1
:arg [int] node_list2: list of node indices describing the subnetwork 2
:rtype: 1D array [node index]
:return: the n.s.i. cross-local clustering coefficient for layer 1.
"""
nodes1 = np.array(node_list1, dtype=NODE)
nodes2 = np.array(node_list2, dtype=NODE)
nsi_cc = np.zeros(nodes1.shape, dtype=DFIELD)
_nsi_cross_local_clustering(
to_cy(self.adjacency + np.eye(self.N, dtype=ADJ), ADJ),
nsi_cc, nodes1, nodes2, to_cy(self.node_weights, DWEIGHT))
norm = self.nsi_cross_degree(nodes1, nodes2) ** 2
nsi_cc[norm != 0] = nsi_cc[norm != 0] / norm[norm != 0]
nsi_cc[norm == 0] = 0
return nsi_cc
[docs]
def nsi_cross_closeness_centrality(self, node_list1, node_list2):
"""
Return the n.s.i. cross-closeness centrality for a pair of induced
subnetworks.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
nsi_cross_closeness_centrality([0,1,2],[3,4,5])
array([ 1. , 0.56756757, 0.48837209])
>>> InteractingNetworks.SmallTestNetwork().\
nsi_cross_closeness_centrality([0,2,5],[1,4])
array([ 0.73333333, 1. , 0.42307692])
:arg [int] node_list1: list of node indices describing the subnetwork 1
:arg [int] node_list2: list of node indices describing the subnetwork 2
:rtype: 1D array [node index]
:return: the n.s.i. cross-closeness for layer 1.
"""
shortest_paths = self.path_lengths()
node_weights = self.node_weights
nsi_shortest_paths = shortest_paths + np.eye(len(shortest_paths))
nsi_shortest_paths[np.isinf(nsi_shortest_paths)] = self.N - 1
nsi_cross_paths = nsi_shortest_paths[node_list1, :][:, node_list2]
W = sum(node_weights[node_list2])
return W / np.dot(nsi_cross_paths, node_weights[node_list2])
[docs]
def nsi_internal_closeness_centrality(self, node_list):
"""
Return the n.s.i. internal closeness sequence of one induced
subnetwork.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
nsi_internal_closeness_centrality([0,3,5])
array([ 1. , 0.68 , 0.73913043])
>>> InteractingNetworks.SmallTestNetwork().\
nsi_internal_closeness_centrality([0,1,3,5])
array([ 0.84 , 0.525 , 0.72413793, 0.6 ])
:arg [int] node_list: list of node indices describing the subnetwork
:rtype: 1D array [node index]
:return: the n.s.i. internal closeness sequence
"""
return self.nsi_cross_closeness_centrality(node_list, node_list)
[docs]
def nsi_cross_global_clustering(self, node_list1, node_list2):
"""
Return the n.s.i. cross-global clustering coefficient for an induced
subnetwork 1 with regard to a second induced subnetwork 2.
**Examples:**
>>> r(InteractingNetworks.SmallTestNetwork().\
nsi_cross_global_clustering([0,1,2],[3,4,5]))
0.6688
:arg [int] node_list1: list of node indices describing the subnetwork 1
:arg [int] node_list2: list of node indices describing the subnetwork 2
:return float: the n.s.i. cross-global clustering coefficient for the
subnetwork 1 with regard to subnetwork 2.
"""
nsi_cc = self.nsi_cross_local_clustering(node_list1, node_list2)
node_weights = self.node_weights[node_list1]
return sum(node_weights * nsi_cc) / sum(node_weights)
[docs]
def nsi_internal_local_clustering(self, node_list):
"""
Return the n.s.i. internal cross-local clustering coefficient for an
induced subnetwork.
**Examples:**
>>> InteractingNetworks.SmallTestNetwork().\
nsi_internal_local_clustering([1,2,3,5])
array([ 0.73333333, 1. , 1. , 1. ])
>>> InteractingNetworks.SmallTestNetwork().\
nsi_internal_local_clustering([0,2,4])
array([ 1. , 1. , 0.86666667])
:arg [int] node_list: list of node indices describing the subnetwork
:rtype: 1D numpy array [node_index]
:return: the n.s.i. internal-local clustering coefficient for all nodes
within the induced subnetwork
"""
return self.nsi_cross_local_clustering(node_list, node_list)
[docs]
def nsi_cross_betweenness(self, node_list1, node_list2):
"""
Return the n.s.i. cross betweenness sequence for the whole network with
respect to a pair of subnetworks.
**Examples:**
>>> r(InteractingNetworks.SmallTestNetwork().\
nsi_cross_betweenness([0,4,5],[1,3]))
array([ 6.5333, 1.2 , 0. ,
0.6769, 0.6769, 0. ])
>>> r(InteractingNetworks.SmallTestNetwork().\
nsi_cross_betweenness([0,1],[2,3,4,5]))
array([ 2.1333, 0. , 0. ,
0.4923, 0.9209, 0. ])
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:rtype: 1D arrays [node index]
:return: the n.s.i. cross betweenness sequence for the whole network
with respect to two subnetworks.
"""
return self.nsi_interregional_betweenness(sources=node_list1,
targets=node_list2)
[docs]
def nsi_cross_edge_density(self, node_list1, node_list2):
"""
Return the n.s.i. density of edges between two subnetworks.
**Examples:**
>>> r(InteractingNetworks.SmallTestNetwork().\
nsi_cross_edge_density([1,2,3],[0,5]))
0.1091
>>> r(InteractingNetworks.SmallTestNetwork().\
nsi_cross_edge_density([0],[1,4,5]))
0.7895
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:return float: the n.s.i. cross density of edges between two
subnetworks 1 and 2.
"""
W_j = sum(self.node_weights[node_list2])
return self.nsi_cross_mean_degree(node_list1, node_list2) / W_j
[docs]
def nsi_cross_transitivity(self, node_list1, node_list2):
"""
Return n.s.i. cross-transitivity for a pair of subnetworks.
**Examples:**
>>> r(InteractingNetworks.SmallTestNetwork().\
nsi_cross_transitivity([1,2],[0,3,4,5]))
0.6352
>>> InteractingNetworks.SmallTestNetwork().\
nsi_cross_transitivity([0,2,3],[1])
1.0
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:return float: the n.s.i. cross transitivity for a pair of subnetworks
1 and 2.
"""
return _nsi_cross_transitivity(
to_cy(self.adjacency + np.eye(self.N, dtype=ADJ), ADJ),
np.array(node_list1, dtype=NODE),
np.array(node_list2, dtype=NODE),
to_cy(self.node_weights, DWEIGHT))
[docs]
def nsi_cross_average_path_length(self, node_list1, node_list2):
"""
Return n.s.i. cross average path length between two induced
subnetworks.
**Examples:**
>>> net = InteractingNetworks.SmallTestNetwork()
>>> r(net.nsi_cross_average_path_length([0,5],[1,2,4]))
3.3306
>>> r(net.nsi_cross_average_path_length([1,3,4,5],[2]))
0.376
:arg [int] node_list1: list of node indices describing the first
subnetwork
:arg [int] node_list2: list of node indices describing the second
subnetwork
:return float: the n.s.i. cross-average path length between a pair of
subnetworks.
"""
shortest_paths = self.path_lengths()
nsi_shortest_paths = shortest_paths + np.eye(len(shortest_paths))
node_weights = self.node_weights
Wi = sum(node_weights[node_list1])
Wj = sum(node_weights[node_list1])
w_v = np.zeros([len(node_list2), len(node_list1)])
w_v[:] = node_weights[node_list1]
w_q = np.zeros([len(node_list1), len(node_list2)])
w_q[:] = node_weights[node_list2]
Wij = w_v.transpose() + w_q
nsi_cross_paths = nsi_shortest_paths[node_list1, :][:, node_list2]
Wij = Wij[np.isinf(nsi_cross_paths)].sum()
nsi_shortest_paths[np.isinf(nsi_shortest_paths)] = self.N - 1
nsi_cross_paths = nsi_shortest_paths[node_list1, :][:, node_list2]
Lij = np.sum(nsi_cross_paths*node_weights[node_list2], axis=1)
Lij = np.sum(Lij * node_weights[node_list1], axis=0)
return Lij / (Wi*Wj - Wij)