Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
|
@ -0,0 +1,19 @@
|
|||
from .betweenness import *
|
||||
from .betweenness_subset import *
|
||||
from .closeness import *
|
||||
from .subgraph_alg import *
|
||||
from .current_flow_closeness import *
|
||||
from .current_flow_betweenness import *
|
||||
from .current_flow_betweenness_subset import *
|
||||
from .degree_alg import *
|
||||
from .dispersion import *
|
||||
from .eigenvector import *
|
||||
from .group import *
|
||||
from .harmonic import *
|
||||
from .katz import *
|
||||
from .load import *
|
||||
from .reaching import *
|
||||
from .percolation import *
|
||||
from .second_order import *
|
||||
from .trophic import *
|
||||
from .voterank_alg import *
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,392 @@
|
|||
"""Betweenness centrality measures."""
|
||||
from heapq import heappush, heappop
|
||||
from itertools import count
|
||||
import warnings
|
||||
|
||||
from networkx.utils import py_random_state
|
||||
from networkx.utils.decorators import not_implemented_for
|
||||
|
||||
__all__ = ["betweenness_centrality", "edge_betweenness_centrality", "edge_betweenness"]
|
||||
|
||||
|
||||
@py_random_state(5)
|
||||
@not_implemented_for("multigraph")
|
||||
def betweenness_centrality(
|
||||
G, k=None, normalized=True, weight=None, endpoints=False, seed=None
|
||||
):
|
||||
r"""Compute the shortest-path betweenness centrality for nodes.
|
||||
|
||||
Betweenness centrality of a node $v$ is the sum of the
|
||||
fraction of all-pairs shortest paths that pass through $v$
|
||||
|
||||
.. math::
|
||||
|
||||
c_B(v) =\sum_{s,t \in V} \frac{\sigma(s, t|v)}{\sigma(s, t)}
|
||||
|
||||
where $V$ is the set of nodes, $\sigma(s, t)$ is the number of
|
||||
shortest $(s, t)$-paths, and $\sigma(s, t|v)$ is the number of
|
||||
those paths passing through some node $v$ other than $s, t$.
|
||||
If $s = t$, $\sigma(s, t) = 1$, and if $v \in {s, t}$,
|
||||
$\sigma(s, t|v) = 0$ [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
k : int, optional (default=None)
|
||||
If k is not None use k node samples to estimate betweenness.
|
||||
The value of k <= n where n is the number of nodes in the graph.
|
||||
Higher values give better approximation.
|
||||
|
||||
normalized : bool, optional
|
||||
If True the betweenness values are normalized by `2/((n-1)(n-2))`
|
||||
for graphs, and `1/((n-1)(n-2))` for directed graphs where `n`
|
||||
is the number of nodes in G.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
endpoints : bool, optional
|
||||
If True include the endpoints in the shortest path counts.
|
||||
|
||||
seed : integer, random_state, or None (default)
|
||||
Indicator of random number generation state.
|
||||
See :ref:`Randomness<randomness>`.
|
||||
Note that this is only used if k is not None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with betweenness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
edge_betweenness_centrality
|
||||
load_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm is from Ulrik Brandes [1]_.
|
||||
See [4]_ for the original first published version and [2]_ for details on
|
||||
algorithms for variations and related metrics.
|
||||
|
||||
For approximate betweenness calculations set k=#samples to use
|
||||
k nodes ("pivots") to estimate the betweenness values. For an estimate
|
||||
of the number of pivots needed see [3]_.
|
||||
|
||||
For weighted graphs the edge weights must be greater than zero.
|
||||
Zero edge weights can produce an infinite number of equal length
|
||||
paths between pairs of nodes.
|
||||
|
||||
The total number of paths between source and target is counted
|
||||
differently for directed and undirected graphs. Directed paths
|
||||
are easy to count. Undirected paths are tricky: should a path
|
||||
from "u" to "v" count as 1 undirected path or as 2 directed paths?
|
||||
|
||||
For betweenness_centrality we report the number of undirected
|
||||
paths when G is undirected.
|
||||
|
||||
For betweenness_centrality_subset the reporting is different.
|
||||
If the source and target subsets are the same, then we want
|
||||
to count undirected paths. But if the source and target subsets
|
||||
differ -- for example, if sources is {0} and targets is {1},
|
||||
then we are only counting the paths in one direction. They are
|
||||
undirected paths but we are counting them in a directed way.
|
||||
To count them as undirected paths, each should count as half a path.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Ulrik Brandes:
|
||||
A Faster Algorithm for Betweenness Centrality.
|
||||
Journal of Mathematical Sociology 25(2):163-177, 2001.
|
||||
http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf
|
||||
.. [2] Ulrik Brandes:
|
||||
On Variants of Shortest-Path Betweenness
|
||||
Centrality and their Generic Computation.
|
||||
Social Networks 30(2):136-145, 2008.
|
||||
http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf
|
||||
.. [3] Ulrik Brandes and Christian Pich:
|
||||
Centrality Estimation in Large Networks.
|
||||
International Journal of Bifurcation and Chaos 17(7):2303-2318, 2007.
|
||||
http://www.inf.uni-konstanz.de/algo/publications/bp-celn-06.pdf
|
||||
.. [4] Linton C. Freeman:
|
||||
A set of measures of centrality based on betweenness.
|
||||
Sociometry 40: 35–41, 1977
|
||||
http://moreno.ss.uci.edu/23.pdf
|
||||
"""
|
||||
betweenness = dict.fromkeys(G, 0.0) # b[v]=0 for v in G
|
||||
if k is None:
|
||||
nodes = G
|
||||
else:
|
||||
nodes = seed.sample(G.nodes(), k)
|
||||
for s in nodes:
|
||||
# single source shortest paths
|
||||
if weight is None: # use BFS
|
||||
S, P, sigma = _single_source_shortest_path_basic(G, s)
|
||||
else: # use Dijkstra's algorithm
|
||||
S, P, sigma = _single_source_dijkstra_path_basic(G, s, weight)
|
||||
# accumulation
|
||||
if endpoints:
|
||||
betweenness = _accumulate_endpoints(betweenness, S, P, sigma, s)
|
||||
else:
|
||||
betweenness = _accumulate_basic(betweenness, S, P, sigma, s)
|
||||
# rescaling
|
||||
betweenness = _rescale(
|
||||
betweenness,
|
||||
len(G),
|
||||
normalized=normalized,
|
||||
directed=G.is_directed(),
|
||||
k=k,
|
||||
endpoints=endpoints,
|
||||
)
|
||||
return betweenness
|
||||
|
||||
|
||||
@py_random_state(4)
|
||||
def edge_betweenness_centrality(G, k=None, normalized=True, weight=None, seed=None):
|
||||
r"""Compute betweenness centrality for edges.
|
||||
|
||||
Betweenness centrality of an edge $e$ is the sum of the
|
||||
fraction of all-pairs shortest paths that pass through $e$
|
||||
|
||||
.. math::
|
||||
|
||||
c_B(e) =\sum_{s,t \in V} \frac{\sigma(s, t|e)}{\sigma(s, t)}
|
||||
|
||||
where $V$ is the set of nodes, $\sigma(s, t)$ is the number of
|
||||
shortest $(s, t)$-paths, and $\sigma(s, t|e)$ is the number of
|
||||
those paths passing through edge $e$ [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
k : int, optional (default=None)
|
||||
If k is not None use k node samples to estimate betweenness.
|
||||
The value of k <= n where n is the number of nodes in the graph.
|
||||
Higher values give better approximation.
|
||||
|
||||
normalized : bool, optional
|
||||
If True the betweenness values are normalized by $2/(n(n-1))$
|
||||
for graphs, and $1/(n(n-1))$ for directed graphs where $n$
|
||||
is the number of nodes in G.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
seed : integer, random_state, or None (default)
|
||||
Indicator of random number generation state.
|
||||
See :ref:`Randomness<randomness>`.
|
||||
Note that this is only used if k is not None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
edges : dictionary
|
||||
Dictionary of edges with betweenness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality
|
||||
edge_load
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm is from Ulrik Brandes [1]_.
|
||||
|
||||
For weighted graphs the edge weights must be greater than zero.
|
||||
Zero edge weights can produce an infinite number of equal length
|
||||
paths between pairs of nodes.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] A Faster Algorithm for Betweenness Centrality. Ulrik Brandes,
|
||||
Journal of Mathematical Sociology 25(2):163-177, 2001.
|
||||
http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf
|
||||
.. [2] Ulrik Brandes: On Variants of Shortest-Path Betweenness
|
||||
Centrality and their Generic Computation.
|
||||
Social Networks 30(2):136-145, 2008.
|
||||
http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf
|
||||
"""
|
||||
betweenness = dict.fromkeys(G, 0.0) # b[v]=0 for v in G
|
||||
# b[e]=0 for e in G.edges()
|
||||
betweenness.update(dict.fromkeys(G.edges(), 0.0))
|
||||
if k is None:
|
||||
nodes = G
|
||||
else:
|
||||
nodes = seed.sample(G.nodes(), k)
|
||||
for s in nodes:
|
||||
# single source shortest paths
|
||||
if weight is None: # use BFS
|
||||
S, P, sigma = _single_source_shortest_path_basic(G, s)
|
||||
else: # use Dijkstra's algorithm
|
||||
S, P, sigma = _single_source_dijkstra_path_basic(G, s, weight)
|
||||
# accumulation
|
||||
betweenness = _accumulate_edges(betweenness, S, P, sigma, s)
|
||||
# rescaling
|
||||
for n in G: # remove nodes to only return edges
|
||||
del betweenness[n]
|
||||
betweenness = _rescale_e(
|
||||
betweenness, len(G), normalized=normalized, directed=G.is_directed()
|
||||
)
|
||||
return betweenness
|
||||
|
||||
|
||||
# obsolete name
|
||||
def edge_betweenness(G, k=None, normalized=True, weight=None, seed=None):
|
||||
warnings.warn(
|
||||
"edge_betweeness is replaced by edge_betweenness_centrality", DeprecationWarning
|
||||
)
|
||||
return edge_betweenness_centrality(G, k, normalized, weight, seed)
|
||||
|
||||
|
||||
# helpers for betweenness centrality
|
||||
|
||||
|
||||
def _single_source_shortest_path_basic(G, s):
|
||||
S = []
|
||||
P = {}
|
||||
for v in G:
|
||||
P[v] = []
|
||||
sigma = dict.fromkeys(G, 0.0) # sigma[v]=0 for v in G
|
||||
D = {}
|
||||
sigma[s] = 1.0
|
||||
D[s] = 0
|
||||
Q = [s]
|
||||
while Q: # use BFS to find shortest paths
|
||||
v = Q.pop(0)
|
||||
S.append(v)
|
||||
Dv = D[v]
|
||||
sigmav = sigma[v]
|
||||
for w in G[v]:
|
||||
if w not in D:
|
||||
Q.append(w)
|
||||
D[w] = Dv + 1
|
||||
if D[w] == Dv + 1: # this is a shortest path, count paths
|
||||
sigma[w] += sigmav
|
||||
P[w].append(v) # predecessors
|
||||
return S, P, sigma
|
||||
|
||||
|
||||
def _single_source_dijkstra_path_basic(G, s, weight):
|
||||
# modified from Eppstein
|
||||
S = []
|
||||
P = {}
|
||||
for v in G:
|
||||
P[v] = []
|
||||
sigma = dict.fromkeys(G, 0.0) # sigma[v]=0 for v in G
|
||||
D = {}
|
||||
sigma[s] = 1.0
|
||||
push = heappush
|
||||
pop = heappop
|
||||
seen = {s: 0}
|
||||
c = count()
|
||||
Q = [] # use Q as heap with (distance,node id) tuples
|
||||
push(Q, (0, next(c), s, s))
|
||||
while Q:
|
||||
(dist, _, pred, v) = pop(Q)
|
||||
if v in D:
|
||||
continue # already searched this node.
|
||||
sigma[v] += sigma[pred] # count paths
|
||||
S.append(v)
|
||||
D[v] = dist
|
||||
for w, edgedata in G[v].items():
|
||||
vw_dist = dist + edgedata.get(weight, 1)
|
||||
if w not in D and (w not in seen or vw_dist < seen[w]):
|
||||
seen[w] = vw_dist
|
||||
push(Q, (vw_dist, next(c), v, w))
|
||||
sigma[w] = 0.0
|
||||
P[w] = [v]
|
||||
elif vw_dist == seen[w]: # handle equal paths
|
||||
sigma[w] += sigma[v]
|
||||
P[w].append(v)
|
||||
return S, P, sigma
|
||||
|
||||
|
||||
def _accumulate_basic(betweenness, S, P, sigma, s):
|
||||
delta = dict.fromkeys(S, 0)
|
||||
while S:
|
||||
w = S.pop()
|
||||
coeff = (1 + delta[w]) / sigma[w]
|
||||
for v in P[w]:
|
||||
delta[v] += sigma[v] * coeff
|
||||
if w != s:
|
||||
betweenness[w] += delta[w]
|
||||
return betweenness
|
||||
|
||||
|
||||
def _accumulate_endpoints(betweenness, S, P, sigma, s):
|
||||
betweenness[s] += len(S) - 1
|
||||
delta = dict.fromkeys(S, 0)
|
||||
while S:
|
||||
w = S.pop()
|
||||
coeff = (1 + delta[w]) / sigma[w]
|
||||
for v in P[w]:
|
||||
delta[v] += sigma[v] * coeff
|
||||
if w != s:
|
||||
betweenness[w] += delta[w] + 1
|
||||
return betweenness
|
||||
|
||||
|
||||
def _accumulate_edges(betweenness, S, P, sigma, s):
|
||||
delta = dict.fromkeys(S, 0)
|
||||
while S:
|
||||
w = S.pop()
|
||||
coeff = (1 + delta[w]) / sigma[w]
|
||||
for v in P[w]:
|
||||
c = sigma[v] * coeff
|
||||
if (v, w) not in betweenness:
|
||||
betweenness[(w, v)] += c
|
||||
else:
|
||||
betweenness[(v, w)] += c
|
||||
delta[v] += c
|
||||
if w != s:
|
||||
betweenness[w] += delta[w]
|
||||
return betweenness
|
||||
|
||||
|
||||
def _rescale(betweenness, n, normalized, directed=False, k=None, endpoints=False):
|
||||
if normalized:
|
||||
if endpoints:
|
||||
if n < 2:
|
||||
scale = None # no normalization
|
||||
else:
|
||||
# Scale factor should include endpoint nodes
|
||||
scale = 1 / (n * (n - 1))
|
||||
elif n <= 2:
|
||||
scale = None # no normalization b=0 for all nodes
|
||||
else:
|
||||
scale = 1 / ((n - 1) * (n - 2))
|
||||
else: # rescale by 2 for undirected graphs
|
||||
if not directed:
|
||||
scale = 0.5
|
||||
else:
|
||||
scale = None
|
||||
if scale is not None:
|
||||
if k is not None:
|
||||
scale = scale * n / k
|
||||
for v in betweenness:
|
||||
betweenness[v] *= scale
|
||||
return betweenness
|
||||
|
||||
|
||||
def _rescale_e(betweenness, n, normalized, directed=False, k=None):
|
||||
if normalized:
|
||||
if n <= 1:
|
||||
scale = None # no normalization b=0 for all nodes
|
||||
else:
|
||||
scale = 1 / (n * (n - 1))
|
||||
else: # rescale by 2 for undirected graphs
|
||||
if not directed:
|
||||
scale = 0.5
|
||||
else:
|
||||
scale = None
|
||||
if scale is not None:
|
||||
if k is not None:
|
||||
scale = scale * n / k
|
||||
for v in betweenness:
|
||||
betweenness[v] *= scale
|
||||
return betweenness
|
|
@ -0,0 +1,275 @@
|
|||
"""Betweenness centrality measures for subsets of nodes."""
|
||||
import warnings
|
||||
|
||||
from networkx.algorithms.centrality.betweenness import (
|
||||
_single_source_dijkstra_path_basic as dijkstra,
|
||||
)
|
||||
from networkx.algorithms.centrality.betweenness import (
|
||||
_single_source_shortest_path_basic as shortest_path,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"betweenness_centrality_subset",
|
||||
"betweenness_centrality_source",
|
||||
"edge_betweenness_centrality_subset",
|
||||
]
|
||||
|
||||
|
||||
def betweenness_centrality_subset(G, sources, targets, normalized=False, weight=None):
|
||||
r"""Compute betweenness centrality for a subset of nodes.
|
||||
|
||||
.. math::
|
||||
|
||||
c_B(v) =\sum_{s\in S, t \in T} \frac{\sigma(s, t|v)}{\sigma(s, t)}
|
||||
|
||||
where $S$ is the set of sources, $T$ is the set of targets,
|
||||
$\sigma(s, t)$ is the number of shortest $(s, t)$-paths,
|
||||
and $\sigma(s, t|v)$ is the number of those paths
|
||||
passing through some node $v$ other than $s, t$.
|
||||
If $s = t$, $\sigma(s, t) = 1$,
|
||||
and if $v \in {s, t}$, $\sigma(s, t|v) = 0$ [2]_.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
sources: list of nodes
|
||||
Nodes to use as sources for shortest paths in betweenness
|
||||
|
||||
targets: list of nodes
|
||||
Nodes to use as targets for shortest paths in betweenness
|
||||
|
||||
normalized : bool, optional
|
||||
If True the betweenness values are normalized by $2/((n-1)(n-2))$
|
||||
for graphs, and $1/((n-1)(n-2))$ for directed graphs where $n$
|
||||
is the number of nodes in G.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with betweenness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
edge_betweenness_centrality
|
||||
load_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The basic algorithm is from [1]_.
|
||||
|
||||
For weighted graphs the edge weights must be greater than zero.
|
||||
Zero edge weights can produce an infinite number of equal length
|
||||
paths between pairs of nodes.
|
||||
|
||||
The normalization might seem a little strange but it is
|
||||
designed to make betweenness_centrality(G) be the same as
|
||||
betweenness_centrality_subset(G,sources=G.nodes(),targets=G.nodes()).
|
||||
|
||||
The total number of paths between source and target is counted
|
||||
differently for directed and undirected graphs. Directed paths
|
||||
are easy to count. Undirected paths are tricky: should a path
|
||||
from "u" to "v" count as 1 undirected path or as 2 directed paths?
|
||||
|
||||
For betweenness_centrality we report the number of undirected
|
||||
paths when G is undirected.
|
||||
|
||||
For betweenness_centrality_subset the reporting is different.
|
||||
If the source and target subsets are the same, then we want
|
||||
to count undirected paths. But if the source and target subsets
|
||||
differ -- for example, if sources is {0} and targets is {1},
|
||||
then we are only counting the paths in one direction. They are
|
||||
undirected paths but we are counting them in a directed way.
|
||||
To count them as undirected paths, each should count as half a path.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Ulrik Brandes, A Faster Algorithm for Betweenness Centrality.
|
||||
Journal of Mathematical Sociology 25(2):163-177, 2001.
|
||||
http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf
|
||||
.. [2] Ulrik Brandes: On Variants of Shortest-Path Betweenness
|
||||
Centrality and their Generic Computation.
|
||||
Social Networks 30(2):136-145, 2008.
|
||||
http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf
|
||||
"""
|
||||
b = dict.fromkeys(G, 0.0) # b[v]=0 for v in G
|
||||
for s in sources:
|
||||
# single source shortest paths
|
||||
if weight is None: # use BFS
|
||||
S, P, sigma = shortest_path(G, s)
|
||||
else: # use Dijkstra's algorithm
|
||||
S, P, sigma = dijkstra(G, s, weight)
|
||||
b = _accumulate_subset(b, S, P, sigma, s, targets)
|
||||
b = _rescale(b, len(G), normalized=normalized, directed=G.is_directed())
|
||||
return b
|
||||
|
||||
|
||||
def edge_betweenness_centrality_subset(
|
||||
G, sources, targets, normalized=False, weight=None
|
||||
):
|
||||
r"""Compute betweenness centrality for edges for a subset of nodes.
|
||||
|
||||
.. math::
|
||||
|
||||
c_B(v) =\sum_{s\in S,t \in T} \frac{\sigma(s, t|e)}{\sigma(s, t)}
|
||||
|
||||
where $S$ is the set of sources, $T$ is the set of targets,
|
||||
$\sigma(s, t)$ is the number of shortest $(s, t)$-paths,
|
||||
and $\sigma(s, t|e)$ is the number of those paths
|
||||
passing through edge $e$ [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A networkx graph.
|
||||
|
||||
sources: list of nodes
|
||||
Nodes to use as sources for shortest paths in betweenness
|
||||
|
||||
targets: list of nodes
|
||||
Nodes to use as targets for shortest paths in betweenness
|
||||
|
||||
normalized : bool, optional
|
||||
If True the betweenness values are normalized by `2/(n(n-1))`
|
||||
for graphs, and `1/(n(n-1))` for directed graphs where `n`
|
||||
is the number of nodes in G.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
Returns
|
||||
-------
|
||||
edges : dictionary
|
||||
Dictionary of edges with Betweenness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality
|
||||
edge_load
|
||||
|
||||
Notes
|
||||
-----
|
||||
The basic algorithm is from [1]_.
|
||||
|
||||
For weighted graphs the edge weights must be greater than zero.
|
||||
Zero edge weights can produce an infinite number of equal length
|
||||
paths between pairs of nodes.
|
||||
|
||||
The normalization might seem a little strange but it is the same
|
||||
as in edge_betweenness_centrality() and is designed to make
|
||||
edge_betweenness_centrality(G) be the same as
|
||||
edge_betweenness_centrality_subset(G,sources=G.nodes(),targets=G.nodes()).
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Ulrik Brandes, A Faster Algorithm for Betweenness Centrality.
|
||||
Journal of Mathematical Sociology 25(2):163-177, 2001.
|
||||
http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf
|
||||
.. [2] Ulrik Brandes: On Variants of Shortest-Path Betweenness
|
||||
Centrality and their Generic Computation.
|
||||
Social Networks 30(2):136-145, 2008.
|
||||
http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf
|
||||
"""
|
||||
b = dict.fromkeys(G, 0.0) # b[v]=0 for v in G
|
||||
b.update(dict.fromkeys(G.edges(), 0.0)) # b[e] for e in G.edges()
|
||||
for s in sources:
|
||||
# single source shortest paths
|
||||
if weight is None: # use BFS
|
||||
S, P, sigma = shortest_path(G, s)
|
||||
else: # use Dijkstra's algorithm
|
||||
S, P, sigma = dijkstra(G, s, weight)
|
||||
b = _accumulate_edges_subset(b, S, P, sigma, s, targets)
|
||||
for n in G: # remove nodes to only return edges
|
||||
del b[n]
|
||||
b = _rescale_e(b, len(G), normalized=normalized, directed=G.is_directed())
|
||||
return b
|
||||
|
||||
|
||||
# obsolete name
|
||||
def betweenness_centrality_source(G, normalized=True, weight=None, sources=None):
|
||||
msg = "betweenness_centrality_source --> betweenness_centrality_subset"
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
if sources is None:
|
||||
sources = G.nodes()
|
||||
targets = list(G)
|
||||
return betweenness_centrality_subset(G, sources, targets, normalized, weight)
|
||||
|
||||
|
||||
def _accumulate_subset(betweenness, S, P, sigma, s, targets):
|
||||
delta = dict.fromkeys(S, 0.0)
|
||||
target_set = set(targets) - {s}
|
||||
while S:
|
||||
w = S.pop()
|
||||
if w in target_set:
|
||||
coeff = (delta[w] + 1.0) / sigma[w]
|
||||
else:
|
||||
coeff = delta[w] / sigma[w]
|
||||
for v in P[w]:
|
||||
delta[v] += sigma[v] * coeff
|
||||
if w != s:
|
||||
betweenness[w] += delta[w]
|
||||
return betweenness
|
||||
|
||||
|
||||
def _accumulate_edges_subset(betweenness, S, P, sigma, s, targets):
|
||||
"""edge_betweenness_centrality_subset helper."""
|
||||
delta = dict.fromkeys(S, 0)
|
||||
target_set = set(targets)
|
||||
while S:
|
||||
w = S.pop()
|
||||
for v in P[w]:
|
||||
if w in target_set:
|
||||
c = (sigma[v] / sigma[w]) * (1.0 + delta[w])
|
||||
else:
|
||||
c = delta[w] / len(P[w])
|
||||
if (v, w) not in betweenness:
|
||||
betweenness[(w, v)] += c
|
||||
else:
|
||||
betweenness[(v, w)] += c
|
||||
delta[v] += c
|
||||
if w != s:
|
||||
betweenness[w] += delta[w]
|
||||
return betweenness
|
||||
|
||||
|
||||
def _rescale(betweenness, n, normalized, directed=False):
|
||||
"""betweenness_centrality_subset helper."""
|
||||
if normalized:
|
||||
if n <= 2:
|
||||
scale = None # no normalization b=0 for all nodes
|
||||
else:
|
||||
scale = 1.0 / ((n - 1) * (n - 2))
|
||||
else: # rescale by 2 for undirected graphs
|
||||
if not directed:
|
||||
scale = 0.5
|
||||
else:
|
||||
scale = None
|
||||
if scale is not None:
|
||||
for v in betweenness:
|
||||
betweenness[v] *= scale
|
||||
return betweenness
|
||||
|
||||
|
||||
def _rescale_e(betweenness, n, normalized, directed=False):
|
||||
"""edge_betweenness_centrality_subset helper."""
|
||||
if normalized:
|
||||
if n <= 1:
|
||||
scale = None # no normalization b=0 for all nodes
|
||||
else:
|
||||
scale = 1.0 / (n * (n - 1))
|
||||
else: # rescale by 2 for undirected graphs
|
||||
if not directed:
|
||||
scale = 0.5
|
||||
else:
|
||||
scale = None
|
||||
if scale is not None:
|
||||
for v in betweenness:
|
||||
betweenness[v] *= scale
|
||||
return betweenness
|
|
@ -0,0 +1,271 @@
|
|||
"""
|
||||
Closeness centrality measures.
|
||||
"""
|
||||
import functools
|
||||
import networkx as nx
|
||||
from networkx.exception import NetworkXError
|
||||
from networkx.utils.decorators import not_implemented_for
|
||||
|
||||
__all__ = ["closeness_centrality", "incremental_closeness_centrality"]
|
||||
|
||||
|
||||
def closeness_centrality(G, u=None, distance=None, wf_improved=True):
|
||||
r"""Compute closeness centrality for nodes.
|
||||
|
||||
Closeness centrality [1]_ of a node `u` is the reciprocal of the
|
||||
average shortest path distance to `u` over all `n-1` reachable nodes.
|
||||
|
||||
.. math::
|
||||
|
||||
C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)},
|
||||
|
||||
where `d(v, u)` is the shortest-path distance between `v` and `u`,
|
||||
and `n` is the number of nodes that can reach `u`. Notice that the
|
||||
closeness distance function computes the incoming distance to `u`
|
||||
for directed graphs. To use outward distance, act on `G.reverse()`.
|
||||
|
||||
Notice that higher values of closeness indicate higher centrality.
|
||||
|
||||
Wasserman and Faust propose an improved formula for graphs with
|
||||
more than one connected component. The result is "a ratio of the
|
||||
fraction of actors in the group who are reachable, to the average
|
||||
distance" from the reachable actors [2]_. You might think this
|
||||
scale factor is inverted but it is not. As is, nodes from small
|
||||
components receive a smaller closeness value. Letting `N` denote
|
||||
the number of nodes in the graph,
|
||||
|
||||
.. math::
|
||||
|
||||
C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)},
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
u : node, optional
|
||||
Return only the value for node u
|
||||
|
||||
distance : edge attribute key, optional (default=None)
|
||||
Use the specified edge attribute as the edge distance in shortest
|
||||
path calculations
|
||||
|
||||
wf_improved : bool, optional (default=True)
|
||||
If True, scale by the fraction of nodes reachable. This gives the
|
||||
Wasserman and Faust improved formula. For single component graphs
|
||||
it is the same as the original formula.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with closeness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality, load_centrality, eigenvector_centrality,
|
||||
degree_centrality, incremental_closeness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The closeness centrality is normalized to `(n-1)/(|G|-1)` where
|
||||
`n` is the number of nodes in the connected part of graph
|
||||
containing the node. If the graph is not completely connected,
|
||||
this algorithm computes the closeness centrality for each
|
||||
connected part separately scaled by that parts size.
|
||||
|
||||
If the 'distance' keyword is set to an edge attribute key then the
|
||||
shortest-path length will be computed using Dijkstra's algorithm with
|
||||
that edge attribute as the edge weight.
|
||||
|
||||
The closeness centrality uses *inward* distance to a node, not outward.
|
||||
If you want to use outword distances apply the function to `G.reverse()`
|
||||
|
||||
In NetworkX 2.2 and earlier a bug caused Dijkstra's algorithm to use the
|
||||
outward distance rather than the inward distance. If you use a 'distance'
|
||||
keyword and a DiGraph, your results will change between v2.2 and v2.3.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Linton C. Freeman: Centrality in networks: I.
|
||||
Conceptual clarification. Social Networks 1:215-239, 1979.
|
||||
http://leonidzhukov.ru/hse/2013/socialnetworks/papers/freeman79-centrality.pdf
|
||||
.. [2] pg. 201 of Wasserman, S. and Faust, K.,
|
||||
Social Network Analysis: Methods and Applications, 1994,
|
||||
Cambridge University Press.
|
||||
"""
|
||||
if G.is_directed():
|
||||
G = G.reverse() # create a reversed graph view
|
||||
|
||||
if distance is not None:
|
||||
# use Dijkstra's algorithm with specified attribute as edge weight
|
||||
path_length = functools.partial(
|
||||
nx.single_source_dijkstra_path_length, weight=distance
|
||||
)
|
||||
else:
|
||||
path_length = nx.single_source_shortest_path_length
|
||||
|
||||
if u is None:
|
||||
nodes = G.nodes
|
||||
else:
|
||||
nodes = [u]
|
||||
closeness_centrality = {}
|
||||
for n in nodes:
|
||||
sp = path_length(G, n)
|
||||
totsp = sum(sp.values())
|
||||
len_G = len(G)
|
||||
_closeness_centrality = 0.0
|
||||
if totsp > 0.0 and len_G > 1:
|
||||
_closeness_centrality = (len(sp) - 1.0) / totsp
|
||||
# normalize to number of nodes-1 in connected part
|
||||
if wf_improved:
|
||||
s = (len(sp) - 1.0) / (len_G - 1)
|
||||
_closeness_centrality *= s
|
||||
closeness_centrality[n] = _closeness_centrality
|
||||
if u is not None:
|
||||
return closeness_centrality[u]
|
||||
else:
|
||||
return closeness_centrality
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def incremental_closeness_centrality(
|
||||
G, edge, prev_cc=None, insertion=True, wf_improved=True
|
||||
):
|
||||
r"""Incremental closeness centrality for nodes.
|
||||
|
||||
Compute closeness centrality for nodes using level-based work filtering
|
||||
as described in Incremental Algorithms for Closeness Centrality by Sariyuce et al.
|
||||
|
||||
Level-based work filtering detects unnecessary updates to the closeness
|
||||
centrality and filters them out.
|
||||
|
||||
---
|
||||
From "Incremental Algorithms for Closeness Centrality":
|
||||
|
||||
Theorem 1: Let :math:`G = (V, E)` be a graph and u and v be two vertices in V
|
||||
such that there is no edge (u, v) in E. Let :math:`G' = (V, E \cup uv)`
|
||||
Then :math:`cc[s] = cc'[s]` if and only if :math:`\left|dG(s, u) - dG(s, v)\right| \leq 1`.
|
||||
|
||||
Where :math:`dG(u, v)` denotes the length of the shortest path between
|
||||
two vertices u, v in a graph G, cc[s] is the closeness centrality for a
|
||||
vertex s in V, and cc'[s] is the closeness centrality for a
|
||||
vertex s in V, with the (u, v) edge added.
|
||||
---
|
||||
|
||||
We use Theorem 1 to filter out updates when adding or removing an edge.
|
||||
When adding an edge (u, v), we compute the shortest path lengths from all
|
||||
other nodes to u and to v before the node is added. When removing an edge,
|
||||
we compute the shortest path lengths after the edge is removed. Then we
|
||||
apply Theorem 1 to use previously computed closeness centrality for nodes
|
||||
where :math:`\left|dG(s, u) - dG(s, v)\right| \leq 1`. This works only for
|
||||
undirected, unweighted graphs; the distance argument is not supported.
|
||||
|
||||
Closeness centrality [1]_ of a node `u` is the reciprocal of the
|
||||
sum of the shortest path distances from `u` to all `n-1` other nodes.
|
||||
Since the sum of distances depends on the number of nodes in the
|
||||
graph, closeness is normalized by the sum of minimum possible
|
||||
distances `n-1`.
|
||||
|
||||
.. math::
|
||||
|
||||
C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)},
|
||||
|
||||
where `d(v, u)` is the shortest-path distance between `v` and `u`,
|
||||
and `n` is the number of nodes in the graph.
|
||||
|
||||
Notice that higher values of closeness indicate higher centrality.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
edge : tuple
|
||||
The modified edge (u, v) in the graph.
|
||||
|
||||
prev_cc : dictionary
|
||||
The previous closeness centrality for all nodes in the graph.
|
||||
|
||||
insertion : bool, optional
|
||||
If True (default) the edge was inserted, otherwise it was deleted from the graph.
|
||||
|
||||
wf_improved : bool, optional (default=True)
|
||||
If True, scale by the fraction of nodes reachable. This gives the
|
||||
Wasserman and Faust improved formula. For single component graphs
|
||||
it is the same as the original formula.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with closeness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality, load_centrality, eigenvector_centrality,
|
||||
degree_centrality, closeness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The closeness centrality is normalized to `(n-1)/(|G|-1)` where
|
||||
`n` is the number of nodes in the connected part of graph
|
||||
containing the node. If the graph is not completely connected,
|
||||
this algorithm computes the closeness centrality for each
|
||||
connected part separately.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Freeman, L.C., 1979. Centrality in networks: I.
|
||||
Conceptual clarification. Social Networks 1, 215--239.
|
||||
http://www.soc.ucsb.edu/faculty/friedkin/Syllabi/Soc146/Freeman78.PDF
|
||||
.. [2] Sariyuce, A.E. ; Kaya, K. ; Saule, E. ; Catalyiirek, U.V. Incremental
|
||||
Algorithms for Closeness Centrality. 2013 IEEE International Conference on Big Data
|
||||
http://sariyuce.com/papers/bigdata13.pdf
|
||||
"""
|
||||
if prev_cc is not None and set(prev_cc.keys()) != set(G.nodes()):
|
||||
raise NetworkXError("prev_cc and G do not have the same nodes")
|
||||
|
||||
# Unpack edge
|
||||
(u, v) = edge
|
||||
path_length = nx.single_source_shortest_path_length
|
||||
|
||||
if insertion:
|
||||
# For edge insertion, we want shortest paths before the edge is inserted
|
||||
du = path_length(G, u)
|
||||
dv = path_length(G, v)
|
||||
|
||||
G.add_edge(u, v)
|
||||
else:
|
||||
G.remove_edge(u, v)
|
||||
|
||||
# For edge removal, we want shortest paths after the edge is removed
|
||||
du = path_length(G, u)
|
||||
dv = path_length(G, v)
|
||||
|
||||
if prev_cc is None:
|
||||
return nx.closeness_centrality(G)
|
||||
|
||||
nodes = G.nodes()
|
||||
closeness_centrality = {}
|
||||
for n in nodes:
|
||||
if n in du and n in dv and abs(du[n] - dv[n]) <= 1:
|
||||
closeness_centrality[n] = prev_cc[n]
|
||||
else:
|
||||
sp = path_length(G, n)
|
||||
totsp = sum(sp.values())
|
||||
len_G = len(G)
|
||||
_closeness_centrality = 0.0
|
||||
if totsp > 0.0 and len_G > 1:
|
||||
_closeness_centrality = (len(sp) - 1.0) / totsp
|
||||
# normalize to number of nodes-1 in connected part
|
||||
if wf_improved:
|
||||
s = (len(sp) - 1.0) / (len_G - 1)
|
||||
_closeness_centrality *= s
|
||||
closeness_centrality[n] = _closeness_centrality
|
||||
|
||||
# Leave the graph as we found it
|
||||
if insertion:
|
||||
G.remove_edge(u, v)
|
||||
else:
|
||||
G.add_edge(u, v)
|
||||
|
||||
return closeness_centrality
|
|
@ -0,0 +1,342 @@
|
|||
"""Current-flow betweenness centrality measures."""
|
||||
import networkx as nx
|
||||
from networkx.algorithms.centrality.flow_matrix import (
|
||||
CGInverseLaplacian,
|
||||
flow_matrix_row,
|
||||
FullInverseLaplacian,
|
||||
laplacian_sparse_matrix,
|
||||
SuperLUInverseLaplacian,
|
||||
)
|
||||
from networkx.utils import (
|
||||
not_implemented_for,
|
||||
reverse_cuthill_mckee_ordering,
|
||||
py_random_state,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"current_flow_betweenness_centrality",
|
||||
"approximate_current_flow_betweenness_centrality",
|
||||
"edge_current_flow_betweenness_centrality",
|
||||
]
|
||||
|
||||
|
||||
@py_random_state(7)
|
||||
@not_implemented_for("directed")
|
||||
def approximate_current_flow_betweenness_centrality(
|
||||
G,
|
||||
normalized=True,
|
||||
weight=None,
|
||||
dtype=float,
|
||||
solver="full",
|
||||
epsilon=0.5,
|
||||
kmax=10000,
|
||||
seed=None,
|
||||
):
|
||||
r"""Compute the approximate current-flow betweenness centrality for nodes.
|
||||
|
||||
Approximates the current-flow betweenness centrality within absolute
|
||||
error of epsilon with high probability [1]_.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
normalized : bool, optional (default=True)
|
||||
If True the betweenness values are normalized by 2/[(n-1)(n-2)] where
|
||||
n is the number of nodes in G.
|
||||
|
||||
weight : string or None, optional (default=None)
|
||||
Key for edge data used as the edge weight.
|
||||
If None, then use 1 as each edge weight.
|
||||
|
||||
dtype : data type (float)
|
||||
Default data type for internal matrices.
|
||||
Set to np.float32 for lower memory consumption.
|
||||
|
||||
solver : string (default='full')
|
||||
Type of linear solver to use for computing the flow matrix.
|
||||
Options are "full" (uses most memory), "lu" (recommended), and
|
||||
"cg" (uses least memory).
|
||||
|
||||
epsilon: float
|
||||
Absolute error tolerance.
|
||||
|
||||
kmax: int
|
||||
Maximum number of sample node pairs to use for approximation.
|
||||
|
||||
seed : integer, random_state, or None (default)
|
||||
Indicator of random number generation state.
|
||||
See :ref:`Randomness<randomness>`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with betweenness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
current_flow_betweenness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The running time is $O((1/\epsilon^2)m{\sqrt k} \log n)$
|
||||
and the space required is $O(m)$ for $n$ nodes and $m$ edges.
|
||||
|
||||
If the edges have a 'weight' attribute they will be used as
|
||||
weights in this algorithm. Unspecified weights are set to 1.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Ulrik Brandes and Daniel Fleischer:
|
||||
Centrality Measures Based on Current Flow.
|
||||
Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05).
|
||||
LNCS 3404, pp. 533-544. Springer-Verlag, 2005.
|
||||
http://algo.uni-konstanz.de/publications/bf-cmbcf-05.pdf
|
||||
"""
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"current_flow_betweenness_centrality requires NumPy " "http://numpy.org/"
|
||||
) from e
|
||||
if not nx.is_connected(G):
|
||||
raise nx.NetworkXError("Graph not connected.")
|
||||
solvername = {
|
||||
"full": FullInverseLaplacian,
|
||||
"lu": SuperLUInverseLaplacian,
|
||||
"cg": CGInverseLaplacian,
|
||||
}
|
||||
n = G.number_of_nodes()
|
||||
ordering = list(reverse_cuthill_mckee_ordering(G))
|
||||
# make a copy with integer labels according to rcm ordering
|
||||
# this could be done without a copy if we really wanted to
|
||||
H = nx.relabel_nodes(G, dict(zip(ordering, range(n))))
|
||||
L = laplacian_sparse_matrix(
|
||||
H, nodelist=range(n), weight=weight, dtype=dtype, format="csc"
|
||||
)
|
||||
C = solvername[solver](L, dtype=dtype) # initialize solver
|
||||
betweenness = dict.fromkeys(H, 0.0)
|
||||
nb = (n - 1.0) * (n - 2.0) # normalization factor
|
||||
cstar = n * (n - 1) / nb
|
||||
l = 1 # parameter in approximation, adjustable
|
||||
k = l * int(np.ceil((cstar / epsilon) ** 2 * np.log(n)))
|
||||
if k > kmax:
|
||||
msg = f"Number random pairs k>kmax ({k}>{kmax}) "
|
||||
raise nx.NetworkXError(msg, "Increase kmax or epsilon")
|
||||
cstar2k = cstar / (2 * k)
|
||||
for i in range(k):
|
||||
s, t = seed.sample(range(n), 2)
|
||||
b = np.zeros(n, dtype=dtype)
|
||||
b[s] = 1
|
||||
b[t] = -1
|
||||
p = C.solve(b)
|
||||
for v in H:
|
||||
if v == s or v == t:
|
||||
continue
|
||||
for nbr in H[v]:
|
||||
w = H[v][nbr].get(weight, 1.0)
|
||||
betweenness[v] += w * np.abs(p[v] - p[nbr]) * cstar2k
|
||||
if normalized:
|
||||
factor = 1.0
|
||||
else:
|
||||
factor = nb / 2.0
|
||||
# remap to original node names and "unnormalize" if required
|
||||
return {ordering[k]: float(v * factor) for k, v in betweenness.items()}
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def current_flow_betweenness_centrality(
|
||||
G, normalized=True, weight=None, dtype=float, solver="full"
|
||||
):
|
||||
r"""Compute current-flow betweenness centrality for nodes.
|
||||
|
||||
Current-flow betweenness centrality uses an electrical current
|
||||
model for information spreading in contrast to betweenness
|
||||
centrality which uses shortest paths.
|
||||
|
||||
Current-flow betweenness centrality is also known as
|
||||
random-walk betweenness centrality [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
normalized : bool, optional (default=True)
|
||||
If True the betweenness values are normalized by 2/[(n-1)(n-2)] where
|
||||
n is the number of nodes in G.
|
||||
|
||||
weight : string or None, optional (default=None)
|
||||
Key for edge data used as the edge weight.
|
||||
If None, then use 1 as each edge weight.
|
||||
|
||||
dtype : data type (float)
|
||||
Default data type for internal matrices.
|
||||
Set to np.float32 for lower memory consumption.
|
||||
|
||||
solver : string (default='full')
|
||||
Type of linear solver to use for computing the flow matrix.
|
||||
Options are "full" (uses most memory), "lu" (recommended), and
|
||||
"cg" (uses least memory).
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with betweenness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
approximate_current_flow_betweenness_centrality
|
||||
betweenness_centrality
|
||||
edge_betweenness_centrality
|
||||
edge_current_flow_betweenness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
Current-flow betweenness can be computed in $O(I(n-1)+mn \log n)$
|
||||
time [1]_, where $I(n-1)$ is the time needed to compute the
|
||||
inverse Laplacian. For a full matrix this is $O(n^3)$ but using
|
||||
sparse methods you can achieve $O(nm{\sqrt k})$ where $k$ is the
|
||||
Laplacian matrix condition number.
|
||||
|
||||
The space required is $O(nw)$ where $w$ is the width of the sparse
|
||||
Laplacian matrix. Worse case is $w=n$ for $O(n^2)$.
|
||||
|
||||
If the edges have a 'weight' attribute they will be used as
|
||||
weights in this algorithm. Unspecified weights are set to 1.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Centrality Measures Based on Current Flow.
|
||||
Ulrik Brandes and Daniel Fleischer,
|
||||
Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05).
|
||||
LNCS 3404, pp. 533-544. Springer-Verlag, 2005.
|
||||
http://algo.uni-konstanz.de/publications/bf-cmbcf-05.pdf
|
||||
|
||||
.. [2] A measure of betweenness centrality based on random walks,
|
||||
M. E. J. Newman, Social Networks 27, 39-54 (2005).
|
||||
"""
|
||||
if not nx.is_connected(G):
|
||||
raise nx.NetworkXError("Graph not connected.")
|
||||
n = G.number_of_nodes()
|
||||
ordering = list(reverse_cuthill_mckee_ordering(G))
|
||||
# make a copy with integer labels according to rcm ordering
|
||||
# this could be done without a copy if we really wanted to
|
||||
H = nx.relabel_nodes(G, dict(zip(ordering, range(n))))
|
||||
betweenness = dict.fromkeys(H, 0.0) # b[v]=0 for v in H
|
||||
for row, (s, t) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver):
|
||||
pos = dict(zip(row.argsort()[::-1], range(n)))
|
||||
for i in range(n):
|
||||
betweenness[s] += (i - pos[i]) * row[i]
|
||||
betweenness[t] += (n - i - 1 - pos[i]) * row[i]
|
||||
if normalized:
|
||||
nb = (n - 1.0) * (n - 2.0) # normalization factor
|
||||
else:
|
||||
nb = 2.0
|
||||
for v in H:
|
||||
betweenness[v] = float((betweenness[v] - v) * 2.0 / nb)
|
||||
return {ordering[k]: v for k, v in betweenness.items()}
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def edge_current_flow_betweenness_centrality(
|
||||
G, normalized=True, weight=None, dtype=float, solver="full"
|
||||
):
|
||||
r"""Compute current-flow betweenness centrality for edges.
|
||||
|
||||
Current-flow betweenness centrality uses an electrical current
|
||||
model for information spreading in contrast to betweenness
|
||||
centrality which uses shortest paths.
|
||||
|
||||
Current-flow betweenness centrality is also known as
|
||||
random-walk betweenness centrality [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
normalized : bool, optional (default=True)
|
||||
If True the betweenness values are normalized by 2/[(n-1)(n-2)] where
|
||||
n is the number of nodes in G.
|
||||
|
||||
weight : string or None, optional (default=None)
|
||||
Key for edge data used as the edge weight.
|
||||
If None, then use 1 as each edge weight.
|
||||
|
||||
dtype : data type (default=float)
|
||||
Default data type for internal matrices.
|
||||
Set to np.float32 for lower memory consumption.
|
||||
|
||||
solver : string (default='full')
|
||||
Type of linear solver to use for computing the flow matrix.
|
||||
Options are "full" (uses most memory), "lu" (recommended), and
|
||||
"cg" (uses least memory).
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of edge tuples with betweenness centrality as the value.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
The algorithm does not support DiGraphs.
|
||||
If the input graph is an instance of DiGraph class, NetworkXError
|
||||
is raised.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality
|
||||
edge_betweenness_centrality
|
||||
current_flow_betweenness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
Current-flow betweenness can be computed in $O(I(n-1)+mn \log n)$
|
||||
time [1]_, where $I(n-1)$ is the time needed to compute the
|
||||
inverse Laplacian. For a full matrix this is $O(n^3)$ but using
|
||||
sparse methods you can achieve $O(nm{\sqrt k})$ where $k$ is the
|
||||
Laplacian matrix condition number.
|
||||
|
||||
The space required is $O(nw)$ where $w$ is the width of the sparse
|
||||
Laplacian matrix. Worse case is $w=n$ for $O(n^2)$.
|
||||
|
||||
If the edges have a 'weight' attribute they will be used as
|
||||
weights in this algorithm. Unspecified weights are set to 1.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Centrality Measures Based on Current Flow.
|
||||
Ulrik Brandes and Daniel Fleischer,
|
||||
Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05).
|
||||
LNCS 3404, pp. 533-544. Springer-Verlag, 2005.
|
||||
http://algo.uni-konstanz.de/publications/bf-cmbcf-05.pdf
|
||||
|
||||
.. [2] A measure of betweenness centrality based on random walks,
|
||||
M. E. J. Newman, Social Networks 27, 39-54 (2005).
|
||||
"""
|
||||
from networkx.utils import reverse_cuthill_mckee_ordering
|
||||
|
||||
if not nx.is_connected(G):
|
||||
raise nx.NetworkXError("Graph not connected.")
|
||||
n = G.number_of_nodes()
|
||||
ordering = list(reverse_cuthill_mckee_ordering(G))
|
||||
# make a copy with integer labels according to rcm ordering
|
||||
# this could be done without a copy if we really wanted to
|
||||
H = nx.relabel_nodes(G, dict(zip(ordering, range(n))))
|
||||
edges = (tuple(sorted((u, v))) for u, v in H.edges())
|
||||
betweenness = dict.fromkeys(edges, 0.0)
|
||||
if normalized:
|
||||
nb = (n - 1.0) * (n - 2.0) # normalization factor
|
||||
else:
|
||||
nb = 2.0
|
||||
for row, (e) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver):
|
||||
pos = dict(zip(row.argsort()[::-1], range(1, n + 1)))
|
||||
for i in range(n):
|
||||
betweenness[e] += (i + 1 - pos[i]) * row[i]
|
||||
betweenness[e] += (n - i - pos[i]) * row[i]
|
||||
betweenness[e] /= nb
|
||||
return {(ordering[s], ordering[t]): float(v) for (s, t), v in betweenness.items()}
|
|
@ -0,0 +1,228 @@
|
|||
"""Current-flow betweenness centrality measures for subsets of nodes."""
|
||||
import networkx as nx
|
||||
from networkx.algorithms.centrality.flow_matrix import flow_matrix_row
|
||||
from networkx.utils import not_implemented_for, reverse_cuthill_mckee_ordering
|
||||
|
||||
__all__ = [
|
||||
"current_flow_betweenness_centrality_subset",
|
||||
"edge_current_flow_betweenness_centrality_subset",
|
||||
]
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def current_flow_betweenness_centrality_subset(
|
||||
G, sources, targets, normalized=True, weight=None, dtype=float, solver="lu"
|
||||
):
|
||||
r"""Compute current-flow betweenness centrality for subsets of nodes.
|
||||
|
||||
Current-flow betweenness centrality uses an electrical current
|
||||
model for information spreading in contrast to betweenness
|
||||
centrality which uses shortest paths.
|
||||
|
||||
Current-flow betweenness centrality is also known as
|
||||
random-walk betweenness centrality [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
sources: list of nodes
|
||||
Nodes to use as sources for current
|
||||
|
||||
targets: list of nodes
|
||||
Nodes to use as sinks for current
|
||||
|
||||
normalized : bool, optional (default=True)
|
||||
If True the betweenness values are normalized by b=b/(n-1)(n-2) where
|
||||
n is the number of nodes in G.
|
||||
|
||||
weight : string or None, optional (default=None)
|
||||
Key for edge data used as the edge weight.
|
||||
If None, then use 1 as each edge weight.
|
||||
|
||||
dtype: data type (float)
|
||||
Default data type for internal matrices.
|
||||
Set to np.float32 for lower memory consumption.
|
||||
|
||||
solver: string (default='lu')
|
||||
Type of linear solver to use for computing the flow matrix.
|
||||
Options are "full" (uses most memory), "lu" (recommended), and
|
||||
"cg" (uses least memory).
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with betweenness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
approximate_current_flow_betweenness_centrality
|
||||
betweenness_centrality
|
||||
edge_betweenness_centrality
|
||||
edge_current_flow_betweenness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
Current-flow betweenness can be computed in $O(I(n-1)+mn \log n)$
|
||||
time [1]_, where $I(n-1)$ is the time needed to compute the
|
||||
inverse Laplacian. For a full matrix this is $O(n^3)$ but using
|
||||
sparse methods you can achieve $O(nm{\sqrt k})$ where $k$ is the
|
||||
Laplacian matrix condition number.
|
||||
|
||||
The space required is $O(nw)$ where $w$ is the width of the sparse
|
||||
Laplacian matrix. Worse case is $w=n$ for $O(n^2)$.
|
||||
|
||||
If the edges have a 'weight' attribute they will be used as
|
||||
weights in this algorithm. Unspecified weights are set to 1.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Centrality Measures Based on Current Flow.
|
||||
Ulrik Brandes and Daniel Fleischer,
|
||||
Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05).
|
||||
LNCS 3404, pp. 533-544. Springer-Verlag, 2005.
|
||||
http://algo.uni-konstanz.de/publications/bf-cmbcf-05.pdf
|
||||
|
||||
.. [2] A measure of betweenness centrality based on random walks,
|
||||
M. E. J. Newman, Social Networks 27, 39-54 (2005).
|
||||
"""
|
||||
from networkx.utils import reverse_cuthill_mckee_ordering
|
||||
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"current_flow_betweenness_centrality requires NumPy ", "http://numpy.org/"
|
||||
) from e
|
||||
if not nx.is_connected(G):
|
||||
raise nx.NetworkXError("Graph not connected.")
|
||||
n = G.number_of_nodes()
|
||||
ordering = list(reverse_cuthill_mckee_ordering(G))
|
||||
# make a copy with integer labels according to rcm ordering
|
||||
# this could be done without a copy if we really wanted to
|
||||
mapping = dict(zip(ordering, range(n)))
|
||||
H = nx.relabel_nodes(G, mapping)
|
||||
betweenness = dict.fromkeys(H, 0.0) # b[v]=0 for v in H
|
||||
for row, (s, t) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver):
|
||||
for ss in sources:
|
||||
i = mapping[ss]
|
||||
for tt in targets:
|
||||
j = mapping[tt]
|
||||
betweenness[s] += 0.5 * np.abs(row[i] - row[j])
|
||||
betweenness[t] += 0.5 * np.abs(row[i] - row[j])
|
||||
if normalized:
|
||||
nb = (n - 1.0) * (n - 2.0) # normalization factor
|
||||
else:
|
||||
nb = 2.0
|
||||
for v in H:
|
||||
betweenness[v] = betweenness[v] / nb + 1.0 / (2 - n)
|
||||
return {ordering[k]: v for k, v in betweenness.items()}
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def edge_current_flow_betweenness_centrality_subset(
|
||||
G, sources, targets, normalized=True, weight=None, dtype=float, solver="lu"
|
||||
):
|
||||
r"""Compute current-flow betweenness centrality for edges using subsets
|
||||
of nodes.
|
||||
|
||||
Current-flow betweenness centrality uses an electrical current
|
||||
model for information spreading in contrast to betweenness
|
||||
centrality which uses shortest paths.
|
||||
|
||||
Current-flow betweenness centrality is also known as
|
||||
random-walk betweenness centrality [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
sources: list of nodes
|
||||
Nodes to use as sources for current
|
||||
|
||||
targets: list of nodes
|
||||
Nodes to use as sinks for current
|
||||
|
||||
normalized : bool, optional (default=True)
|
||||
If True the betweenness values are normalized by b=b/(n-1)(n-2) where
|
||||
n is the number of nodes in G.
|
||||
|
||||
weight : string or None, optional (default=None)
|
||||
Key for edge data used as the edge weight.
|
||||
If None, then use 1 as each edge weight.
|
||||
|
||||
dtype: data type (float)
|
||||
Default data type for internal matrices.
|
||||
Set to np.float32 for lower memory consumption.
|
||||
|
||||
solver: string (default='lu')
|
||||
Type of linear solver to use for computing the flow matrix.
|
||||
Options are "full" (uses most memory), "lu" (recommended), and
|
||||
"cg" (uses least memory).
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dict
|
||||
Dictionary of edge tuples with betweenness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality
|
||||
edge_betweenness_centrality
|
||||
current_flow_betweenness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
Current-flow betweenness can be computed in $O(I(n-1)+mn \log n)$
|
||||
time [1]_, where $I(n-1)$ is the time needed to compute the
|
||||
inverse Laplacian. For a full matrix this is $O(n^3)$ but using
|
||||
sparse methods you can achieve $O(nm{\sqrt k})$ where $k$ is the
|
||||
Laplacian matrix condition number.
|
||||
|
||||
The space required is $O(nw)$ where $w$ is the width of the sparse
|
||||
Laplacian matrix. Worse case is $w=n$ for $O(n^2)$.
|
||||
|
||||
If the edges have a 'weight' attribute they will be used as
|
||||
weights in this algorithm. Unspecified weights are set to 1.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Centrality Measures Based on Current Flow.
|
||||
Ulrik Brandes and Daniel Fleischer,
|
||||
Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05).
|
||||
LNCS 3404, pp. 533-544. Springer-Verlag, 2005.
|
||||
http://algo.uni-konstanz.de/publications/bf-cmbcf-05.pdf
|
||||
|
||||
.. [2] A measure of betweenness centrality based on random walks,
|
||||
M. E. J. Newman, Social Networks 27, 39-54 (2005).
|
||||
"""
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"current_flow_betweenness_centrality requires NumPy " "http://numpy.org/"
|
||||
) from e
|
||||
if not nx.is_connected(G):
|
||||
raise nx.NetworkXError("Graph not connected.")
|
||||
n = G.number_of_nodes()
|
||||
ordering = list(reverse_cuthill_mckee_ordering(G))
|
||||
# make a copy with integer labels according to rcm ordering
|
||||
# this could be done without a copy if we really wanted to
|
||||
mapping = dict(zip(ordering, range(n)))
|
||||
H = nx.relabel_nodes(G, mapping)
|
||||
edges = (tuple(sorted((u, v))) for u, v in H.edges())
|
||||
betweenness = dict.fromkeys(edges, 0.0)
|
||||
if normalized:
|
||||
nb = (n - 1.0) * (n - 2.0) # normalization factor
|
||||
else:
|
||||
nb = 2.0
|
||||
for row, (e) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver):
|
||||
for ss in sources:
|
||||
i = mapping[ss]
|
||||
for tt in targets:
|
||||
j = mapping[tt]
|
||||
betweenness[e] += 0.5 * np.abs(row[i] - row[j])
|
||||
betweenness[e] /= nb
|
||||
return {(ordering[s], ordering[t]): v for (s, t), v in betweenness.items()}
|
|
@ -0,0 +1,97 @@
|
|||
"""Current-flow closeness centrality measures."""
|
||||
import networkx as nx
|
||||
|
||||
from networkx.utils import not_implemented_for, reverse_cuthill_mckee_ordering
|
||||
from networkx.algorithms.centrality.flow_matrix import (
|
||||
CGInverseLaplacian,
|
||||
FullInverseLaplacian,
|
||||
laplacian_sparse_matrix,
|
||||
SuperLUInverseLaplacian,
|
||||
)
|
||||
|
||||
__all__ = ["current_flow_closeness_centrality", "information_centrality"]
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def current_flow_closeness_centrality(G, weight=None, dtype=float, solver="lu"):
|
||||
"""Compute current-flow closeness centrality for nodes.
|
||||
|
||||
Current-flow closeness centrality is variant of closeness
|
||||
centrality based on effective resistance between nodes in
|
||||
a network. This metric is also known as information centrality.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
dtype: data type (default=float)
|
||||
Default data type for internal matrices.
|
||||
Set to np.float32 for lower memory consumption.
|
||||
|
||||
solver: string (default='lu')
|
||||
Type of linear solver to use for computing the flow matrix.
|
||||
Options are "full" (uses most memory), "lu" (recommended), and
|
||||
"cg" (uses least memory).
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with current flow closeness centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
closeness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm is from Brandes [1]_.
|
||||
|
||||
See also [2]_ for the original definition of information centrality.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Ulrik Brandes and Daniel Fleischer,
|
||||
Centrality Measures Based on Current Flow.
|
||||
Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05).
|
||||
LNCS 3404, pp. 533-544. Springer-Verlag, 2005.
|
||||
http://algo.uni-konstanz.de/publications/bf-cmbcf-05.pdf
|
||||
|
||||
.. [2] Karen Stephenson and Marvin Zelen:
|
||||
Rethinking centrality: Methods and examples.
|
||||
Social Networks 11(1):1-37, 1989.
|
||||
https://doi.org/10.1016/0378-8733(89)90016-6
|
||||
"""
|
||||
if not nx.is_connected(G):
|
||||
raise nx.NetworkXError("Graph not connected.")
|
||||
solvername = {
|
||||
"full": FullInverseLaplacian,
|
||||
"lu": SuperLUInverseLaplacian,
|
||||
"cg": CGInverseLaplacian,
|
||||
}
|
||||
n = G.number_of_nodes()
|
||||
ordering = list(reverse_cuthill_mckee_ordering(G))
|
||||
# make a copy with integer labels according to rcm ordering
|
||||
# this could be done without a copy if we really wanted to
|
||||
H = nx.relabel_nodes(G, dict(zip(ordering, range(n))))
|
||||
betweenness = dict.fromkeys(H, 0.0) # b[v]=0 for v in H
|
||||
n = H.number_of_nodes()
|
||||
L = laplacian_sparse_matrix(
|
||||
H, nodelist=range(n), weight=weight, dtype=dtype, format="csc"
|
||||
)
|
||||
C2 = solvername[solver](L, width=1, dtype=dtype) # initialize solver
|
||||
for v in H:
|
||||
col = C2.get_row(v)
|
||||
for w in H:
|
||||
betweenness[v] += col[v] - 2 * col[w]
|
||||
betweenness[w] += col[v]
|
||||
for v in H:
|
||||
betweenness[v] = 1.0 / (betweenness[v])
|
||||
return {ordering[k]: float(v) for k, v in betweenness.items()}
|
||||
|
||||
|
||||
information_centrality = current_flow_closeness_centrality
|
|
@ -0,0 +1,127 @@
|
|||
"""Degree centrality measures."""
|
||||
from networkx.utils.decorators import not_implemented_for
|
||||
|
||||
__all__ = ["degree_centrality", "in_degree_centrality", "out_degree_centrality"]
|
||||
|
||||
|
||||
def degree_centrality(G):
|
||||
"""Compute the degree centrality for nodes.
|
||||
|
||||
The degree centrality for a node v is the fraction of nodes it
|
||||
is connected to.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A networkx graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with degree centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality, load_centrality, eigenvector_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The degree centrality values are normalized by dividing by the maximum
|
||||
possible degree in a simple graph n-1 where n is the number of nodes in G.
|
||||
|
||||
For multigraphs or graphs with self loops the maximum degree might
|
||||
be higher than n-1 and values of degree centrality greater than 1
|
||||
are possible.
|
||||
"""
|
||||
if len(G) <= 1:
|
||||
return {n: 1 for n in G}
|
||||
|
||||
s = 1.0 / (len(G) - 1.0)
|
||||
centrality = {n: d * s for n, d in G.degree()}
|
||||
return centrality
|
||||
|
||||
|
||||
@not_implemented_for("undirected")
|
||||
def in_degree_centrality(G):
|
||||
"""Compute the in-degree centrality for nodes.
|
||||
|
||||
The in-degree centrality for a node v is the fraction of nodes its
|
||||
incoming edges are connected to.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with in-degree centrality as values.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If G is undirected.
|
||||
|
||||
See Also
|
||||
--------
|
||||
degree_centrality, out_degree_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The degree centrality values are normalized by dividing by the maximum
|
||||
possible degree in a simple graph n-1 where n is the number of nodes in G.
|
||||
|
||||
For multigraphs or graphs with self loops the maximum degree might
|
||||
be higher than n-1 and values of degree centrality greater than 1
|
||||
are possible.
|
||||
"""
|
||||
if len(G) <= 1:
|
||||
return {n: 1 for n in G}
|
||||
|
||||
s = 1.0 / (len(G) - 1.0)
|
||||
centrality = {n: d * s for n, d in G.in_degree()}
|
||||
return centrality
|
||||
|
||||
|
||||
@not_implemented_for("undirected")
|
||||
def out_degree_centrality(G):
|
||||
"""Compute the out-degree centrality for nodes.
|
||||
|
||||
The out-degree centrality for a node v is the fraction of nodes its
|
||||
outgoing edges are connected to.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with out-degree centrality as values.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If G is undirected.
|
||||
|
||||
See Also
|
||||
--------
|
||||
degree_centrality, in_degree_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The degree centrality values are normalized by dividing by the maximum
|
||||
possible degree in a simple graph n-1 where n is the number of nodes in G.
|
||||
|
||||
For multigraphs or graphs with self loops the maximum degree might
|
||||
be higher than n-1 and values of degree centrality greater than 1
|
||||
are possible.
|
||||
"""
|
||||
if len(G) <= 1:
|
||||
return {n: 1 for n in G}
|
||||
|
||||
s = 1.0 / (len(G) - 1.0)
|
||||
centrality = {n: d * s for n, d in G.out_degree()}
|
||||
return centrality
|
|
@ -0,0 +1,101 @@
|
|||
from itertools import combinations
|
||||
|
||||
__all__ = ["dispersion"]
|
||||
|
||||
|
||||
def dispersion(G, u=None, v=None, normalized=True, alpha=1.0, b=0.0, c=0.0):
|
||||
r"""Calculate dispersion between `u` and `v` in `G`.
|
||||
|
||||
A link between two actors (`u` and `v`) has a high dispersion when their
|
||||
mutual ties (`s` and `t`) are not well connected with each other.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
u : node, optional
|
||||
The source for the dispersion score (e.g. ego node of the network).
|
||||
v : node, optional
|
||||
The target of the dispersion score if specified.
|
||||
normalized : bool
|
||||
If True (default) normalize by the embededness of the nodes (u and v).
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
If u (v) is specified, returns a dictionary of nodes with dispersion
|
||||
score for all "target" ("source") nodes. If neither u nor v is
|
||||
specified, returns a dictionary of dictionaries for all nodes 'u' in the
|
||||
graph with a dispersion score for each node 'v'.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This implementation follows Lars Backstrom and Jon Kleinberg [1]_. Typical
|
||||
usage would be to run dispersion on the ego network $G_u$ if $u$ were
|
||||
specified. Running :func:`dispersion` with neither $u$ nor $v$ specified
|
||||
can take some time to complete.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Romantic Partnerships and the Dispersion of Social Ties:
|
||||
A Network Analysis of Relationship Status on Facebook.
|
||||
Lars Backstrom, Jon Kleinberg.
|
||||
https://arxiv.org/pdf/1310.6753v1.pdf
|
||||
|
||||
"""
|
||||
|
||||
def _dispersion(G_u, u, v):
|
||||
"""dispersion for all nodes 'v' in a ego network G_u of node 'u'"""
|
||||
u_nbrs = set(G_u[u])
|
||||
ST = {n for n in G_u[v] if n in u_nbrs}
|
||||
set_uv = {u, v}
|
||||
# all possible ties of connections that u and b share
|
||||
possib = combinations(ST, 2)
|
||||
total = 0
|
||||
for (s, t) in possib:
|
||||
# neighbors of s that are in G_u, not including u and v
|
||||
nbrs_s = u_nbrs.intersection(G_u[s]) - set_uv
|
||||
# s and t are not directly connected
|
||||
if t not in nbrs_s:
|
||||
# s and t do not share a connection
|
||||
if nbrs_s.isdisjoint(G_u[t]):
|
||||
# tick for disp(u, v)
|
||||
total += 1
|
||||
# neighbors that u and v share
|
||||
embededness = len(ST)
|
||||
|
||||
if normalized:
|
||||
if embededness + c != 0:
|
||||
norm_disp = ((total + b) ** alpha) / (embededness + c)
|
||||
else:
|
||||
norm_disp = (total + b) ** alpha
|
||||
dispersion = norm_disp
|
||||
|
||||
else:
|
||||
dispersion = total
|
||||
|
||||
return dispersion
|
||||
|
||||
if u is None:
|
||||
# v and u are not specified
|
||||
if v is None:
|
||||
results = {n: {} for n in G}
|
||||
for u in G:
|
||||
for v in G[u]:
|
||||
results[u][v] = _dispersion(G, u, v)
|
||||
# u is not specified, but v is
|
||||
else:
|
||||
results = dict.fromkeys(G[v], {})
|
||||
for u in G[v]:
|
||||
results[u] = _dispersion(G, v, u)
|
||||
else:
|
||||
# u is specified with no target v
|
||||
if v is None:
|
||||
results = dict.fromkeys(G[u], {})
|
||||
for v in G[u]:
|
||||
results[v] = _dispersion(G, u, v)
|
||||
# both u and v are specified
|
||||
else:
|
||||
results = _dispersion(G, u, v)
|
||||
|
||||
return results
|
|
@ -0,0 +1,229 @@
|
|||
"""Functions for computing eigenvector centrality."""
|
||||
|
||||
from math import sqrt
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
__all__ = ["eigenvector_centrality", "eigenvector_centrality_numpy"]
|
||||
|
||||
|
||||
@not_implemented_for("multigraph")
|
||||
def eigenvector_centrality(G, max_iter=100, tol=1.0e-6, nstart=None, weight=None):
|
||||
r"""Compute the eigenvector centrality for the graph `G`.
|
||||
|
||||
Eigenvector centrality computes the centrality for a node based on the
|
||||
centrality of its neighbors. The eigenvector centrality for node $i$ is
|
||||
the $i$-th element of the vector $x$ defined by the equation
|
||||
|
||||
.. math::
|
||||
|
||||
Ax = \lambda x
|
||||
|
||||
where $A$ is the adjacency matrix of the graph `G` with eigenvalue
|
||||
$\lambda$. By virtue of the Perron–Frobenius theorem, there is a unique
|
||||
solution $x$, all of whose entries are positive, if $\lambda$ is the
|
||||
largest eigenvalue of the adjacency matrix $A$ ([2]_).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A networkx graph
|
||||
|
||||
max_iter : integer, optional (default=100)
|
||||
Maximum number of iterations in power method.
|
||||
|
||||
tol : float, optional (default=1.0e-6)
|
||||
Error tolerance used to check convergence in power method iteration.
|
||||
|
||||
nstart : dictionary, optional (default=None)
|
||||
Starting value of eigenvector iteration for each node.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with eigenvector centrality as the value.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> centrality = nx.eigenvector_centrality(G)
|
||||
>>> sorted((v, f"{c:0.2f}") for v, c in centrality.items())
|
||||
[(0, '0.37'), (1, '0.60'), (2, '0.60'), (3, '0.37')]
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXPointlessConcept
|
||||
If the graph `G` is the null graph.
|
||||
|
||||
NetworkXError
|
||||
If each value in `nstart` is zero.
|
||||
|
||||
PowerIterationFailedConvergence
|
||||
If the algorithm fails to converge to the specified tolerance
|
||||
within the specified number of iterations of the power iteration
|
||||
method.
|
||||
|
||||
See Also
|
||||
--------
|
||||
eigenvector_centrality_numpy
|
||||
pagerank
|
||||
hits
|
||||
|
||||
Notes
|
||||
-----
|
||||
The measure was introduced by [1]_ and is discussed in [2]_.
|
||||
|
||||
The power iteration method is used to compute the eigenvector and
|
||||
convergence is **not** guaranteed. Our method stops after ``max_iter``
|
||||
iterations or when the change in the computed vector between two
|
||||
iterations is smaller than an error tolerance of
|
||||
``G.number_of_nodes() * tol``. This implementation uses ($A + I$)
|
||||
rather than the adjacency matrix $A$ because it shifts the spectrum
|
||||
to enable discerning the correct eigenvector even for networks with
|
||||
multiple dominant eigenvalues.
|
||||
|
||||
For directed graphs this is "left" eigenvector centrality which corresponds
|
||||
to the in-edges in the graph. For out-edges eigenvector centrality
|
||||
first reverse the graph with ``G.reverse()``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Phillip Bonacich.
|
||||
"Power and Centrality: A Family of Measures."
|
||||
*American Journal of Sociology* 92(5):1170–1182, 1986
|
||||
<http://www.leonidzhukov.net/hse/2014/socialnetworks/papers/Bonacich-Centrality.pdf>
|
||||
.. [2] Mark E. J. Newman.
|
||||
*Networks: An Introduction.*
|
||||
Oxford University Press, USA, 2010, pp. 169.
|
||||
|
||||
"""
|
||||
if len(G) == 0:
|
||||
raise nx.NetworkXPointlessConcept(
|
||||
"cannot compute centrality for the null graph"
|
||||
)
|
||||
# If no initial vector is provided, start with the all-ones vector.
|
||||
if nstart is None:
|
||||
nstart = {v: 1 for v in G}
|
||||
if all(v == 0 for v in nstart.values()):
|
||||
raise nx.NetworkXError("initial vector cannot have all zero values")
|
||||
# Normalize the initial vector so that each entry is in [0, 1]. This is
|
||||
# guaranteed to never have a divide-by-zero error by the previous line.
|
||||
nstart_sum = sum(nstart.values())
|
||||
x = {k: v / nstart_sum for k, v in nstart.items()}
|
||||
nnodes = G.number_of_nodes()
|
||||
# make up to max_iter iterations
|
||||
for i in range(max_iter):
|
||||
xlast = x
|
||||
x = xlast.copy() # Start with xlast times I to iterate with (A+I)
|
||||
# do the multiplication y^T = x^T A (left eigenvector)
|
||||
for n in x:
|
||||
for nbr in G[n]:
|
||||
w = G[n][nbr].get(weight, 1) if weight else 1
|
||||
x[nbr] += xlast[n] * w
|
||||
# Normalize the vector. The normalization denominator `norm`
|
||||
# should never be zero by the Perron--Frobenius
|
||||
# theorem. However, in case it is due to numerical error, we
|
||||
# assume the norm to be one instead.
|
||||
norm = sqrt(sum(z ** 2 for z in x.values())) or 1
|
||||
x = {k: v / norm for k, v in x.items()}
|
||||
# Check for convergence (in the L_1 norm).
|
||||
if sum(abs(x[n] - xlast[n]) for n in x) < nnodes * tol:
|
||||
return x
|
||||
raise nx.PowerIterationFailedConvergence(max_iter)
|
||||
|
||||
|
||||
def eigenvector_centrality_numpy(G, weight=None, max_iter=50, tol=0):
|
||||
r"""Compute the eigenvector centrality for the graph G.
|
||||
|
||||
Eigenvector centrality computes the centrality for a node based on the
|
||||
centrality of its neighbors. The eigenvector centrality for node $i$ is
|
||||
|
||||
.. math::
|
||||
|
||||
Ax = \lambda x
|
||||
|
||||
where $A$ is the adjacency matrix of the graph G with eigenvalue $\lambda$.
|
||||
By virtue of the Perron–Frobenius theorem, there is a unique and positive
|
||||
solution if $\lambda$ is the largest eigenvalue associated with the
|
||||
eigenvector of the adjacency matrix $A$ ([2]_).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A networkx graph
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
The name of the edge attribute used as weight.
|
||||
If None, all edge weights are considered equal.
|
||||
|
||||
max_iter : integer, optional (default=100)
|
||||
Maximum number of iterations in power method.
|
||||
|
||||
tol : float, optional (default=1.0e-6)
|
||||
Relative accuracy for eigenvalues (stopping criterion).
|
||||
The default value of 0 implies machine precision.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with eigenvector centrality as the value.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> centrality = nx.eigenvector_centrality_numpy(G)
|
||||
>>> print([f"{node} {centrality[node]:0.2f}" for node in centrality])
|
||||
['0 0.37', '1 0.60', '2 0.60', '3 0.37']
|
||||
|
||||
See Also
|
||||
--------
|
||||
eigenvector_centrality
|
||||
pagerank
|
||||
hits
|
||||
|
||||
Notes
|
||||
-----
|
||||
The measure was introduced by [1]_.
|
||||
|
||||
This algorithm uses the SciPy sparse eigenvalue solver (ARPACK) to
|
||||
find the largest eigenvalue/eigenvector pair.
|
||||
|
||||
For directed graphs this is "left" eigenvector centrality which corresponds
|
||||
to the in-edges in the graph. For out-edges eigenvector centrality
|
||||
first reverse the graph with ``G.reverse()``.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXPointlessConcept
|
||||
If the graph ``G`` is the null graph.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Phillip Bonacich:
|
||||
Power and Centrality: A Family of Measures.
|
||||
American Journal of Sociology 92(5):1170–1182, 1986
|
||||
http://www.leonidzhukov.net/hse/2014/socialnetworks/papers/Bonacich-Centrality.pdf
|
||||
.. [2] Mark E. J. Newman:
|
||||
Networks: An Introduction.
|
||||
Oxford University Press, USA, 2010, pp. 169.
|
||||
"""
|
||||
import numpy as np
|
||||
import scipy as sp
|
||||
from scipy.sparse import linalg
|
||||
|
||||
if len(G) == 0:
|
||||
raise nx.NetworkXPointlessConcept(
|
||||
"cannot compute centrality for the null graph"
|
||||
)
|
||||
M = nx.to_scipy_sparse_matrix(G, nodelist=list(G), weight=weight, dtype=float)
|
||||
eigenvalue, eigenvector = linalg.eigs(
|
||||
M.T, k=1, which="LR", maxiter=max_iter, tol=tol
|
||||
)
|
||||
largest = eigenvector.flatten().real
|
||||
norm = np.sign(largest.sum()) * sp.linalg.norm(largest)
|
||||
return dict(zip(G, largest / norm))
|
|
@ -0,0 +1,144 @@
|
|||
# Helpers for current-flow betweenness and current-flow closness
|
||||
# Lazy computations for inverse Laplacian and flow-matrix rows.
|
||||
import networkx as nx
|
||||
|
||||
|
||||
def flow_matrix_row(G, weight=None, dtype=float, solver="lu"):
|
||||
# Generate a row of the current-flow matrix
|
||||
import numpy as np
|
||||
|
||||
solvername = {
|
||||
"full": FullInverseLaplacian,
|
||||
"lu": SuperLUInverseLaplacian,
|
||||
"cg": CGInverseLaplacian,
|
||||
}
|
||||
n = G.number_of_nodes()
|
||||
L = laplacian_sparse_matrix(
|
||||
G, nodelist=range(n), weight=weight, dtype=dtype, format="csc"
|
||||
)
|
||||
C = solvername[solver](L, dtype=dtype) # initialize solver
|
||||
w = C.w # w is the Laplacian matrix width
|
||||
# row-by-row flow matrix
|
||||
for u, v in sorted(sorted((u, v)) for u, v in G.edges()):
|
||||
B = np.zeros(w, dtype=dtype)
|
||||
c = G[u][v].get(weight, 1.0)
|
||||
B[u % w] = c
|
||||
B[v % w] = -c
|
||||
# get only the rows needed in the inverse laplacian
|
||||
# and multiply to get the flow matrix row
|
||||
row = np.dot(B, C.get_rows(u, v))
|
||||
yield row, (u, v)
|
||||
|
||||
|
||||
# Class to compute the inverse laplacian only for specified rows
|
||||
# Allows computation of the current-flow matrix without storing entire
|
||||
# inverse laplacian matrix
|
||||
class InverseLaplacian:
|
||||
def __init__(self, L, width=None, dtype=None):
|
||||
global np
|
||||
import numpy as np
|
||||
|
||||
(n, n) = L.shape
|
||||
self.dtype = dtype
|
||||
self.n = n
|
||||
if width is None:
|
||||
self.w = self.width(L)
|
||||
else:
|
||||
self.w = width
|
||||
self.C = np.zeros((self.w, n), dtype=dtype)
|
||||
self.L1 = L[1:, 1:]
|
||||
self.init_solver(L)
|
||||
|
||||
def init_solver(self, L):
|
||||
pass
|
||||
|
||||
def solve(self, r):
|
||||
raise nx.NetworkXError("Implement solver")
|
||||
|
||||
def solve_inverse(self, r):
|
||||
raise nx.NetworkXError("Implement solver")
|
||||
|
||||
def get_rows(self, r1, r2):
|
||||
for r in range(r1, r2 + 1):
|
||||
self.C[r % self.w, 1:] = self.solve_inverse(r)
|
||||
return self.C
|
||||
|
||||
def get_row(self, r):
|
||||
self.C[r % self.w, 1:] = self.solve_inverse(r)
|
||||
return self.C[r % self.w]
|
||||
|
||||
def width(self, L):
|
||||
m = 0
|
||||
for i, row in enumerate(L):
|
||||
w = 0
|
||||
x, y = np.nonzero(row)
|
||||
if len(y) > 0:
|
||||
v = y - i
|
||||
w = v.max() - v.min() + 1
|
||||
m = max(w, m)
|
||||
return m
|
||||
|
||||
|
||||
class FullInverseLaplacian(InverseLaplacian):
|
||||
def init_solver(self, L):
|
||||
self.IL = np.zeros(L.shape, dtype=self.dtype)
|
||||
self.IL[1:, 1:] = np.linalg.inv(self.L1.todense())
|
||||
|
||||
def solve(self, rhs):
|
||||
s = np.zeros(rhs.shape, dtype=self.dtype)
|
||||
s = np.dot(self.IL, rhs)
|
||||
return s
|
||||
|
||||
def solve_inverse(self, r):
|
||||
return self.IL[r, 1:]
|
||||
|
||||
|
||||
class SuperLUInverseLaplacian(InverseLaplacian):
|
||||
def init_solver(self, L):
|
||||
from scipy.sparse import linalg
|
||||
|
||||
self.lusolve = linalg.factorized(self.L1.tocsc())
|
||||
|
||||
def solve_inverse(self, r):
|
||||
rhs = np.zeros(self.n, dtype=self.dtype)
|
||||
rhs[r] = 1
|
||||
return self.lusolve(rhs[1:])
|
||||
|
||||
def solve(self, rhs):
|
||||
s = np.zeros(rhs.shape, dtype=self.dtype)
|
||||
s[1:] = self.lusolve(rhs[1:])
|
||||
return s
|
||||
|
||||
|
||||
class CGInverseLaplacian(InverseLaplacian):
|
||||
def init_solver(self, L):
|
||||
global linalg
|
||||
from scipy.sparse import linalg
|
||||
|
||||
ilu = linalg.spilu(self.L1.tocsc())
|
||||
n = self.n - 1
|
||||
self.M = linalg.LinearOperator(shape=(n, n), matvec=ilu.solve)
|
||||
|
||||
def solve(self, rhs):
|
||||
s = np.zeros(rhs.shape, dtype=self.dtype)
|
||||
s[1:] = linalg.cg(self.L1, rhs[1:], M=self.M, atol=0)[0]
|
||||
return s
|
||||
|
||||
def solve_inverse(self, r):
|
||||
rhs = np.zeros(self.n, self.dtype)
|
||||
rhs[r] = 1
|
||||
return linalg.cg(self.L1, rhs[1:], M=self.M, atol=0)[0]
|
||||
|
||||
|
||||
# graph laplacian, sparse version, will move to linalg/laplacianmatrix.py
|
||||
def laplacian_sparse_matrix(G, nodelist=None, weight=None, dtype=None, format="csr"):
|
||||
import numpy as np
|
||||
import scipy.sparse
|
||||
|
||||
A = nx.to_scipy_sparse_matrix(
|
||||
G, nodelist=nodelist, weight=weight, dtype=dtype, format=format
|
||||
)
|
||||
(n, n) = A.shape
|
||||
data = np.asarray(A.sum(axis=1).T)
|
||||
D = scipy.sparse.spdiags(data, 0, n, n, format=format)
|
||||
return D - A
|
366
venv/Lib/site-packages/networkx/algorithms/centrality/group.py
Normal file
366
venv/Lib/site-packages/networkx/algorithms/centrality/group.py
Normal file
|
@ -0,0 +1,366 @@
|
|||
"""Group centrality measures."""
|
||||
from itertools import combinations
|
||||
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils.decorators import not_implemented_for
|
||||
|
||||
|
||||
__all__ = [
|
||||
"group_betweenness_centrality",
|
||||
"group_closeness_centrality",
|
||||
"group_degree_centrality",
|
||||
"group_in_degree_centrality",
|
||||
"group_out_degree_centrality",
|
||||
]
|
||||
|
||||
|
||||
def group_betweenness_centrality(G, C, normalized=True, weight=None):
|
||||
r"""Compute the group betweenness centrality for a group of nodes.
|
||||
|
||||
Group betweenness centrality of a group of nodes $C$ is the sum of the
|
||||
fraction of all-pairs shortest paths that pass through any vertex in $C$
|
||||
|
||||
.. math::
|
||||
|
||||
c_B(C) =\sum_{s,t \in V-C; s<t} \frac{\sigma(s, t|C)}{\sigma(s, t)}
|
||||
|
||||
where $V$ is the set of nodes, $\sigma(s, t)$ is the number of
|
||||
shortest $(s, t)$-paths, and $\sigma(s, t|C)$ is the number of
|
||||
those paths passing through some node in group $C$. Note that
|
||||
$(s, t)$ are not members of the group ($V-C$ is the set of nodes
|
||||
in $V$ that are not in $C$).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
C : list or set
|
||||
C is a group of nodes which belong to G, for which group betweenness
|
||||
centrality is to be calculated.
|
||||
|
||||
normalized : bool, optional
|
||||
If True, group betweenness is normalized by `2/((|V|-|C|)(|V|-|C|-1))`
|
||||
for graphs and `1/((|V|-|C|)(|V|-|C|-1))` for directed graphs where `|V|`
|
||||
is the number of nodes in G and `|C|` is the number of nodes in C.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
Raises
|
||||
------
|
||||
NodeNotFound
|
||||
If node(s) in C are not present in G.
|
||||
|
||||
Returns
|
||||
-------
|
||||
betweenness : float
|
||||
Group betweenness centrality of the group C.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The measure is described in [1]_.
|
||||
The algorithm is an extension of the one proposed by Ulrik Brandes for
|
||||
betweenness centrality of nodes. Group betweenness is also mentioned in
|
||||
his paper [2]_ along with the algorithm. The importance of the measure is
|
||||
discussed in [3]_.
|
||||
|
||||
The number of nodes in the group must be a maximum of n - 2 where `n`
|
||||
is the total number of nodes in the graph.
|
||||
|
||||
For weighted graphs the edge weights must be greater than zero.
|
||||
Zero edge weights can produce an infinite number of equal length
|
||||
paths between pairs of nodes.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] M G Everett and S P Borgatti:
|
||||
The Centrality of Groups and Classes.
|
||||
Journal of Mathematical Sociology. 23(3): 181-201. 1999.
|
||||
http://www.analytictech.com/borgatti/group_centrality.htm
|
||||
.. [2] Ulrik Brandes:
|
||||
On Variants of Shortest-Path Betweenness
|
||||
Centrality and their Generic Computation.
|
||||
Social Networks 30(2):136-145, 2008.
|
||||
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.9610&rep=rep1&type=pdf
|
||||
.. [3] Sourav Medya et. al.:
|
||||
Group Centrality Maximization via Network Design.
|
||||
SIAM International Conference on Data Mining, SDM 2018, 126–134.
|
||||
https://sites.cs.ucsb.edu/~arlei/pubs/sdm18.pdf
|
||||
"""
|
||||
betweenness = 0 # initialize betweenness to 0
|
||||
V = set(G) # set of nodes in G
|
||||
C = set(C) # set of nodes in C (group)
|
||||
if len(C - V) != 0: # element(s) of C not in V
|
||||
raise nx.NodeNotFound(
|
||||
"The node(s) " + str(list(C - V)) + " are not " "in the graph."
|
||||
)
|
||||
V_C = V - C # set of nodes in V but not in C
|
||||
# accumulation
|
||||
for pair in combinations(V_C, 2): # (s, t) pairs of V_C
|
||||
try:
|
||||
paths = 0
|
||||
paths_through_C = 0
|
||||
for path in nx.all_shortest_paths(
|
||||
G, source=pair[0], target=pair[1], weight=weight
|
||||
):
|
||||
if set(path) & C:
|
||||
paths_through_C += 1
|
||||
paths += 1
|
||||
betweenness += paths_through_C / paths
|
||||
except nx.exception.NetworkXNoPath:
|
||||
betweenness += 0
|
||||
# rescaling
|
||||
v, c = len(G), len(C)
|
||||
if normalized:
|
||||
scale = 1 / ((v - c) * (v - c - 1))
|
||||
if not G.is_directed():
|
||||
scale *= 2
|
||||
else:
|
||||
scale = None
|
||||
if scale is not None:
|
||||
betweenness *= scale
|
||||
return betweenness
|
||||
|
||||
|
||||
def group_closeness_centrality(G, S, weight=None):
|
||||
r"""Compute the group closeness centrality for a group of nodes.
|
||||
|
||||
Group closeness centrality of a group of nodes $S$ is a measure
|
||||
of how close the group is to the other nodes in the graph.
|
||||
|
||||
.. math::
|
||||
|
||||
c_{close}(S) = \frac{|V-S|}{\sum_{v \in V-S} d_{S, v}}
|
||||
|
||||
d_{S, v} = min_{u \in S} (d_{u, v})
|
||||
|
||||
where $V$ is the set of nodes, $d_{S, v}$ is the distance of
|
||||
the group $S$ from $v$ defined as above. ($V-S$ is the set of nodes
|
||||
in $V$ that are not in $S$).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
S : list or set
|
||||
S is a group of nodes which belong to G, for which group closeness
|
||||
centrality is to be calculated.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
Raises
|
||||
------
|
||||
NodeNotFound
|
||||
If node(s) in S are not present in G.
|
||||
|
||||
Returns
|
||||
-------
|
||||
closeness : float
|
||||
Group closeness centrality of the group S.
|
||||
|
||||
See Also
|
||||
--------
|
||||
closeness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The measure was introduced in [1]_.
|
||||
The formula implemented here is described in [2]_.
|
||||
|
||||
Higher values of closeness indicate greater centrality.
|
||||
|
||||
It is assumed that 1 / 0 is 0 (required in the case of directed graphs,
|
||||
or when a shortest path length is 0).
|
||||
|
||||
The number of nodes in the group must be a maximum of n - 1 where `n`
|
||||
is the total number of nodes in the graph.
|
||||
|
||||
For directed graphs, the incoming distance is utilized here. To use the
|
||||
outward distance, act on `G.reverse()`.
|
||||
|
||||
For weighted graphs the edge weights must be greater than zero.
|
||||
Zero edge weights can produce an infinite number of equal length
|
||||
paths between pairs of nodes.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] M G Everett and S P Borgatti:
|
||||
The Centrality of Groups and Classes.
|
||||
Journal of Mathematical Sociology. 23(3): 181-201. 1999.
|
||||
http://www.analytictech.com/borgatti/group_centrality.htm
|
||||
.. [2] J. Zhao et. al.:
|
||||
Measuring and Maximizing Group Closeness Centrality over
|
||||
Disk Resident Graphs.
|
||||
WWWConference Proceedings, 2014. 689-694.
|
||||
http://wwwconference.org/proceedings/www2014/companion/p689.pdf
|
||||
"""
|
||||
if G.is_directed():
|
||||
G = G.reverse() # reverse view
|
||||
closeness = 0 # initialize to 0
|
||||
V = set(G) # set of nodes in G
|
||||
S = set(S) # set of nodes in group S
|
||||
V_S = V - S # set of nodes in V but not S
|
||||
shortest_path_lengths = nx.multi_source_dijkstra_path_length(G, S, weight=weight)
|
||||
# accumulation
|
||||
for v in V_S:
|
||||
try:
|
||||
closeness += shortest_path_lengths[v]
|
||||
except KeyError: # no path exists
|
||||
closeness += 0
|
||||
try:
|
||||
closeness = len(V_S) / closeness
|
||||
except ZeroDivisionError: # 1 / 0 assumed as 0
|
||||
closeness = 0
|
||||
return closeness
|
||||
|
||||
|
||||
def group_degree_centrality(G, S):
|
||||
"""Compute the group degree centrality for a group of nodes.
|
||||
|
||||
Group degree centrality of a group of nodes $S$ is the fraction
|
||||
of non-group members connected to group members.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
S : list or set
|
||||
S is a group of nodes which belong to G, for which group degree
|
||||
centrality is to be calculated.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If node(s) in S are not in G.
|
||||
|
||||
Returns
|
||||
-------
|
||||
centrality : float
|
||||
Group degree centrality of the group S.
|
||||
|
||||
See Also
|
||||
--------
|
||||
degree_centrality
|
||||
group_in_degree_centrality
|
||||
group_out_degree_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The measure was introduced in [1]_.
|
||||
|
||||
The number of nodes in the group must be a maximum of n - 1 where `n`
|
||||
is the total number of nodes in the graph.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] M G Everett and S P Borgatti:
|
||||
The Centrality of Groups and Classes.
|
||||
Journal of Mathematical Sociology. 23(3): 181-201. 1999.
|
||||
http://www.analytictech.com/borgatti/group_centrality.htm
|
||||
"""
|
||||
centrality = len(set().union(*list(set(G.neighbors(i)) for i in S)) - set(S))
|
||||
centrality /= len(G.nodes()) - len(S)
|
||||
return centrality
|
||||
|
||||
|
||||
@not_implemented_for("undirected")
|
||||
def group_in_degree_centrality(G, S):
|
||||
"""Compute the group in-degree centrality for a group of nodes.
|
||||
|
||||
Group in-degree centrality of a group of nodes $S$ is the fraction
|
||||
of non-group members connected to group members by incoming edges.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
S : list or set
|
||||
S is a group of nodes which belong to G, for which group in-degree
|
||||
centrality is to be calculated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
centrality : float
|
||||
Group in-degree centrality of the group S.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If G is undirected.
|
||||
|
||||
NodeNotFound
|
||||
If node(s) in S are not in G.
|
||||
|
||||
See Also
|
||||
--------
|
||||
degree_centrality
|
||||
group_degree_centrality
|
||||
group_out_degree_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The number of nodes in the group must be a maximum of n - 1 where `n`
|
||||
is the total number of nodes in the graph.
|
||||
|
||||
`G.neighbors(i)` gives nodes with an outward edge from i, in a DiGraph,
|
||||
so for group in-degree centrality, the reverse graph is used.
|
||||
"""
|
||||
return group_degree_centrality(G.reverse(), S)
|
||||
|
||||
|
||||
@not_implemented_for("undirected")
|
||||
def group_out_degree_centrality(G, S):
|
||||
"""Compute the group out-degree centrality for a group of nodes.
|
||||
|
||||
Group out-degree centrality of a group of nodes $S$ is the fraction
|
||||
of non-group members connected to group members by outgoing edges.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
S : list or set
|
||||
S is a group of nodes which belong to G, for which group in-degree
|
||||
centrality is to be calculated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
centrality : float
|
||||
Group out-degree centrality of the group S.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If G is undirected.
|
||||
|
||||
NodeNotFound
|
||||
If node(s) in S are not in G.
|
||||
|
||||
See Also
|
||||
--------
|
||||
degree_centrality
|
||||
group_degree_centrality
|
||||
group_in_degree_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The number of nodes in the group must be a maximum of n - 1 where `n`
|
||||
is the total number of nodes in the graph.
|
||||
|
||||
`G.neighbors(i)` gives nodes with an outward edge from i, in a DiGraph,
|
||||
so for group out-degree centrality, the graph itself is used.
|
||||
"""
|
||||
return group_degree_centrality(G, S)
|
|
@ -0,0 +1,63 @@
|
|||
"""Functions for computing the harmonic centrality of a graph."""
|
||||
from functools import partial
|
||||
|
||||
import networkx as nx
|
||||
|
||||
__all__ = ["harmonic_centrality"]
|
||||
|
||||
|
||||
def harmonic_centrality(G, nbunch=None, distance=None):
|
||||
r"""Compute harmonic centrality for nodes.
|
||||
|
||||
Harmonic centrality [1]_ of a node `u` is the sum of the reciprocal
|
||||
of the shortest path distances from all other nodes to `u`
|
||||
|
||||
.. math::
|
||||
|
||||
C(u) = \sum_{v \neq u} \frac{1}{d(v, u)}
|
||||
|
||||
where `d(v, u)` is the shortest-path distance between `v` and `u`.
|
||||
|
||||
Notice that higher values indicate higher centrality.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
nbunch : container
|
||||
Container of nodes. If provided harmonic centrality will be computed
|
||||
only over the nodes in nbunch.
|
||||
|
||||
distance : edge attribute key, optional (default=None)
|
||||
Use the specified edge attribute as the edge distance in shortest
|
||||
path calculations. If `None`, then each edge will have distance equal to 1.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with harmonic centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality, load_centrality, eigenvector_centrality,
|
||||
degree_centrality, closeness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
If the 'distance' keyword is set to an edge attribute key then the
|
||||
shortest-path length will be computed using Dijkstra's algorithm with
|
||||
that edge attribute as the edge weight.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Boldi, Paolo, and Sebastiano Vigna. "Axioms for centrality."
|
||||
Internet Mathematics 10.3-4 (2014): 222-262.
|
||||
"""
|
||||
if G.is_directed():
|
||||
G = G.reverse()
|
||||
spl = partial(nx.shortest_path_length, G, weight=distance)
|
||||
return {
|
||||
u: sum(1 / d if d > 0 else 0 for v, d in spl(source=u).items())
|
||||
for u in G.nbunch_iter(nbunch)
|
||||
}
|
333
venv/Lib/site-packages/networkx/algorithms/centrality/katz.py
Normal file
333
venv/Lib/site-packages/networkx/algorithms/centrality/katz.py
Normal file
|
@ -0,0 +1,333 @@
|
|||
"""Katz centrality."""
|
||||
from math import sqrt
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
__all__ = ["katz_centrality", "katz_centrality_numpy"]
|
||||
|
||||
|
||||
@not_implemented_for("multigraph")
|
||||
def katz_centrality(
|
||||
G,
|
||||
alpha=0.1,
|
||||
beta=1.0,
|
||||
max_iter=1000,
|
||||
tol=1.0e-6,
|
||||
nstart=None,
|
||||
normalized=True,
|
||||
weight=None,
|
||||
):
|
||||
r"""Compute the Katz centrality for the nodes of the graph G.
|
||||
|
||||
Katz centrality computes the centrality for a node based on the centrality
|
||||
of its neighbors. It is a generalization of the eigenvector centrality. The
|
||||
Katz centrality for node $i$ is
|
||||
|
||||
.. math::
|
||||
|
||||
x_i = \alpha \sum_{j} A_{ij} x_j + \beta,
|
||||
|
||||
where $A$ is the adjacency matrix of graph G with eigenvalues $\lambda$.
|
||||
|
||||
The parameter $\beta$ controls the initial centrality and
|
||||
|
||||
.. math::
|
||||
|
||||
\alpha < \frac{1}{\lambda_{\max}}.
|
||||
|
||||
Katz centrality computes the relative influence of a node within a
|
||||
network by measuring the number of the immediate neighbors (first
|
||||
degree nodes) and also all other nodes in the network that connect
|
||||
to the node under consideration through these immediate neighbors.
|
||||
|
||||
Extra weight can be provided to immediate neighbors through the
|
||||
parameter $\beta$. Connections made with distant neighbors
|
||||
are, however, penalized by an attenuation factor $\alpha$ which
|
||||
should be strictly less than the inverse largest eigenvalue of the
|
||||
adjacency matrix in order for the Katz centrality to be computed
|
||||
correctly. More information is provided in [1]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
alpha : float
|
||||
Attenuation factor
|
||||
|
||||
beta : scalar or dictionary, optional (default=1.0)
|
||||
Weight attributed to the immediate neighborhood. If not a scalar, the
|
||||
dictionary must have an value for every node.
|
||||
|
||||
max_iter : integer, optional (default=1000)
|
||||
Maximum number of iterations in power method.
|
||||
|
||||
tol : float, optional (default=1.0e-6)
|
||||
Error tolerance used to check convergence in power method iteration.
|
||||
|
||||
nstart : dictionary, optional
|
||||
Starting value of Katz iteration for each node.
|
||||
|
||||
normalized : bool, optional (default=True)
|
||||
If True normalize the resulting values.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with Katz centrality as the value.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the parameter `beta` is not a scalar but lacks a value for at least
|
||||
one node
|
||||
|
||||
PowerIterationFailedConvergence
|
||||
If the algorithm fails to converge to the specified tolerance
|
||||
within the specified number of iterations of the power iteration
|
||||
method.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import math
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> phi = (1 + math.sqrt(5)) / 2.0 # largest eigenvalue of adj matrix
|
||||
>>> centrality = nx.katz_centrality(G, 1 / phi - 0.01)
|
||||
>>> for n, c in sorted(centrality.items()):
|
||||
... print(f"{n} {c:.2f}")
|
||||
0 0.37
|
||||
1 0.60
|
||||
2 0.60
|
||||
3 0.37
|
||||
|
||||
See Also
|
||||
--------
|
||||
katz_centrality_numpy
|
||||
eigenvector_centrality
|
||||
eigenvector_centrality_numpy
|
||||
pagerank
|
||||
hits
|
||||
|
||||
Notes
|
||||
-----
|
||||
Katz centrality was introduced by [2]_.
|
||||
|
||||
This algorithm it uses the power method to find the eigenvector
|
||||
corresponding to the largest eigenvalue of the adjacency matrix of ``G``.
|
||||
The parameter ``alpha`` should be strictly less than the inverse of largest
|
||||
eigenvalue of the adjacency matrix for the algorithm to converge.
|
||||
You can use ``max(nx.adjacency_spectrum(G))`` to get $\lambda_{\max}$ the largest
|
||||
eigenvalue of the adjacency matrix.
|
||||
The iteration will stop after ``max_iter`` iterations or an error tolerance of
|
||||
``number_of_nodes(G) * tol`` has been reached.
|
||||
|
||||
When $\alpha = 1/\lambda_{\max}$ and $\beta=0$, Katz centrality is the same
|
||||
as eigenvector centrality.
|
||||
|
||||
For directed graphs this finds "left" eigenvectors which corresponds
|
||||
to the in-edges in the graph. For out-edges Katz centrality
|
||||
first reverse the graph with ``G.reverse()``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Mark E. J. Newman:
|
||||
Networks: An Introduction.
|
||||
Oxford University Press, USA, 2010, p. 720.
|
||||
.. [2] Leo Katz:
|
||||
A New Status Index Derived from Sociometric Index.
|
||||
Psychometrika 18(1):39–43, 1953
|
||||
http://phya.snu.ac.kr/~dkim/PRL87278701.pdf
|
||||
"""
|
||||
if len(G) == 0:
|
||||
return {}
|
||||
|
||||
nnodes = G.number_of_nodes()
|
||||
|
||||
if nstart is None:
|
||||
# choose starting vector with entries of 0
|
||||
x = {n: 0 for n in G}
|
||||
else:
|
||||
x = nstart
|
||||
|
||||
try:
|
||||
b = dict.fromkeys(G, float(beta))
|
||||
except (TypeError, ValueError, AttributeError) as e:
|
||||
b = beta
|
||||
if set(beta) != set(G):
|
||||
raise nx.NetworkXError(
|
||||
"beta dictionary " "must have a value for every node"
|
||||
) from e
|
||||
|
||||
# make up to max_iter iterations
|
||||
for i in range(max_iter):
|
||||
xlast = x
|
||||
x = dict.fromkeys(xlast, 0)
|
||||
# do the multiplication y^T = Alpha * x^T A - Beta
|
||||
for n in x:
|
||||
for nbr in G[n]:
|
||||
x[nbr] += xlast[n] * G[n][nbr].get(weight, 1)
|
||||
for n in x:
|
||||
x[n] = alpha * x[n] + b[n]
|
||||
|
||||
# check convergence
|
||||
err = sum([abs(x[n] - xlast[n]) for n in x])
|
||||
if err < nnodes * tol:
|
||||
if normalized:
|
||||
# normalize vector
|
||||
try:
|
||||
s = 1.0 / sqrt(sum(v ** 2 for v in x.values()))
|
||||
# this should never be zero?
|
||||
except ZeroDivisionError:
|
||||
s = 1.0
|
||||
else:
|
||||
s = 1
|
||||
for n in x:
|
||||
x[n] *= s
|
||||
return x
|
||||
raise nx.PowerIterationFailedConvergence(max_iter)
|
||||
|
||||
|
||||
@not_implemented_for("multigraph")
|
||||
def katz_centrality_numpy(G, alpha=0.1, beta=1.0, normalized=True, weight=None):
|
||||
r"""Compute the Katz centrality for the graph G.
|
||||
|
||||
Katz centrality computes the centrality for a node based on the centrality
|
||||
of its neighbors. It is a generalization of the eigenvector centrality. The
|
||||
Katz centrality for node $i$ is
|
||||
|
||||
.. math::
|
||||
|
||||
x_i = \alpha \sum_{j} A_{ij} x_j + \beta,
|
||||
|
||||
where $A$ is the adjacency matrix of graph G with eigenvalues $\lambda$.
|
||||
|
||||
The parameter $\beta$ controls the initial centrality and
|
||||
|
||||
.. math::
|
||||
|
||||
\alpha < \frac{1}{\lambda_{\max}}.
|
||||
|
||||
Katz centrality computes the relative influence of a node within a
|
||||
network by measuring the number of the immediate neighbors (first
|
||||
degree nodes) and also all other nodes in the network that connect
|
||||
to the node under consideration through these immediate neighbors.
|
||||
|
||||
Extra weight can be provided to immediate neighbors through the
|
||||
parameter $\beta$. Connections made with distant neighbors
|
||||
are, however, penalized by an attenuation factor $\alpha$ which
|
||||
should be strictly less than the inverse largest eigenvalue of the
|
||||
adjacency matrix in order for the Katz centrality to be computed
|
||||
correctly. More information is provided in [1]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
|
||||
alpha : float
|
||||
Attenuation factor
|
||||
|
||||
beta : scalar or dictionary, optional (default=1.0)
|
||||
Weight attributed to the immediate neighborhood. If not a scalar the
|
||||
dictionary must have an value for every node.
|
||||
|
||||
normalized : bool
|
||||
If True normalize the resulting values.
|
||||
|
||||
weight : None or string, optional
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with Katz centrality as the value.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the parameter `beta` is not a scalar but lacks a value for at least
|
||||
one node
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import math
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> phi = (1 + math.sqrt(5)) / 2.0 # largest eigenvalue of adj matrix
|
||||
>>> centrality = nx.katz_centrality_numpy(G, 1 / phi)
|
||||
>>> for n, c in sorted(centrality.items()):
|
||||
... print(f"{n} {c:.2f}")
|
||||
0 0.37
|
||||
1 0.60
|
||||
2 0.60
|
||||
3 0.37
|
||||
|
||||
See Also
|
||||
--------
|
||||
katz_centrality
|
||||
eigenvector_centrality_numpy
|
||||
eigenvector_centrality
|
||||
pagerank
|
||||
hits
|
||||
|
||||
Notes
|
||||
-----
|
||||
Katz centrality was introduced by [2]_.
|
||||
|
||||
This algorithm uses a direct linear solver to solve the above equation.
|
||||
The parameter ``alpha`` should be strictly less than the inverse of largest
|
||||
eigenvalue of the adjacency matrix for there to be a solution.
|
||||
You can use ``max(nx.adjacency_spectrum(G))`` to get $\lambda_{\max}$ the largest
|
||||
eigenvalue of the adjacency matrix.
|
||||
|
||||
When $\alpha = 1/\lambda_{\max}$ and $\beta=0$, Katz centrality is the same
|
||||
as eigenvector centrality.
|
||||
|
||||
For directed graphs this finds "left" eigenvectors which corresponds
|
||||
to the in-edges in the graph. For out-edges Katz centrality
|
||||
first reverse the graph with ``G.reverse()``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Mark E. J. Newman:
|
||||
Networks: An Introduction.
|
||||
Oxford University Press, USA, 2010, p. 720.
|
||||
.. [2] Leo Katz:
|
||||
A New Status Index Derived from Sociometric Index.
|
||||
Psychometrika 18(1):39–43, 1953
|
||||
http://phya.snu.ac.kr/~dkim/PRL87278701.pdf
|
||||
"""
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError as e:
|
||||
raise ImportError("Requires NumPy: http://numpy.org/") from e
|
||||
if len(G) == 0:
|
||||
return {}
|
||||
try:
|
||||
nodelist = beta.keys()
|
||||
if set(nodelist) != set(G):
|
||||
raise nx.NetworkXError(
|
||||
"beta dictionary " "must have a value for every node"
|
||||
)
|
||||
b = np.array(list(beta.values()), dtype=float)
|
||||
except AttributeError:
|
||||
nodelist = list(G)
|
||||
try:
|
||||
b = np.ones((len(nodelist), 1)) * float(beta)
|
||||
except (TypeError, ValueError, AttributeError) as e:
|
||||
raise nx.NetworkXError("beta must be a number") from e
|
||||
|
||||
A = nx.adj_matrix(G, nodelist=nodelist, weight=weight).todense().T
|
||||
n = A.shape[0]
|
||||
centrality = np.linalg.solve(np.eye(n, n) - (alpha * A), b)
|
||||
if normalized:
|
||||
norm = np.sign(sum(centrality)) * np.linalg.norm(centrality)
|
||||
else:
|
||||
norm = 1.0
|
||||
centrality = dict(zip(nodelist, map(float, centrality / norm)))
|
||||
return centrality
|
197
venv/Lib/site-packages/networkx/algorithms/centrality/load.py
Normal file
197
venv/Lib/site-packages/networkx/algorithms/centrality/load.py
Normal file
|
@ -0,0 +1,197 @@
|
|||
"""Load centrality."""
|
||||
from operator import itemgetter
|
||||
|
||||
import networkx as nx
|
||||
|
||||
__all__ = ["load_centrality", "edge_load_centrality"]
|
||||
|
||||
|
||||
def newman_betweenness_centrality(G, v=None, cutoff=None, normalized=True, weight=None):
|
||||
"""Compute load centrality for nodes.
|
||||
|
||||
The load centrality of a node is the fraction of all shortest
|
||||
paths that pass through that node.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A networkx graph.
|
||||
|
||||
normalized : bool, optional (default=True)
|
||||
If True the betweenness values are normalized by b=b/(n-1)(n-2) where
|
||||
n is the number of nodes in G.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, edge weights are ignored.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
cutoff : bool, optional (default=None)
|
||||
If specified, only consider paths of length <= cutoff.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality()
|
||||
|
||||
Notes
|
||||
-----
|
||||
Load centrality is slightly different than betweenness. It was originally
|
||||
introduced by [2]_. For this load algorithm see [1]_.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Mark E. J. Newman:
|
||||
Scientific collaboration networks. II.
|
||||
Shortest paths, weighted networks, and centrality.
|
||||
Physical Review E 64, 016132, 2001.
|
||||
http://journals.aps.org/pre/abstract/10.1103/PhysRevE.64.016132
|
||||
.. [2] Kwang-Il Goh, Byungnam Kahng and Doochul Kim
|
||||
Universal behavior of Load Distribution in Scale-Free Networks.
|
||||
Physical Review Letters 87(27):1–4, 2001.
|
||||
http://phya.snu.ac.kr/~dkim/PRL87278701.pdf
|
||||
"""
|
||||
if v is not None: # only one node
|
||||
betweenness = 0.0
|
||||
for source in G:
|
||||
ubetween = _node_betweenness(G, source, cutoff, False, weight)
|
||||
betweenness += ubetween[v] if v in ubetween else 0
|
||||
if normalized:
|
||||
order = G.order()
|
||||
if order <= 2:
|
||||
return betweenness # no normalization b=0 for all nodes
|
||||
betweenness *= 1.0 / ((order - 1) * (order - 2))
|
||||
return betweenness
|
||||
else:
|
||||
betweenness = {}.fromkeys(G, 0.0)
|
||||
for source in betweenness:
|
||||
ubetween = _node_betweenness(G, source, cutoff, False, weight)
|
||||
for vk in ubetween:
|
||||
betweenness[vk] += ubetween[vk]
|
||||
if normalized:
|
||||
order = G.order()
|
||||
if order <= 2:
|
||||
return betweenness # no normalization b=0 for all nodes
|
||||
scale = 1.0 / ((order - 1) * (order - 2))
|
||||
for v in betweenness:
|
||||
betweenness[v] *= scale
|
||||
return betweenness # all nodes
|
||||
|
||||
|
||||
def _node_betweenness(G, source, cutoff=False, normalized=True, weight=None):
|
||||
"""Node betweenness_centrality helper:
|
||||
|
||||
See betweenness_centrality for what you probably want.
|
||||
This actually computes "load" and not betweenness.
|
||||
See https://networkx.lanl.gov/ticket/103
|
||||
|
||||
This calculates the load of each node for paths from a single source.
|
||||
(The fraction of number of shortests paths from source that go
|
||||
through each node.)
|
||||
|
||||
To get the load for a node you need to do all-pairs shortest paths.
|
||||
|
||||
If weight is not None then use Dijkstra for finding shortest paths.
|
||||
"""
|
||||
# get the predecessor and path length data
|
||||
if weight is None:
|
||||
(pred, length) = nx.predecessor(G, source, cutoff=cutoff, return_seen=True)
|
||||
else:
|
||||
(pred, length) = nx.dijkstra_predecessor_and_distance(G, source, cutoff, weight)
|
||||
|
||||
# order the nodes by path length
|
||||
onodes = [(l, vert) for (vert, l) in length.items()]
|
||||
onodes.sort()
|
||||
onodes[:] = [vert for (l, vert) in onodes if l > 0]
|
||||
|
||||
# initialize betweenness
|
||||
between = {}.fromkeys(length, 1.0)
|
||||
|
||||
while onodes:
|
||||
v = onodes.pop()
|
||||
if v in pred:
|
||||
num_paths = len(pred[v]) # Discount betweenness if more than
|
||||
for x in pred[v]: # one shortest path.
|
||||
if x == source: # stop if hit source because all remaining v
|
||||
break # also have pred[v]==[source]
|
||||
between[x] += between[v] / float(num_paths)
|
||||
# remove source
|
||||
for v in between:
|
||||
between[v] -= 1
|
||||
# rescale to be between 0 and 1
|
||||
if normalized:
|
||||
l = len(between)
|
||||
if l > 2:
|
||||
# scale by 1/the number of possible paths
|
||||
scale = 1.0 / float((l - 1) * (l - 2))
|
||||
for v in between:
|
||||
between[v] *= scale
|
||||
return between
|
||||
|
||||
|
||||
load_centrality = newman_betweenness_centrality
|
||||
|
||||
|
||||
def edge_load_centrality(G, cutoff=False):
|
||||
"""Compute edge load.
|
||||
|
||||
WARNING: This concept of edge load has not been analysed
|
||||
or discussed outside of NetworkX that we know of.
|
||||
It is based loosely on load_centrality in the sense that
|
||||
it counts the number of shortest paths which cross each edge.
|
||||
This function is for demonstration and testing purposes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A networkx graph
|
||||
|
||||
cutoff : bool, optional (default=False)
|
||||
If specified, only consider paths of length <= cutoff.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A dict keyed by edge 2-tuple to the number of shortest paths
|
||||
which use that edge. Where more than one path is shortest
|
||||
the count is divided equally among paths.
|
||||
"""
|
||||
betweenness = {}
|
||||
for u, v in G.edges():
|
||||
betweenness[(u, v)] = 0.0
|
||||
betweenness[(v, u)] = 0.0
|
||||
|
||||
for source in G:
|
||||
ubetween = _edge_betweenness(G, source, cutoff=cutoff)
|
||||
for e, ubetweenv in ubetween.items():
|
||||
betweenness[e] += ubetweenv # cumulative total
|
||||
return betweenness
|
||||
|
||||
|
||||
def _edge_betweenness(G, source, nodes=None, cutoff=False):
|
||||
"""Edge betweenness helper."""
|
||||
# get the predecessor data
|
||||
(pred, length) = nx.predecessor(G, source, cutoff=cutoff, return_seen=True)
|
||||
# order the nodes by path length
|
||||
onodes = [n for n, d in sorted(length.items(), key=itemgetter(1))]
|
||||
# initialize betweenness, doesn't account for any edge weights
|
||||
between = {}
|
||||
for u, v in G.edges(nodes):
|
||||
between[(u, v)] = 1.0
|
||||
between[(v, u)] = 1.0
|
||||
|
||||
while onodes: # work through all paths
|
||||
v = onodes.pop()
|
||||
if v in pred:
|
||||
# Discount betweenness if more than one shortest path.
|
||||
num_paths = len(pred[v])
|
||||
for w in pred[v]:
|
||||
if w in pred:
|
||||
# Discount betweenness, mult path
|
||||
num_paths = len(pred[w])
|
||||
for x in pred[w]:
|
||||
between[(w, x)] += between[(v, w)] / num_paths
|
||||
between[(x, w)] += between[(w, v)] / num_paths
|
||||
return between
|
|
@ -0,0 +1,123 @@
|
|||
"""Percolation centrality measures."""
|
||||
|
||||
import networkx as nx
|
||||
|
||||
from networkx.algorithms.centrality.betweenness import (
|
||||
_single_source_dijkstra_path_basic as dijkstra,
|
||||
)
|
||||
from networkx.algorithms.centrality.betweenness import (
|
||||
_single_source_shortest_path_basic as shortest_path,
|
||||
)
|
||||
|
||||
__all__ = ["percolation_centrality"]
|
||||
|
||||
|
||||
def percolation_centrality(G, attribute="percolation", states=None, weight=None):
|
||||
r"""Compute the percolation centrality for nodes.
|
||||
|
||||
Percolation centrality of a node $v$, at a given time, is defined
|
||||
as the proportion of ‘percolated paths’ that go through that node.
|
||||
|
||||
This measure quantifies relative impact of nodes based on their
|
||||
topological connectivity, as well as their percolation states.
|
||||
|
||||
Percolation states of nodes are used to depict network percolation
|
||||
scenarios (such as during infection transmission in a social network
|
||||
of individuals, spreading of computer viruses on computer networks, or
|
||||
transmission of disease over a network of towns) over time. In this
|
||||
measure usually the percolation state is expressed as a decimal
|
||||
between 0.0 and 1.0.
|
||||
|
||||
When all nodes are in the same percolated state this measure is
|
||||
equivalent to betweenness centrality.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
attribute : None or string, optional (default='percolation')
|
||||
Name of the node attribute to use for percolation state, used
|
||||
if `states` is None.
|
||||
|
||||
states : None or dict, optional (default=None)
|
||||
Specify percolation states for the nodes, nodes as keys states
|
||||
as values.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, all edge weights are considered equal.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with percolation centrality as the value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm is from Mahendra Piraveenan, Mikhail Prokopenko, and
|
||||
Liaquat Hossain [1]_
|
||||
Pair dependecies are calculated and accumulated using [2]_
|
||||
|
||||
For weighted graphs the edge weights must be greater than zero.
|
||||
Zero edge weights can produce an infinite number of equal length
|
||||
paths between pairs of nodes.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Mahendra Piraveenan, Mikhail Prokopenko, Liaquat Hossain
|
||||
Percolation Centrality: Quantifying Graph-Theoretic Impact of Nodes
|
||||
during Percolation in Networks
|
||||
http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0053095
|
||||
.. [2] Ulrik Brandes:
|
||||
A Faster Algorithm for Betweenness Centrality.
|
||||
Journal of Mathematical Sociology 25(2):163-177, 2001.
|
||||
http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf
|
||||
"""
|
||||
percolation = dict.fromkeys(G, 0.0) # b[v]=0 for v in G
|
||||
|
||||
nodes = G
|
||||
|
||||
if states is None:
|
||||
states = nx.get_node_attributes(nodes, attribute)
|
||||
|
||||
# sum of all percolation states
|
||||
p_sigma_x_t = 0.0
|
||||
for v in states.values():
|
||||
p_sigma_x_t += v
|
||||
|
||||
for s in nodes:
|
||||
# single source shortest paths
|
||||
if weight is None: # use BFS
|
||||
S, P, sigma = shortest_path(G, s)
|
||||
else: # use Dijkstra's algorithm
|
||||
S, P, sigma = dijkstra(G, s, weight)
|
||||
# accumulation
|
||||
percolation = _accumulate_percolation(
|
||||
percolation, G, S, P, sigma, s, states, p_sigma_x_t
|
||||
)
|
||||
|
||||
n = len(G)
|
||||
|
||||
for v in percolation:
|
||||
percolation[v] *= 1 / (n - 2)
|
||||
|
||||
return percolation
|
||||
|
||||
|
||||
def _accumulate_percolation(percolation, G, S, P, sigma, s, states, p_sigma_x_t):
|
||||
delta = dict.fromkeys(S, 0)
|
||||
while S:
|
||||
w = S.pop()
|
||||
coeff = (1 + delta[w]) / sigma[w]
|
||||
for v in P[w]:
|
||||
delta[v] += sigma[v] * coeff
|
||||
if w != s:
|
||||
# percolation weight
|
||||
pw_s_w = states[s] / (p_sigma_x_t - states[w])
|
||||
percolation[w] += delta[w] * pw_s_w
|
||||
return percolation
|
|
@ -0,0 +1,205 @@
|
|||
"""Functions for computing reaching centrality of a node or a graph."""
|
||||
|
||||
import networkx as nx
|
||||
|
||||
from networkx.utils import pairwise
|
||||
|
||||
__all__ = ["global_reaching_centrality", "local_reaching_centrality"]
|
||||
|
||||
|
||||
def _average_weight(G, path, weight=None):
|
||||
"""Returns the average weight of an edge in a weighted path.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A networkx graph.
|
||||
|
||||
path: list
|
||||
A list of vertices that define the path.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
If None, edge weights are ignored. Then the average weight of an edge
|
||||
is assumed to be the multiplicative inverse of the length of the path.
|
||||
Otherwise holds the name of the edge attribute used as weight.
|
||||
"""
|
||||
path_length = len(path) - 1
|
||||
if path_length <= 0:
|
||||
return 0
|
||||
if weight is None:
|
||||
return 1 / path_length
|
||||
total_weight = sum(G.edges[i, j][weight] for i, j in pairwise(path))
|
||||
return total_weight / path_length
|
||||
|
||||
|
||||
def global_reaching_centrality(G, weight=None, normalized=True):
|
||||
"""Returns the global reaching centrality of a directed graph.
|
||||
|
||||
The *global reaching centrality* of a weighted directed graph is the
|
||||
average over all nodes of the difference between the local reaching
|
||||
centrality of the node and the greatest local reaching centrality of
|
||||
any node in the graph [1]_. For more information on the local
|
||||
reaching centrality, see :func:`local_reaching_centrality`.
|
||||
Informally, the local reaching centrality is the proportion of the
|
||||
graph that is reachable from the neighbors of the node.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : DiGraph
|
||||
A networkx DiGraph.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
Attribute to use for edge weights. If ``None``, each edge weight
|
||||
is assumed to be one. A higher weight implies a stronger
|
||||
connection between nodes and a *shorter* path length.
|
||||
|
||||
normalized : bool, optional (default=True)
|
||||
Whether to normalize the edge weights by the total sum of edge
|
||||
weights.
|
||||
|
||||
Returns
|
||||
-------
|
||||
h : float
|
||||
The global reaching centrality of the graph.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.DiGraph()
|
||||
>>> G.add_edge(1, 2)
|
||||
>>> G.add_edge(1, 3)
|
||||
>>> nx.global_reaching_centrality(G)
|
||||
1.0
|
||||
>>> G.add_edge(3, 2)
|
||||
>>> nx.global_reaching_centrality(G)
|
||||
0.75
|
||||
|
||||
See also
|
||||
--------
|
||||
local_reaching_centrality
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Mones, Enys, Lilla Vicsek, and Tamás Vicsek.
|
||||
"Hierarchy Measure for Complex Networks."
|
||||
*PLoS ONE* 7.3 (2012): e33799.
|
||||
https://doi.org/10.1371/journal.pone.0033799
|
||||
"""
|
||||
if nx.is_negatively_weighted(G, weight=weight):
|
||||
raise nx.NetworkXError("edge weights must be positive")
|
||||
total_weight = G.size(weight=weight)
|
||||
if total_weight <= 0:
|
||||
raise nx.NetworkXError("Size of G must be positive")
|
||||
|
||||
# If provided, weights must be interpreted as connection strength
|
||||
# (so higher weights are more likely to be chosen). However, the
|
||||
# shortest path algorithms in NetworkX assume the provided "weight"
|
||||
# is actually a distance (so edges with higher weight are less
|
||||
# likely to be chosen). Therefore we need to invert the weights when
|
||||
# computing shortest paths.
|
||||
#
|
||||
# If weight is None, we leave it as-is so that the shortest path
|
||||
# algorithm can use a faster, unweighted algorithm.
|
||||
if weight is not None:
|
||||
|
||||
def as_distance(u, v, d):
|
||||
return total_weight / d.get(weight, 1)
|
||||
|
||||
shortest_paths = nx.shortest_path(G, weight=as_distance)
|
||||
else:
|
||||
shortest_paths = nx.shortest_path(G)
|
||||
|
||||
centrality = local_reaching_centrality
|
||||
# TODO This can be trivially parallelized.
|
||||
lrc = [
|
||||
centrality(G, node, paths=paths, weight=weight, normalized=normalized)
|
||||
for node, paths in shortest_paths.items()
|
||||
]
|
||||
|
||||
max_lrc = max(lrc)
|
||||
return sum(max_lrc - c for c in lrc) / (len(G) - 1)
|
||||
|
||||
|
||||
def local_reaching_centrality(G, v, paths=None, weight=None, normalized=True):
|
||||
"""Returns the local reaching centrality of a node in a directed
|
||||
graph.
|
||||
|
||||
The *local reaching centrality* of a node in a directed graph is the
|
||||
proportion of other nodes reachable from that node [1]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : DiGraph
|
||||
A NetworkX DiGraph.
|
||||
|
||||
v : node
|
||||
A node in the directed graph `G`.
|
||||
|
||||
paths : dictionary (default=None)
|
||||
If this is not `None` it must be a dictionary representation
|
||||
of single-source shortest paths, as computed by, for example,
|
||||
:func:`networkx.shortest_path` with source node `v`. Use this
|
||||
keyword argument if you intend to invoke this function many
|
||||
times but don't want the paths to be recomputed each time.
|
||||
|
||||
weight : None or string, optional (default=None)
|
||||
Attribute to use for edge weights. If `None`, each edge weight
|
||||
is assumed to be one. A higher weight implies a stronger
|
||||
connection between nodes and a *shorter* path length.
|
||||
|
||||
normalized : bool, optional (default=True)
|
||||
Whether to normalize the edge weights by the total sum of edge
|
||||
weights.
|
||||
|
||||
Returns
|
||||
-------
|
||||
h : float
|
||||
The local reaching centrality of the node ``v`` in the graph
|
||||
``G``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.DiGraph()
|
||||
>>> G.add_edges_from([(1, 2), (1, 3)])
|
||||
>>> nx.local_reaching_centrality(G, 3)
|
||||
0.0
|
||||
>>> G.add_edge(3, 2)
|
||||
>>> nx.local_reaching_centrality(G, 3)
|
||||
0.5
|
||||
|
||||
See also
|
||||
--------
|
||||
global_reaching_centrality
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Mones, Enys, Lilla Vicsek, and Tamás Vicsek.
|
||||
"Hierarchy Measure for Complex Networks."
|
||||
*PLoS ONE* 7.3 (2012): e33799.
|
||||
https://doi.org/10.1371/journal.pone.0033799
|
||||
"""
|
||||
if paths is None:
|
||||
if nx.is_negatively_weighted(G, weight=weight):
|
||||
raise nx.NetworkXError("edge weights must be positive")
|
||||
total_weight = G.size(weight=weight)
|
||||
if total_weight <= 0:
|
||||
raise nx.NetworkXError("Size of G must be positive")
|
||||
if weight is not None:
|
||||
# Interpret weights as lengths.
|
||||
def as_distance(u, v, d):
|
||||
return total_weight / d.get(weight, 1)
|
||||
|
||||
paths = nx.shortest_path(G, source=v, weight=as_distance)
|
||||
else:
|
||||
paths = nx.shortest_path(G, source=v)
|
||||
# If the graph is unweighted, simply return the proportion of nodes
|
||||
# reachable from the source node ``v``.
|
||||
if weight is None and G.is_directed():
|
||||
return (len(paths) - 1) / (len(G) - 1)
|
||||
if normalized and weight is not None:
|
||||
norm = G.size(weight=weight) / G.size()
|
||||
else:
|
||||
norm = 1
|
||||
# TODO This can be trivially parallelized.
|
||||
avgw = (_average_weight(G, path, weight=weight) for path in paths.values())
|
||||
sum_avg_weight = sum(avgw) / norm
|
||||
return sum_avg_weight / (len(G) - 1)
|
|
@ -0,0 +1,138 @@
|
|||
"""Copyright (c) 2015 – Thomson Licensing, SAS
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted (subject to the limitations in the
|
||||
disclaimer below) provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of Thomson Licensing, or Technicolor, nor the names
|
||||
of its contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
|
||||
GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
|
||||
HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
# Authors: Erwan Le Merrer (erwan.lemerrer@technicolor.com)
|
||||
""" Second order centrality measure."""
|
||||
|
||||
__all__ = ["second_order_centrality"]
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def second_order_centrality(G):
|
||||
"""Compute the second order centrality for nodes of G.
|
||||
|
||||
The second order centrality of a given node is the standard deviation of
|
||||
the return times to that node of a perpetual random walk on G:
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX connected and undirected graph.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary keyed by node with second order centrality as the value.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.star_graph(10)
|
||||
>>> soc = nx.second_order_centrality(G)
|
||||
>>> print(sorted(soc.items(), key=lambda x: x[1])[0][0]) # pick first id
|
||||
0
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXException
|
||||
If the graph G is empty, non connected or has negative weights.
|
||||
|
||||
See Also
|
||||
--------
|
||||
betweenness_centrality
|
||||
|
||||
Notes
|
||||
-----
|
||||
Lower values of second order centrality indicate higher centrality.
|
||||
|
||||
The algorithm is from Kermarrec, Le Merrer, Sericola and Trédan [1]_.
|
||||
|
||||
This code implements the analytical version of the algorithm, i.e.,
|
||||
there is no simulation of a random walk process involved. The random walk
|
||||
is here unbiased (corresponding to eq 6 of the paper [1]_), thus the
|
||||
centrality values are the standard deviations for random walk return times
|
||||
on the transformed input graph G (equal in-degree at each nodes by adding
|
||||
self-loops).
|
||||
|
||||
Complexity of this implementation, made to run locally on a single machine,
|
||||
is O(n^3), with n the size of G, which makes it viable only for small
|
||||
graphs.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Anne-Marie Kermarrec, Erwan Le Merrer, Bruno Sericola, Gilles Trédan
|
||||
"Second order centrality: Distributed assessment of nodes criticity in
|
||||
complex networks", Elsevier Computer Communications 34(5):619-628, 2011.
|
||||
"""
|
||||
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError as e:
|
||||
raise ImportError("Requires NumPy: http://numpy.org/") from e
|
||||
|
||||
n = len(G)
|
||||
|
||||
if n == 0:
|
||||
raise nx.NetworkXException("Empty graph.")
|
||||
if not nx.is_connected(G):
|
||||
raise nx.NetworkXException("Non connected graph.")
|
||||
if any(d.get("weight", 0) < 0 for u, v, d in G.edges(data=True)):
|
||||
raise nx.NetworkXException("Graph has negative edge weights.")
|
||||
|
||||
# balancing G for Metropolis-Hastings random walks
|
||||
G = nx.DiGraph(G)
|
||||
in_deg = dict(G.in_degree(weight="weight"))
|
||||
d_max = max(in_deg.values())
|
||||
for i, deg in in_deg.items():
|
||||
if deg < d_max:
|
||||
G.add_edge(i, i, weight=d_max - deg)
|
||||
|
||||
P = nx.to_numpy_array(G)
|
||||
P /= P.sum(axis=1)[:, np.newaxis] # to transition probability matrix
|
||||
|
||||
def _Qj(P, j):
|
||||
P = P.copy()
|
||||
P[:, j] = 0
|
||||
return P
|
||||
|
||||
M = np.empty([n, n])
|
||||
|
||||
for i in range(n):
|
||||
M[:, i] = np.linalg.solve(
|
||||
np.identity(n) - _Qj(P, i), np.ones([n, 1])[:, 0]
|
||||
) # eq 3
|
||||
|
||||
return dict(
|
||||
zip(G.nodes, [np.sqrt(2 * np.sum(M[:, i]) - n * (n + 1)) for i in range(n)])
|
||||
) # eq 6
|
|
@ -0,0 +1,347 @@
|
|||
"""
|
||||
Subraph centrality and communicability betweenness.
|
||||
"""
|
||||
import networkx as nx
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
__all__ = [
|
||||
"subgraph_centrality_exp",
|
||||
"subgraph_centrality",
|
||||
"communicability_betweenness_centrality",
|
||||
"estrada_index",
|
||||
]
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def subgraph_centrality_exp(G):
|
||||
r"""Returns the subgraph centrality for each node of G.
|
||||
|
||||
Subgraph centrality of a node `n` is the sum of weighted closed
|
||||
walks of all lengths starting and ending at node `n`. The weights
|
||||
decrease with path length. Each closed walk is associated with a
|
||||
connected subgraph ([1]_).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G: graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes:dictionary
|
||||
Dictionary of nodes with subgraph centrality as the value.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the graph is not undirected and simple.
|
||||
|
||||
See Also
|
||||
--------
|
||||
subgraph_centrality:
|
||||
Alternative algorithm of the subgraph centrality for each node of G.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This version of the algorithm exponentiates the adjacency matrix.
|
||||
|
||||
The subgraph centrality of a node `u` in G can be found using
|
||||
the matrix exponential of the adjacency matrix of G [1]_,
|
||||
|
||||
.. math::
|
||||
|
||||
SC(u)=(e^A)_{uu} .
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Ernesto Estrada, Juan A. Rodriguez-Velazquez,
|
||||
"Subgraph centrality in complex networks",
|
||||
Physical Review E 71, 056103 (2005).
|
||||
https://arxiv.org/abs/cond-mat/0504730
|
||||
|
||||
Examples
|
||||
--------
|
||||
(Example from [1]_)
|
||||
>>> G = nx.Graph(
|
||||
... [
|
||||
... (1, 2),
|
||||
... (1, 5),
|
||||
... (1, 8),
|
||||
... (2, 3),
|
||||
... (2, 8),
|
||||
... (3, 4),
|
||||
... (3, 6),
|
||||
... (4, 5),
|
||||
... (4, 7),
|
||||
... (5, 6),
|
||||
... (6, 7),
|
||||
... (7, 8),
|
||||
... ]
|
||||
... )
|
||||
>>> sc = nx.subgraph_centrality_exp(G)
|
||||
>>> print([f"{node} {sc[node]:0.2f}" for node in sorted(sc)])
|
||||
['1 3.90', '2 3.90', '3 3.64', '4 3.71', '5 3.64', '6 3.71', '7 3.64', '8 3.90']
|
||||
"""
|
||||
# alternative implementation that calculates the matrix exponential
|
||||
import scipy.linalg
|
||||
|
||||
nodelist = list(G) # ordering of nodes in matrix
|
||||
A = nx.to_numpy_array(G, nodelist)
|
||||
# convert to 0-1 matrix
|
||||
A[A != 0.0] = 1
|
||||
expA = scipy.linalg.expm(A)
|
||||
# convert diagonal to dictionary keyed by node
|
||||
sc = dict(zip(nodelist, map(float, expA.diagonal())))
|
||||
return sc
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def subgraph_centrality(G):
|
||||
r"""Returns subgraph centrality for each node in G.
|
||||
|
||||
Subgraph centrality of a node `n` is the sum of weighted closed
|
||||
walks of all lengths starting and ending at node `n`. The weights
|
||||
decrease with path length. Each closed walk is associated with a
|
||||
connected subgraph ([1]_).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G: graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with subgraph centrality as the value.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the graph is not undirected and simple.
|
||||
|
||||
See Also
|
||||
--------
|
||||
subgraph_centrality_exp:
|
||||
Alternative algorithm of the subgraph centrality for each node of G.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This version of the algorithm computes eigenvalues and eigenvectors
|
||||
of the adjacency matrix.
|
||||
|
||||
Subgraph centrality of a node `u` in G can be found using
|
||||
a spectral decomposition of the adjacency matrix [1]_,
|
||||
|
||||
.. math::
|
||||
|
||||
SC(u)=\sum_{j=1}^{N}(v_{j}^{u})^2 e^{\lambda_{j}},
|
||||
|
||||
where `v_j` is an eigenvector of the adjacency matrix `A` of G
|
||||
corresponding corresponding to the eigenvalue `\lambda_j`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
(Example from [1]_)
|
||||
>>> G = nx.Graph(
|
||||
... [
|
||||
... (1, 2),
|
||||
... (1, 5),
|
||||
... (1, 8),
|
||||
... (2, 3),
|
||||
... (2, 8),
|
||||
... (3, 4),
|
||||
... (3, 6),
|
||||
... (4, 5),
|
||||
... (4, 7),
|
||||
... (5, 6),
|
||||
... (6, 7),
|
||||
... (7, 8),
|
||||
... ]
|
||||
... )
|
||||
>>> sc = nx.subgraph_centrality(G)
|
||||
>>> print([f"{node} {sc[node]:0.2f}" for node in sorted(sc)])
|
||||
['1 3.90', '2 3.90', '3 3.64', '4 3.71', '5 3.64', '6 3.71', '7 3.64', '8 3.90']
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Ernesto Estrada, Juan A. Rodriguez-Velazquez,
|
||||
"Subgraph centrality in complex networks",
|
||||
Physical Review E 71, 056103 (2005).
|
||||
https://arxiv.org/abs/cond-mat/0504730
|
||||
|
||||
"""
|
||||
import numpy as np
|
||||
import numpy.linalg
|
||||
|
||||
nodelist = list(G) # ordering of nodes in matrix
|
||||
A = nx.to_numpy_array(G, nodelist)
|
||||
# convert to 0-1 matrix
|
||||
A[np.nonzero(A)] = 1
|
||||
w, v = numpy.linalg.eigh(A)
|
||||
vsquare = np.array(v) ** 2
|
||||
expw = np.exp(w)
|
||||
xg = np.dot(vsquare, expw)
|
||||
# convert vector dictionary keyed by node
|
||||
sc = dict(zip(nodelist, map(float, xg)))
|
||||
return sc
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def communicability_betweenness_centrality(G, normalized=True):
|
||||
r"""Returns subgraph communicability for all pairs of nodes in G.
|
||||
|
||||
Communicability betweenness measure makes use of the number of walks
|
||||
connecting every pair of nodes as the basis of a betweenness centrality
|
||||
measure.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G: graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes : dictionary
|
||||
Dictionary of nodes with communicability betweenness as the value.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the graph is not undirected and simple.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Let `G=(V,E)` be a simple undirected graph with `n` nodes and `m` edges,
|
||||
and `A` denote the adjacency matrix of `G`.
|
||||
|
||||
Let `G(r)=(V,E(r))` be the graph resulting from
|
||||
removing all edges connected to node `r` but not the node itself.
|
||||
|
||||
The adjacency matrix for `G(r)` is `A+E(r)`, where `E(r)` has nonzeros
|
||||
only in row and column `r`.
|
||||
|
||||
The subraph betweenness of a node `r` is [1]_
|
||||
|
||||
.. math::
|
||||
|
||||
\omega_{r} = \frac{1}{C}\sum_{p}\sum_{q}\frac{G_{prq}}{G_{pq}},
|
||||
p\neq q, q\neq r,
|
||||
|
||||
where
|
||||
`G_{prq}=(e^{A}_{pq} - (e^{A+E(r)})_{pq}` is the number of walks
|
||||
involving node r,
|
||||
`G_{pq}=(e^{A})_{pq}` is the number of closed walks starting
|
||||
at node `p` and ending at node `q`,
|
||||
and `C=(n-1)^{2}-(n-1)` is a normalization factor equal to the
|
||||
number of terms in the sum.
|
||||
|
||||
The resulting `\omega_{r}` takes values between zero and one.
|
||||
The lower bound cannot be attained for a connected
|
||||
graph, and the upper bound is attained in the star graph.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Ernesto Estrada, Desmond J. Higham, Naomichi Hatano,
|
||||
"Communicability Betweenness in Complex Networks"
|
||||
Physica A 388 (2009) 764-774.
|
||||
https://arxiv.org/abs/0905.4102
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.Graph([(0, 1), (1, 2), (1, 5), (5, 4), (2, 4), (2, 3), (4, 3), (3, 6)])
|
||||
>>> cbc = nx.communicability_betweenness_centrality(G)
|
||||
>>> print([f"{node} {cbc[node]:0.2f}" for node in sorted(cbc)])
|
||||
['0 0.03', '1 0.45', '2 0.51', '3 0.45', '4 0.40', '5 0.19', '6 0.03']
|
||||
"""
|
||||
import numpy as np
|
||||
import scipy.linalg
|
||||
|
||||
nodelist = list(G) # ordering of nodes in matrix
|
||||
n = len(nodelist)
|
||||
A = nx.to_numpy_array(G, nodelist)
|
||||
# convert to 0-1 matrix
|
||||
A[np.nonzero(A)] = 1
|
||||
expA = scipy.linalg.expm(A)
|
||||
mapping = dict(zip(nodelist, range(n)))
|
||||
cbc = {}
|
||||
for v in G:
|
||||
# remove row and col of node v
|
||||
i = mapping[v]
|
||||
row = A[i, :].copy()
|
||||
col = A[:, i].copy()
|
||||
A[i, :] = 0
|
||||
A[:, i] = 0
|
||||
B = (expA - scipy.linalg.expm(A)) / expA
|
||||
# sum with row/col of node v and diag set to zero
|
||||
B[i, :] = 0
|
||||
B[:, i] = 0
|
||||
B -= np.diag(np.diag(B))
|
||||
cbc[v] = float(B.sum())
|
||||
# put row and col back
|
||||
A[i, :] = row
|
||||
A[:, i] = col
|
||||
# rescaling
|
||||
cbc = _rescale(cbc, normalized=normalized)
|
||||
return cbc
|
||||
|
||||
|
||||
def _rescale(cbc, normalized):
|
||||
# helper to rescale betweenness centrality
|
||||
if normalized is True:
|
||||
order = len(cbc)
|
||||
if order <= 2:
|
||||
scale = None
|
||||
else:
|
||||
scale = 1.0 / ((order - 1.0) ** 2 - (order - 1.0))
|
||||
if scale is not None:
|
||||
for v in cbc:
|
||||
cbc[v] *= scale
|
||||
return cbc
|
||||
|
||||
|
||||
def estrada_index(G):
|
||||
r"""Returns the Estrada index of a the graph G.
|
||||
|
||||
The Estrada Index is a topological index of folding or 3D "compactness" ([1]_).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G: graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
estrada index: float
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the graph is not undirected and simple.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Let `G=(V,E)` be a simple undirected graph with `n` nodes and let
|
||||
`\lambda_{1}\leq\lambda_{2}\leq\cdots\lambda_{n}`
|
||||
be a non-increasing ordering of the eigenvalues of its adjacency
|
||||
matrix `A`. The Estrada index is ([1]_, [2]_)
|
||||
|
||||
.. math::
|
||||
EE(G)=\sum_{j=1}^n e^{\lambda _j}.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] E. Estrada, "Characterization of 3D molecular structure",
|
||||
Chem. Phys. Lett. 319, 713 (2000).
|
||||
https://doi.org/10.1016/S0009-2614(00)00158-5
|
||||
.. [2] José Antonio de la Peñaa, Ivan Gutman, Juan Rada,
|
||||
"Estimating the Estrada index",
|
||||
Linear Algebra and its Applications. 427, 1 (2007).
|
||||
https://doi.org/10.1016/j.laa.2007.06.020
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.Graph([(0, 1), (1, 2), (1, 5), (5, 4), (2, 4), (2, 3), (4, 3), (3, 6)])
|
||||
>>> ei = nx.estrada_index(G)
|
||||
>>> print(f"{ei:0.5}")
|
||||
20.55
|
||||
"""
|
||||
return sum(subgraph_centrality(G).values())
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,657 @@
|
|||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
def weighted_G():
|
||||
G = nx.Graph()
|
||||
G.add_edge(0, 1, weight=3)
|
||||
G.add_edge(0, 2, weight=2)
|
||||
G.add_edge(0, 3, weight=6)
|
||||
G.add_edge(0, 4, weight=4)
|
||||
G.add_edge(1, 3, weight=5)
|
||||
G.add_edge(1, 5, weight=5)
|
||||
G.add_edge(2, 4, weight=1)
|
||||
G.add_edge(3, 4, weight=2)
|
||||
G.add_edge(3, 5, weight=1)
|
||||
G.add_edge(4, 5, weight=4)
|
||||
return G
|
||||
|
||||
|
||||
class TestBetweennessCentrality:
|
||||
def test_K5(self):
|
||||
"""Betweenness centrality: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=False)
|
||||
b_answer = {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_K5_endpoints(self):
|
||||
"""Betweenness centrality: K5 endpoints"""
|
||||
G = nx.complete_graph(5)
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=False, endpoints=True)
|
||||
b_answer = {0: 4.0, 1: 4.0, 2: 4.0, 3: 4.0, 4: 4.0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
# normalized = True case
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=True, endpoints=True)
|
||||
b_answer = {0: 0.4, 1: 0.4, 2: 0.4, 3: 0.4, 4: 0.4}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P3_normalized(self):
|
||||
"""Betweenness centrality: P3 normalized"""
|
||||
G = nx.path_graph(3)
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=True)
|
||||
b_answer = {0: 0.0, 1: 1.0, 2: 0.0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P3(self):
|
||||
"""Betweenness centrality: P3"""
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 0.0, 1: 1.0, 2: 0.0}
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=False)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_sample_from_P3(self):
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 0.0, 1: 1.0, 2: 0.0}
|
||||
b = nx.betweenness_centrality(G, k=3, weight=None, normalized=False, seed=1)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
b = nx.betweenness_centrality(G, k=2, weight=None, normalized=False, seed=1)
|
||||
# python versions give different results with same seed
|
||||
b_approx1 = {0: 0.0, 1: 1.5, 2: 0.0}
|
||||
b_approx2 = {0: 0.0, 1: 0.75, 2: 0.0}
|
||||
for n in sorted(G):
|
||||
assert b[n] in (b_approx1[n], b_approx2[n])
|
||||
|
||||
def test_P3_endpoints(self):
|
||||
"""Betweenness centrality: P3 endpoints"""
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 2.0, 1: 3.0, 2: 2.0}
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=False, endpoints=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
# normalized = True case
|
||||
b_answer = {0: 2 / 3, 1: 1.0, 2: 2 / 3}
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=True, endpoints=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_krackhardt_kite_graph(self):
|
||||
"""Betweenness centrality: Krackhardt kite graph"""
|
||||
G = nx.krackhardt_kite_graph()
|
||||
b_answer = {
|
||||
0: 1.667,
|
||||
1: 1.667,
|
||||
2: 0.000,
|
||||
3: 7.333,
|
||||
4: 0.000,
|
||||
5: 16.667,
|
||||
6: 16.667,
|
||||
7: 28.000,
|
||||
8: 16.000,
|
||||
9: 0.000,
|
||||
}
|
||||
for b in b_answer:
|
||||
b_answer[b] /= 2
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=False)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_krackhardt_kite_graph_normalized(self):
|
||||
"""Betweenness centrality: Krackhardt kite graph normalized"""
|
||||
G = nx.krackhardt_kite_graph()
|
||||
b_answer = {
|
||||
0: 0.023,
|
||||
1: 0.023,
|
||||
2: 0.000,
|
||||
3: 0.102,
|
||||
4: 0.000,
|
||||
5: 0.231,
|
||||
6: 0.231,
|
||||
7: 0.389,
|
||||
8: 0.222,
|
||||
9: 0.000,
|
||||
}
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_florentine_families_graph(self):
|
||||
"""Betweenness centrality: Florentine families graph"""
|
||||
G = nx.florentine_families_graph()
|
||||
b_answer = {
|
||||
"Acciaiuoli": 0.000,
|
||||
"Albizzi": 0.212,
|
||||
"Barbadori": 0.093,
|
||||
"Bischeri": 0.104,
|
||||
"Castellani": 0.055,
|
||||
"Ginori": 0.000,
|
||||
"Guadagni": 0.255,
|
||||
"Lamberteschi": 0.000,
|
||||
"Medici": 0.522,
|
||||
"Pazzi": 0.000,
|
||||
"Peruzzi": 0.022,
|
||||
"Ridolfi": 0.114,
|
||||
"Salviati": 0.143,
|
||||
"Strozzi": 0.103,
|
||||
"Tornabuoni": 0.092,
|
||||
}
|
||||
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_les_miserables_graph(self):
|
||||
"""Betweenness centrality: Les Miserables graph"""
|
||||
G = nx.les_miserables_graph()
|
||||
b_answer = {
|
||||
"Napoleon": 0.000,
|
||||
"Myriel": 0.177,
|
||||
"MlleBaptistine": 0.000,
|
||||
"MmeMagloire": 0.000,
|
||||
"CountessDeLo": 0.000,
|
||||
"Geborand": 0.000,
|
||||
"Champtercier": 0.000,
|
||||
"Cravatte": 0.000,
|
||||
"Count": 0.000,
|
||||
"OldMan": 0.000,
|
||||
"Valjean": 0.570,
|
||||
"Labarre": 0.000,
|
||||
"Marguerite": 0.000,
|
||||
"MmeDeR": 0.000,
|
||||
"Isabeau": 0.000,
|
||||
"Gervais": 0.000,
|
||||
"Listolier": 0.000,
|
||||
"Tholomyes": 0.041,
|
||||
"Fameuil": 0.000,
|
||||
"Blacheville": 0.000,
|
||||
"Favourite": 0.000,
|
||||
"Dahlia": 0.000,
|
||||
"Zephine": 0.000,
|
||||
"Fantine": 0.130,
|
||||
"MmeThenardier": 0.029,
|
||||
"Thenardier": 0.075,
|
||||
"Cosette": 0.024,
|
||||
"Javert": 0.054,
|
||||
"Fauchelevent": 0.026,
|
||||
"Bamatabois": 0.008,
|
||||
"Perpetue": 0.000,
|
||||
"Simplice": 0.009,
|
||||
"Scaufflaire": 0.000,
|
||||
"Woman1": 0.000,
|
||||
"Judge": 0.000,
|
||||
"Champmathieu": 0.000,
|
||||
"Brevet": 0.000,
|
||||
"Chenildieu": 0.000,
|
||||
"Cochepaille": 0.000,
|
||||
"Pontmercy": 0.007,
|
||||
"Boulatruelle": 0.000,
|
||||
"Eponine": 0.011,
|
||||
"Anzelma": 0.000,
|
||||
"Woman2": 0.000,
|
||||
"MotherInnocent": 0.000,
|
||||
"Gribier": 0.000,
|
||||
"MmeBurgon": 0.026,
|
||||
"Jondrette": 0.000,
|
||||
"Gavroche": 0.165,
|
||||
"Gillenormand": 0.020,
|
||||
"Magnon": 0.000,
|
||||
"MlleGillenormand": 0.048,
|
||||
"MmePontmercy": 0.000,
|
||||
"MlleVaubois": 0.000,
|
||||
"LtGillenormand": 0.000,
|
||||
"Marius": 0.132,
|
||||
"BaronessT": 0.000,
|
||||
"Mabeuf": 0.028,
|
||||
"Enjolras": 0.043,
|
||||
"Combeferre": 0.001,
|
||||
"Prouvaire": 0.000,
|
||||
"Feuilly": 0.001,
|
||||
"Courfeyrac": 0.005,
|
||||
"Bahorel": 0.002,
|
||||
"Bossuet": 0.031,
|
||||
"Joly": 0.002,
|
||||
"Grantaire": 0.000,
|
||||
"MotherPlutarch": 0.000,
|
||||
"Gueulemer": 0.005,
|
||||
"Babet": 0.005,
|
||||
"Claquesous": 0.005,
|
||||
"Montparnasse": 0.004,
|
||||
"Toussaint": 0.000,
|
||||
"Child1": 0.000,
|
||||
"Child2": 0.000,
|
||||
"Brujon": 0.000,
|
||||
"MmeHucheloup": 0.000,
|
||||
}
|
||||
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_ladder_graph(self):
|
||||
"""Betweenness centrality: Ladder graph"""
|
||||
G = nx.Graph() # ladder_graph(3)
|
||||
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (4, 5), (3, 5)])
|
||||
b_answer = {0: 1.667, 1: 1.667, 2: 6.667, 3: 6.667, 4: 1.667, 5: 1.667}
|
||||
for b in b_answer:
|
||||
b_answer[b] /= 2
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=False)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_disconnected_path(self):
|
||||
"""Betweenness centrality: disconnected path"""
|
||||
G = nx.Graph()
|
||||
nx.add_path(G, [0, 1, 2])
|
||||
nx.add_path(G, [3, 4, 5, 6])
|
||||
b_answer = {0: 0, 1: 1, 2: 0, 3: 0, 4: 2, 5: 2, 6: 0}
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=False)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_disconnected_path_endpoints(self):
|
||||
"""Betweenness centrality: disconnected path endpoints"""
|
||||
G = nx.Graph()
|
||||
nx.add_path(G, [0, 1, 2])
|
||||
nx.add_path(G, [3, 4, 5, 6])
|
||||
b_answer = {0: 2, 1: 3, 2: 2, 3: 3, 4: 5, 5: 5, 6: 3}
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=False, endpoints=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
# normalized = True case
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=True, endpoints=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n] / 21)
|
||||
|
||||
def test_directed_path(self):
|
||||
"""Betweenness centrality: directed path"""
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, [0, 1, 2])
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=False)
|
||||
b_answer = {0: 0.0, 1: 1.0, 2: 0.0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_directed_path_normalized(self):
|
||||
"""Betweenness centrality: directed path normalized"""
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, [0, 1, 2])
|
||||
b = nx.betweenness_centrality(G, weight=None, normalized=True)
|
||||
b_answer = {0: 0.0, 1: 0.5, 2: 0.0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
|
||||
class TestWeightedBetweennessCentrality:
|
||||
def test_K5(self):
|
||||
"""Weighted betweenness centrality: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
b = nx.betweenness_centrality(G, weight="weight", normalized=False)
|
||||
b_answer = {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P3_normalized(self):
|
||||
"""Weighted betweenness centrality: P3 normalized"""
|
||||
G = nx.path_graph(3)
|
||||
b = nx.betweenness_centrality(G, weight="weight", normalized=True)
|
||||
b_answer = {0: 0.0, 1: 1.0, 2: 0.0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P3(self):
|
||||
"""Weighted betweenness centrality: P3"""
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 0.0, 1: 1.0, 2: 0.0}
|
||||
b = nx.betweenness_centrality(G, weight="weight", normalized=False)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_krackhardt_kite_graph(self):
|
||||
"""Weighted betweenness centrality: Krackhardt kite graph"""
|
||||
G = nx.krackhardt_kite_graph()
|
||||
b_answer = {
|
||||
0: 1.667,
|
||||
1: 1.667,
|
||||
2: 0.000,
|
||||
3: 7.333,
|
||||
4: 0.000,
|
||||
5: 16.667,
|
||||
6: 16.667,
|
||||
7: 28.000,
|
||||
8: 16.000,
|
||||
9: 0.000,
|
||||
}
|
||||
for b in b_answer:
|
||||
b_answer[b] /= 2
|
||||
|
||||
b = nx.betweenness_centrality(G, weight="weight", normalized=False)
|
||||
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_krackhardt_kite_graph_normalized(self):
|
||||
"""Weighted betweenness centrality:
|
||||
Krackhardt kite graph normalized
|
||||
"""
|
||||
G = nx.krackhardt_kite_graph()
|
||||
b_answer = {
|
||||
0: 0.023,
|
||||
1: 0.023,
|
||||
2: 0.000,
|
||||
3: 0.102,
|
||||
4: 0.000,
|
||||
5: 0.231,
|
||||
6: 0.231,
|
||||
7: 0.389,
|
||||
8: 0.222,
|
||||
9: 0.000,
|
||||
}
|
||||
b = nx.betweenness_centrality(G, weight="weight", normalized=True)
|
||||
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_florentine_families_graph(self):
|
||||
"""Weighted betweenness centrality:
|
||||
Florentine families graph"""
|
||||
G = nx.florentine_families_graph()
|
||||
b_answer = {
|
||||
"Acciaiuoli": 0.000,
|
||||
"Albizzi": 0.212,
|
||||
"Barbadori": 0.093,
|
||||
"Bischeri": 0.104,
|
||||
"Castellani": 0.055,
|
||||
"Ginori": 0.000,
|
||||
"Guadagni": 0.255,
|
||||
"Lamberteschi": 0.000,
|
||||
"Medici": 0.522,
|
||||
"Pazzi": 0.000,
|
||||
"Peruzzi": 0.022,
|
||||
"Ridolfi": 0.114,
|
||||
"Salviati": 0.143,
|
||||
"Strozzi": 0.103,
|
||||
"Tornabuoni": 0.092,
|
||||
}
|
||||
|
||||
b = nx.betweenness_centrality(G, weight="weight", normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_les_miserables_graph(self):
|
||||
"""Weighted betweenness centrality: Les Miserables graph"""
|
||||
G = nx.les_miserables_graph()
|
||||
b_answer = {
|
||||
"Napoleon": 0.000,
|
||||
"Myriel": 0.177,
|
||||
"MlleBaptistine": 0.000,
|
||||
"MmeMagloire": 0.000,
|
||||
"CountessDeLo": 0.000,
|
||||
"Geborand": 0.000,
|
||||
"Champtercier": 0.000,
|
||||
"Cravatte": 0.000,
|
||||
"Count": 0.000,
|
||||
"OldMan": 0.000,
|
||||
"Valjean": 0.454,
|
||||
"Labarre": 0.000,
|
||||
"Marguerite": 0.009,
|
||||
"MmeDeR": 0.000,
|
||||
"Isabeau": 0.000,
|
||||
"Gervais": 0.000,
|
||||
"Listolier": 0.000,
|
||||
"Tholomyes": 0.066,
|
||||
"Fameuil": 0.000,
|
||||
"Blacheville": 0.000,
|
||||
"Favourite": 0.000,
|
||||
"Dahlia": 0.000,
|
||||
"Zephine": 0.000,
|
||||
"Fantine": 0.114,
|
||||
"MmeThenardier": 0.046,
|
||||
"Thenardier": 0.129,
|
||||
"Cosette": 0.075,
|
||||
"Javert": 0.193,
|
||||
"Fauchelevent": 0.026,
|
||||
"Bamatabois": 0.080,
|
||||
"Perpetue": 0.000,
|
||||
"Simplice": 0.001,
|
||||
"Scaufflaire": 0.000,
|
||||
"Woman1": 0.000,
|
||||
"Judge": 0.000,
|
||||
"Champmathieu": 0.000,
|
||||
"Brevet": 0.000,
|
||||
"Chenildieu": 0.000,
|
||||
"Cochepaille": 0.000,
|
||||
"Pontmercy": 0.023,
|
||||
"Boulatruelle": 0.000,
|
||||
"Eponine": 0.023,
|
||||
"Anzelma": 0.000,
|
||||
"Woman2": 0.000,
|
||||
"MotherInnocent": 0.000,
|
||||
"Gribier": 0.000,
|
||||
"MmeBurgon": 0.026,
|
||||
"Jondrette": 0.000,
|
||||
"Gavroche": 0.285,
|
||||
"Gillenormand": 0.024,
|
||||
"Magnon": 0.005,
|
||||
"MlleGillenormand": 0.036,
|
||||
"MmePontmercy": 0.005,
|
||||
"MlleVaubois": 0.000,
|
||||
"LtGillenormand": 0.015,
|
||||
"Marius": 0.072,
|
||||
"BaronessT": 0.004,
|
||||
"Mabeuf": 0.089,
|
||||
"Enjolras": 0.003,
|
||||
"Combeferre": 0.000,
|
||||
"Prouvaire": 0.000,
|
||||
"Feuilly": 0.004,
|
||||
"Courfeyrac": 0.001,
|
||||
"Bahorel": 0.007,
|
||||
"Bossuet": 0.028,
|
||||
"Joly": 0.000,
|
||||
"Grantaire": 0.036,
|
||||
"MotherPlutarch": 0.000,
|
||||
"Gueulemer": 0.025,
|
||||
"Babet": 0.015,
|
||||
"Claquesous": 0.042,
|
||||
"Montparnasse": 0.050,
|
||||
"Toussaint": 0.011,
|
||||
"Child1": 0.000,
|
||||
"Child2": 0.000,
|
||||
"Brujon": 0.002,
|
||||
"MmeHucheloup": 0.034,
|
||||
}
|
||||
|
||||
b = nx.betweenness_centrality(G, weight="weight", normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_ladder_graph(self):
|
||||
"""Weighted betweenness centrality: Ladder graph"""
|
||||
G = nx.Graph() # ladder_graph(3)
|
||||
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (4, 5), (3, 5)])
|
||||
b_answer = {0: 1.667, 1: 1.667, 2: 6.667, 3: 6.667, 4: 1.667, 5: 1.667}
|
||||
for b in b_answer:
|
||||
b_answer[b] /= 2
|
||||
b = nx.betweenness_centrality(G, weight="weight", normalized=False)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_G(self):
|
||||
"""Weighted betweenness centrality: G"""
|
||||
G = weighted_G()
|
||||
b_answer = {0: 2.0, 1: 0.0, 2: 4.0, 3: 3.0, 4: 4.0, 5: 0.0}
|
||||
b = nx.betweenness_centrality(G, weight="weight", normalized=False)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_G2(self):
|
||||
"""Weighted betweenness centrality: G2"""
|
||||
G = nx.DiGraph()
|
||||
G.add_weighted_edges_from(
|
||||
[
|
||||
("s", "u", 10),
|
||||
("s", "x", 5),
|
||||
("u", "v", 1),
|
||||
("u", "x", 2),
|
||||
("v", "y", 1),
|
||||
("x", "u", 3),
|
||||
("x", "v", 5),
|
||||
("x", "y", 2),
|
||||
("y", "s", 7),
|
||||
("y", "v", 6),
|
||||
]
|
||||
)
|
||||
|
||||
b_answer = {"y": 5.0, "x": 5.0, "s": 4.0, "u": 2.0, "v": 2.0}
|
||||
|
||||
b = nx.betweenness_centrality(G, weight="weight", normalized=False)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
|
||||
class TestEdgeBetweennessCentrality:
|
||||
def test_K5(self):
|
||||
"""Edge betweenness centrality: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
b = nx.edge_betweenness_centrality(G, weight=None, normalized=False)
|
||||
b_answer = dict.fromkeys(G.edges(), 1)
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_normalized_K5(self):
|
||||
"""Edge betweenness centrality: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
b = nx.edge_betweenness_centrality(G, weight=None, normalized=True)
|
||||
b_answer = dict.fromkeys(G.edges(), 1 / 10)
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_C4(self):
|
||||
"""Edge betweenness centrality: C4"""
|
||||
G = nx.cycle_graph(4)
|
||||
b = nx.edge_betweenness_centrality(G, weight=None, normalized=True)
|
||||
b_answer = {(0, 1): 2, (0, 3): 2, (1, 2): 2, (2, 3): 2}
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n] / 6)
|
||||
|
||||
def test_P4(self):
|
||||
"""Edge betweenness centrality: P4"""
|
||||
G = nx.path_graph(4)
|
||||
b = nx.edge_betweenness_centrality(G, weight=None, normalized=False)
|
||||
b_answer = {(0, 1): 3, (1, 2): 4, (2, 3): 3}
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_normalized_P4(self):
|
||||
"""Edge betweenness centrality: P4"""
|
||||
G = nx.path_graph(4)
|
||||
b = nx.edge_betweenness_centrality(G, weight=None, normalized=True)
|
||||
b_answer = {(0, 1): 3, (1, 2): 4, (2, 3): 3}
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n] / 6)
|
||||
|
||||
def test_balanced_tree(self):
|
||||
"""Edge betweenness centrality: balanced tree"""
|
||||
G = nx.balanced_tree(r=2, h=2)
|
||||
b = nx.edge_betweenness_centrality(G, weight=None, normalized=False)
|
||||
b_answer = {(0, 1): 12, (0, 2): 12, (1, 3): 6, (1, 4): 6, (2, 5): 6, (2, 6): 6}
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
|
||||
class TestWeightedEdgeBetweennessCentrality:
|
||||
def test_K5(self):
|
||||
"""Edge betweenness centrality: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False)
|
||||
b_answer = dict.fromkeys(G.edges(), 1)
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_C4(self):
|
||||
"""Edge betweenness centrality: C4"""
|
||||
G = nx.cycle_graph(4)
|
||||
b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False)
|
||||
b_answer = {(0, 1): 2, (0, 3): 2, (1, 2): 2, (2, 3): 2}
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P4(self):
|
||||
"""Edge betweenness centrality: P4"""
|
||||
G = nx.path_graph(4)
|
||||
b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False)
|
||||
b_answer = {(0, 1): 3, (1, 2): 4, (2, 3): 3}
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_balanced_tree(self):
|
||||
"""Edge betweenness centrality: balanced tree"""
|
||||
G = nx.balanced_tree(r=2, h=2)
|
||||
b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False)
|
||||
b_answer = {(0, 1): 12, (0, 2): 12, (1, 3): 6, (1, 4): 6, (2, 5): 6, (2, 6): 6}
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_weighted_graph(self):
|
||||
eList = [
|
||||
(0, 1, 5),
|
||||
(0, 2, 4),
|
||||
(0, 3, 3),
|
||||
(0, 4, 2),
|
||||
(1, 2, 4),
|
||||
(1, 3, 1),
|
||||
(1, 4, 3),
|
||||
(2, 4, 5),
|
||||
(3, 4, 4),
|
||||
]
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from(eList)
|
||||
b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False)
|
||||
b_answer = {
|
||||
(0, 1): 0.0,
|
||||
(0, 2): 1.0,
|
||||
(0, 3): 2.0,
|
||||
(0, 4): 1.0,
|
||||
(1, 2): 2.0,
|
||||
(1, 3): 3.5,
|
||||
(1, 4): 1.5,
|
||||
(2, 4): 1.0,
|
||||
(3, 4): 0.5,
|
||||
}
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_normalized_weighted_graph(self):
|
||||
eList = [
|
||||
(0, 1, 5),
|
||||
(0, 2, 4),
|
||||
(0, 3, 3),
|
||||
(0, 4, 2),
|
||||
(1, 2, 4),
|
||||
(1, 3, 1),
|
||||
(1, 4, 3),
|
||||
(2, 4, 5),
|
||||
(3, 4, 4),
|
||||
]
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from(eList)
|
||||
b = nx.edge_betweenness_centrality(G, weight="weight", normalized=True)
|
||||
b_answer = {
|
||||
(0, 1): 0.0,
|
||||
(0, 2): 1.0,
|
||||
(0, 3): 2.0,
|
||||
(0, 4): 1.0,
|
||||
(1, 2): 2.0,
|
||||
(1, 3): 3.5,
|
||||
(1, 4): 1.5,
|
||||
(2, 4): 1.0,
|
||||
(3, 4): 0.5,
|
||||
}
|
||||
norm = len(G) * (len(G) - 1) / 2
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n] / norm)
|
|
@ -0,0 +1,226 @@
|
|||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
class TestSubsetBetweennessCentrality:
|
||||
def test_K5(self):
|
||||
"""Betweenness Centrality Subset: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
b = nx.betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[1, 3], weight=None
|
||||
)
|
||||
b_answer = {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P5_directed(self):
|
||||
"""Betweenness Centrality Subset: P5 directed"""
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, range(5))
|
||||
b_answer = {0: 0, 1: 1, 2: 1, 3: 0, 4: 0, 5: 0}
|
||||
b = nx.betweenness_centrality_subset(G, sources=[0], targets=[3], weight=None)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P5(self):
|
||||
"""Betweenness Centrality Subset: P5"""
|
||||
G = nx.Graph()
|
||||
nx.add_path(G, range(5))
|
||||
b_answer = {0: 0, 1: 0.5, 2: 0.5, 3: 0, 4: 0, 5: 0}
|
||||
b = nx.betweenness_centrality_subset(G, sources=[0], targets=[3], weight=None)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P5_multiple_target(self):
|
||||
"""Betweenness Centrality Subset: P5 multiple target"""
|
||||
G = nx.Graph()
|
||||
nx.add_path(G, range(5))
|
||||
b_answer = {0: 0, 1: 1, 2: 1, 3: 0.5, 4: 0, 5: 0}
|
||||
b = nx.betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[3, 4], weight=None
|
||||
)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_box(self):
|
||||
"""Betweenness Centrality Subset: box"""
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
|
||||
b_answer = {0: 0, 1: 0.25, 2: 0.25, 3: 0}
|
||||
b = nx.betweenness_centrality_subset(G, sources=[0], targets=[3], weight=None)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_box_and_path(self):
|
||||
"""Betweenness Centrality Subset: box and path"""
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (3, 4), (4, 5)])
|
||||
b_answer = {0: 0, 1: 0.5, 2: 0.5, 3: 0.5, 4: 0, 5: 0}
|
||||
b = nx.betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[3, 4], weight=None
|
||||
)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_box_and_path2(self):
|
||||
"""Betweenness Centrality Subset: box and path multiple target"""
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (1, 2), (2, 3), (1, 20), (20, 3), (3, 4)])
|
||||
b_answer = {0: 0, 1: 1.0, 2: 0.5, 20: 0.5, 3: 0.5, 4: 0}
|
||||
b = nx.betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[3, 4], weight=None
|
||||
)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_diamond_multi_path(self):
|
||||
"""Betweenness Centrality Subset: Diamond Multi Path"""
|
||||
G = nx.Graph()
|
||||
G.add_edges_from(
|
||||
[
|
||||
(1, 2),
|
||||
(1, 3),
|
||||
(1, 4),
|
||||
(1, 5),
|
||||
(1, 10),
|
||||
(10, 11),
|
||||
(11, 12),
|
||||
(12, 9),
|
||||
(2, 6),
|
||||
(3, 6),
|
||||
(4, 6),
|
||||
(5, 7),
|
||||
(7, 8),
|
||||
(6, 8),
|
||||
(8, 9),
|
||||
]
|
||||
)
|
||||
b = nx.betweenness_centrality_subset(G, sources=[1], targets=[9], weight=None)
|
||||
|
||||
expected_b = {
|
||||
1: 0,
|
||||
2: 1.0 / 10,
|
||||
3: 1.0 / 10,
|
||||
4: 1.0 / 10,
|
||||
5: 1.0 / 10,
|
||||
6: 3.0 / 10,
|
||||
7: 1.0 / 10,
|
||||
8: 4.0 / 10,
|
||||
9: 0,
|
||||
10: 1.0 / 10,
|
||||
11: 1.0 / 10,
|
||||
12: 1.0 / 10,
|
||||
}
|
||||
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], expected_b[n])
|
||||
|
||||
|
||||
class TestBetweennessCentralitySources:
|
||||
def test_K5(self):
|
||||
"""Betweenness Centrality Sources: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
b = nx.betweenness_centrality_source(G, weight=None, normalized=False)
|
||||
b_answer = {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P3(self):
|
||||
"""Betweenness Centrality Sources: P3"""
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 0.0, 1: 1.0, 2: 0.0}
|
||||
b = nx.betweenness_centrality_source(G, weight=None, normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
|
||||
class TestEdgeSubsetBetweennessCentrality:
|
||||
def test_K5(self):
|
||||
"""Edge betweenness subset centrality: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
b = nx.edge_betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[1, 3], weight=None
|
||||
)
|
||||
b_answer = dict.fromkeys(G.edges(), 0)
|
||||
b_answer[(0, 3)] = b_answer[(0, 1)] = 0.5
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P5_directed(self):
|
||||
"""Edge betweenness subset centrality: P5 directed"""
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, range(5))
|
||||
b_answer = dict.fromkeys(G.edges(), 0)
|
||||
b_answer[(0, 1)] = b_answer[(1, 2)] = b_answer[(2, 3)] = 1
|
||||
b = nx.edge_betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[3], weight=None
|
||||
)
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P5(self):
|
||||
"""Edge betweenness subset centrality: P5"""
|
||||
G = nx.Graph()
|
||||
nx.add_path(G, range(5))
|
||||
b_answer = dict.fromkeys(G.edges(), 0)
|
||||
b_answer[(0, 1)] = b_answer[(1, 2)] = b_answer[(2, 3)] = 0.5
|
||||
b = nx.edge_betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[3], weight=None
|
||||
)
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P5_multiple_target(self):
|
||||
"""Edge betweenness subset centrality: P5 multiple target"""
|
||||
G = nx.Graph()
|
||||
nx.add_path(G, range(5))
|
||||
b_answer = dict.fromkeys(G.edges(), 0)
|
||||
b_answer[(0, 1)] = b_answer[(1, 2)] = b_answer[(2, 3)] = 1
|
||||
b_answer[(3, 4)] = 0.5
|
||||
b = nx.edge_betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[3, 4], weight=None
|
||||
)
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_box(self):
|
||||
"""Edge betweenness subset centrality: box"""
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
|
||||
b_answer = dict.fromkeys(G.edges(), 0)
|
||||
b_answer[(0, 1)] = b_answer[(0, 2)] = 0.25
|
||||
b_answer[(1, 3)] = b_answer[(2, 3)] = 0.25
|
||||
b = nx.edge_betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[3], weight=None
|
||||
)
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_box_and_path(self):
|
||||
"""Edge betweenness subset centrality: box and path"""
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (3, 4), (4, 5)])
|
||||
b_answer = dict.fromkeys(G.edges(), 0)
|
||||
b_answer[(0, 1)] = b_answer[(0, 2)] = 0.5
|
||||
b_answer[(1, 3)] = b_answer[(2, 3)] = 0.5
|
||||
b_answer[(3, 4)] = 0.5
|
||||
b = nx.edge_betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[3, 4], weight=None
|
||||
)
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_box_and_path2(self):
|
||||
"""Edge betweenness subset centrality: box and path multiple target"""
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (1, 2), (2, 3), (1, 20), (20, 3), (3, 4)])
|
||||
b_answer = dict.fromkeys(G.edges(), 0)
|
||||
b_answer[(0, 1)] = 1.0
|
||||
b_answer[(1, 20)] = b_answer[(3, 20)] = 0.5
|
||||
b_answer[(1, 2)] = b_answer[(2, 3)] = 0.5
|
||||
b_answer[(3, 4)] = 0.5
|
||||
b = nx.edge_betweenness_centrality_subset(
|
||||
G, sources=[0], targets=[3, 4], weight=None
|
||||
)
|
||||
for n in sorted(G.edges()):
|
||||
assert almost_equal(b[n], b_answer[n])
|
|
@ -0,0 +1,306 @@
|
|||
"""
|
||||
Tests for closeness centrality.
|
||||
"""
|
||||
import pytest
|
||||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
class TestClosenessCentrality:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.K = nx.krackhardt_kite_graph()
|
||||
cls.P3 = nx.path_graph(3)
|
||||
cls.P4 = nx.path_graph(4)
|
||||
cls.K5 = nx.complete_graph(5)
|
||||
|
||||
cls.C4 = nx.cycle_graph(4)
|
||||
cls.T = nx.balanced_tree(r=2, h=2)
|
||||
cls.Gb = nx.Graph()
|
||||
cls.Gb.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (4, 5), (3, 5)])
|
||||
|
||||
F = nx.florentine_families_graph()
|
||||
cls.F = F
|
||||
|
||||
cls.LM = nx.les_miserables_graph()
|
||||
|
||||
# Create random undirected, unweighted graph for testing incremental version
|
||||
cls.undirected_G = nx.fast_gnp_random_graph(n=100, p=0.6, seed=123)
|
||||
cls.undirected_G_cc = nx.closeness_centrality(cls.undirected_G)
|
||||
|
||||
def test_wf_improved(self):
|
||||
G = nx.union(self.P4, nx.path_graph([4, 5, 6]))
|
||||
c = nx.closeness_centrality(G)
|
||||
cwf = nx.closeness_centrality(G, wf_improved=False)
|
||||
res = {0: 0.25, 1: 0.375, 2: 0.375, 3: 0.25, 4: 0.222, 5: 0.333, 6: 0.222}
|
||||
wf_res = {0: 0.5, 1: 0.75, 2: 0.75, 3: 0.5, 4: 0.667, 5: 1.0, 6: 0.667}
|
||||
for n in G:
|
||||
assert almost_equal(c[n], res[n], places=3)
|
||||
assert almost_equal(cwf[n], wf_res[n], places=3)
|
||||
|
||||
def test_digraph(self):
|
||||
G = nx.path_graph(3, create_using=nx.DiGraph())
|
||||
c = nx.closeness_centrality(G)
|
||||
cr = nx.closeness_centrality(G.reverse())
|
||||
d = {0: 0.0, 1: 0.500, 2: 0.667}
|
||||
dr = {0: 0.667, 1: 0.500, 2: 0.0}
|
||||
for n in sorted(self.P3):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
assert almost_equal(cr[n], dr[n], places=3)
|
||||
|
||||
def test_k5_closeness(self):
|
||||
c = nx.closeness_centrality(self.K5)
|
||||
d = {0: 1.000, 1: 1.000, 2: 1.000, 3: 1.000, 4: 1.000}
|
||||
for n in sorted(self.K5):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_p3_closeness(self):
|
||||
c = nx.closeness_centrality(self.P3)
|
||||
d = {0: 0.667, 1: 1.000, 2: 0.667}
|
||||
for n in sorted(self.P3):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_krackhardt_closeness(self):
|
||||
c = nx.closeness_centrality(self.K)
|
||||
d = {
|
||||
0: 0.529,
|
||||
1: 0.529,
|
||||
2: 0.500,
|
||||
3: 0.600,
|
||||
4: 0.500,
|
||||
5: 0.643,
|
||||
6: 0.643,
|
||||
7: 0.600,
|
||||
8: 0.429,
|
||||
9: 0.310,
|
||||
}
|
||||
for n in sorted(self.K):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_florentine_families_closeness(self):
|
||||
c = nx.closeness_centrality(self.F)
|
||||
d = {
|
||||
"Acciaiuoli": 0.368,
|
||||
"Albizzi": 0.483,
|
||||
"Barbadori": 0.4375,
|
||||
"Bischeri": 0.400,
|
||||
"Castellani": 0.389,
|
||||
"Ginori": 0.333,
|
||||
"Guadagni": 0.467,
|
||||
"Lamberteschi": 0.326,
|
||||
"Medici": 0.560,
|
||||
"Pazzi": 0.286,
|
||||
"Peruzzi": 0.368,
|
||||
"Ridolfi": 0.500,
|
||||
"Salviati": 0.389,
|
||||
"Strozzi": 0.4375,
|
||||
"Tornabuoni": 0.483,
|
||||
}
|
||||
for n in sorted(self.F):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_les_miserables_closeness(self):
|
||||
c = nx.closeness_centrality(self.LM)
|
||||
d = {
|
||||
"Napoleon": 0.302,
|
||||
"Myriel": 0.429,
|
||||
"MlleBaptistine": 0.413,
|
||||
"MmeMagloire": 0.413,
|
||||
"CountessDeLo": 0.302,
|
||||
"Geborand": 0.302,
|
||||
"Champtercier": 0.302,
|
||||
"Cravatte": 0.302,
|
||||
"Count": 0.302,
|
||||
"OldMan": 0.302,
|
||||
"Valjean": 0.644,
|
||||
"Labarre": 0.394,
|
||||
"Marguerite": 0.413,
|
||||
"MmeDeR": 0.394,
|
||||
"Isabeau": 0.394,
|
||||
"Gervais": 0.394,
|
||||
"Listolier": 0.341,
|
||||
"Tholomyes": 0.392,
|
||||
"Fameuil": 0.341,
|
||||
"Blacheville": 0.341,
|
||||
"Favourite": 0.341,
|
||||
"Dahlia": 0.341,
|
||||
"Zephine": 0.341,
|
||||
"Fantine": 0.461,
|
||||
"MmeThenardier": 0.461,
|
||||
"Thenardier": 0.517,
|
||||
"Cosette": 0.478,
|
||||
"Javert": 0.517,
|
||||
"Fauchelevent": 0.402,
|
||||
"Bamatabois": 0.427,
|
||||
"Perpetue": 0.318,
|
||||
"Simplice": 0.418,
|
||||
"Scaufflaire": 0.394,
|
||||
"Woman1": 0.396,
|
||||
"Judge": 0.404,
|
||||
"Champmathieu": 0.404,
|
||||
"Brevet": 0.404,
|
||||
"Chenildieu": 0.404,
|
||||
"Cochepaille": 0.404,
|
||||
"Pontmercy": 0.373,
|
||||
"Boulatruelle": 0.342,
|
||||
"Eponine": 0.396,
|
||||
"Anzelma": 0.352,
|
||||
"Woman2": 0.402,
|
||||
"MotherInnocent": 0.398,
|
||||
"Gribier": 0.288,
|
||||
"MmeBurgon": 0.344,
|
||||
"Jondrette": 0.257,
|
||||
"Gavroche": 0.514,
|
||||
"Gillenormand": 0.442,
|
||||
"Magnon": 0.335,
|
||||
"MlleGillenormand": 0.442,
|
||||
"MmePontmercy": 0.315,
|
||||
"MlleVaubois": 0.308,
|
||||
"LtGillenormand": 0.365,
|
||||
"Marius": 0.531,
|
||||
"BaronessT": 0.352,
|
||||
"Mabeuf": 0.396,
|
||||
"Enjolras": 0.481,
|
||||
"Combeferre": 0.392,
|
||||
"Prouvaire": 0.357,
|
||||
"Feuilly": 0.392,
|
||||
"Courfeyrac": 0.400,
|
||||
"Bahorel": 0.394,
|
||||
"Bossuet": 0.475,
|
||||
"Joly": 0.394,
|
||||
"Grantaire": 0.358,
|
||||
"MotherPlutarch": 0.285,
|
||||
"Gueulemer": 0.463,
|
||||
"Babet": 0.463,
|
||||
"Claquesous": 0.452,
|
||||
"Montparnasse": 0.458,
|
||||
"Toussaint": 0.402,
|
||||
"Child1": 0.342,
|
||||
"Child2": 0.342,
|
||||
"Brujon": 0.380,
|
||||
"MmeHucheloup": 0.353,
|
||||
}
|
||||
for n in sorted(self.LM):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_weighted_closeness(self):
|
||||
edges = [
|
||||
("s", "u", 10),
|
||||
("s", "x", 5),
|
||||
("u", "v", 1),
|
||||
("u", "x", 2),
|
||||
("v", "y", 1),
|
||||
("x", "u", 3),
|
||||
("x", "v", 5),
|
||||
("x", "y", 2),
|
||||
("y", "s", 7),
|
||||
("y", "v", 6),
|
||||
]
|
||||
XG = nx.Graph()
|
||||
XG.add_weighted_edges_from(edges)
|
||||
c = nx.closeness_centrality(XG, distance="weight")
|
||||
d = {"y": 0.200, "x": 0.286, "s": 0.138, "u": 0.235, "v": 0.200}
|
||||
for n in sorted(XG):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
#
|
||||
# Tests for incremental closeness centrality.
|
||||
#
|
||||
@staticmethod
|
||||
def pick_add_edge(g):
|
||||
u = nx.utils.arbitrary_element(g)
|
||||
possible_nodes = set(g.nodes())
|
||||
neighbors = list(g.neighbors(u)) + [u]
|
||||
possible_nodes.difference_update(neighbors)
|
||||
v = nx.utils.arbitrary_element(possible_nodes)
|
||||
return (u, v)
|
||||
|
||||
@staticmethod
|
||||
def pick_remove_edge(g):
|
||||
u = nx.utils.arbitrary_element(g)
|
||||
possible_nodes = list(g.neighbors(u))
|
||||
v = nx.utils.arbitrary_element(possible_nodes)
|
||||
return (u, v)
|
||||
|
||||
def test_directed_raises(self):
|
||||
with pytest.raises(nx.NetworkXNotImplemented):
|
||||
dir_G = nx.gn_graph(n=5)
|
||||
prev_cc = None
|
||||
edge = self.pick_add_edge(dir_G)
|
||||
insert = True
|
||||
nx.incremental_closeness_centrality(dir_G, edge, prev_cc, insert)
|
||||
|
||||
def test_wrong_size_prev_cc_raises(self):
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = self.undirected_G.copy()
|
||||
edge = self.pick_add_edge(G)
|
||||
insert = True
|
||||
prev_cc = self.undirected_G_cc.copy()
|
||||
prev_cc.pop(0)
|
||||
nx.incremental_closeness_centrality(G, edge, prev_cc, insert)
|
||||
|
||||
def test_wrong_nodes_prev_cc_raises(self):
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = self.undirected_G.copy()
|
||||
edge = self.pick_add_edge(G)
|
||||
insert = True
|
||||
prev_cc = self.undirected_G_cc.copy()
|
||||
num_nodes = len(prev_cc)
|
||||
prev_cc.pop(0)
|
||||
prev_cc[num_nodes] = 0.5
|
||||
nx.incremental_closeness_centrality(G, edge, prev_cc, insert)
|
||||
|
||||
def test_zero_centrality(self):
|
||||
G = nx.path_graph(3)
|
||||
prev_cc = nx.closeness_centrality(G)
|
||||
edge = self.pick_remove_edge(G)
|
||||
test_cc = nx.incremental_closeness_centrality(G, edge, prev_cc, insertion=False)
|
||||
G.remove_edges_from([edge])
|
||||
real_cc = nx.closeness_centrality(G)
|
||||
shared_items = set(test_cc.items()) & set(real_cc.items())
|
||||
assert len(shared_items) == len(real_cc)
|
||||
assert 0 in test_cc.values()
|
||||
|
||||
def test_incremental(self):
|
||||
# Check that incremental and regular give same output
|
||||
G = self.undirected_G.copy()
|
||||
prev_cc = None
|
||||
for i in range(5):
|
||||
if i % 2 == 0:
|
||||
# Remove an edge
|
||||
insert = False
|
||||
edge = self.pick_remove_edge(G)
|
||||
else:
|
||||
# Add an edge
|
||||
insert = True
|
||||
edge = self.pick_add_edge(G)
|
||||
|
||||
# start = timeit.default_timer()
|
||||
test_cc = nx.incremental_closeness_centrality(G, edge, prev_cc, insert)
|
||||
# inc_elapsed = (timeit.default_timer() - start)
|
||||
# print(f"incremental time: {inc_elapsed}")
|
||||
|
||||
if insert:
|
||||
G.add_edges_from([edge])
|
||||
else:
|
||||
G.remove_edges_from([edge])
|
||||
|
||||
# start = timeit.default_timer()
|
||||
real_cc = nx.closeness_centrality(G)
|
||||
# reg_elapsed = (timeit.default_timer() - start)
|
||||
# print(f"regular time: {reg_elapsed}")
|
||||
# Example output:
|
||||
# incremental time: 0.208
|
||||
# regular time: 0.276
|
||||
# incremental time: 0.00683
|
||||
# regular time: 0.260
|
||||
# incremental time: 0.0224
|
||||
# regular time: 0.278
|
||||
# incremental time: 0.00804
|
||||
# regular time: 0.208
|
||||
# incremental time: 0.00947
|
||||
# regular time: 0.188
|
||||
|
||||
assert set(test_cc.items()) == set(real_cc.items())
|
||||
|
||||
prev_cc = test_cc
|
|
@ -0,0 +1,180 @@
|
|||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
from networkx import edge_current_flow_betweenness_centrality as edge_current_flow
|
||||
from networkx import approximate_current_flow_betweenness_centrality as approximate_cfbc
|
||||
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
npt = pytest.importorskip("numpy.testing")
|
||||
scipy = pytest.importorskip("scipy")
|
||||
|
||||
|
||||
class TestFlowBetweennessCentrality:
|
||||
def test_K4_normalized(self):
|
||||
"""Betweenness centrality: K4"""
|
||||
G = nx.complete_graph(4)
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
b_answer = {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
G.add_edge(0, 1, weight=0.5, other=0.3)
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=True, weight=None)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
wb_answer = {0: 0.2222222, 1: 0.2222222, 2: 0.30555555, 3: 0.30555555}
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=True, weight="weight")
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], wb_answer[n])
|
||||
wb_answer = {0: 0.2051282, 1: 0.2051282, 2: 0.33974358, 3: 0.33974358}
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=True, weight="other")
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], wb_answer[n])
|
||||
|
||||
def test_K4(self):
|
||||
"""Betweenness centrality: K4"""
|
||||
G = nx.complete_graph(4)
|
||||
for solver in ["full", "lu", "cg"]:
|
||||
b = nx.current_flow_betweenness_centrality(
|
||||
G, normalized=False, solver=solver
|
||||
)
|
||||
b_answer = {0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P4_normalized(self):
|
||||
"""Betweenness centrality: P4 normalized"""
|
||||
G = nx.path_graph(4)
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
b_answer = {0: 0, 1: 2.0 / 3, 2: 2.0 / 3, 3: 0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P4(self):
|
||||
"""Betweenness centrality: P4"""
|
||||
G = nx.path_graph(4)
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=False)
|
||||
b_answer = {0: 0, 1: 2, 2: 2, 3: 0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_star(self):
|
||||
"""Betweenness centrality: star """
|
||||
G = nx.Graph()
|
||||
nx.add_star(G, ["a", "b", "c", "d"])
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
b_answer = {"a": 1.0, "b": 0.0, "c": 0.0, "d": 0.0}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_solvers2(self):
|
||||
"""Betweenness centrality: alternate solvers"""
|
||||
G = nx.complete_graph(4)
|
||||
for solver in ["full", "lu", "cg"]:
|
||||
b = nx.current_flow_betweenness_centrality(
|
||||
G, normalized=False, solver=solver
|
||||
)
|
||||
b_answer = {0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
|
||||
class TestApproximateFlowBetweennessCentrality:
|
||||
def test_K4_normalized(self):
|
||||
"Approximate current-flow betweenness centrality: K4 normalized"
|
||||
G = nx.complete_graph(4)
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
epsilon = 0.1
|
||||
ba = approximate_cfbc(G, normalized=True, epsilon=0.5 * epsilon)
|
||||
for n in sorted(G):
|
||||
npt.assert_allclose(b[n], ba[n], atol=epsilon)
|
||||
|
||||
def test_K4(self):
|
||||
"Approximate current-flow betweenness centrality: K4"
|
||||
G = nx.complete_graph(4)
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=False)
|
||||
epsilon = 0.1
|
||||
ba = approximate_cfbc(G, normalized=False, epsilon=0.5 * epsilon)
|
||||
for n in sorted(G):
|
||||
npt.assert_allclose(b[n], ba[n], atol=epsilon * len(G) ** 2)
|
||||
|
||||
def test_star(self):
|
||||
"Approximate current-flow betweenness centrality: star"
|
||||
G = nx.Graph()
|
||||
nx.add_star(G, ["a", "b", "c", "d"])
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
epsilon = 0.1
|
||||
ba = approximate_cfbc(G, normalized=True, epsilon=0.5 * epsilon)
|
||||
for n in sorted(G):
|
||||
npt.assert_allclose(b[n], ba[n], atol=epsilon)
|
||||
|
||||
def test_grid(self):
|
||||
"Approximate current-flow betweenness centrality: 2d grid"
|
||||
G = nx.grid_2d_graph(4, 4)
|
||||
b = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
epsilon = 0.1
|
||||
ba = approximate_cfbc(G, normalized=True, epsilon=0.5 * epsilon)
|
||||
for n in sorted(G):
|
||||
npt.assert_allclose(b[n], ba[n], atol=epsilon)
|
||||
|
||||
def test_seed(self):
|
||||
G = nx.complete_graph(4)
|
||||
b = approximate_cfbc(G, normalized=False, epsilon=0.05, seed=1)
|
||||
b_answer = {0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75}
|
||||
for n in sorted(G):
|
||||
npt.assert_allclose(b[n], b_answer[n], atol=0.1)
|
||||
|
||||
def test_solvers(self):
|
||||
"Approximate current-flow betweenness centrality: solvers"
|
||||
G = nx.complete_graph(4)
|
||||
epsilon = 0.1
|
||||
for solver in ["full", "lu", "cg"]:
|
||||
b = approximate_cfbc(
|
||||
G, normalized=False, solver=solver, epsilon=0.5 * epsilon
|
||||
)
|
||||
b_answer = {0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75}
|
||||
for n in sorted(G):
|
||||
npt.assert_allclose(b[n], b_answer[n], atol=epsilon)
|
||||
|
||||
|
||||
class TestWeightedFlowBetweennessCentrality:
|
||||
pass
|
||||
|
||||
|
||||
class TestEdgeFlowBetweennessCentrality:
|
||||
def test_K4(self):
|
||||
"""Edge flow betweenness centrality: K4"""
|
||||
G = nx.complete_graph(4)
|
||||
b = edge_current_flow(G, normalized=True)
|
||||
b_answer = dict.fromkeys(G.edges(), 0.25)
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
||||
|
||||
def test_K4_normalized(self):
|
||||
"""Edge flow betweenness centrality: K4"""
|
||||
G = nx.complete_graph(4)
|
||||
b = edge_current_flow(G, normalized=False)
|
||||
b_answer = dict.fromkeys(G.edges(), 0.75)
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
||||
|
||||
def test_C4(self):
|
||||
"""Edge flow betweenness centrality: C4"""
|
||||
G = nx.cycle_graph(4)
|
||||
b = edge_current_flow(G, normalized=False)
|
||||
b_answer = {(0, 1): 1.25, (0, 3): 1.25, (1, 2): 1.25, (2, 3): 1.25}
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
||||
|
||||
def test_P4(self):
|
||||
"""Edge betweenness centrality: P4"""
|
||||
G = nx.path_graph(4)
|
||||
b = edge_current_flow(G, normalized=False)
|
||||
b_answer = {(0, 1): 1.5, (1, 2): 2.0, (2, 3): 1.5}
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
|
@ -0,0 +1,150 @@
|
|||
import pytest
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
scipy = pytest.importorskip("scipy")
|
||||
|
||||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
from networkx import edge_current_flow_betweenness_centrality as edge_current_flow
|
||||
|
||||
from networkx import (
|
||||
edge_current_flow_betweenness_centrality_subset as edge_current_flow_subset,
|
||||
)
|
||||
|
||||
|
||||
class TestFlowBetweennessCentrality:
|
||||
def test_K4_normalized(self):
|
||||
"""Betweenness centrality: K4"""
|
||||
G = nx.complete_graph(4)
|
||||
b = nx.current_flow_betweenness_centrality_subset(
|
||||
G, list(G), list(G), normalized=True
|
||||
)
|
||||
b_answer = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_K4(self):
|
||||
"""Betweenness centrality: K4"""
|
||||
G = nx.complete_graph(4)
|
||||
b = nx.current_flow_betweenness_centrality_subset(
|
||||
G, list(G), list(G), normalized=True
|
||||
)
|
||||
b_answer = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
# test weighted network
|
||||
G.add_edge(0, 1, weight=0.5, other=0.3)
|
||||
b = nx.current_flow_betweenness_centrality_subset(
|
||||
G, list(G), list(G), normalized=True, weight=None
|
||||
)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
b = nx.current_flow_betweenness_centrality_subset(
|
||||
G, list(G), list(G), normalized=True
|
||||
)
|
||||
b_answer = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
b = nx.current_flow_betweenness_centrality_subset(
|
||||
G, list(G), list(G), normalized=True, weight="other"
|
||||
)
|
||||
b_answer = nx.current_flow_betweenness_centrality(
|
||||
G, normalized=True, weight="other"
|
||||
)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P4_normalized(self):
|
||||
"""Betweenness centrality: P4 normalized"""
|
||||
G = nx.path_graph(4)
|
||||
b = nx.current_flow_betweenness_centrality_subset(
|
||||
G, list(G), list(G), normalized=True
|
||||
)
|
||||
b_answer = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P4(self):
|
||||
"""Betweenness centrality: P4"""
|
||||
G = nx.path_graph(4)
|
||||
b = nx.current_flow_betweenness_centrality_subset(
|
||||
G, list(G), list(G), normalized=True
|
||||
)
|
||||
b_answer = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_star(self):
|
||||
"""Betweenness centrality: star """
|
||||
G = nx.Graph()
|
||||
nx.add_star(G, ["a", "b", "c", "d"])
|
||||
b = nx.current_flow_betweenness_centrality_subset(
|
||||
G, list(G), list(G), normalized=True
|
||||
)
|
||||
b_answer = nx.current_flow_betweenness_centrality(G, normalized=True)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
|
||||
# class TestWeightedFlowBetweennessCentrality():
|
||||
# pass
|
||||
|
||||
|
||||
class TestEdgeFlowBetweennessCentrality:
|
||||
def test_K4_normalized(self):
|
||||
"""Betweenness centrality: K4"""
|
||||
G = nx.complete_graph(4)
|
||||
b = edge_current_flow_subset(G, list(G), list(G), normalized=True)
|
||||
b_answer = edge_current_flow(G, normalized=True)
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
||||
|
||||
def test_K4(self):
|
||||
"""Betweenness centrality: K4"""
|
||||
G = nx.complete_graph(4)
|
||||
b = edge_current_flow_subset(G, list(G), list(G), normalized=False)
|
||||
b_answer = edge_current_flow(G, normalized=False)
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
||||
# test weighted network
|
||||
G.add_edge(0, 1, weight=0.5, other=0.3)
|
||||
b = edge_current_flow_subset(G, list(G), list(G), normalized=False, weight=None)
|
||||
# weight is None => same as unweighted network
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
||||
|
||||
b = edge_current_flow_subset(G, list(G), list(G), normalized=False)
|
||||
b_answer = edge_current_flow(G, normalized=False)
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
||||
|
||||
b = edge_current_flow_subset(
|
||||
G, list(G), list(G), normalized=False, weight="other"
|
||||
)
|
||||
b_answer = edge_current_flow(G, normalized=False, weight="other")
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
||||
|
||||
def test_C4(self):
|
||||
"""Edge betweenness centrality: C4"""
|
||||
G = nx.cycle_graph(4)
|
||||
b = edge_current_flow_subset(G, list(G), list(G), normalized=True)
|
||||
b_answer = edge_current_flow(G, normalized=True)
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
||||
|
||||
def test_P4(self):
|
||||
"""Edge betweenness centrality: P4"""
|
||||
G = nx.path_graph(4)
|
||||
b = edge_current_flow_subset(G, list(G), list(G), normalized=True)
|
||||
b_answer = edge_current_flow(G, normalized=True)
|
||||
for (s, t), v1 in b_answer.items():
|
||||
v2 = b.get((s, t), b.get((t, s)))
|
||||
assert almost_equal(v1, v2)
|
|
@ -0,0 +1,38 @@
|
|||
import pytest
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
scipy = pytest.importorskip("scipy")
|
||||
|
||||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
class TestFlowClosenessCentrality:
|
||||
def test_K4(self):
|
||||
"""Closeness centrality: K4"""
|
||||
G = nx.complete_graph(4)
|
||||
b = nx.current_flow_closeness_centrality(G)
|
||||
b_answer = {0: 2.0 / 3, 1: 2.0 / 3, 2: 2.0 / 3, 3: 2.0 / 3}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P4(self):
|
||||
"""Closeness centrality: P4"""
|
||||
G = nx.path_graph(4)
|
||||
b = nx.current_flow_closeness_centrality(G)
|
||||
b_answer = {0: 1.0 / 6, 1: 1.0 / 4, 2: 1.0 / 4, 3: 1.0 / 6}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_star(self):
|
||||
"""Closeness centrality: star """
|
||||
G = nx.Graph()
|
||||
nx.add_star(G, ["a", "b", "c", "d"])
|
||||
b = nx.current_flow_closeness_centrality(G)
|
||||
b_answer = {"a": 1.0 / 3, "b": 0.6 / 3, "c": 0.6 / 3, "d": 0.6 / 3}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
|
||||
class TestWeightedFlowClosenessCentrality:
|
||||
pass
|
|
@ -0,0 +1,145 @@
|
|||
"""
|
||||
Unit tests for degree centrality.
|
||||
"""
|
||||
|
||||
|
||||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
class TestDegreeCentrality:
|
||||
def setup_method(self):
|
||||
|
||||
self.K = nx.krackhardt_kite_graph()
|
||||
self.P3 = nx.path_graph(3)
|
||||
self.K5 = nx.complete_graph(5)
|
||||
|
||||
F = nx.Graph() # Florentine families
|
||||
F.add_edge("Acciaiuoli", "Medici")
|
||||
F.add_edge("Castellani", "Peruzzi")
|
||||
F.add_edge("Castellani", "Strozzi")
|
||||
F.add_edge("Castellani", "Barbadori")
|
||||
F.add_edge("Medici", "Barbadori")
|
||||
F.add_edge("Medici", "Ridolfi")
|
||||
F.add_edge("Medici", "Tornabuoni")
|
||||
F.add_edge("Medici", "Albizzi")
|
||||
F.add_edge("Medici", "Salviati")
|
||||
F.add_edge("Salviati", "Pazzi")
|
||||
F.add_edge("Peruzzi", "Strozzi")
|
||||
F.add_edge("Peruzzi", "Bischeri")
|
||||
F.add_edge("Strozzi", "Ridolfi")
|
||||
F.add_edge("Strozzi", "Bischeri")
|
||||
F.add_edge("Ridolfi", "Tornabuoni")
|
||||
F.add_edge("Tornabuoni", "Guadagni")
|
||||
F.add_edge("Albizzi", "Ginori")
|
||||
F.add_edge("Albizzi", "Guadagni")
|
||||
F.add_edge("Bischeri", "Guadagni")
|
||||
F.add_edge("Guadagni", "Lamberteschi")
|
||||
self.F = F
|
||||
|
||||
G = nx.DiGraph()
|
||||
G.add_edge(0, 5)
|
||||
G.add_edge(1, 5)
|
||||
G.add_edge(2, 5)
|
||||
G.add_edge(3, 5)
|
||||
G.add_edge(4, 5)
|
||||
G.add_edge(5, 6)
|
||||
G.add_edge(5, 7)
|
||||
G.add_edge(5, 8)
|
||||
self.G = G
|
||||
|
||||
def test_degree_centrality_1(self):
|
||||
d = nx.degree_centrality(self.K5)
|
||||
exact = dict(zip(range(5), [1] * 5))
|
||||
for n, dc in d.items():
|
||||
assert almost_equal(exact[n], dc)
|
||||
|
||||
def test_degree_centrality_2(self):
|
||||
d = nx.degree_centrality(self.P3)
|
||||
exact = {0: 0.5, 1: 1, 2: 0.5}
|
||||
for n, dc in d.items():
|
||||
assert almost_equal(exact[n], dc)
|
||||
|
||||
def test_degree_centrality_3(self):
|
||||
d = nx.degree_centrality(self.K)
|
||||
exact = {
|
||||
0: 0.444,
|
||||
1: 0.444,
|
||||
2: 0.333,
|
||||
3: 0.667,
|
||||
4: 0.333,
|
||||
5: 0.556,
|
||||
6: 0.556,
|
||||
7: 0.333,
|
||||
8: 0.222,
|
||||
9: 0.111,
|
||||
}
|
||||
for n, dc in d.items():
|
||||
assert almost_equal(exact[n], float(f"{dc:.3f}"))
|
||||
|
||||
def test_degree_centrality_4(self):
|
||||
d = nx.degree_centrality(self.F)
|
||||
names = sorted(self.F.nodes())
|
||||
dcs = [
|
||||
0.071,
|
||||
0.214,
|
||||
0.143,
|
||||
0.214,
|
||||
0.214,
|
||||
0.071,
|
||||
0.286,
|
||||
0.071,
|
||||
0.429,
|
||||
0.071,
|
||||
0.214,
|
||||
0.214,
|
||||
0.143,
|
||||
0.286,
|
||||
0.214,
|
||||
]
|
||||
exact = dict(zip(names, dcs))
|
||||
for n, dc in d.items():
|
||||
assert almost_equal(exact[n], float(f"{dc:.3f}"))
|
||||
|
||||
def test_indegree_centrality(self):
|
||||
d = nx.in_degree_centrality(self.G)
|
||||
exact = {
|
||||
0: 0.0,
|
||||
1: 0.0,
|
||||
2: 0.0,
|
||||
3: 0.0,
|
||||
4: 0.0,
|
||||
5: 0.625,
|
||||
6: 0.125,
|
||||
7: 0.125,
|
||||
8: 0.125,
|
||||
}
|
||||
for n, dc in d.items():
|
||||
assert almost_equal(exact[n], dc)
|
||||
|
||||
def test_outdegree_centrality(self):
|
||||
d = nx.out_degree_centrality(self.G)
|
||||
exact = {
|
||||
0: 0.125,
|
||||
1: 0.125,
|
||||
2: 0.125,
|
||||
3: 0.125,
|
||||
4: 0.125,
|
||||
5: 0.375,
|
||||
6: 0.0,
|
||||
7: 0.0,
|
||||
8: 0.0,
|
||||
}
|
||||
for n, dc in d.items():
|
||||
assert almost_equal(exact[n], dc)
|
||||
|
||||
def test_small_graph_centrality(self):
|
||||
G = nx.empty_graph(create_using=nx.DiGraph)
|
||||
assert {} == nx.degree_centrality(G)
|
||||
assert {} == nx.out_degree_centrality(G)
|
||||
assert {} == nx.in_degree_centrality(G)
|
||||
|
||||
G = nx.empty_graph(1, create_using=nx.DiGraph)
|
||||
assert {0: 1} == nx.degree_centrality(G)
|
||||
assert {0: 1} == nx.out_degree_centrality(G)
|
||||
assert {0: 1} == nx.in_degree_centrality(G)
|
|
@ -0,0 +1,66 @@
|
|||
import networkx as nx
|
||||
|
||||
|
||||
def small_ego_G():
|
||||
"""The sample network from https://arxiv.org/pdf/1310.6753v1.pdf"""
|
||||
edges = [
|
||||
("a", "b"),
|
||||
("a", "c"),
|
||||
("b", "c"),
|
||||
("b", "d"),
|
||||
("b", "e"),
|
||||
("b", "f"),
|
||||
("c", "d"),
|
||||
("c", "f"),
|
||||
("c", "h"),
|
||||
("d", "f"),
|
||||
("e", "f"),
|
||||
("f", "h"),
|
||||
("h", "j"),
|
||||
("h", "k"),
|
||||
("i", "j"),
|
||||
("i", "k"),
|
||||
("j", "k"),
|
||||
("u", "a"),
|
||||
("u", "b"),
|
||||
("u", "c"),
|
||||
("u", "d"),
|
||||
("u", "e"),
|
||||
("u", "f"),
|
||||
("u", "g"),
|
||||
("u", "h"),
|
||||
("u", "i"),
|
||||
("u", "j"),
|
||||
("u", "k"),
|
||||
]
|
||||
G = nx.Graph()
|
||||
G.add_edges_from(edges)
|
||||
|
||||
return G
|
||||
|
||||
|
||||
class TestDispersion:
|
||||
def test_article(self):
|
||||
"""our algorithm matches article's"""
|
||||
G = small_ego_G()
|
||||
disp_uh = nx.dispersion(G, "u", "h", normalized=False)
|
||||
disp_ub = nx.dispersion(G, "u", "b", normalized=False)
|
||||
assert disp_uh == 4
|
||||
assert disp_ub == 1
|
||||
|
||||
def test_results_length(self):
|
||||
"""there is a result for every node"""
|
||||
G = small_ego_G()
|
||||
disp = nx.dispersion(G)
|
||||
disp_Gu = nx.dispersion(G, "u")
|
||||
disp_uv = nx.dispersion(G, "u", "h")
|
||||
assert len(disp) == len(G)
|
||||
assert len(disp_Gu) == len(G) - 1
|
||||
assert type(disp_uv) is float
|
||||
|
||||
def test_impossible_things(self):
|
||||
G = nx.karate_club_graph()
|
||||
disp = nx.dispersion(G)
|
||||
for u in disp:
|
||||
for v in disp[u]:
|
||||
assert disp[u][v] >= 0
|
|
@ -0,0 +1,168 @@
|
|||
import math
|
||||
import pytest
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
scipy = pytest.importorskip("scipy")
|
||||
|
||||
|
||||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
class TestEigenvectorCentrality:
|
||||
def test_K5(self):
|
||||
"""Eigenvector centrality: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
b = nx.eigenvector_centrality(G)
|
||||
v = math.sqrt(1 / 5.0)
|
||||
b_answer = dict.fromkeys(G, v)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
nstart = {n: 1 for n in G}
|
||||
b = nx.eigenvector_centrality(G, nstart=nstart)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
b = nx.eigenvector_centrality_numpy(G)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_P3(self):
|
||||
"""Eigenvector centrality: P3"""
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 0.5, 1: 0.7071, 2: 0.5}
|
||||
b = nx.eigenvector_centrality_numpy(G)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=4)
|
||||
b = nx.eigenvector_centrality(G)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=4)
|
||||
|
||||
def test_P3_unweighted(self):
|
||||
"""Eigenvector centrality: P3"""
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 0.5, 1: 0.7071, 2: 0.5}
|
||||
b = nx.eigenvector_centrality_numpy(G, weight=None)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=4)
|
||||
|
||||
def test_maxiter(self):
|
||||
with pytest.raises(nx.PowerIterationFailedConvergence):
|
||||
G = nx.path_graph(3)
|
||||
b = nx.eigenvector_centrality(G, max_iter=0)
|
||||
|
||||
|
||||
class TestEigenvectorCentralityDirected:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
G = nx.DiGraph()
|
||||
|
||||
edges = [
|
||||
(1, 2),
|
||||
(1, 3),
|
||||
(2, 4),
|
||||
(3, 2),
|
||||
(3, 5),
|
||||
(4, 2),
|
||||
(4, 5),
|
||||
(4, 6),
|
||||
(5, 6),
|
||||
(5, 7),
|
||||
(5, 8),
|
||||
(6, 8),
|
||||
(7, 1),
|
||||
(7, 5),
|
||||
(7, 8),
|
||||
(8, 6),
|
||||
(8, 7),
|
||||
]
|
||||
|
||||
G.add_edges_from(edges, weight=2.0)
|
||||
cls.G = G.reverse()
|
||||
cls.G.evc = [
|
||||
0.25368793,
|
||||
0.19576478,
|
||||
0.32817092,
|
||||
0.40430835,
|
||||
0.48199885,
|
||||
0.15724483,
|
||||
0.51346196,
|
||||
0.32475403,
|
||||
]
|
||||
|
||||
H = nx.DiGraph()
|
||||
|
||||
edges = [
|
||||
(1, 2),
|
||||
(1, 3),
|
||||
(2, 4),
|
||||
(3, 2),
|
||||
(3, 5),
|
||||
(4, 2),
|
||||
(4, 5),
|
||||
(4, 6),
|
||||
(5, 6),
|
||||
(5, 7),
|
||||
(5, 8),
|
||||
(6, 8),
|
||||
(7, 1),
|
||||
(7, 5),
|
||||
(7, 8),
|
||||
(8, 6),
|
||||
(8, 7),
|
||||
]
|
||||
|
||||
G.add_edges_from(edges)
|
||||
cls.H = G.reverse()
|
||||
cls.H.evc = [
|
||||
0.25368793,
|
||||
0.19576478,
|
||||
0.32817092,
|
||||
0.40430835,
|
||||
0.48199885,
|
||||
0.15724483,
|
||||
0.51346196,
|
||||
0.32475403,
|
||||
]
|
||||
|
||||
def test_eigenvector_centrality_weighted(self):
|
||||
G = self.G
|
||||
p = nx.eigenvector_centrality(G)
|
||||
for (a, b) in zip(list(p.values()), self.G.evc):
|
||||
assert almost_equal(a, b, places=4)
|
||||
|
||||
def test_eigenvector_centrality_weighted_numpy(self):
|
||||
G = self.G
|
||||
p = nx.eigenvector_centrality_numpy(G)
|
||||
for (a, b) in zip(list(p.values()), self.G.evc):
|
||||
assert almost_equal(a, b)
|
||||
|
||||
def test_eigenvector_centrality_unweighted(self):
|
||||
G = self.H
|
||||
p = nx.eigenvector_centrality(G)
|
||||
for (a, b) in zip(list(p.values()), self.G.evc):
|
||||
assert almost_equal(a, b, places=4)
|
||||
|
||||
def test_eigenvector_centrality_unweighted_numpy(self):
|
||||
G = self.H
|
||||
p = nx.eigenvector_centrality_numpy(G)
|
||||
for (a, b) in zip(list(p.values()), self.G.evc):
|
||||
assert almost_equal(a, b)
|
||||
|
||||
|
||||
class TestEigenvectorCentralityExceptions:
|
||||
def test_multigraph(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
e = nx.eigenvector_centrality(nx.MultiGraph())
|
||||
|
||||
def test_multigraph_numpy(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
e = nx.eigenvector_centrality_numpy(nx.MultiGraph())
|
||||
|
||||
def test_empty(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
e = nx.eigenvector_centrality(nx.Graph())
|
||||
|
||||
def test_empty_numpy(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
e = nx.eigenvector_centrality_numpy(nx.Graph())
|
|
@ -0,0 +1,154 @@
|
|||
"""
|
||||
Tests for Group Centrality Measures
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
import networkx as nx
|
||||
|
||||
|
||||
class TestGroupBetweennessCentrality:
|
||||
def test_group_betweenness_single_node(self):
|
||||
"""
|
||||
Group betweenness centrality for single node group
|
||||
"""
|
||||
G = nx.path_graph(5)
|
||||
C = [1]
|
||||
b = nx.group_betweenness_centrality(G, C, weight=None, normalized=False)
|
||||
b_answer = 3.0
|
||||
assert b == b_answer
|
||||
|
||||
def test_group_betweenness_normalized(self):
|
||||
"""
|
||||
Group betweenness centrality for group with more than
|
||||
1 node and normalized
|
||||
"""
|
||||
G = nx.path_graph(5)
|
||||
C = [1, 3]
|
||||
b = nx.group_betweenness_centrality(G, C, weight=None, normalized=True)
|
||||
b_answer = 1.0
|
||||
assert b == b_answer
|
||||
|
||||
def test_group_betweenness_value_zero(self):
|
||||
"""
|
||||
Group betweenness centrality value of 0
|
||||
"""
|
||||
G = nx.cycle_graph(6)
|
||||
C = [0, 1, 5]
|
||||
b = nx.group_betweenness_centrality(G, C, weight=None)
|
||||
b_answer = 0.0
|
||||
assert b == b_answer
|
||||
|
||||
def test_group_betweenness_disconnected_graph(self):
|
||||
"""
|
||||
Group betweenness centrality in a disconnected graph
|
||||
"""
|
||||
G = nx.path_graph(5)
|
||||
G.remove_edge(0, 1)
|
||||
C = [1]
|
||||
b = nx.group_betweenness_centrality(G, C, weight=None)
|
||||
b_answer = 0.0
|
||||
assert b == b_answer
|
||||
|
||||
def test_group_betweenness_node_not_in_graph(self):
|
||||
"""
|
||||
Node(s) in C not in graph, raises NodeNotFound exception
|
||||
"""
|
||||
with pytest.raises(nx.NodeNotFound):
|
||||
b = nx.group_betweenness_centrality(nx.path_graph(5), [6, 7, 8])
|
||||
|
||||
|
||||
class TestGroupClosenessCentrality:
|
||||
def test_group_closeness_single_node(self):
|
||||
"""
|
||||
Group closeness centrality for a single node group
|
||||
"""
|
||||
G = nx.path_graph(5)
|
||||
c = nx.group_closeness_centrality(G, [1])
|
||||
c_answer = nx.closeness_centrality(G, 1)
|
||||
assert c == c_answer
|
||||
|
||||
def test_group_closeness_disconnected(self):
|
||||
"""
|
||||
Group closeness centrality for a disconnected graph
|
||||
"""
|
||||
G = nx.Graph()
|
||||
G.add_nodes_from([1, 2, 3, 4])
|
||||
c = nx.group_closeness_centrality(G, [1, 2])
|
||||
c_answer = 0
|
||||
assert c == c_answer
|
||||
|
||||
def test_group_closeness_multiple_node(self):
|
||||
"""
|
||||
Group closeness centrality for a group with more than
|
||||
1 node
|
||||
"""
|
||||
G = nx.path_graph(4)
|
||||
c = nx.group_closeness_centrality(G, [1, 2])
|
||||
c_answer = 1
|
||||
assert c == c_answer
|
||||
|
||||
def test_group_closeness_node_not_in_graph(self):
|
||||
"""
|
||||
Node(s) in S not in graph, raises NodeNotFound exception
|
||||
"""
|
||||
with pytest.raises(nx.NodeNotFound):
|
||||
c = nx.group_closeness_centrality(nx.path_graph(5), [6, 7, 8])
|
||||
|
||||
|
||||
class TestGroupDegreeCentrality:
|
||||
def test_group_degree_centrality_single_node(self):
|
||||
"""
|
||||
Group degree centrality for a single node group
|
||||
"""
|
||||
G = nx.path_graph(4)
|
||||
d = nx.group_degree_centrality(G, [1])
|
||||
d_answer = nx.degree_centrality(G)[1]
|
||||
assert d == d_answer
|
||||
|
||||
def test_group_degree_centrality_multiple_node(self):
|
||||
"""
|
||||
Group degree centrality for group with more than
|
||||
1 node
|
||||
"""
|
||||
G = nx.Graph()
|
||||
G.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8])
|
||||
G.add_edges_from(
|
||||
[(1, 2), (1, 3), (1, 6), (1, 7), (1, 8), (2, 3), (2, 4), (2, 5)]
|
||||
)
|
||||
d = nx.group_degree_centrality(G, [1, 2])
|
||||
d_answer = 1
|
||||
assert d == d_answer
|
||||
|
||||
def test_group_in_degree_centrality(self):
|
||||
"""
|
||||
Group in-degree centrality in a DiGraph
|
||||
"""
|
||||
G = nx.DiGraph()
|
||||
G.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8])
|
||||
G.add_edges_from(
|
||||
[(1, 2), (1, 3), (1, 6), (1, 7), (1, 8), (2, 3), (2, 4), (2, 5)]
|
||||
)
|
||||
d = nx.group_in_degree_centrality(G, [1, 2])
|
||||
d_answer = 0
|
||||
assert d == d_answer
|
||||
|
||||
def test_group_out_degree_centrality(self):
|
||||
"""
|
||||
Group out-degree centrality in a DiGraph
|
||||
"""
|
||||
G = nx.DiGraph()
|
||||
G.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8])
|
||||
G.add_edges_from(
|
||||
[(1, 2), (1, 3), (1, 6), (1, 7), (1, 8), (2, 3), (2, 4), (2, 5)]
|
||||
)
|
||||
d = nx.group_out_degree_centrality(G, [1, 2])
|
||||
d_answer = 1
|
||||
assert d == d_answer
|
||||
|
||||
def test_group_degree_centrality_node_not_in_graph(self):
|
||||
"""
|
||||
Node(s) in S not in graph, raises NetworkXError
|
||||
"""
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
b = nx.group_degree_centrality(nx.path_graph(5), [6, 7, 8])
|
|
@ -0,0 +1,94 @@
|
|||
"""
|
||||
Tests for degree centrality.
|
||||
"""
|
||||
import networkx as nx
|
||||
from networkx.algorithms.centrality import harmonic_centrality
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
class TestClosenessCentrality:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.P3 = nx.path_graph(3)
|
||||
cls.P4 = nx.path_graph(4)
|
||||
cls.K5 = nx.complete_graph(5)
|
||||
|
||||
cls.C4 = nx.cycle_graph(4)
|
||||
cls.C5 = nx.cycle_graph(5)
|
||||
|
||||
cls.T = nx.balanced_tree(r=2, h=2)
|
||||
|
||||
cls.Gb = nx.DiGraph()
|
||||
cls.Gb.add_edges_from([(0, 1), (0, 2), (0, 4), (2, 1), (2, 3), (4, 3)])
|
||||
|
||||
def test_p3_harmonic(self):
|
||||
c = harmonic_centrality(self.P3)
|
||||
d = {0: 1.5, 1: 2, 2: 1.5}
|
||||
for n in sorted(self.P3):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_p4_harmonic(self):
|
||||
c = harmonic_centrality(self.P4)
|
||||
d = {0: 1.8333333, 1: 2.5, 2: 2.5, 3: 1.8333333}
|
||||
for n in sorted(self.P4):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_clique_complete(self):
|
||||
c = harmonic_centrality(self.K5)
|
||||
d = {0: 4, 1: 4, 2: 4, 3: 4, 4: 4}
|
||||
for n in sorted(self.P3):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_cycle_C4(self):
|
||||
c = harmonic_centrality(self.C4)
|
||||
d = {0: 2.5, 1: 2.5, 2: 2.5, 3: 2.5}
|
||||
for n in sorted(self.C4):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_cycle_C5(self):
|
||||
c = harmonic_centrality(self.C5)
|
||||
d = {0: 3, 1: 3, 2: 3, 3: 3, 4: 3, 5: 4}
|
||||
for n in sorted(self.C5):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_bal_tree(self):
|
||||
c = harmonic_centrality(self.T)
|
||||
d = {0: 4.0, 1: 4.1666, 2: 4.1666, 3: 2.8333, 4: 2.8333, 5: 2.8333, 6: 2.8333}
|
||||
for n in sorted(self.T):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_exampleGraph(self):
|
||||
c = harmonic_centrality(self.Gb)
|
||||
d = {0: 0, 1: 2, 2: 1, 3: 2.5, 4: 1}
|
||||
for n in sorted(self.Gb):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_weighted_harmonic(self):
|
||||
XG = nx.DiGraph()
|
||||
XG.add_weighted_edges_from(
|
||||
[
|
||||
("a", "b", 10),
|
||||
("d", "c", 5),
|
||||
("a", "c", 1),
|
||||
("e", "f", 2),
|
||||
("f", "c", 1),
|
||||
("a", "f", 3),
|
||||
]
|
||||
)
|
||||
c = harmonic_centrality(XG, distance="weight")
|
||||
d = {"a": 0, "b": 0.1, "c": 2.533, "d": 0, "e": 0, "f": 0.83333}
|
||||
for n in sorted(XG):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_empty(self):
|
||||
G = nx.DiGraph()
|
||||
c = harmonic_centrality(G, distance="weight")
|
||||
d = {}
|
||||
assert c == d
|
||||
|
||||
def test_singleton(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_node(0)
|
||||
c = harmonic_centrality(G, distance="weight")
|
||||
d = {0: 0}
|
||||
assert c == d
|
|
@ -0,0 +1,355 @@
|
|||
import math
|
||||
|
||||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
import pytest
|
||||
|
||||
|
||||
class TestKatzCentrality:
|
||||
def test_K5(self):
|
||||
"""Katz centrality: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
alpha = 0.1
|
||||
b = nx.katz_centrality(G, alpha)
|
||||
v = math.sqrt(1 / 5.0)
|
||||
b_answer = dict.fromkeys(G, v)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
nstart = {n: 1 for n in G}
|
||||
b = nx.katz_centrality(G, alpha, nstart=nstart)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
|
||||
def test_P3(self):
|
||||
"""Katz centrality: P3"""
|
||||
alpha = 0.1
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162}
|
||||
b = nx.katz_centrality(G, alpha)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=4)
|
||||
|
||||
def test_maxiter(self):
|
||||
with pytest.raises(nx.PowerIterationFailedConvergence):
|
||||
alpha = 0.1
|
||||
G = nx.path_graph(3)
|
||||
max_iter = 0
|
||||
try:
|
||||
b = nx.katz_centrality(G, alpha, max_iter=max_iter)
|
||||
except nx.NetworkXError as e:
|
||||
assert str(max_iter) in e.args[0], "max_iter value not in error msg"
|
||||
raise # So that the decorater sees the exception.
|
||||
|
||||
def test_beta_as_scalar(self):
|
||||
alpha = 0.1
|
||||
beta = 0.1
|
||||
b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162}
|
||||
G = nx.path_graph(3)
|
||||
b = nx.katz_centrality(G, alpha, beta)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=4)
|
||||
|
||||
def test_beta_as_dict(self):
|
||||
alpha = 0.1
|
||||
beta = {0: 1.0, 1: 1.0, 2: 1.0}
|
||||
b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162}
|
||||
G = nx.path_graph(3)
|
||||
b = nx.katz_centrality(G, alpha, beta)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=4)
|
||||
|
||||
def test_multiple_alpha(self):
|
||||
alpha_list = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
|
||||
for alpha in alpha_list:
|
||||
b_answer = {
|
||||
0.1: {
|
||||
0: 0.5598852584152165,
|
||||
1: 0.6107839182711449,
|
||||
2: 0.5598852584152162,
|
||||
},
|
||||
0.2: {
|
||||
0: 0.5454545454545454,
|
||||
1: 0.6363636363636365,
|
||||
2: 0.5454545454545454,
|
||||
},
|
||||
0.3: {
|
||||
0: 0.5333964609104419,
|
||||
1: 0.6564879518897746,
|
||||
2: 0.5333964609104419,
|
||||
},
|
||||
0.4: {
|
||||
0: 0.5232045649263551,
|
||||
1: 0.6726915834767423,
|
||||
2: 0.5232045649263551,
|
||||
},
|
||||
0.5: {
|
||||
0: 0.5144957746691622,
|
||||
1: 0.6859943117075809,
|
||||
2: 0.5144957746691622,
|
||||
},
|
||||
0.6: {
|
||||
0: 0.5069794004195823,
|
||||
1: 0.6970966755769258,
|
||||
2: 0.5069794004195823,
|
||||
},
|
||||
}
|
||||
G = nx.path_graph(3)
|
||||
b = nx.katz_centrality(G, alpha)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[alpha][n], places=4)
|
||||
|
||||
def test_multigraph(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
e = nx.katz_centrality(nx.MultiGraph(), 0.1)
|
||||
|
||||
def test_empty(self):
|
||||
e = nx.katz_centrality(nx.Graph(), 0.1)
|
||||
assert e == {}
|
||||
|
||||
def test_bad_beta(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
G = nx.Graph([(0, 1)])
|
||||
beta = {0: 77}
|
||||
e = nx.katz_centrality(G, 0.1, beta=beta)
|
||||
|
||||
def test_bad_beta_numbe(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
G = nx.Graph([(0, 1)])
|
||||
e = nx.katz_centrality(G, 0.1, beta="foo")
|
||||
|
||||
|
||||
class TestKatzCentralityNumpy:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
global np
|
||||
np = pytest.importorskip("numpy")
|
||||
scipy = pytest.importorskip("scipy")
|
||||
|
||||
def test_K5(self):
|
||||
"""Katz centrality: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
alpha = 0.1
|
||||
b = nx.katz_centrality(G, alpha)
|
||||
v = math.sqrt(1 / 5.0)
|
||||
b_answer = dict.fromkeys(G, v)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
nstart = {n: 1 for n in G}
|
||||
b = nx.eigenvector_centrality_numpy(G)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_P3(self):
|
||||
"""Katz centrality: P3"""
|
||||
alpha = 0.1
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162}
|
||||
b = nx.katz_centrality_numpy(G, alpha)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=4)
|
||||
|
||||
def test_beta_as_scalar(self):
|
||||
alpha = 0.1
|
||||
beta = 0.1
|
||||
b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162}
|
||||
G = nx.path_graph(3)
|
||||
b = nx.katz_centrality_numpy(G, alpha, beta)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=4)
|
||||
|
||||
def test_beta_as_dict(self):
|
||||
alpha = 0.1
|
||||
beta = {0: 1.0, 1: 1.0, 2: 1.0}
|
||||
b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162}
|
||||
G = nx.path_graph(3)
|
||||
b = nx.katz_centrality_numpy(G, alpha, beta)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=4)
|
||||
|
||||
def test_multiple_alpha(self):
|
||||
alpha_list = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
|
||||
for alpha in alpha_list:
|
||||
b_answer = {
|
||||
0.1: {
|
||||
0: 0.5598852584152165,
|
||||
1: 0.6107839182711449,
|
||||
2: 0.5598852584152162,
|
||||
},
|
||||
0.2: {
|
||||
0: 0.5454545454545454,
|
||||
1: 0.6363636363636365,
|
||||
2: 0.5454545454545454,
|
||||
},
|
||||
0.3: {
|
||||
0: 0.5333964609104419,
|
||||
1: 0.6564879518897746,
|
||||
2: 0.5333964609104419,
|
||||
},
|
||||
0.4: {
|
||||
0: 0.5232045649263551,
|
||||
1: 0.6726915834767423,
|
||||
2: 0.5232045649263551,
|
||||
},
|
||||
0.5: {
|
||||
0: 0.5144957746691622,
|
||||
1: 0.6859943117075809,
|
||||
2: 0.5144957746691622,
|
||||
},
|
||||
0.6: {
|
||||
0: 0.5069794004195823,
|
||||
1: 0.6970966755769258,
|
||||
2: 0.5069794004195823,
|
||||
},
|
||||
}
|
||||
G = nx.path_graph(3)
|
||||
b = nx.katz_centrality_numpy(G, alpha)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[alpha][n], places=4)
|
||||
|
||||
def test_multigraph(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
e = nx.katz_centrality(nx.MultiGraph(), 0.1)
|
||||
|
||||
def test_empty(self):
|
||||
e = nx.katz_centrality(nx.Graph(), 0.1)
|
||||
assert e == {}
|
||||
|
||||
def test_bad_beta(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
G = nx.Graph([(0, 1)])
|
||||
beta = {0: 77}
|
||||
e = nx.katz_centrality_numpy(G, 0.1, beta=beta)
|
||||
|
||||
def test_bad_beta_numbe(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
G = nx.Graph([(0, 1)])
|
||||
e = nx.katz_centrality_numpy(G, 0.1, beta="foo")
|
||||
|
||||
def test_K5_unweighted(self):
|
||||
"""Katz centrality: K5"""
|
||||
G = nx.complete_graph(5)
|
||||
alpha = 0.1
|
||||
b = nx.katz_centrality(G, alpha, weight=None)
|
||||
v = math.sqrt(1 / 5.0)
|
||||
b_answer = dict.fromkeys(G, v)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n])
|
||||
nstart = {n: 1 for n in G}
|
||||
b = nx.eigenvector_centrality_numpy(G, weight=None)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=3)
|
||||
|
||||
def test_P3_unweighted(self):
|
||||
"""Katz centrality: P3"""
|
||||
alpha = 0.1
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162}
|
||||
b = nx.katz_centrality_numpy(G, alpha, weight=None)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=4)
|
||||
|
||||
|
||||
class TestKatzCentralityDirected:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
G = nx.DiGraph()
|
||||
edges = [
|
||||
(1, 2),
|
||||
(1, 3),
|
||||
(2, 4),
|
||||
(3, 2),
|
||||
(3, 5),
|
||||
(4, 2),
|
||||
(4, 5),
|
||||
(4, 6),
|
||||
(5, 6),
|
||||
(5, 7),
|
||||
(5, 8),
|
||||
(6, 8),
|
||||
(7, 1),
|
||||
(7, 5),
|
||||
(7, 8),
|
||||
(8, 6),
|
||||
(8, 7),
|
||||
]
|
||||
G.add_edges_from(edges, weight=2.0)
|
||||
cls.G = G.reverse()
|
||||
cls.G.alpha = 0.1
|
||||
cls.G.evc = [
|
||||
0.3289589783189635,
|
||||
0.2832077296243516,
|
||||
0.3425906003685471,
|
||||
0.3970420865198392,
|
||||
0.41074871061646284,
|
||||
0.272257430756461,
|
||||
0.4201989685435462,
|
||||
0.34229059218038554,
|
||||
]
|
||||
|
||||
H = nx.DiGraph(edges)
|
||||
cls.H = G.reverse()
|
||||
cls.H.alpha = 0.1
|
||||
cls.H.evc = [
|
||||
0.3289589783189635,
|
||||
0.2832077296243516,
|
||||
0.3425906003685471,
|
||||
0.3970420865198392,
|
||||
0.41074871061646284,
|
||||
0.272257430756461,
|
||||
0.4201989685435462,
|
||||
0.34229059218038554,
|
||||
]
|
||||
|
||||
def test_katz_centrality_weighted(self):
|
||||
G = self.G
|
||||
alpha = self.G.alpha
|
||||
p = nx.katz_centrality(G, alpha, weight="weight")
|
||||
for (a, b) in zip(list(p.values()), self.G.evc):
|
||||
assert almost_equal(a, b)
|
||||
|
||||
def test_katz_centrality_unweighted(self):
|
||||
H = self.H
|
||||
alpha = self.H.alpha
|
||||
p = nx.katz_centrality(H, alpha, weight="weight")
|
||||
for (a, b) in zip(list(p.values()), self.H.evc):
|
||||
assert almost_equal(a, b)
|
||||
|
||||
|
||||
class TestKatzCentralityDirectedNumpy(TestKatzCentralityDirected):
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
global np
|
||||
np = pytest.importorskip("numpy")
|
||||
scipy = pytest.importorskip("scipy")
|
||||
|
||||
def test_katz_centrality_weighted(self):
|
||||
G = self.G
|
||||
alpha = self.G.alpha
|
||||
p = nx.katz_centrality_numpy(G, alpha, weight="weight")
|
||||
for (a, b) in zip(list(p.values()), self.G.evc):
|
||||
assert almost_equal(a, b)
|
||||
|
||||
def test_katz_centrality_unweighted(self):
|
||||
H = self.H
|
||||
alpha = self.H.alpha
|
||||
p = nx.katz_centrality_numpy(H, alpha, weight="weight")
|
||||
for (a, b) in zip(list(p.values()), self.H.evc):
|
||||
assert almost_equal(a, b)
|
||||
|
||||
|
||||
class TestKatzEigenvectorVKatz:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
global np
|
||||
global eigvals
|
||||
np = pytest.importorskip("numpy")
|
||||
scipy = pytest.importorskip("scipy")
|
||||
from numpy.linalg import eigvals
|
||||
|
||||
def test_eigenvector_v_katz_random(self):
|
||||
G = nx.gnp_random_graph(10, 0.5, seed=1234)
|
||||
l = float(max(eigvals(nx.adjacency_matrix(G).todense())))
|
||||
e = nx.eigenvector_centrality_numpy(G)
|
||||
k = nx.katz_centrality_numpy(G, 1.0 / l)
|
||||
for n in G:
|
||||
assert almost_equal(e[n], k[n])
|
|
@ -0,0 +1,336 @@
|
|||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
class TestLoadCentrality:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
|
||||
G = nx.Graph()
|
||||
G.add_edge(0, 1, weight=3)
|
||||
G.add_edge(0, 2, weight=2)
|
||||
G.add_edge(0, 3, weight=6)
|
||||
G.add_edge(0, 4, weight=4)
|
||||
G.add_edge(1, 3, weight=5)
|
||||
G.add_edge(1, 5, weight=5)
|
||||
G.add_edge(2, 4, weight=1)
|
||||
G.add_edge(3, 4, weight=2)
|
||||
G.add_edge(3, 5, weight=1)
|
||||
G.add_edge(4, 5, weight=4)
|
||||
cls.G = G
|
||||
cls.exact_weighted = {0: 4.0, 1: 0.0, 2: 8.0, 3: 6.0, 4: 8.0, 5: 0.0}
|
||||
cls.K = nx.krackhardt_kite_graph()
|
||||
cls.P3 = nx.path_graph(3)
|
||||
cls.P4 = nx.path_graph(4)
|
||||
cls.K5 = nx.complete_graph(5)
|
||||
|
||||
cls.C4 = nx.cycle_graph(4)
|
||||
cls.T = nx.balanced_tree(r=2, h=2)
|
||||
cls.Gb = nx.Graph()
|
||||
cls.Gb.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (4, 5), (3, 5)])
|
||||
cls.F = nx.florentine_families_graph()
|
||||
cls.LM = nx.les_miserables_graph()
|
||||
cls.D = nx.cycle_graph(3, create_using=nx.DiGraph())
|
||||
cls.D.add_edges_from([(3, 0), (4, 3)])
|
||||
|
||||
def test_not_strongly_connected(self):
|
||||
b = nx.load_centrality(self.D)
|
||||
result = {0: 5.0 / 12, 1: 1.0 / 4, 2: 1.0 / 12, 3: 1.0 / 4, 4: 0.000}
|
||||
for n in sorted(self.D):
|
||||
assert almost_equal(result[n], b[n], places=3)
|
||||
assert almost_equal(result[n], nx.load_centrality(self.D, n), places=3)
|
||||
|
||||
def test_weighted_load(self):
|
||||
b = nx.load_centrality(self.G, weight="weight", normalized=False)
|
||||
for n in sorted(self.G):
|
||||
assert b[n] == self.exact_weighted[n]
|
||||
|
||||
def test_k5_load(self):
|
||||
G = self.K5
|
||||
c = nx.load_centrality(G)
|
||||
d = {0: 0.000, 1: 0.000, 2: 0.000, 3: 0.000, 4: 0.000}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_p3_load(self):
|
||||
G = self.P3
|
||||
c = nx.load_centrality(G)
|
||||
d = {0: 0.000, 1: 1.000, 2: 0.000}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
c = nx.load_centrality(G, v=1)
|
||||
assert almost_equal(c, 1.0)
|
||||
c = nx.load_centrality(G, v=1, normalized=True)
|
||||
assert almost_equal(c, 1.0)
|
||||
|
||||
def test_p2_load(self):
|
||||
G = nx.path_graph(2)
|
||||
c = nx.load_centrality(G)
|
||||
d = {0: 0.000, 1: 0.000}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_krackhardt_load(self):
|
||||
G = self.K
|
||||
c = nx.load_centrality(G)
|
||||
d = {
|
||||
0: 0.023,
|
||||
1: 0.023,
|
||||
2: 0.000,
|
||||
3: 0.102,
|
||||
4: 0.000,
|
||||
5: 0.231,
|
||||
6: 0.231,
|
||||
7: 0.389,
|
||||
8: 0.222,
|
||||
9: 0.000,
|
||||
}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_florentine_families_load(self):
|
||||
G = self.F
|
||||
c = nx.load_centrality(G)
|
||||
d = {
|
||||
"Acciaiuoli": 0.000,
|
||||
"Albizzi": 0.211,
|
||||
"Barbadori": 0.093,
|
||||
"Bischeri": 0.104,
|
||||
"Castellani": 0.055,
|
||||
"Ginori": 0.000,
|
||||
"Guadagni": 0.251,
|
||||
"Lamberteschi": 0.000,
|
||||
"Medici": 0.522,
|
||||
"Pazzi": 0.000,
|
||||
"Peruzzi": 0.022,
|
||||
"Ridolfi": 0.117,
|
||||
"Salviati": 0.143,
|
||||
"Strozzi": 0.106,
|
||||
"Tornabuoni": 0.090,
|
||||
}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_les_miserables_load(self):
|
||||
G = self.LM
|
||||
c = nx.load_centrality(G)
|
||||
d = {
|
||||
"Napoleon": 0.000,
|
||||
"Myriel": 0.177,
|
||||
"MlleBaptistine": 0.000,
|
||||
"MmeMagloire": 0.000,
|
||||
"CountessDeLo": 0.000,
|
||||
"Geborand": 0.000,
|
||||
"Champtercier": 0.000,
|
||||
"Cravatte": 0.000,
|
||||
"Count": 0.000,
|
||||
"OldMan": 0.000,
|
||||
"Valjean": 0.567,
|
||||
"Labarre": 0.000,
|
||||
"Marguerite": 0.000,
|
||||
"MmeDeR": 0.000,
|
||||
"Isabeau": 0.000,
|
||||
"Gervais": 0.000,
|
||||
"Listolier": 0.000,
|
||||
"Tholomyes": 0.043,
|
||||
"Fameuil": 0.000,
|
||||
"Blacheville": 0.000,
|
||||
"Favourite": 0.000,
|
||||
"Dahlia": 0.000,
|
||||
"Zephine": 0.000,
|
||||
"Fantine": 0.128,
|
||||
"MmeThenardier": 0.029,
|
||||
"Thenardier": 0.075,
|
||||
"Cosette": 0.024,
|
||||
"Javert": 0.054,
|
||||
"Fauchelevent": 0.026,
|
||||
"Bamatabois": 0.008,
|
||||
"Perpetue": 0.000,
|
||||
"Simplice": 0.009,
|
||||
"Scaufflaire": 0.000,
|
||||
"Woman1": 0.000,
|
||||
"Judge": 0.000,
|
||||
"Champmathieu": 0.000,
|
||||
"Brevet": 0.000,
|
||||
"Chenildieu": 0.000,
|
||||
"Cochepaille": 0.000,
|
||||
"Pontmercy": 0.007,
|
||||
"Boulatruelle": 0.000,
|
||||
"Eponine": 0.012,
|
||||
"Anzelma": 0.000,
|
||||
"Woman2": 0.000,
|
||||
"MotherInnocent": 0.000,
|
||||
"Gribier": 0.000,
|
||||
"MmeBurgon": 0.026,
|
||||
"Jondrette": 0.000,
|
||||
"Gavroche": 0.164,
|
||||
"Gillenormand": 0.021,
|
||||
"Magnon": 0.000,
|
||||
"MlleGillenormand": 0.047,
|
||||
"MmePontmercy": 0.000,
|
||||
"MlleVaubois": 0.000,
|
||||
"LtGillenormand": 0.000,
|
||||
"Marius": 0.133,
|
||||
"BaronessT": 0.000,
|
||||
"Mabeuf": 0.028,
|
||||
"Enjolras": 0.041,
|
||||
"Combeferre": 0.001,
|
||||
"Prouvaire": 0.000,
|
||||
"Feuilly": 0.001,
|
||||
"Courfeyrac": 0.006,
|
||||
"Bahorel": 0.002,
|
||||
"Bossuet": 0.032,
|
||||
"Joly": 0.002,
|
||||
"Grantaire": 0.000,
|
||||
"MotherPlutarch": 0.000,
|
||||
"Gueulemer": 0.005,
|
||||
"Babet": 0.005,
|
||||
"Claquesous": 0.005,
|
||||
"Montparnasse": 0.004,
|
||||
"Toussaint": 0.000,
|
||||
"Child1": 0.000,
|
||||
"Child2": 0.000,
|
||||
"Brujon": 0.000,
|
||||
"MmeHucheloup": 0.000,
|
||||
}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_unnormalized_k5_load(self):
|
||||
G = self.K5
|
||||
c = nx.load_centrality(G, normalized=False)
|
||||
d = {0: 0.000, 1: 0.000, 2: 0.000, 3: 0.000, 4: 0.000}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_unnormalized_p3_load(self):
|
||||
G = self.P3
|
||||
c = nx.load_centrality(G, normalized=False)
|
||||
d = {0: 0.000, 1: 2.000, 2: 0.000}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_unnormalized_krackhardt_load(self):
|
||||
G = self.K
|
||||
c = nx.load_centrality(G, normalized=False)
|
||||
d = {
|
||||
0: 1.667,
|
||||
1: 1.667,
|
||||
2: 0.000,
|
||||
3: 7.333,
|
||||
4: 0.000,
|
||||
5: 16.667,
|
||||
6: 16.667,
|
||||
7: 28.000,
|
||||
8: 16.000,
|
||||
9: 0.000,
|
||||
}
|
||||
|
||||
for n in sorted(G):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_unnormalized_florentine_families_load(self):
|
||||
G = self.F
|
||||
c = nx.load_centrality(G, normalized=False)
|
||||
|
||||
d = {
|
||||
"Acciaiuoli": 0.000,
|
||||
"Albizzi": 38.333,
|
||||
"Barbadori": 17.000,
|
||||
"Bischeri": 19.000,
|
||||
"Castellani": 10.000,
|
||||
"Ginori": 0.000,
|
||||
"Guadagni": 45.667,
|
||||
"Lamberteschi": 0.000,
|
||||
"Medici": 95.000,
|
||||
"Pazzi": 0.000,
|
||||
"Peruzzi": 4.000,
|
||||
"Ridolfi": 21.333,
|
||||
"Salviati": 26.000,
|
||||
"Strozzi": 19.333,
|
||||
"Tornabuoni": 16.333,
|
||||
}
|
||||
for n in sorted(G):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_load_betweenness_difference(self):
|
||||
# Difference Between Load and Betweenness
|
||||
# --------------------------------------- The smallest graph
|
||||
# that shows the difference between load and betweenness is
|
||||
# G=ladder_graph(3) (Graph B below)
|
||||
|
||||
# Graph A and B are from Tao Zhou, Jian-Guo Liu, Bing-Hong
|
||||
# Wang: Comment on "Scientific collaboration
|
||||
# networks. II. Shortest paths, weighted networks, and
|
||||
# centrality". https://arxiv.org/pdf/physics/0511084
|
||||
|
||||
# Notice that unlike here, their calculation adds to 1 to the
|
||||
# betweennes of every node i for every path from i to every
|
||||
# other node. This is exactly what it should be, based on
|
||||
# Eqn. (1) in their paper: the eqn is B(v) = \sum_{s\neq t,
|
||||
# s\neq v}{\frac{\sigma_{st}(v)}{\sigma_{st}}}, therefore,
|
||||
# they allow v to be the target node.
|
||||
|
||||
# We follow Brandes 2001, who follows Freeman 1977 that make
|
||||
# the sum for betweenness of v exclude paths where v is either
|
||||
# the source or target node. To agree with their numbers, we
|
||||
# must additionally, remove edge (4,8) from the graph, see AC
|
||||
# example following (there is a mistake in the figure in their
|
||||
# paper - personal communication).
|
||||
|
||||
# A = nx.Graph()
|
||||
# A.add_edges_from([(0,1), (1,2), (1,3), (2,4),
|
||||
# (3,5), (4,6), (4,7), (4,8),
|
||||
# (5,8), (6,9), (7,9), (8,9)])
|
||||
B = nx.Graph() # ladder_graph(3)
|
||||
B.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (4, 5), (3, 5)])
|
||||
c = nx.load_centrality(B, normalized=False)
|
||||
d = {0: 1.750, 1: 1.750, 2: 6.500, 3: 6.500, 4: 1.750, 5: 1.750}
|
||||
for n in sorted(B):
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_c4_edge_load(self):
|
||||
G = self.C4
|
||||
c = nx.edge_load_centrality(G)
|
||||
d = {(0, 1): 6.000, (0, 3): 6.000, (1, 2): 6.000, (2, 3): 6.000}
|
||||
for n in G.edges():
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_p4_edge_load(self):
|
||||
G = self.P4
|
||||
c = nx.edge_load_centrality(G)
|
||||
d = {(0, 1): 6.000, (1, 2): 8.000, (2, 3): 6.000}
|
||||
for n in G.edges():
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_k5_edge_load(self):
|
||||
G = self.K5
|
||||
c = nx.edge_load_centrality(G)
|
||||
d = {
|
||||
(0, 1): 5.000,
|
||||
(0, 2): 5.000,
|
||||
(0, 3): 5.000,
|
||||
(0, 4): 5.000,
|
||||
(1, 2): 5.000,
|
||||
(1, 3): 5.000,
|
||||
(1, 4): 5.000,
|
||||
(2, 3): 5.000,
|
||||
(2, 4): 5.000,
|
||||
(3, 4): 5.000,
|
||||
}
|
||||
for n in G.edges():
|
||||
assert almost_equal(c[n], d[n], places=3)
|
||||
|
||||
def test_tree_edge_load(self):
|
||||
G = self.T
|
||||
c = nx.edge_load_centrality(G)
|
||||
d = {
|
||||
(0, 1): 24.000,
|
||||
(0, 2): 24.000,
|
||||
(1, 3): 12.000,
|
||||
(1, 4): 12.000,
|
||||
(2, 5): 12.000,
|
||||
(2, 6): 12.000,
|
||||
}
|
||||
for n in G.edges():
|
||||
assert almost_equal(c[n], d[n], places=3)
|
|
@ -0,0 +1,81 @@
|
|||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
def example1a_G():
|
||||
G = nx.Graph()
|
||||
G.add_node(1, percolation=0.1)
|
||||
G.add_node(2, percolation=0.2)
|
||||
G.add_node(3, percolation=0.2)
|
||||
G.add_node(4, percolation=0.2)
|
||||
G.add_node(5, percolation=0.3)
|
||||
G.add_node(6, percolation=0.2)
|
||||
G.add_node(7, percolation=0.5)
|
||||
G.add_node(8, percolation=0.5)
|
||||
G.add_edges_from([(1, 4), (2, 4), (3, 4), (4, 5), (5, 6), (6, 7), (6, 8)])
|
||||
return G
|
||||
|
||||
|
||||
def example1b_G():
|
||||
G = nx.Graph()
|
||||
G.add_node(1, percolation=0.3)
|
||||
G.add_node(2, percolation=0.5)
|
||||
G.add_node(3, percolation=0.5)
|
||||
G.add_node(4, percolation=0.2)
|
||||
G.add_node(5, percolation=0.3)
|
||||
G.add_node(6, percolation=0.2)
|
||||
G.add_node(7, percolation=0.1)
|
||||
G.add_node(8, percolation=0.1)
|
||||
G.add_edges_from([(1, 4), (2, 4), (3, 4), (4, 5), (5, 6), (6, 7), (6, 8)])
|
||||
return G
|
||||
|
||||
|
||||
class TestPercolationCentrality:
|
||||
def test_percolation_example1a(self):
|
||||
"""percolation centrality: example 1a"""
|
||||
G = example1a_G()
|
||||
p = nx.percolation_centrality(G)
|
||||
p_answer = {4: 0.625, 6: 0.667}
|
||||
for n in p_answer:
|
||||
assert almost_equal(p[n], p_answer[n], places=3)
|
||||
|
||||
def test_percolation_example1b(self):
|
||||
"""percolation centrality: example 1a"""
|
||||
G = example1b_G()
|
||||
p = nx.percolation_centrality(G)
|
||||
p_answer = {4: 0.825, 6: 0.4}
|
||||
for n in p_answer:
|
||||
assert almost_equal(p[n], p_answer[n], places=3)
|
||||
|
||||
def test_converge_to_betweenness(self):
|
||||
"""percolation centrality: should converge to betweenness
|
||||
centrality when all nodes are percolated the same"""
|
||||
# taken from betweenness test test_florentine_families_graph
|
||||
G = nx.florentine_families_graph()
|
||||
b_answer = {
|
||||
"Acciaiuoli": 0.000,
|
||||
"Albizzi": 0.212,
|
||||
"Barbadori": 0.093,
|
||||
"Bischeri": 0.104,
|
||||
"Castellani": 0.055,
|
||||
"Ginori": 0.000,
|
||||
"Guadagni": 0.255,
|
||||
"Lamberteschi": 0.000,
|
||||
"Medici": 0.522,
|
||||
"Pazzi": 0.000,
|
||||
"Peruzzi": 0.022,
|
||||
"Ridolfi": 0.114,
|
||||
"Salviati": 0.143,
|
||||
"Strozzi": 0.103,
|
||||
"Tornabuoni": 0.092,
|
||||
}
|
||||
|
||||
p_states = {k: 1.0 for k, v in b_answer.items()}
|
||||
p_answer = nx.percolation_centrality(G, states=p_states)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(p_answer[n], b_answer[n], places=3)
|
||||
|
||||
p_states = {k: 0.3 for k, v in b_answer.items()}
|
||||
p_answer = nx.percolation_centrality(G, states=p_states)
|
||||
for n in sorted(G):
|
||||
assert almost_equal(p_answer[n], b_answer[n], places=3)
|
|
@ -0,0 +1,110 @@
|
|||
"""Unit tests for the :mod:`networkx.algorithms.centrality.reaching` module."""
|
||||
import pytest
|
||||
|
||||
from networkx import nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
class TestGlobalReachingCentrality:
|
||||
"""Unit tests for the global reaching centrality function."""
|
||||
|
||||
def test_non_positive_weights(self):
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = nx.DiGraph()
|
||||
nx.global_reaching_centrality(G, weight="weight")
|
||||
|
||||
def test_negatively_weighted(self):
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(0, 1, -2), (1, 2, +1)])
|
||||
nx.global_reaching_centrality(G, weight="weight")
|
||||
|
||||
def test_directed_star(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_weighted_edges_from([(1, 2, 0.5), (1, 3, 0.5)])
|
||||
grc = nx.global_reaching_centrality
|
||||
assert grc(G, normalized=False, weight="weight") == 0.5
|
||||
assert grc(G) == 1
|
||||
|
||||
def test_undirected_unweighted_star(self):
|
||||
G = nx.star_graph(2)
|
||||
grc = nx.global_reaching_centrality
|
||||
assert grc(G, normalized=False, weight=None) == 0.25
|
||||
|
||||
def test_undirected_weighted_star(self):
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(1, 2, 1), (1, 3, 2)])
|
||||
grc = nx.global_reaching_centrality
|
||||
assert grc(G, normalized=False, weight="weight") == 0.375
|
||||
|
||||
def test_cycle_directed_unweighted(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_edge(1, 2)
|
||||
G.add_edge(2, 1)
|
||||
assert nx.global_reaching_centrality(G, weight=None) == 0
|
||||
|
||||
def test_cycle_undirected_unweighted(self):
|
||||
G = nx.Graph()
|
||||
G.add_edge(1, 2)
|
||||
assert nx.global_reaching_centrality(G, weight=None) == 0
|
||||
|
||||
def test_cycle_directed_weighted(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_weighted_edges_from([(1, 2, 1), (2, 1, 1)])
|
||||
assert nx.global_reaching_centrality(G) == 0
|
||||
|
||||
def test_cycle_undirected_weighted(self):
|
||||
G = nx.Graph()
|
||||
G.add_edge(1, 2, weight=1)
|
||||
grc = nx.global_reaching_centrality
|
||||
assert grc(G, normalized=False) == 0
|
||||
|
||||
def test_directed_weighted(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("A", "B", weight=5)
|
||||
G.add_edge("B", "C", weight=1)
|
||||
G.add_edge("B", "D", weight=0.25)
|
||||
G.add_edge("D", "E", weight=1)
|
||||
|
||||
denom = len(G) - 1
|
||||
A_local = sum([5, 3, 2.625, 2.0833333333333]) / denom
|
||||
B_local = sum([1, 0.25, 0.625]) / denom
|
||||
C_local = 0
|
||||
D_local = sum([1]) / denom
|
||||
E_local = 0
|
||||
|
||||
local_reach_ctrs = [A_local, C_local, B_local, D_local, E_local]
|
||||
max_local = max(local_reach_ctrs)
|
||||
expected = sum(max_local - lrc for lrc in local_reach_ctrs) / denom
|
||||
grc = nx.global_reaching_centrality
|
||||
actual = grc(G, normalized=False, weight="weight")
|
||||
assert almost_equal(expected, actual, places=7)
|
||||
|
||||
|
||||
class TestLocalReachingCentrality:
|
||||
"""Unit tests for the local reaching centrality function."""
|
||||
|
||||
def test_non_positive_weights(self):
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = nx.DiGraph()
|
||||
G.add_weighted_edges_from([(0, 1, 0)])
|
||||
nx.local_reaching_centrality(G, 0, weight="weight")
|
||||
|
||||
def test_negatively_weighted(self):
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(0, 1, -2), (1, 2, +1)])
|
||||
nx.local_reaching_centrality(G, 0, weight="weight")
|
||||
|
||||
def test_undirected_unweighted_star(self):
|
||||
G = nx.star_graph(2)
|
||||
grc = nx.local_reaching_centrality
|
||||
assert grc(G, 1, weight=None, normalized=False) == 0.75
|
||||
|
||||
def test_undirected_weighted_star(self):
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(1, 2, 1), (1, 3, 2)])
|
||||
centrality = nx.local_reaching_centrality(
|
||||
G, 1, normalized=False, weight="weight"
|
||||
)
|
||||
assert centrality == 1.5
|
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
Tests for second order centrality.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
scipy = pytest.importorskip("scipy")
|
||||
|
||||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
class TestSecondOrderCentrality:
|
||||
def test_empty(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
G = nx.empty_graph()
|
||||
nx.second_order_centrality(G)
|
||||
|
||||
def test_non_connected(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
G = nx.Graph()
|
||||
G.add_node(0)
|
||||
G.add_node(1)
|
||||
nx.second_order_centrality(G)
|
||||
|
||||
def test_non_negative_edge_weights(self):
|
||||
with pytest.raises(nx.NetworkXException):
|
||||
G = nx.path_graph(2)
|
||||
G.add_edge(0, 1, weight=-1)
|
||||
nx.second_order_centrality(G)
|
||||
|
||||
def test_one_node_graph(self):
|
||||
"""Second order centrality: single node"""
|
||||
G = nx.Graph()
|
||||
G.add_node(0)
|
||||
G.add_edge(0, 0)
|
||||
assert nx.second_order_centrality(G)[0] == 0
|
||||
|
||||
def test_P3(self):
|
||||
"""Second order centrality: line graph, as defined in paper"""
|
||||
G = nx.path_graph(3)
|
||||
b_answer = {0: 3.741, 1: 1.414, 2: 3.741}
|
||||
|
||||
b = nx.second_order_centrality(G)
|
||||
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=2)
|
||||
|
||||
def test_K3(self):
|
||||
"""Second order centrality: complete graph, as defined in paper"""
|
||||
G = nx.complete_graph(3)
|
||||
b_answer = {0: 1.414, 1: 1.414, 2: 1.414}
|
||||
|
||||
b = nx.second_order_centrality(G)
|
||||
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=2)
|
||||
|
||||
def test_ring_graph(self):
|
||||
"""Second order centrality: ring graph, as defined in paper"""
|
||||
G = nx.cycle_graph(5)
|
||||
b_answer = {0: 4.472, 1: 4.472, 2: 4.472, 3: 4.472, 4: 4.472}
|
||||
|
||||
b = nx.second_order_centrality(G)
|
||||
|
||||
for n in sorted(G):
|
||||
assert almost_equal(b[n], b_answer[n], places=2)
|
|
@ -0,0 +1,92 @@
|
|||
import pytest
|
||||
|
||||
numpy = pytest.importorskip("numpy")
|
||||
scipy = pytest.importorskip("scipy")
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.centrality.subgraph_alg import (
|
||||
estrada_index,
|
||||
communicability_betweenness_centrality,
|
||||
subgraph_centrality,
|
||||
subgraph_centrality_exp,
|
||||
)
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
class TestSubgraph:
|
||||
def test_subgraph_centrality(self):
|
||||
answer = {0: 1.5430806348152433, 1: 1.5430806348152433}
|
||||
result = subgraph_centrality(nx.path_graph(2))
|
||||
for k, v in result.items():
|
||||
assert almost_equal(answer[k], result[k], places=7)
|
||||
|
||||
answer1 = {
|
||||
"1": 1.6445956054135658,
|
||||
"Albert": 2.4368257358712189,
|
||||
"Aric": 2.4368257358712193,
|
||||
"Dan": 3.1306328496328168,
|
||||
"Franck": 2.3876142275231915,
|
||||
}
|
||||
G1 = nx.Graph(
|
||||
[
|
||||
("Franck", "Aric"),
|
||||
("Aric", "Dan"),
|
||||
("Dan", "Albert"),
|
||||
("Albert", "Franck"),
|
||||
("Dan", "1"),
|
||||
("Franck", "Albert"),
|
||||
]
|
||||
)
|
||||
result1 = subgraph_centrality(G1)
|
||||
for k, v in result1.items():
|
||||
assert almost_equal(answer1[k], result1[k], places=7)
|
||||
result1 = subgraph_centrality_exp(G1)
|
||||
for k, v in result1.items():
|
||||
assert almost_equal(answer1[k], result1[k], places=7)
|
||||
|
||||
def test_subgraph_centrality_big_graph(self):
|
||||
g199 = nx.complete_graph(199)
|
||||
g200 = nx.complete_graph(200)
|
||||
|
||||
comm199 = nx.subgraph_centrality(g199)
|
||||
comm199_exp = nx.subgraph_centrality_exp(g199)
|
||||
|
||||
comm200 = nx.subgraph_centrality(g200)
|
||||
comm200_exp = nx.subgraph_centrality_exp(g200)
|
||||
|
||||
def test_communicability_betweenness_centrality(self):
|
||||
answer = {
|
||||
0: 0.07017447951484615,
|
||||
1: 0.71565598701107991,
|
||||
2: 0.71565598701107991,
|
||||
3: 0.07017447951484615,
|
||||
}
|
||||
result = communicability_betweenness_centrality(nx.path_graph(4))
|
||||
for k, v in result.items():
|
||||
assert almost_equal(answer[k], result[k], places=7)
|
||||
|
||||
answer1 = {
|
||||
"1": 0.060039074193949521,
|
||||
"Albert": 0.315470761661372,
|
||||
"Aric": 0.31547076166137211,
|
||||
"Dan": 0.68297778678316201,
|
||||
"Franck": 0.21977926617449497,
|
||||
}
|
||||
G1 = nx.Graph(
|
||||
[
|
||||
("Franck", "Aric"),
|
||||
("Aric", "Dan"),
|
||||
("Dan", "Albert"),
|
||||
("Albert", "Franck"),
|
||||
("Dan", "1"),
|
||||
("Franck", "Albert"),
|
||||
]
|
||||
)
|
||||
result1 = communicability_betweenness_centrality(G1)
|
||||
for k, v in result1.items():
|
||||
assert almost_equal(answer1[k], result1[k], places=7)
|
||||
|
||||
def test_estrada_index(self):
|
||||
answer = 1041.2470334195475
|
||||
result = estrada_index(nx.karate_club_graph())
|
||||
assert almost_equal(answer, result, places=7)
|
|
@ -0,0 +1,304 @@
|
|||
"""Test trophic levels, trophic differences and trophic coherence
|
||||
"""
|
||||
import pytest
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
|
||||
import networkx as nx
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
|
||||
def test_trophic_levels():
|
||||
"""Trivial example
|
||||
"""
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("a", "b")
|
||||
G.add_edge("b", "c")
|
||||
|
||||
d = nx.trophic_levels(G)
|
||||
assert d == {"a": 1, "b": 2, "c": 3}
|
||||
|
||||
|
||||
def test_trophic_levels_levine():
|
||||
"""Example from Figure 5 in Stephen Levine (1980) J. theor. Biol. 83,
|
||||
195-207
|
||||
"""
|
||||
S = nx.DiGraph()
|
||||
S.add_edge(1, 2, weight=1.0)
|
||||
S.add_edge(1, 3, weight=0.2)
|
||||
S.add_edge(1, 4, weight=0.8)
|
||||
S.add_edge(2, 3, weight=0.2)
|
||||
S.add_edge(2, 5, weight=0.3)
|
||||
S.add_edge(4, 3, weight=0.6)
|
||||
S.add_edge(4, 5, weight=0.7)
|
||||
S.add_edge(5, 4, weight=0.2)
|
||||
|
||||
# save copy for later, test intermediate implementation details first
|
||||
S2 = S.copy()
|
||||
|
||||
# drop nodes of in-degree zero
|
||||
z = [nid for nid, d in S.in_degree if d == 0]
|
||||
for nid in z:
|
||||
S.remove_node(nid)
|
||||
|
||||
# find adjacency matrix
|
||||
q = nx.linalg.graphmatrix.adjacency_matrix(S).T
|
||||
|
||||
# fmt: off
|
||||
expected_q = np.array([
|
||||
[0, 0, 0., 0],
|
||||
[0.2, 0, 0.6, 0],
|
||||
[0, 0, 0, 0.2],
|
||||
[0.3, 0, 0.7, 0]
|
||||
])
|
||||
# fmt: on
|
||||
assert np.array_equal(q.todense(), expected_q)
|
||||
|
||||
# must be square, size of number of nodes
|
||||
assert len(q.shape) == 2
|
||||
assert q.shape[0] == q.shape[1]
|
||||
assert q.shape[0] == len(S)
|
||||
|
||||
nn = q.shape[0]
|
||||
|
||||
i = np.eye(nn)
|
||||
n = np.linalg.inv(i - q)
|
||||
y = np.dot(np.asarray(n), np.ones(nn))
|
||||
|
||||
expected_y = np.array([1, 2.07906977, 1.46511628, 2.3255814])
|
||||
assert np.allclose(y, expected_y)
|
||||
|
||||
expected_d = {1: 1, 2: 2, 3: 3.07906977, 4: 2.46511628, 5: 3.3255814}
|
||||
|
||||
d = nx.trophic_levels(S2)
|
||||
|
||||
for nid, level in d.items():
|
||||
expected_level = expected_d[nid]
|
||||
assert almost_equal(expected_level, level)
|
||||
|
||||
|
||||
def test_trophic_levels_simple():
|
||||
matrix_a = np.array([[0, 0], [1, 0]])
|
||||
G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph)
|
||||
d = nx.trophic_levels(G)
|
||||
assert almost_equal(d[0], 2)
|
||||
assert almost_equal(d[1], 1)
|
||||
|
||||
|
||||
def test_trophic_levels_more_complex():
|
||||
# fmt: off
|
||||
matrix = np.array([
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 0, 0]
|
||||
])
|
||||
# fmt: on
|
||||
G = nx.from_numpy_array(matrix, create_using=nx.DiGraph)
|
||||
d = nx.trophic_levels(G)
|
||||
expected_result = [1, 2, 3, 4]
|
||||
for ind in range(4):
|
||||
assert almost_equal(d[ind], expected_result[ind])
|
||||
|
||||
# fmt: off
|
||||
matrix = np.array([
|
||||
[0, 1, 1, 0],
|
||||
[0, 0, 1, 1],
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 0, 0]
|
||||
])
|
||||
# fmt: on
|
||||
G = nx.from_numpy_array(matrix, create_using=nx.DiGraph)
|
||||
d = nx.trophic_levels(G)
|
||||
|
||||
expected_result = [1, 2, 2.5, 3.25]
|
||||
print("Calculated result: ", d)
|
||||
print("Expected Result: ", expected_result)
|
||||
|
||||
for ind in range(4):
|
||||
assert almost_equal(d[ind], expected_result[ind])
|
||||
|
||||
|
||||
def test_trophic_levels_even_more_complex():
|
||||
# fmt: off
|
||||
# Another, bigger matrix
|
||||
matrix = np.array([
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0]
|
||||
])
|
||||
# Generated this linear system using pen and paper:
|
||||
K = np.array([
|
||||
[1, 0, -1, 0, 0],
|
||||
[0, 0.5, 0, -0.5, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, -0.5, 0, 1, -0.5],
|
||||
[0, 0, 0, 0, 1],
|
||||
])
|
||||
# fmt: on
|
||||
result_1 = np.ravel(np.matmul(np.linalg.inv(K), np.ones(5)))
|
||||
G = nx.from_numpy_array(matrix, create_using=nx.DiGraph)
|
||||
result_2 = nx.trophic_levels(G)
|
||||
|
||||
for ind in range(5):
|
||||
assert almost_equal(result_1[ind], result_2[ind])
|
||||
|
||||
|
||||
def test_trophic_levels_singular_matrix():
|
||||
"""Should raise an error with graphs with only non-basal nodes
|
||||
"""
|
||||
matrix = np.identity(4)
|
||||
G = nx.from_numpy_array(matrix, create_using=nx.DiGraph)
|
||||
with pytest.raises(nx.NetworkXError) as e:
|
||||
nx.trophic_levels(G)
|
||||
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)."
|
||||
)
|
||||
assert msg in str(e.value)
|
||||
|
||||
|
||||
def test_trophic_levels_singular_with_basal():
|
||||
"""Should fail to compute if there are any parts of the graph which are not
|
||||
reachable from any basal node (with in-degree zero).
|
||||
"""
|
||||
G = nx.DiGraph()
|
||||
# a has in-degree zero
|
||||
G.add_edge("a", "b")
|
||||
|
||||
# b is one level above a, c and d
|
||||
G.add_edge("c", "b")
|
||||
G.add_edge("d", "b")
|
||||
|
||||
# c and d form a loop, neither are reachable from a
|
||||
G.add_edge("c", "d")
|
||||
G.add_edge("d", "c")
|
||||
|
||||
with pytest.raises(nx.NetworkXError) as e:
|
||||
nx.trophic_levels(G)
|
||||
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)."
|
||||
)
|
||||
assert msg in str(e.value)
|
||||
|
||||
# if self-loops are allowed, smaller example:
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("a", "b") # a has in-degree zero
|
||||
G.add_edge("c", "b") # b is one level above a and c
|
||||
G.add_edge("c", "c") # c has a self-loop
|
||||
with pytest.raises(nx.NetworkXError) as e:
|
||||
nx.trophic_levels(G)
|
||||
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)."
|
||||
)
|
||||
assert msg in str(e.value)
|
||||
|
||||
|
||||
def test_trophic_differences():
|
||||
matrix_a = np.array([[0, 1], [0, 0]])
|
||||
G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph)
|
||||
diffs = nx.trophic_differences(G)
|
||||
assert almost_equal(diffs[(0, 1)], 1)
|
||||
|
||||
# fmt: off
|
||||
matrix_b = np.array([
|
||||
[0, 1, 1, 0],
|
||||
[0, 0, 1, 1],
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 0, 0]
|
||||
])
|
||||
# fmt: on
|
||||
G = nx.from_numpy_array(matrix_b, create_using=nx.DiGraph)
|
||||
diffs = nx.trophic_differences(G)
|
||||
|
||||
assert almost_equal(diffs[(0, 1)], 1)
|
||||
assert almost_equal(diffs[(0, 2)], 1.5)
|
||||
assert almost_equal(diffs[(1, 2)], 0.5)
|
||||
assert almost_equal(diffs[(1, 3)], 1.25)
|
||||
assert almost_equal(diffs[(2, 3)], 0.75)
|
||||
|
||||
|
||||
def test_trophic_incoherence_parameter_no_cannibalism():
|
||||
matrix_a = np.array([[0, 1], [0, 0]])
|
||||
G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph)
|
||||
q = nx.trophic_incoherence_parameter(G, cannibalism=False)
|
||||
assert almost_equal(q, 0)
|
||||
|
||||
# fmt: off
|
||||
matrix_b = np.array([
|
||||
[0, 1, 1, 0],
|
||||
[0, 0, 1, 1],
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 0, 0]
|
||||
])
|
||||
# fmt: on
|
||||
G = nx.from_numpy_array(matrix_b, create_using=nx.DiGraph)
|
||||
q = nx.trophic_incoherence_parameter(G, cannibalism=False)
|
||||
assert almost_equal(q, np.std([1, 1.5, 0.5, 0.75, 1.25]))
|
||||
|
||||
# fmt: off
|
||||
matrix_c = np.array([
|
||||
[0, 1, 1, 0],
|
||||
[0, 1, 1, 1],
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 0, 1]
|
||||
])
|
||||
# fmt: on
|
||||
G = nx.from_numpy_array(matrix_c, create_using=nx.DiGraph)
|
||||
q = nx.trophic_incoherence_parameter(G, cannibalism=False)
|
||||
# Ignore the -link
|
||||
assert almost_equal(q, np.std([1, 1.5, 0.5, 0.75, 1.25]))
|
||||
|
||||
# no self-loops case
|
||||
# fmt: off
|
||||
matrix_d = np.array([
|
||||
[0, 1, 1, 0],
|
||||
[0, 0, 1, 1],
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 0, 0]
|
||||
])
|
||||
# fmt: on
|
||||
G = nx.from_numpy_array(matrix_d, create_using=nx.DiGraph)
|
||||
q = nx.trophic_incoherence_parameter(G, cannibalism=False)
|
||||
# Ignore the -link
|
||||
assert almost_equal(q, np.std([1, 1.5, 0.5, 0.75, 1.25]))
|
||||
|
||||
|
||||
def test_trophic_incoherence_parameter_cannibalism():
|
||||
matrix_a = np.array([[0, 1], [0, 0]])
|
||||
G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph)
|
||||
q = nx.trophic_incoherence_parameter(G, cannibalism=True)
|
||||
assert almost_equal(q, 0)
|
||||
|
||||
# fmt: off
|
||||
matrix_b = np.array([
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0]
|
||||
])
|
||||
# fmt: on
|
||||
G = nx.from_numpy_array(matrix_b, create_using=nx.DiGraph)
|
||||
q = nx.trophic_incoherence_parameter(G, cannibalism=True)
|
||||
assert almost_equal(q, 2)
|
||||
|
||||
# fmt: off
|
||||
matrix_c = np.array([
|
||||
[0, 1, 1, 0],
|
||||
[0, 0, 1, 1],
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 0, 0]
|
||||
])
|
||||
# fmt: on
|
||||
G = nx.from_numpy_array(matrix_c, create_using=nx.DiGraph)
|
||||
q = nx.trophic_incoherence_parameter(G, cannibalism=True)
|
||||
# Ignore the -link
|
||||
assert almost_equal(q, np.std([1, 1.5, 0.5, 0.75, 1.25]))
|
|
@ -0,0 +1,61 @@
|
|||
"""
|
||||
Unit tests for VoteRank.
|
||||
"""
|
||||
|
||||
|
||||
import networkx as nx
|
||||
|
||||
|
||||
class TestVoteRankCentrality:
|
||||
# Example Graph present in reference paper
|
||||
def test_voterank_centrality_1(self):
|
||||
G = nx.Graph()
|
||||
G.add_edges_from(
|
||||
[
|
||||
(7, 8),
|
||||
(7, 5),
|
||||
(7, 9),
|
||||
(5, 0),
|
||||
(0, 1),
|
||||
(0, 2),
|
||||
(0, 3),
|
||||
(0, 4),
|
||||
(1, 6),
|
||||
(2, 6),
|
||||
(3, 6),
|
||||
(4, 6),
|
||||
]
|
||||
)
|
||||
assert [0, 7, 6] == nx.voterank(G)
|
||||
|
||||
# Graph unit test
|
||||
def test_voterank_centrality_2(self):
|
||||
G = nx.florentine_families_graph()
|
||||
d = nx.voterank(G, 4)
|
||||
exact = ["Medici", "Strozzi", "Guadagni", "Castellani"]
|
||||
assert exact == d
|
||||
|
||||
# DiGraph unit test
|
||||
def test_voterank_centrality_3(self):
|
||||
G = nx.gnc_graph(10, seed=7)
|
||||
d = nx.voterank(G, 4)
|
||||
exact = [3, 6, 8]
|
||||
assert exact == d
|
||||
|
||||
# MultiGraph unit test
|
||||
def test_voterank_centrality_4(self):
|
||||
G = nx.MultiGraph()
|
||||
G.add_edges_from(
|
||||
[(0, 1), (0, 1), (1, 2), (2, 5), (2, 5), (5, 6), (5, 6), (2, 4), (4, 3)]
|
||||
)
|
||||
exact = [2, 1, 5, 4]
|
||||
assert exact == nx.voterank(G)
|
||||
|
||||
# MultiDiGraph unit test
|
||||
def test_voterank_centrality_5(self):
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_edges_from(
|
||||
[(0, 1), (0, 1), (1, 2), (2, 5), (2, 5), (5, 6), (5, 6), (2, 4), (4, 3)]
|
||||
)
|
||||
exact = [2, 0, 5, 4]
|
||||
assert exact == nx.voterank(G)
|
168
venv/Lib/site-packages/networkx/algorithms/centrality/trophic.py
Normal file
168
venv/Lib/site-packages/networkx/algorithms/centrality/trophic.py
Normal file
|
@ -0,0 +1,168 @@
|
|||
"""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 vale.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] 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[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"
|
||||
"""
|
||||
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()))
|
|
@ -0,0 +1,75 @@
|
|||
"""Algorithm to select influential nodes in a graph using VoteRank."""
|
||||
|
||||
__all__ = ["voterank"]
|
||||
|
||||
|
||||
def voterank(G, number_of_nodes=None):
|
||||
"""Select a list of influential nodes in a graph using VoteRank algorithm
|
||||
|
||||
VoteRank [1]_ computes a ranking of the nodes in a graph G based on a
|
||||
voting scheme. With VoteRank, all nodes vote for each of its in-neighbours
|
||||
and the node with the highest votes is elected iteratively. The voting
|
||||
ability of out-neighbors of elected nodes is decreased in subsequent turns.
|
||||
|
||||
Note: We treat each edge independently in case of multigraphs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph.
|
||||
|
||||
number_of_nodes : integer, optional
|
||||
Number of ranked nodes to extract (default all nodes).
|
||||
|
||||
Returns
|
||||
-------
|
||||
voterank : list
|
||||
Ordered list of computed seeds.
|
||||
Only nodes with positive number of votes are returned.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Zhang, J.-X. et al. (2016).
|
||||
Identifying a set of influential spreaders in complex networks.
|
||||
Sci. Rep. 6, 27823; doi: 10.1038/srep27823.
|
||||
"""
|
||||
influential_nodes = []
|
||||
voterank = {}
|
||||
if len(G) == 0:
|
||||
return influential_nodes
|
||||
if number_of_nodes is None or number_of_nodes > len(G):
|
||||
number_of_nodes = len(G)
|
||||
if G.is_directed():
|
||||
# For directed graphs compute average out-degree
|
||||
avgDegree = sum(deg for _, deg in G.out_degree()) / len(G)
|
||||
else:
|
||||
# For undirected graphs compute average degree
|
||||
avgDegree = sum(deg for _, deg in G.degree()) / len(G)
|
||||
# step 1 - initiate all nodes to (0,1) (score, voting ability)
|
||||
for n in G.nodes():
|
||||
voterank[n] = [0, 1]
|
||||
# Repeat steps 1b to 4 until num_seeds are elected.
|
||||
for _ in range(number_of_nodes):
|
||||
# step 1b - reset rank
|
||||
for n in G.nodes():
|
||||
voterank[n][0] = 0
|
||||
# step 2 - vote
|
||||
for n, nbr in G.edges():
|
||||
# In directed graphs nodes only vote for their in-neighbors
|
||||
voterank[n][0] += voterank[nbr][1]
|
||||
if not G.is_directed():
|
||||
voterank[nbr][0] += voterank[n][1]
|
||||
for n in influential_nodes:
|
||||
voterank[n][0] = 0
|
||||
# step 3 - select top node
|
||||
n = max(G.nodes, key=lambda x: voterank[x][0])
|
||||
if voterank[n][0] == 0:
|
||||
return influential_nodes
|
||||
influential_nodes.append(n)
|
||||
# weaken the selected node
|
||||
voterank[n] = [0, 0]
|
||||
# step 4 - update voterank properties
|
||||
for _, nbr in G.edges(n):
|
||||
voterank[nbr][1] -= 1 / avgDegree
|
||||
voterank[nbr][1] = max(voterank[nbr][1], 0)
|
||||
return influential_nodes
|
Loading…
Add table
Add a link
Reference in a new issue