Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/centrality/eigenvector.py: 23%
39 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
1"""Functions for computing eigenvector centrality."""
2import math
4import networkx as nx
5from networkx.utils import not_implemented_for
7__all__ = ["eigenvector_centrality", "eigenvector_centrality_numpy"]
10@not_implemented_for("multigraph")
11@nx._dispatch(edge_attrs="weight")
12def eigenvector_centrality(G, max_iter=100, tol=1.0e-6, nstart=None, weight=None):
13 r"""Compute the eigenvector centrality for the graph G.
15 Eigenvector centrality computes the centrality for a node by adding
16 the centrality of its predecessors. The centrality for node $i$ is the
17 $i$-th element of a left eigenvector associated with the eigenvalue $\lambda$
18 of maximum modulus that is positive. Such an eigenvector $x$ is
19 defined up to a multiplicative constant by the equation
21 .. math::
23 \lambda x^T = x^T A,
25 where $A$ is the adjacency matrix of the graph G. By definition of
26 row-column product, the equation above is equivalent to
28 .. math::
30 \lambda x_i = \sum_{j\to i}x_j.
32 That is, adding the eigenvector centralities of the predecessors of
33 $i$ one obtains the eigenvector centrality of $i$ multiplied by
34 $\lambda$. In the case of undirected graphs, $x$ also solves the familiar
35 right-eigenvector equation $Ax = \lambda x$.
37 By virtue of the Perron–Frobenius theorem [1]_, if G is strongly
38 connected there is a unique eigenvector $x$, and all its entries
39 are strictly positive.
41 If G is not strongly connected there might be several left
42 eigenvectors associated with $\lambda$, and some of their elements
43 might be zero.
45 Parameters
46 ----------
47 G : graph
48 A networkx graph.
50 max_iter : integer, optional (default=100)
51 Maximum number of power iterations.
53 tol : float, optional (default=1.0e-6)
54 Error tolerance (in Euclidean norm) used to check convergence in
55 power iteration.
57 nstart : dictionary, optional (default=None)
58 Starting value of power iteration for each node. Must have a nonzero
59 projection on the desired eigenvector for the power method to converge.
60 If None, this implementation uses an all-ones vector, which is a safe
61 choice.
63 weight : None or string, optional (default=None)
64 If None, all edge weights are considered equal. Otherwise holds the
65 name of the edge attribute used as weight. In this measure the
66 weight is interpreted as the connection strength.
68 Returns
69 -------
70 nodes : dictionary
71 Dictionary of nodes with eigenvector centrality as the value. The
72 associated vector has unit Euclidean norm and the values are
73 nonegative.
75 Examples
76 --------
77 >>> G = nx.path_graph(4)
78 >>> centrality = nx.eigenvector_centrality(G)
79 >>> sorted((v, f"{c:0.2f}") for v, c in centrality.items())
80 [(0, '0.37'), (1, '0.60'), (2, '0.60'), (3, '0.37')]
82 Raises
83 ------
84 NetworkXPointlessConcept
85 If the graph G is the null graph.
87 NetworkXError
88 If each value in `nstart` is zero.
90 PowerIterationFailedConvergence
91 If the algorithm fails to converge to the specified tolerance
92 within the specified number of iterations of the power iteration
93 method.
95 See Also
96 --------
97 eigenvector_centrality_numpy
98 :func:`~networkx.algorithms.link_analysis.pagerank_alg.pagerank`
99 :func:`~networkx.algorithms.link_analysis.hits_alg.hits`
101 Notes
102 -----
103 Eigenvector centrality was introduced by Landau [2]_ for chess
104 tournaments. It was later rediscovered by Wei [3]_ and then
105 popularized by Kendall [4]_ in the context of sport ranking. Berge
106 introduced a general definition for graphs based on social connections
107 [5]_. Bonacich [6]_ reintroduced again eigenvector centrality and made
108 it popular in link analysis.
110 This function computes the left dominant eigenvector, which corresponds
111 to adding the centrality of predecessors: this is the usual approach.
112 To add the centrality of successors first reverse the graph with
113 ``G.reverse()``.
115 The implementation uses power iteration [7]_ to compute a dominant
116 eigenvector starting from the provided vector `nstart`. Convergence is
117 guaranteed as long as `nstart` has a nonzero projection on a dominant
118 eigenvector, which certainly happens using the default value.
120 The method stops when the change in the computed vector between two
121 iterations is smaller than an error tolerance of ``G.number_of_nodes()
122 * tol`` or after ``max_iter`` iterations, but in the second case it
123 raises an exception.
125 This implementation uses $(A + I)$ rather than the adjacency matrix
126 $A$ because the change preserves eigenvectors, but it shifts the
127 spectrum, thus guaranteeing convergence even for networks with
128 negative eigenvalues of maximum modulus.
130 References
131 ----------
132 .. [1] Abraham Berman and Robert J. Plemmons.
133 "Nonnegative Matrices in the Mathematical Sciences."
134 Classics in Applied Mathematics. SIAM, 1994.
136 .. [2] Edmund Landau.
137 "Zur relativen Wertbemessung der Turnierresultate."
138 Deutsches Wochenschach, 11:366–369, 1895.
140 .. [3] Teh-Hsing Wei.
141 "The Algebraic Foundations of Ranking Theory."
142 PhD thesis, University of Cambridge, 1952.
144 .. [4] Maurice G. Kendall.
145 "Further contributions to the theory of paired comparisons."
146 Biometrics, 11(1):43–62, 1955.
147 https://www.jstor.org/stable/3001479
149 .. [5] Claude Berge
150 "Théorie des graphes et ses applications."
151 Dunod, Paris, France, 1958.
153 .. [6] Phillip Bonacich.
154 "Technique for analyzing overlapping memberships."
155 Sociological Methodology, 4:176–185, 1972.
156 https://www.jstor.org/stable/270732
158 .. [7] Power iteration:: https://en.wikipedia.org/wiki/Power_iteration
160 """
161 if len(G) == 0:
162 raise nx.NetworkXPointlessConcept(
163 "cannot compute centrality for the null graph"
164 )
165 # If no initial vector is provided, start with the all-ones vector.
166 if nstart is None:
167 nstart = {v: 1 for v in G}
168 if all(v == 0 for v in nstart.values()):
169 raise nx.NetworkXError("initial vector cannot have all zero values")
170 # Normalize the initial vector so that each entry is in [0, 1]. This is
171 # guaranteed to never have a divide-by-zero error by the previous line.
172 nstart_sum = sum(nstart.values())
173 x = {k: v / nstart_sum for k, v in nstart.items()}
174 nnodes = G.number_of_nodes()
175 # make up to max_iter iterations
176 for _ in range(max_iter):
177 xlast = x
178 x = xlast.copy() # Start with xlast times I to iterate with (A+I)
179 # do the multiplication y^T = x^T A (left eigenvector)
180 for n in x:
181 for nbr in G[n]:
182 w = G[n][nbr].get(weight, 1) if weight else 1
183 x[nbr] += xlast[n] * w
184 # Normalize the vector. The normalization denominator `norm`
185 # should never be zero by the Perron--Frobenius
186 # theorem. However, in case it is due to numerical error, we
187 # assume the norm to be one instead.
188 norm = math.hypot(*x.values()) or 1
189 x = {k: v / norm for k, v in x.items()}
190 # Check for convergence (in the L_1 norm).
191 if sum(abs(x[n] - xlast[n]) for n in x) < nnodes * tol:
192 return x
193 raise nx.PowerIterationFailedConvergence(max_iter)
196@nx._dispatch(edge_attrs="weight")
197def eigenvector_centrality_numpy(G, weight=None, max_iter=50, tol=0):
198 r"""Compute the eigenvector centrality for the graph G.
200 Eigenvector centrality computes the centrality for a node by adding
201 the centrality of its predecessors. The centrality for node $i$ is the
202 $i$-th element of a left eigenvector associated with the eigenvalue $\lambda$
203 of maximum modulus that is positive. Such an eigenvector $x$ is
204 defined up to a multiplicative constant by the equation
206 .. math::
208 \lambda x^T = x^T A,
210 where $A$ is the adjacency matrix of the graph G. By definition of
211 row-column product, the equation above is equivalent to
213 .. math::
215 \lambda x_i = \sum_{j\to i}x_j.
217 That is, adding the eigenvector centralities of the predecessors of
218 $i$ one obtains the eigenvector centrality of $i$ multiplied by
219 $\lambda$. In the case of undirected graphs, $x$ also solves the familiar
220 right-eigenvector equation $Ax = \lambda x$.
222 By virtue of the Perron–Frobenius theorem [1]_, if G is strongly
223 connected there is a unique eigenvector $x$, and all its entries
224 are strictly positive.
226 If G is not strongly connected there might be several left
227 eigenvectors associated with $\lambda$, and some of their elements
228 might be zero.
230 Parameters
231 ----------
232 G : graph
233 A networkx graph.
235 max_iter : integer, optional (default=50)
236 Maximum number of Arnoldi update iterations allowed.
238 tol : float, optional (default=0)
239 Relative accuracy for eigenvalues (stopping criterion).
240 The default value of 0 implies machine precision.
242 weight : None or string, optional (default=None)
243 If None, all edge weights are considered equal. Otherwise holds the
244 name of the edge attribute used as weight. In this measure the
245 weight is interpreted as the connection strength.
247 Returns
248 -------
249 nodes : dictionary
250 Dictionary of nodes with eigenvector centrality as the value. The
251 associated vector has unit Euclidean norm and the values are
252 nonegative.
254 Examples
255 --------
256 >>> G = nx.path_graph(4)
257 >>> centrality = nx.eigenvector_centrality_numpy(G)
258 >>> print([f"{node} {centrality[node]:0.2f}" for node in centrality])
259 ['0 0.37', '1 0.60', '2 0.60', '3 0.37']
261 Raises
262 ------
263 NetworkXPointlessConcept
264 If the graph G is the null graph.
266 ArpackNoConvergence
267 When the requested convergence is not obtained. The currently
268 converged eigenvalues and eigenvectors can be found as
269 eigenvalues and eigenvectors attributes of the exception object.
271 See Also
272 --------
273 :func:`scipy.sparse.linalg.eigs`
274 eigenvector_centrality
275 :func:`~networkx.algorithms.link_analysis.pagerank_alg.pagerank`
276 :func:`~networkx.algorithms.link_analysis.hits_alg.hits`
278 Notes
279 -----
280 Eigenvector centrality was introduced by Landau [2]_ for chess
281 tournaments. It was later rediscovered by Wei [3]_ and then
282 popularized by Kendall [4]_ in the context of sport ranking. Berge
283 introduced a general definition for graphs based on social connections
284 [5]_. Bonacich [6]_ reintroduced again eigenvector centrality and made
285 it popular in link analysis.
287 This function computes the left dominant eigenvector, which corresponds
288 to adding the centrality of predecessors: this is the usual approach.
289 To add the centrality of successors first reverse the graph with
290 ``G.reverse()``.
292 This implementation uses the
293 :func:`SciPy sparse eigenvalue solver<scipy.sparse.linalg.eigs>` (ARPACK)
294 to find the largest eigenvalue/eigenvector pair using Arnoldi iterations
295 [7]_.
297 References
298 ----------
299 .. [1] Abraham Berman and Robert J. Plemmons.
300 "Nonnegative Matrices in the Mathematical Sciences."
301 Classics in Applied Mathematics. SIAM, 1994.
303 .. [2] Edmund Landau.
304 "Zur relativen Wertbemessung der Turnierresultate."
305 Deutsches Wochenschach, 11:366–369, 1895.
307 .. [3] Teh-Hsing Wei.
308 "The Algebraic Foundations of Ranking Theory."
309 PhD thesis, University of Cambridge, 1952.
311 .. [4] Maurice G. Kendall.
312 "Further contributions to the theory of paired comparisons."
313 Biometrics, 11(1):43–62, 1955.
314 https://www.jstor.org/stable/3001479
316 .. [5] Claude Berge
317 "Théorie des graphes et ses applications."
318 Dunod, Paris, France, 1958.
320 .. [6] Phillip Bonacich.
321 "Technique for analyzing overlapping memberships."
322 Sociological Methodology, 4:176–185, 1972.
323 https://www.jstor.org/stable/270732
325 .. [7] Arnoldi iteration:: https://en.wikipedia.org/wiki/Arnoldi_iteration
327 """
328 import numpy as np
329 import scipy as sp
331 if len(G) == 0:
332 raise nx.NetworkXPointlessConcept(
333 "cannot compute centrality for the null graph"
334 )
335 M = nx.to_scipy_sparse_array(G, nodelist=list(G), weight=weight, dtype=float)
336 _, eigenvector = sp.sparse.linalg.eigs(
337 M.T, k=1, which="LR", maxiter=max_iter, tol=tol
338 )
339 largest = eigenvector.flatten().real
340 norm = np.sign(largest.sum()) * sp.linalg.norm(largest)
341 return dict(zip(G, largest / norm))