1"""
2Closeness centrality measures.
3"""
4
5import functools
6
7import networkx as nx
8from networkx.exception import NetworkXError
9from networkx.utils.decorators import not_implemented_for
10
11__all__ = ["closeness_centrality", "incremental_closeness_centrality"]
12
13
14@nx._dispatchable(edge_attrs="distance")
15def closeness_centrality(G, u=None, distance=None, wf_improved=True):
16 r"""Compute closeness centrality for nodes.
17
18 Closeness centrality [1]_ of a node `u` is the reciprocal of the
19 average shortest path distance to `u` over all `n-1` reachable nodes.
20
21 .. math::
22
23 C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)},
24
25 where `d(v, u)` is the shortest-path distance between `v` and `u`,
26 and `n-1` is the number of nodes reachable from `u`. Notice that the
27 closeness distance function computes the incoming distance to `u`
28 for directed graphs. To use outward distance, act on `G.reverse()`.
29
30 Notice that higher values of closeness indicate higher centrality.
31
32 Wasserman and Faust propose an improved formula for graphs with
33 more than one connected component. The result is "a ratio of the
34 fraction of actors in the group who are reachable, to the average
35 distance" from the reachable actors [2]_. You might think this
36 scale factor is inverted but it is not. As is, nodes from small
37 components receive a smaller closeness value. Letting `N` denote
38 the number of nodes in the graph,
39
40 .. math::
41
42 C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)},
43
44 Parameters
45 ----------
46 G : graph
47 A NetworkX graph
48
49 u : node, optional
50 Return only the value for node u
51
52 distance : edge attribute key, optional (default=None)
53 Use the specified edge attribute as the edge distance in shortest
54 path calculations. If `None` (the default) all edges have a distance of 1.
55 Absent edge attributes are assigned a distance of 1. Note that no check
56 is performed to ensure that edges have the provided attribute.
57
58 wf_improved : bool, optional (default=True)
59 If True, scale by the fraction of nodes reachable. This gives the
60 Wasserman and Faust improved formula. For single component graphs
61 it is the same as the original formula.
62
63 Returns
64 -------
65 nodes : dictionary
66 Dictionary of nodes with closeness centrality as the value.
67
68 Examples
69 --------
70 >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)])
71 >>> nx.closeness_centrality(G)
72 {0: 1.0, 1: 1.0, 2: 0.75, 3: 0.75}
73
74 See Also
75 --------
76 betweenness_centrality, load_centrality, eigenvector_centrality,
77 degree_centrality, incremental_closeness_centrality
78
79 Notes
80 -----
81 The closeness centrality is normalized to `(n-1)/(|G|-1)` where
82 `n` is the number of nodes in the connected part of graph
83 containing the node. If the graph is not completely connected,
84 this algorithm computes the closeness centrality for each
85 connected part separately scaled by that parts size.
86
87 If the 'distance' keyword is set to an edge attribute key then the
88 shortest-path length will be computed using Dijkstra's algorithm with
89 that edge attribute as the edge weight.
90
91 The closeness centrality uses *inward* distance to a node, not outward.
92 If you want to use outword distances apply the function to `G.reverse()`
93
94 In NetworkX 2.2 and earlier a bug caused Dijkstra's algorithm to use the
95 outward distance rather than the inward distance. If you use a 'distance'
96 keyword and a DiGraph, your results will change between v2.2 and v2.3.
97
98 References
99 ----------
100 .. [1] Linton C. Freeman: Centrality in networks: I.
101 Conceptual clarification. Social Networks 1:215-239, 1979.
102 https://doi.org/10.1016/0378-8733(78)90021-7
103 .. [2] pg. 201 of Wasserman, S. and Faust, K.,
104 Social Network Analysis: Methods and Applications, 1994,
105 Cambridge University Press.
106 """
107 if G.is_directed():
108 G = G.reverse() # create a reversed graph view
109
110 if distance is not None:
111 # use Dijkstra's algorithm with specified attribute as edge weight
112 path_length = functools.partial(
113 nx.single_source_dijkstra_path_length, weight=distance
114 )
115 else:
116 path_length = nx.single_source_shortest_path_length
117
118 if u is None:
119 nodes = G.nodes
120 else:
121 nodes = [u]
122 closeness_dict = {}
123 for n in nodes:
124 sp = path_length(G, n)
125 totsp = sum(sp.values())
126 len_G = len(G)
127 _closeness_centrality = 0.0
128 if totsp > 0.0 and len_G > 1:
129 _closeness_centrality = (len(sp) - 1.0) / totsp
130 # normalize to number of nodes-1 in connected part
131 if wf_improved:
132 s = (len(sp) - 1.0) / (len_G - 1)
133 _closeness_centrality *= s
134 closeness_dict[n] = _closeness_centrality
135 if u is not None:
136 return closeness_dict[u]
137 return closeness_dict
138
139
140@not_implemented_for("directed")
141@nx._dispatchable(mutates_input=True)
142def incremental_closeness_centrality(
143 G, edge, prev_cc=None, insertion=True, wf_improved=True
144):
145 r"""Incremental closeness centrality for nodes.
146
147 Compute closeness centrality for nodes using level-based work filtering
148 as described in Incremental Algorithms for Closeness Centrality by Sariyuce et al.
149
150 Level-based work filtering detects unnecessary updates to the closeness
151 centrality and filters them out.
152
153 ---
154 From "Incremental Algorithms for Closeness Centrality":
155
156 Theorem 1: Let :math:`G = (V, E)` be a graph and u and v be two vertices in V
157 such that there is no edge (u, v) in E. Let :math:`G' = (V, E \cup uv)`
158 Then :math:`cc[s] = cc'[s]` if and only if :math:`\left|dG(s, u) - dG(s, v)\right| \leq 1`.
159
160 Where :math:`dG(u, v)` denotes the length of the shortest path between
161 two vertices u, v in a graph G, cc[s] is the closeness centrality for a
162 vertex s in V, and cc'[s] is the closeness centrality for a
163 vertex s in V, with the (u, v) edge added.
164 ---
165
166 We use Theorem 1 to filter out updates when adding or removing an edge.
167 When adding an edge (u, v), we compute the shortest path lengths from all
168 other nodes to u and to v before the node is added. When removing an edge,
169 we compute the shortest path lengths after the edge is removed. Then we
170 apply Theorem 1 to use previously computed closeness centrality for nodes
171 where :math:`\left|dG(s, u) - dG(s, v)\right| \leq 1`. This works only for
172 undirected, unweighted graphs; the distance argument is not supported.
173
174 Closeness centrality [1]_ of a node `u` is the reciprocal of the
175 sum of the shortest path distances from `u` to all `n-1` other nodes.
176 Since the sum of distances depends on the number of nodes in the
177 graph, closeness is normalized by the sum of minimum possible
178 distances `n-1`.
179
180 .. math::
181
182 C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)},
183
184 where `d(v, u)` is the shortest-path distance between `v` and `u`,
185 and `n` is the number of nodes in the graph.
186
187 Notice that higher values of closeness indicate higher centrality.
188
189 Parameters
190 ----------
191 G : graph
192 A NetworkX graph
193
194 edge : tuple
195 The modified edge (u, v) in the graph.
196
197 prev_cc : dictionary
198 The previous closeness centrality for all nodes in the graph.
199
200 insertion : bool, optional
201 If True (default) the edge was inserted, otherwise it was deleted from the graph.
202
203 wf_improved : bool, optional (default=True)
204 If True, scale by the fraction of nodes reachable. This gives the
205 Wasserman and Faust improved formula. For single component graphs
206 it is the same as the original formula.
207
208 Returns
209 -------
210 nodes : dictionary
211 Dictionary of nodes with closeness centrality as the value.
212
213 See Also
214 --------
215 betweenness_centrality, load_centrality, eigenvector_centrality,
216 degree_centrality, closeness_centrality
217
218 Notes
219 -----
220 The closeness centrality is normalized to `(n-1)/(|G|-1)` where
221 `n` is the number of nodes in the connected part of graph
222 containing the node. If the graph is not completely connected,
223 this algorithm computes the closeness centrality for each
224 connected part separately.
225
226 References
227 ----------
228 .. [1] Freeman, L.C., 1979. Centrality in networks: I.
229 Conceptual clarification. Social Networks 1, 215--239.
230 https://doi.org/10.1016/0378-8733(78)90021-7
231 .. [2] Sariyuce, A.E. ; Kaya, K. ; Saule, E. ; Catalyiirek, U.V. Incremental
232 Algorithms for Closeness Centrality. 2013 IEEE International Conference on Big Data
233 http://sariyuce.com/papers/bigdata13.pdf
234 """
235 if prev_cc is not None and set(prev_cc.keys()) != set(G.nodes()):
236 raise NetworkXError("prev_cc and G do not have the same nodes")
237
238 # Unpack edge
239 (u, v) = edge
240 path_length = nx.single_source_shortest_path_length
241
242 if insertion:
243 # For edge insertion, we want shortest paths before the edge is inserted
244 du = path_length(G, u)
245 dv = path_length(G, v)
246
247 G.add_edge(u, v)
248 else:
249 G.remove_edge(u, v)
250
251 # For edge removal, we want shortest paths after the edge is removed
252 du = path_length(G, u)
253 dv = path_length(G, v)
254
255 if prev_cc is None:
256 return nx.closeness_centrality(G)
257
258 nodes = G.nodes()
259 closeness_dict = {}
260 for n in nodes:
261 if n in du and n in dv and abs(du[n] - dv[n]) <= 1:
262 closeness_dict[n] = prev_cc[n]
263 else:
264 sp = path_length(G, n)
265 totsp = sum(sp.values())
266 len_G = len(G)
267 _closeness_centrality = 0.0
268 if totsp > 0.0 and len_G > 1:
269 _closeness_centrality = (len(sp) - 1.0) / totsp
270 # normalize to number of nodes-1 in connected part
271 if wf_improved:
272 s = (len(sp) - 1.0) / (len_G - 1)
273 _closeness_centrality *= s
274 closeness_dict[n] = _closeness_centrality
275
276 # Leave the graph as we found it
277 if insertion:
278 G.remove_edge(u, v)
279 else:
280 G.add_edge(u, v)
281
282 return closeness_dict