comparison env/lib/python3.9/site-packages/networkx/algorithms/smallworld.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 """Functions for estimating the small-world-ness of graphs.
2
3 A small world network is characterized by a small average shortest path length,
4 and a large clustering coefficient.
5
6 Small-worldness is commonly measured with the coefficient sigma or omega.
7
8 Both coefficients compare the average clustering coefficient and shortest path
9 length of a given graph against the same quantities for an equivalent random
10 or lattice graph.
11
12 For more information, see the Wikipedia article on small-world network [1]_.
13
14 .. [1] Small-world network:: https://en.wikipedia.org/wiki/Small-world_network
15
16 """
17 import networkx as nx
18 from networkx.utils import not_implemented_for
19 from networkx.utils import py_random_state
20
21 __all__ = ["random_reference", "lattice_reference", "sigma", "omega"]
22
23
24 @py_random_state(3)
25 @not_implemented_for("directed")
26 @not_implemented_for("multigraph")
27 def random_reference(G, niter=1, connectivity=True, seed=None):
28 """Compute a random graph by swapping edges of a given graph.
29
30 Parameters
31 ----------
32 G : graph
33 An undirected graph with 4 or more nodes.
34
35 niter : integer (optional, default=1)
36 An edge is rewired approximately `niter` times.
37
38 connectivity : boolean (optional, default=True)
39 When True, ensure connectivity for the randomized graph.
40
41 seed : integer, random_state, or None (default)
42 Indicator of random number generation state.
43 See :ref:`Randomness<randomness>`.
44
45 Returns
46 -------
47 G : graph
48 The randomized graph.
49
50 Notes
51 -----
52 The implementation is adapted from the algorithm by Maslov and Sneppen
53 (2002) [1]_.
54
55 References
56 ----------
57 .. [1] Maslov, Sergei, and Kim Sneppen.
58 "Specificity and stability in topology of protein networks."
59 Science 296.5569 (2002): 910-913.
60 """
61 if G.is_directed():
62 msg = "random_reference() not defined for directed graphs."
63 raise nx.NetworkXError(msg)
64 if len(G) < 4:
65 raise nx.NetworkXError("Graph has less than four nodes.")
66
67 from networkx.utils import cumulative_distribution, discrete_sequence
68
69 local_conn = nx.connectivity.local_edge_connectivity
70
71 G = G.copy()
72 keys, degrees = zip(*G.degree()) # keys, degree
73 cdf = cumulative_distribution(degrees) # cdf of degree
74 nnodes = len(G)
75 nedges = nx.number_of_edges(G)
76 niter = niter * nedges
77 ntries = int(nnodes * nedges / (nnodes * (nnodes - 1) / 2))
78 swapcount = 0
79
80 for i in range(niter):
81 n = 0
82 while n < ntries:
83 # pick two random edges without creating edge list
84 # choose source node indices from discrete distribution
85 (ai, ci) = discrete_sequence(2, cdistribution=cdf, seed=seed)
86 if ai == ci:
87 continue # same source, skip
88 a = keys[ai] # convert index to label
89 c = keys[ci]
90 # choose target uniformly from neighbors
91 b = seed.choice(list(G.neighbors(a)))
92 d = seed.choice(list(G.neighbors(c)))
93 bi = keys.index(b)
94 di = keys.index(d)
95 if b in [a, c, d] or d in [a, b, c]:
96 continue # all vertices should be different
97
98 # don't create parallel edges
99 if (d not in G[a]) and (b not in G[c]):
100 G.add_edge(a, d)
101 G.add_edge(c, b)
102 G.remove_edge(a, b)
103 G.remove_edge(c, d)
104
105 # Check if the graph is still connected
106 if connectivity and local_conn(G, a, b) == 0:
107 # Not connected, revert the swap
108 G.remove_edge(a, d)
109 G.remove_edge(c, b)
110 G.add_edge(a, b)
111 G.add_edge(c, d)
112 else:
113 swapcount += 1
114 break
115 n += 1
116 return G
117
118
119 @py_random_state(4)
120 @not_implemented_for("directed")
121 @not_implemented_for("multigraph")
122 def lattice_reference(G, niter=1, D=None, connectivity=True, seed=None):
123 """Latticize the given graph by swapping edges.
124
125 Parameters
126 ----------
127 G : graph
128 An undirected graph with 4 or more nodes.
129
130 niter : integer (optional, default=1)
131 An edge is rewired approximatively niter times.
132
133 D : numpy.array (optional, default=None)
134 Distance to the diagonal matrix.
135
136 connectivity : boolean (optional, default=True)
137 Ensure connectivity for the latticized graph when set to True.
138
139 seed : integer, random_state, or None (default)
140 Indicator of random number generation state.
141 See :ref:`Randomness<randomness>`.
142
143 Returns
144 -------
145 G : graph
146 The latticized graph.
147
148 Notes
149 -----
150 The implementation is adapted from the algorithm by Sporns et al. [1]_.
151 which is inspired from the original work by Maslov and Sneppen(2002) [2]_.
152
153 References
154 ----------
155 .. [1] Sporns, Olaf, and Jonathan D. Zwi.
156 "The small world of the cerebral cortex."
157 Neuroinformatics 2.2 (2004): 145-162.
158 .. [2] Maslov, Sergei, and Kim Sneppen.
159 "Specificity and stability in topology of protein networks."
160 Science 296.5569 (2002): 910-913.
161 """
162 import numpy as np
163 from networkx.utils import cumulative_distribution, discrete_sequence
164
165 local_conn = nx.connectivity.local_edge_connectivity
166
167 if G.is_directed():
168 msg = "lattice_reference() not defined for directed graphs."
169 raise nx.NetworkXError(msg)
170 if len(G) < 4:
171 raise nx.NetworkXError("Graph has less than four nodes.")
172 # Instead of choosing uniformly at random from a generated edge list,
173 # this algorithm chooses nonuniformly from the set of nodes with
174 # probability weighted by degree.
175 G = G.copy()
176 keys, degrees = zip(*G.degree()) # keys, degree
177 cdf = cumulative_distribution(degrees) # cdf of degree
178
179 nnodes = len(G)
180 nedges = nx.number_of_edges(G)
181 if D is None:
182 D = np.zeros((nnodes, nnodes))
183 un = np.arange(1, nnodes)
184 um = np.arange(nnodes - 1, 0, -1)
185 u = np.append((0,), np.where(un < um, un, um))
186
187 for v in range(int(np.ceil(nnodes / 2))):
188 D[nnodes - v - 1, :] = np.append(u[v + 1 :], u[: v + 1])
189 D[v, :] = D[nnodes - v - 1, :][::-1]
190
191 niter = niter * nedges
192 ntries = int(nnodes * nedges / (nnodes * (nnodes - 1) / 2))
193 swapcount = 0
194
195 for i in range(niter):
196 n = 0
197 while n < ntries:
198 # pick two random edges without creating edge list
199 # choose source node indices from discrete distribution
200 (ai, ci) = discrete_sequence(2, cdistribution=cdf, seed=seed)
201 if ai == ci:
202 continue # same source, skip
203 a = keys[ai] # convert index to label
204 c = keys[ci]
205 # choose target uniformly from neighbors
206 b = seed.choice(list(G.neighbors(a)))
207 d = seed.choice(list(G.neighbors(c)))
208 bi = keys.index(b)
209 di = keys.index(d)
210
211 if b in [a, c, d] or d in [a, b, c]:
212 continue # all vertices should be different
213
214 # don't create parallel edges
215 if (d not in G[a]) and (b not in G[c]):
216 if D[ai, bi] + D[ci, di] >= D[ai, ci] + D[bi, di]:
217 # only swap if we get closer to the diagonal
218 G.add_edge(a, d)
219 G.add_edge(c, b)
220 G.remove_edge(a, b)
221 G.remove_edge(c, d)
222
223 # Check if the graph is still connected
224 if connectivity and local_conn(G, a, b) == 0:
225 # Not connected, revert the swap
226 G.remove_edge(a, d)
227 G.remove_edge(c, b)
228 G.add_edge(a, b)
229 G.add_edge(c, d)
230 else:
231 swapcount += 1
232 break
233 n += 1
234
235 return G
236
237
238 @py_random_state(3)
239 @not_implemented_for("directed")
240 @not_implemented_for("multigraph")
241 def sigma(G, niter=100, nrand=10, seed=None):
242 """Returns the small-world coefficient (sigma) of the given graph.
243
244 The small-world coefficient is defined as:
245 sigma = C/Cr / L/Lr
246 where C and L are respectively the average clustering coefficient and
247 average shortest path length of G. Cr and Lr are respectively the average
248 clustering coefficient and average shortest path length of an equivalent
249 random graph.
250
251 A graph is commonly classified as small-world if sigma>1.
252
253 Parameters
254 ----------
255 G : NetworkX graph
256 An undirected graph.
257 niter : integer (optional, default=100)
258 Approximate number of rewiring per edge to compute the equivalent
259 random graph.
260 nrand : integer (optional, default=10)
261 Number of random graphs generated to compute the average clustering
262 coefficient (Cr) and average shortest path length (Lr).
263 seed : integer, random_state, or None (default)
264 Indicator of random number generation state.
265 See :ref:`Randomness<randomness>`.
266
267 Returns
268 -------
269 sigma : float
270 The small-world coefficient of G.
271
272 Notes
273 -----
274 The implementation is adapted from Humphries et al. [1]_ [2]_.
275
276 References
277 ----------
278 .. [1] The brainstem reticular formation is a small-world, not scale-free,
279 network M. D. Humphries, K. Gurney and T. J. Prescott,
280 Proc. Roy. Soc. B 2006 273, 503-511, doi:10.1098/rspb.2005.3354.
281 .. [2] Humphries and Gurney (2008).
282 "Network 'Small-World-Ness': A Quantitative Method for Determining
283 Canonical Network Equivalence".
284 PLoS One. 3 (4). PMID 18446219. doi:10.1371/journal.pone.0002051.
285 """
286 import numpy as np
287
288 # Compute the mean clustering coefficient and average shortest path length
289 # for an equivalent random graph
290 randMetrics = {"C": [], "L": []}
291 for i in range(nrand):
292 Gr = random_reference(G, niter=niter, seed=seed)
293 randMetrics["C"].append(nx.transitivity(Gr))
294 randMetrics["L"].append(nx.average_shortest_path_length(Gr))
295
296 C = nx.transitivity(G)
297 L = nx.average_shortest_path_length(G)
298 Cr = np.mean(randMetrics["C"])
299 Lr = np.mean(randMetrics["L"])
300
301 sigma = (C / Cr) / (L / Lr)
302
303 return sigma
304
305
306 @py_random_state(3)
307 @not_implemented_for("directed")
308 @not_implemented_for("multigraph")
309 def omega(G, niter=100, nrand=10, seed=None):
310 """Returns the small-world coefficient (omega) of a graph
311
312 The small-world coefficient of a graph G is:
313
314 omega = Lr/L - C/Cl
315
316 where C and L are respectively the average clustering coefficient and
317 average shortest path length of G. Lr is the average shortest path length
318 of an equivalent random graph and Cl is the average clustering coefficient
319 of an equivalent lattice graph.
320
321 The small-world coefficient (omega) ranges between -1 and 1. Values close
322 to 0 means the G features small-world characteristics. Values close to -1
323 means G has a lattice shape whereas values close to 1 means G is a random
324 graph.
325
326 Parameters
327 ----------
328 G : NetworkX graph
329 An undirected graph.
330
331 niter: integer (optional, default=100)
332 Approximate number of rewiring per edge to compute the equivalent
333 random graph.
334
335 nrand: integer (optional, default=10)
336 Number of random graphs generated to compute the average clustering
337 coefficient (Cr) and average shortest path length (Lr).
338
339 seed : integer, random_state, or None (default)
340 Indicator of random number generation state.
341 See :ref:`Randomness<randomness>`.
342
343
344 Returns
345 -------
346 omega : float
347 The small-work coefficient (omega)
348
349 Notes
350 -----
351 The implementation is adapted from the algorithm by Telesford et al. [1]_.
352
353 References
354 ----------
355 .. [1] Telesford, Joyce, Hayasaka, Burdette, and Laurienti (2011).
356 "The Ubiquity of Small-World Networks".
357 Brain Connectivity. 1 (0038): 367-75. PMC 3604768. PMID 22432451.
358 doi:10.1089/brain.2011.0038.
359 """
360 import numpy as np
361
362 # Compute the mean clustering coefficient and average shortest path length
363 # for an equivalent random graph
364 randMetrics = {"C": [], "L": []}
365 for i in range(nrand):
366 Gr = random_reference(G, niter=niter, seed=seed)
367 Gl = lattice_reference(G, niter=niter, seed=seed)
368 randMetrics["C"].append(nx.transitivity(Gl))
369 randMetrics["L"].append(nx.average_shortest_path_length(Gr))
370
371 C = nx.transitivity(G)
372 L = nx.average_shortest_path_length(G)
373 Cl = np.mean(randMetrics["C"])
374 Lr = np.mean(randMetrics["L"])
375
376 omega = (Lr / L) - (C / Cl)
377
378 return omega