334 lines
10 KiB
Python
334 lines
10 KiB
Python
"""Katz centrality."""
|
||
import math
|
||
|
||
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.
|
||
In this measure the weight is interpreted as the connection strength.
|
||
|
||
Returns
|
||
-------
|
||
nodes : dictionary
|
||
Dictionary of nodes with Katz centrality as the value.
|
||
|
||
Raises
|
||
------
|
||
NetworkXError
|
||
If the parameter `beta` is not a scalar but lacks a value for at least
|
||
one node
|
||
|
||
PowerIterationFailedConvergence
|
||
If the algorithm fails to converge to the specified tolerance
|
||
within the specified number of iterations of the power iteration
|
||
method.
|
||
|
||
Examples
|
||
--------
|
||
>>> import math
|
||
>>> G = nx.path_graph(4)
|
||
>>> phi = (1 + math.sqrt(5)) / 2.0 # largest eigenvalue of adj matrix
|
||
>>> centrality = nx.katz_centrality(G, 1 / phi - 0.01)
|
||
>>> for n, c in sorted(centrality.items()):
|
||
... print(f"{n} {c:.2f}")
|
||
0 0.37
|
||
1 0.60
|
||
2 0.60
|
||
3 0.37
|
||
|
||
See Also
|
||
--------
|
||
katz_centrality_numpy
|
||
eigenvector_centrality
|
||
eigenvector_centrality_numpy
|
||
pagerank
|
||
hits
|
||
|
||
Notes
|
||
-----
|
||
Katz centrality was introduced by [2]_.
|
||
|
||
This algorithm it uses the power method to find the eigenvector
|
||
corresponding to the largest eigenvalue of the adjacency matrix of ``G``.
|
||
The parameter ``alpha`` should be strictly less than the inverse of largest
|
||
eigenvalue of the adjacency matrix for the algorithm to converge.
|
||
You can use ``max(nx.adjacency_spectrum(G))`` to get $\lambda_{\max}$ the largest
|
||
eigenvalue of the adjacency matrix.
|
||
The iteration will stop after ``max_iter`` iterations or an error tolerance of
|
||
``number_of_nodes(G) * tol`` has been reached.
|
||
|
||
When $\alpha = 1/\lambda_{\max}$ and $\beta=0$, Katz centrality is the same
|
||
as eigenvector centrality.
|
||
|
||
For directed graphs this finds "left" eigenvectors which corresponds
|
||
to the in-edges in the graph. For out-edges Katz centrality
|
||
first reverse the graph with ``G.reverse()``.
|
||
|
||
References
|
||
----------
|
||
.. [1] Mark E. J. Newman:
|
||
Networks: An Introduction.
|
||
Oxford University Press, USA, 2010, p. 720.
|
||
.. [2] Leo Katz:
|
||
A New Status Index Derived from Sociometric Index.
|
||
Psychometrika 18(1):39–43, 1953
|
||
https://link.springer.com/content/pdf/10.1007/BF02289026.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 err:
|
||
b = beta
|
||
if set(beta) != set(G):
|
||
raise nx.NetworkXError(
|
||
"beta dictionary " "must have a value for every node"
|
||
) from err
|
||
|
||
# make up to max_iter iterations
|
||
for _ 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
|
||
error = sum(abs(x[n] - xlast[n]) for n in x)
|
||
if error < nnodes * tol:
|
||
if normalized:
|
||
# normalize vector
|
||
try:
|
||
s = 1.0 / math.hypot(*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.
|
||
In this measure the weight is interpreted as the connection strength.
|
||
|
||
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. 173.
|
||
.. [2] Leo Katz:
|
||
A New Status Index Derived from Sociometric Index.
|
||
Psychometrika 18(1):39–43, 1953
|
||
https://link.springer.com/content/pdf/10.1007/BF02289026.pdf
|
||
"""
|
||
import numpy as np
|
||
|
||
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)) * beta
|
||
except (TypeError, ValueError, AttributeError) as err:
|
||
raise nx.NetworkXError("beta must be a number") from err
|
||
|
||
A = nx.adjacency_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
|