Fixed database typo and removed unnecessary class identifier.

This commit is contained in:
Batuhan Berk Başoğlu 2020-10-14 10:10:37 -04:00
parent 00ad49a143
commit 45fb349a7d
5098 changed files with 952558 additions and 85 deletions

View file

@ -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 *

View file

@ -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: 3541, 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

View file

@ -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

View file

@ -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

View file

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

View file

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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 PerronFrobenius 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):11701182, 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 PerronFrobenius 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):11701182, 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))

View file

@ -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

View 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, 126134.
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)

View file

@ -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)
}

View 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):3943, 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):3943, 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

View 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):14, 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

View file

@ -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

View file

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

View file

@ -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

View file

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

View file

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

View file

@ -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])

View file

@ -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

View file

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

View file

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

View file

@ -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

View file

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

View file

@ -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

View file

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

View file

@ -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])

View file

@ -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

View file

@ -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])

View file

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

View file

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

View file

@ -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

View file

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

View file

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

View file

@ -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]))

View file

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

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

View file

@ -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