Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/algorithms/smallworld.py: 17%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

143 statements  

1"""Functions for estimating the small-world-ness of graphs. 

2 

3A small world network is characterized by a small average shortest path length, 

4and a large clustering coefficient. 

5 

6Small-worldness is commonly measured with the coefficient sigma or omega. 

7 

8Both coefficients compare the average clustering coefficient and shortest path 

9length of a given graph against the same quantities for an equivalent random 

10or lattice graph. 

11 

12For 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 

18import networkx as nx 

19from networkx.utils import not_implemented_for, py_random_state 

20 

21__all__ = ["random_reference", "lattice_reference", "sigma", "omega"] 

22 

23 

24@not_implemented_for("directed") 

25@not_implemented_for("multigraph") 

26@py_random_state(3) 

27@nx._dispatchable(returns_graph=True) 

28def random_reference(G, niter=1, connectivity=True, seed=None): 

29 """Compute a random graph by swapping edges of a given graph. 

30 

31 Parameters 

32 ---------- 

33 G : graph 

34 An undirected graph with 4 or more nodes. 

35 

36 niter : integer (optional, default=1) 

37 An edge is rewired approximately `niter` times. 

38 

39 connectivity : boolean (optional, default=True) 

40 When True, ensure connectivity for the randomized graph. 

41 

42 seed : integer, random_state, or None (default) 

43 Indicator of random number generation state. 

44 See :ref:`Randomness<randomness>`. 

45 

46 Returns 

47 ------- 

48 G : graph 

49 The randomized graph. 

50 

51 Raises 

52 ------ 

53 NetworkXError 

54 If there are fewer than 4 nodes or 2 edges in `G` 

55 

56 Notes 

57 ----- 

58 The implementation is adapted from the algorithm by Maslov and Sneppen 

59 (2002) [1]_. 

60 

61 References 

62 ---------- 

63 .. [1] Maslov, Sergei, and Kim Sneppen. 

64 "Specificity and stability in topology of protein networks." 

65 Science 296.5569 (2002): 910-913. 

66 """ 

67 if len(G) < 4: 

68 raise nx.NetworkXError("Graph has fewer than four nodes.") 

69 if len(G.edges) < 2: 

70 raise nx.NetworkXError("Graph has fewer that 2 edges") 

71 

72 from networkx.utils import cumulative_distribution, discrete_sequence 

73 

74 local_conn = nx.connectivity.local_edge_connectivity 

75 

76 G = G.copy() 

77 keys, degrees = zip(*G.degree()) # keys, degree 

78 cdf = cumulative_distribution(degrees) # cdf of degree 

79 nnodes = len(G) 

80 nedges = nx.number_of_edges(G) 

81 niter = niter * nedges 

82 ntries = int(nnodes * nedges / (nnodes * (nnodes - 1) / 2)) 

83 swapcount = 0 

84 

85 for i in range(niter): 

86 n = 0 

87 while n < ntries: 

88 # pick two random edges without creating edge list 

89 # choose source node indices from discrete distribution 

90 (ai, ci) = discrete_sequence(2, cdistribution=cdf, seed=seed) 

91 if ai == ci: 

92 continue # same source, skip 

93 a = keys[ai] # convert index to label 

94 c = keys[ci] 

95 # choose target uniformly from neighbors 

96 b = seed.choice(list(G.neighbors(a))) 

97 d = seed.choice(list(G.neighbors(c))) 

98 if b in [a, c, d] or d in [a, b, c]: 

99 continue # all vertices should be different 

100 

101 # don't create parallel edges 

102 if (d not in G[a]) and (b not in G[c]): 

103 G.add_edge(a, d) 

104 G.add_edge(c, b) 

105 G.remove_edge(a, b) 

106 G.remove_edge(c, d) 

107 

108 # Check if the graph is still connected 

109 if connectivity and local_conn(G, a, b) == 0: 

110 # Not connected, revert the swap 

111 G.remove_edge(a, d) 

112 G.remove_edge(c, b) 

113 G.add_edge(a, b) 

114 G.add_edge(c, d) 

115 else: 

116 swapcount += 1 

117 break 

118 n += 1 

119 return G 

120 

121 

122@not_implemented_for("directed") 

123@not_implemented_for("multigraph") 

124@py_random_state(4) 

125@nx._dispatchable(returns_graph=True) 

126def lattice_reference(G, niter=5, D=None, connectivity=True, seed=None): 

127 """Latticize the given graph by swapping edges. 

128 

129 Parameters 

130 ---------- 

131 G : graph 

132 An undirected graph. 

133 

134 niter : integer (optional, default=1) 

135 An edge is rewired approximately niter times. 

136 

137 D : numpy.array (optional, default=None) 

138 Distance to the diagonal matrix. 

139 

140 connectivity : boolean (optional, default=True) 

141 Ensure connectivity for the latticized graph when set to True. 

142 

143 seed : integer, random_state, or None (default) 

144 Indicator of random number generation state. 

145 See :ref:`Randomness<randomness>`. 

146 

147 Returns 

148 ------- 

149 G : graph 

150 The latticized graph. 

151 

152 Raises 

153 ------ 

154 NetworkXError 

155 If there are fewer than 4 nodes or 2 edges in `G` 

156 

157 Notes 

158 ----- 

159 The implementation is adapted from the algorithm by Sporns et al. [1]_. 

160 which is inspired from the original work by Maslov and Sneppen(2002) [2]_. 

161 

162 References 

163 ---------- 

164 .. [1] Sporns, Olaf, and Jonathan D. Zwi. 

165 "The small world of the cerebral cortex." 

166 Neuroinformatics 2.2 (2004): 145-162. 

167 .. [2] Maslov, Sergei, and Kim Sneppen. 

168 "Specificity and stability in topology of protein networks." 

169 Science 296.5569 (2002): 910-913. 

170 """ 

171 import numpy as np 

172 

173 from networkx.utils import cumulative_distribution, discrete_sequence 

174 

175 local_conn = nx.connectivity.local_edge_connectivity 

176 

177 if len(G) < 4: 

178 raise nx.NetworkXError("Graph has fewer than four nodes.") 

179 if len(G.edges) < 2: 

180 raise nx.NetworkXError("Graph has fewer that 2 edges") 

181 # Instead of choosing uniformly at random from a generated edge list, 

182 # this algorithm chooses nonuniformly from the set of nodes with 

183 # probability weighted by degree. 

184 G = G.copy() 

185 keys, degrees = zip(*G.degree()) # keys, degree 

186 cdf = cumulative_distribution(degrees) # cdf of degree 

187 

188 nnodes = len(G) 

189 nedges = nx.number_of_edges(G) 

190 if D is None: 

191 D = np.zeros((nnodes, nnodes)) 

192 un = np.arange(1, nnodes) 

193 um = np.arange(nnodes - 1, 0, -1) 

194 u = np.append((0,), np.where(un < um, un, um)) 

195 

196 for v in range(int(np.ceil(nnodes / 2))): 

197 D[nnodes - v - 1, :] = np.append(u[v + 1 :], u[: v + 1]) 

198 D[v, :] = D[nnodes - v - 1, :][::-1] 

199 

200 niter = niter * nedges 

201 # maximal number of rewiring attempts per 'niter' 

202 max_attempts = int(nnodes * nedges / (nnodes * (nnodes - 1) / 2)) 

203 

204 for _ in range(niter): 

205 n = 0 

206 while n < max_attempts: 

207 # pick two random edges without creating edge list 

208 # choose source node indices from discrete distribution 

209 (ai, ci) = discrete_sequence(2, cdistribution=cdf, seed=seed) 

210 if ai == ci: 

211 continue # same source, skip 

212 a = keys[ai] # convert index to label 

213 c = keys[ci] 

214 # choose target uniformly from neighbors 

215 b = seed.choice(list(G.neighbors(a))) 

216 d = seed.choice(list(G.neighbors(c))) 

217 bi = keys.index(b) 

218 di = keys.index(d) 

219 

220 if b in [a, c, d] or d in [a, b, c]: 

221 continue # all vertices should be different 

222 

223 # don't create parallel edges 

224 if (d not in G[a]) and (b not in G[c]): 

225 if D[ai, bi] + D[ci, di] >= D[ai, ci] + D[bi, di]: 

226 # only swap if we get closer to the diagonal 

227 G.add_edge(a, d) 

228 G.add_edge(c, b) 

229 G.remove_edge(a, b) 

230 G.remove_edge(c, d) 

231 

232 # Check if the graph is still connected 

233 if connectivity and local_conn(G, a, b) == 0: 

234 # Not connected, revert the swap 

235 G.remove_edge(a, d) 

236 G.remove_edge(c, b) 

237 G.add_edge(a, b) 

238 G.add_edge(c, d) 

239 else: 

240 break 

241 n += 1 

242 

243 return G 

244 

245 

246@not_implemented_for("directed") 

247@not_implemented_for("multigraph") 

248@py_random_state(3) 

249@nx._dispatchable 

250def sigma(G, niter=100, nrand=10, seed=None): 

251 """Returns the small-world coefficient (sigma) of the given graph. 

252 

253 The small-world coefficient is defined as: 

254 sigma = C/Cr / L/Lr 

255 where C and L are respectively the average clustering coefficient and 

256 average shortest path length of G. Cr and Lr are respectively the average 

257 clustering coefficient and average shortest path length of an equivalent 

258 random graph. 

259 

260 A graph is commonly classified as small-world if sigma>1. 

261 

262 Parameters 

263 ---------- 

264 G : NetworkX graph 

265 An undirected graph. 

266 niter : integer (optional, default=100) 

267 Approximate number of rewiring per edge to compute the equivalent 

268 random graph. 

269 nrand : integer (optional, default=10) 

270 Number of random graphs generated to compute the average clustering 

271 coefficient (Cr) and average shortest path length (Lr). 

272 seed : integer, random_state, or None (default) 

273 Indicator of random number generation state. 

274 See :ref:`Randomness<randomness>`. 

275 

276 Returns 

277 ------- 

278 sigma : float 

279 The small-world coefficient of G. 

280 

281 Notes 

282 ----- 

283 The implementation is adapted from Humphries et al. [1]_ [2]_. 

284 

285 References 

286 ---------- 

287 .. [1] The brainstem reticular formation is a small-world, not scale-free, 

288 network M. D. Humphries, K. Gurney and T. J. Prescott, 

289 Proc. Roy. Soc. B 2006 273, 503-511, doi:10.1098/rspb.2005.3354. 

290 .. [2] Humphries and Gurney (2008). 

291 "Network 'Small-World-Ness': A Quantitative Method for Determining 

292 Canonical Network Equivalence". 

293 PLoS One. 3 (4). PMID 18446219. doi:10.1371/journal.pone.0002051. 

294 """ 

295 import numpy as np 

296 

297 # Compute the mean clustering coefficient and average shortest path length 

298 # for an equivalent random graph 

299 randMetrics = {"C": [], "L": []} 

300 for i in range(nrand): 

301 Gr = random_reference(G, niter=niter, seed=seed) 

302 randMetrics["C"].append(nx.transitivity(Gr)) 

303 randMetrics["L"].append(nx.average_shortest_path_length(Gr)) 

304 

305 C = nx.transitivity(G) 

306 L = nx.average_shortest_path_length(G) 

307 Cr = np.mean(randMetrics["C"]) 

308 Lr = np.mean(randMetrics["L"]) 

309 

310 sigma = (C / Cr) / (L / Lr) 

311 

312 return float(sigma) 

313 

314 

315@not_implemented_for("directed") 

316@not_implemented_for("multigraph") 

317@py_random_state(3) 

318@nx._dispatchable 

319def omega(G, niter=5, nrand=10, seed=None): 

320 """Returns the small-world coefficient (omega) of a graph 

321 

322 The small-world coefficient of a graph G is: 

323 

324 omega = Lr/L - C/Cl 

325 

326 where C and L are respectively the average clustering coefficient and 

327 average shortest path length of G. Lr is the average shortest path length 

328 of an equivalent random graph and Cl is the average clustering coefficient 

329 of an equivalent lattice graph. 

330 

331 The small-world coefficient (omega) measures how much G is like a lattice 

332 or a random graph. Negative values mean G is similar to a lattice whereas 

333 positive values mean G is a random graph. 

334 Values close to 0 mean that G has small-world characteristics. 

335 

336 Parameters 

337 ---------- 

338 G : NetworkX graph 

339 An undirected graph. 

340 

341 niter: integer (optional, default=5) 

342 Approximate number of rewiring per edge to compute the equivalent 

343 random graph. 

344 

345 nrand: integer (optional, default=10) 

346 Number of random graphs generated to compute the maximal clustering 

347 coefficient (Cr) and average shortest path length (Lr). 

348 

349 seed : integer, random_state, or None (default) 

350 Indicator of random number generation state. 

351 See :ref:`Randomness<randomness>`. 

352 

353 

354 Returns 

355 ------- 

356 omega : float 

357 The small-world coefficient (omega) 

358 

359 Notes 

360 ----- 

361 The implementation is adapted from the algorithm by Telesford et al. [1]_. 

362 

363 References 

364 ---------- 

365 .. [1] Telesford, Joyce, Hayasaka, Burdette, and Laurienti (2011). 

366 "The Ubiquity of Small-World Networks". 

367 Brain Connectivity. 1 (0038): 367-75. PMC 3604768. PMID 22432451. 

368 doi:10.1089/brain.2011.0038. 

369 """ 

370 import numpy as np 

371 

372 # Compute the mean clustering coefficient and average shortest path length 

373 # for an equivalent random graph 

374 randMetrics = {"C": [], "L": []} 

375 

376 # Calculate initial average clustering coefficient which potentially will 

377 # get replaced by higher clustering coefficients from generated lattice 

378 # reference graphs 

379 Cl = nx.average_clustering(G) 

380 

381 niter_lattice_reference = niter 

382 niter_random_reference = niter * 2 

383 

384 for _ in range(nrand): 

385 # Generate random graph 

386 Gr = random_reference(G, niter=niter_random_reference, seed=seed) 

387 randMetrics["L"].append(nx.average_shortest_path_length(Gr)) 

388 

389 # Generate lattice graph 

390 Gl = lattice_reference(G, niter=niter_lattice_reference, seed=seed) 

391 

392 # Replace old clustering coefficient, if clustering is higher in 

393 # generated lattice reference 

394 Cl_temp = nx.average_clustering(Gl) 

395 if Cl_temp > Cl: 

396 Cl = Cl_temp 

397 

398 C = nx.average_clustering(G) 

399 L = nx.average_shortest_path_length(G) 

400 Lr = np.mean(randMetrics["L"]) 

401 

402 omega = (Lr / L) - (C / Cl) 

403 

404 return float(omega)