triads.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. # See https://github.com/networkx/networkx/pull/1474
  2. # Copyright 2011 Reya Group <http://www.reyagroup.com>
  3. # Copyright 2011 Alex Levenson <alex@isnotinvain.com>
  4. # Copyright 2011 Diederik van Liere <diederik.vanliere@rotman.utoronto.ca>
  5. """Functions for analyzing triads of a graph."""
  6. from collections import defaultdict
  7. from itertools import combinations, permutations
  8. import networkx as nx
  9. from networkx.utils import not_implemented_for, py_random_state
  10. __all__ = [
  11. "triadic_census",
  12. "is_triad",
  13. "all_triplets",
  14. "all_triads",
  15. "triads_by_type",
  16. "triad_type",
  17. "random_triad",
  18. ]
  19. #: The integer codes representing each type of triad.
  20. #:
  21. #: Triads that are the same up to symmetry have the same code.
  22. TRICODES = (
  23. 1,
  24. 2,
  25. 2,
  26. 3,
  27. 2,
  28. 4,
  29. 6,
  30. 8,
  31. 2,
  32. 6,
  33. 5,
  34. 7,
  35. 3,
  36. 8,
  37. 7,
  38. 11,
  39. 2,
  40. 6,
  41. 4,
  42. 8,
  43. 5,
  44. 9,
  45. 9,
  46. 13,
  47. 6,
  48. 10,
  49. 9,
  50. 14,
  51. 7,
  52. 14,
  53. 12,
  54. 15,
  55. 2,
  56. 5,
  57. 6,
  58. 7,
  59. 6,
  60. 9,
  61. 10,
  62. 14,
  63. 4,
  64. 9,
  65. 9,
  66. 12,
  67. 8,
  68. 13,
  69. 14,
  70. 15,
  71. 3,
  72. 7,
  73. 8,
  74. 11,
  75. 7,
  76. 12,
  77. 14,
  78. 15,
  79. 8,
  80. 14,
  81. 13,
  82. 15,
  83. 11,
  84. 15,
  85. 15,
  86. 16,
  87. )
  88. #: The names of each type of triad. The order of the elements is
  89. #: important: it corresponds to the tricodes given in :data:`TRICODES`.
  90. TRIAD_NAMES = (
  91. "003",
  92. "012",
  93. "102",
  94. "021D",
  95. "021U",
  96. "021C",
  97. "111D",
  98. "111U",
  99. "030T",
  100. "030C",
  101. "201",
  102. "120D",
  103. "120U",
  104. "120C",
  105. "210",
  106. "300",
  107. )
  108. #: A dictionary mapping triad code to triad name.
  109. TRICODE_TO_NAME = {i: TRIAD_NAMES[code - 1] for i, code in enumerate(TRICODES)}
  110. def _tricode(G, v, u, w):
  111. """Returns the integer code of the given triad.
  112. This is some fancy magic that comes from Batagelj and Mrvar's paper. It
  113. treats each edge joining a pair of `v`, `u`, and `w` as a bit in
  114. the binary representation of an integer.
  115. """
  116. combos = ((v, u, 1), (u, v, 2), (v, w, 4), (w, v, 8), (u, w, 16), (w, u, 32))
  117. return sum(x for u, v, x in combos if v in G[u])
  118. @not_implemented_for("undirected")
  119. def triadic_census(G, nodelist=None):
  120. """Determines the triadic census of a directed graph.
  121. The triadic census is a count of how many of the 16 possible types of
  122. triads are present in a directed graph. If a list of nodes is passed, then
  123. only those triads are taken into account which have elements of nodelist in them.
  124. Parameters
  125. ----------
  126. G : digraph
  127. A NetworkX DiGraph
  128. nodelist : list
  129. List of nodes for which you want to calculate triadic census
  130. Returns
  131. -------
  132. census : dict
  133. Dictionary with triad type as keys and number of occurrences as values.
  134. Examples
  135. --------
  136. >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 1), (3, 4), (4, 1), (4, 2)])
  137. >>> triadic_census = nx.triadic_census(G)
  138. >>> for key, value in triadic_census.items():
  139. ... print(f"{key}: {value}")
  140. ...
  141. 003: 0
  142. 012: 0
  143. 102: 0
  144. 021D: 0
  145. 021U: 0
  146. 021C: 0
  147. 111D: 0
  148. 111U: 0
  149. 030T: 2
  150. 030C: 2
  151. 201: 0
  152. 120D: 0
  153. 120U: 0
  154. 120C: 0
  155. 210: 0
  156. 300: 0
  157. Notes
  158. -----
  159. This algorithm has complexity $O(m)$ where $m$ is the number of edges in
  160. the graph.
  161. Raises
  162. ------
  163. ValueError
  164. If `nodelist` contains duplicate nodes or nodes not in `G`.
  165. If you want to ignore this you can preprocess with `set(nodelist) & G.nodes`
  166. See also
  167. --------
  168. triad_graph
  169. References
  170. ----------
  171. .. [1] Vladimir Batagelj and Andrej Mrvar, A subquadratic triad census
  172. algorithm for large sparse networks with small maximum degree,
  173. University of Ljubljana,
  174. http://vlado.fmf.uni-lj.si/pub/networks/doc/triads/triads.pdf
  175. """
  176. nodeset = set(G.nbunch_iter(nodelist))
  177. if nodelist is not None and len(nodelist) != len(nodeset):
  178. raise ValueError("nodelist includes duplicate nodes or nodes not in G")
  179. N = len(G)
  180. Nnot = N - len(nodeset) # can signal special counting for subset of nodes
  181. # create an ordering of nodes with nodeset nodes first
  182. m = {n: i for i, n in enumerate(nodeset)}
  183. if Nnot:
  184. # add non-nodeset nodes later in the ordering
  185. not_nodeset = G.nodes - nodeset
  186. m.update((n, i + N) for i, n in enumerate(not_nodeset))
  187. # build all_neighbor dicts for easy counting
  188. # After Python 3.8 can leave off these keys(). Speedup also using G._pred
  189. # nbrs = {n: G._pred[n].keys() | G._succ[n].keys() for n in G}
  190. nbrs = {n: G.pred[n].keys() | G.succ[n].keys() for n in G}
  191. dbl_nbrs = {n: G.pred[n].keys() & G.succ[n].keys() for n in G}
  192. if Nnot:
  193. sgl_nbrs = {n: G.pred[n].keys() ^ G.succ[n].keys() for n in not_nodeset}
  194. # find number of edges not incident to nodes in nodeset
  195. sgl = sum(1 for n in not_nodeset for nbr in sgl_nbrs[n] if nbr not in nodeset)
  196. sgl_edges_outside = sgl // 2
  197. dbl = sum(1 for n in not_nodeset for nbr in dbl_nbrs[n] if nbr not in nodeset)
  198. dbl_edges_outside = dbl // 2
  199. # Initialize the count for each triad to be zero.
  200. census = {name: 0 for name in TRIAD_NAMES}
  201. # Main loop over nodes
  202. for v in nodeset:
  203. vnbrs = nbrs[v]
  204. dbl_vnbrs = dbl_nbrs[v]
  205. if Nnot:
  206. # set up counts of edges attached to v.
  207. sgl_unbrs_bdy = sgl_unbrs_out = dbl_unbrs_bdy = dbl_unbrs_out = 0
  208. for u in vnbrs:
  209. if m[u] <= m[v]:
  210. continue
  211. unbrs = nbrs[u]
  212. neighbors = (vnbrs | unbrs) - {u, v}
  213. # Count connected triads.
  214. for w in neighbors:
  215. if m[u] < m[w] or (m[v] < m[w] < m[u] and v not in nbrs[w]):
  216. code = _tricode(G, v, u, w)
  217. census[TRICODE_TO_NAME[code]] += 1
  218. # Use a formula for dyadic triads with edge incident to v
  219. if u in dbl_vnbrs:
  220. census["102"] += N - len(neighbors) - 2
  221. else:
  222. census["012"] += N - len(neighbors) - 2
  223. # Count edges attached to v. Subtract later to get triads with v isolated
  224. # _out are (u,unbr) for unbrs outside boundary of nodeset
  225. # _bdy are (u,unbr) for unbrs on boundary of nodeset (get double counted)
  226. if Nnot and u not in nodeset:
  227. sgl_unbrs = sgl_nbrs[u]
  228. sgl_unbrs_bdy += len(sgl_unbrs & vnbrs - nodeset)
  229. sgl_unbrs_out += len(sgl_unbrs - vnbrs - nodeset)
  230. dbl_unbrs = dbl_nbrs[u]
  231. dbl_unbrs_bdy += len(dbl_unbrs & vnbrs - nodeset)
  232. dbl_unbrs_out += len(dbl_unbrs - vnbrs - nodeset)
  233. # if nodeset == G.nodes, skip this b/c we will find the edge later.
  234. if Nnot:
  235. # Count edges outside nodeset not connected with v (v isolated triads)
  236. census["012"] += sgl_edges_outside - (sgl_unbrs_out + sgl_unbrs_bdy // 2)
  237. census["102"] += dbl_edges_outside - (dbl_unbrs_out + dbl_unbrs_bdy // 2)
  238. # calculate null triads: "003"
  239. # null triads = total number of possible triads - all found triads
  240. total_triangles = (N * (N - 1) * (N - 2)) // 6
  241. triangles_without_nodeset = (Nnot * (Nnot - 1) * (Nnot - 2)) // 6
  242. total_census = total_triangles - triangles_without_nodeset
  243. census["003"] = total_census - sum(census.values())
  244. return census
  245. @nx._dispatch()
  246. def is_triad(G):
  247. """Returns True if the graph G is a triad, else False.
  248. Parameters
  249. ----------
  250. G : graph
  251. A NetworkX Graph
  252. Returns
  253. -------
  254. istriad : boolean
  255. Whether G is a valid triad
  256. Examples
  257. --------
  258. >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
  259. >>> nx.is_triad(G)
  260. True
  261. >>> G.add_edge(0, 1)
  262. >>> nx.is_triad(G)
  263. False
  264. """
  265. if isinstance(G, nx.Graph):
  266. if G.order() == 3 and nx.is_directed(G):
  267. if not any((n, n) in G.edges() for n in G.nodes()):
  268. return True
  269. return False
  270. @not_implemented_for("undirected")
  271. def all_triplets(G):
  272. """Returns a generator of all possible sets of 3 nodes in a DiGraph.
  273. Parameters
  274. ----------
  275. G : digraph
  276. A NetworkX DiGraph
  277. Returns
  278. -------
  279. triplets : generator of 3-tuples
  280. Generator of tuples of 3 nodes
  281. Examples
  282. --------
  283. >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
  284. >>> list(nx.all_triplets(G))
  285. [(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]
  286. """
  287. triplets = combinations(G.nodes(), 3)
  288. return triplets
  289. @not_implemented_for("undirected")
  290. def all_triads(G):
  291. """A generator of all possible triads in G.
  292. Parameters
  293. ----------
  294. G : digraph
  295. A NetworkX DiGraph
  296. Returns
  297. -------
  298. all_triads : generator of DiGraphs
  299. Generator of triads (order-3 DiGraphs)
  300. Examples
  301. --------
  302. >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 1), (3, 4), (4, 1), (4, 2)])
  303. >>> for triad in nx.all_triads(G):
  304. ... print(triad.edges)
  305. [(1, 2), (2, 3), (3, 1)]
  306. [(1, 2), (4, 1), (4, 2)]
  307. [(3, 1), (3, 4), (4, 1)]
  308. [(2, 3), (3, 4), (4, 2)]
  309. """
  310. triplets = combinations(G.nodes(), 3)
  311. for triplet in triplets:
  312. yield G.subgraph(triplet).copy()
  313. @not_implemented_for("undirected")
  314. def triads_by_type(G):
  315. """Returns a list of all triads for each triad type in a directed graph.
  316. There are exactly 16 different types of triads possible. Suppose 1, 2, 3 are three
  317. nodes, they will be classified as a particular triad type if their connections
  318. are as follows:
  319. - 003: 1, 2, 3
  320. - 012: 1 -> 2, 3
  321. - 102: 1 <-> 2, 3
  322. - 021D: 1 <- 2 -> 3
  323. - 021U: 1 -> 2 <- 3
  324. - 021C: 1 -> 2 -> 3
  325. - 111D: 1 <-> 2 <- 3
  326. - 111U: 1 <-> 2 -> 3
  327. - 030T: 1 -> 2 -> 3, 1 -> 3
  328. - 030C: 1 <- 2 <- 3, 1 -> 3
  329. - 201: 1 <-> 2 <-> 3
  330. - 120D: 1 <- 2 -> 3, 1 <-> 3
  331. - 120U: 1 -> 2 <- 3, 1 <-> 3
  332. - 120C: 1 -> 2 -> 3, 1 <-> 3
  333. - 210: 1 -> 2 <-> 3, 1 <-> 3
  334. - 300: 1 <-> 2 <-> 3, 1 <-> 3
  335. Refer to the :doc:`example gallery </auto_examples/graph/plot_triad_types>`
  336. for visual examples of the triad types.
  337. Parameters
  338. ----------
  339. G : digraph
  340. A NetworkX DiGraph
  341. Returns
  342. -------
  343. tri_by_type : dict
  344. Dictionary with triad types as keys and lists of triads as values.
  345. Examples
  346. --------
  347. >>> G = nx.DiGraph([(1, 2), (1, 3), (2, 3), (3, 1), (5, 6), (5, 4), (6, 7)])
  348. >>> dict = nx.triads_by_type(G)
  349. >>> dict['120C'][0].edges()
  350. OutEdgeView([(1, 2), (1, 3), (2, 3), (3, 1)])
  351. >>> dict['012'][0].edges()
  352. OutEdgeView([(1, 2)])
  353. References
  354. ----------
  355. .. [1] Snijders, T. (2012). "Transitivity and triads." University of
  356. Oxford.
  357. https://web.archive.org/web/20170830032057/http://www.stats.ox.ac.uk/~snijders/Trans_Triads_ha.pdf
  358. """
  359. # num_triads = o * (o - 1) * (o - 2) // 6
  360. # if num_triads > TRIAD_LIMIT: print(WARNING)
  361. all_tri = all_triads(G)
  362. tri_by_type = defaultdict(list)
  363. for triad in all_tri:
  364. name = triad_type(triad)
  365. tri_by_type[name].append(triad)
  366. return tri_by_type
  367. @not_implemented_for("undirected")
  368. def triad_type(G):
  369. """Returns the sociological triad type for a triad.
  370. Parameters
  371. ----------
  372. G : digraph
  373. A NetworkX DiGraph with 3 nodes
  374. Returns
  375. -------
  376. triad_type : str
  377. A string identifying the triad type
  378. Examples
  379. --------
  380. >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
  381. >>> nx.triad_type(G)
  382. '030C'
  383. >>> G.add_edge(1, 3)
  384. >>> nx.triad_type(G)
  385. '120C'
  386. Notes
  387. -----
  388. There can be 6 unique edges in a triad (order-3 DiGraph) (so 2^^6=64 unique
  389. triads given 3 nodes). These 64 triads each display exactly 1 of 16
  390. topologies of triads (topologies can be permuted). These topologies are
  391. identified by the following notation:
  392. {m}{a}{n}{type} (for example: 111D, 210, 102)
  393. Here:
  394. {m} = number of mutual ties (takes 0, 1, 2, 3); a mutual tie is (0,1)
  395. AND (1,0)
  396. {a} = number of asymmetric ties (takes 0, 1, 2, 3); an asymmetric tie
  397. is (0,1) BUT NOT (1,0) or vice versa
  398. {n} = number of null ties (takes 0, 1, 2, 3); a null tie is NEITHER
  399. (0,1) NOR (1,0)
  400. {type} = a letter (takes U, D, C, T) corresponding to up, down, cyclical
  401. and transitive. This is only used for topologies that can have
  402. more than one form (eg: 021D and 021U).
  403. References
  404. ----------
  405. .. [1] Snijders, T. (2012). "Transitivity and triads." University of
  406. Oxford.
  407. https://web.archive.org/web/20170830032057/http://www.stats.ox.ac.uk/~snijders/Trans_Triads_ha.pdf
  408. """
  409. if not is_triad(G):
  410. raise nx.NetworkXAlgorithmError("G is not a triad (order-3 DiGraph)")
  411. num_edges = len(G.edges())
  412. if num_edges == 0:
  413. return "003"
  414. elif num_edges == 1:
  415. return "012"
  416. elif num_edges == 2:
  417. e1, e2 = G.edges()
  418. if set(e1) == set(e2):
  419. return "102"
  420. elif e1[0] == e2[0]:
  421. return "021D"
  422. elif e1[1] == e2[1]:
  423. return "021U"
  424. elif e1[1] == e2[0] or e2[1] == e1[0]:
  425. return "021C"
  426. elif num_edges == 3:
  427. for e1, e2, e3 in permutations(G.edges(), 3):
  428. if set(e1) == set(e2):
  429. if e3[0] in e1:
  430. return "111U"
  431. # e3[1] in e1:
  432. return "111D"
  433. elif set(e1).symmetric_difference(set(e2)) == set(e3):
  434. if {e1[0], e2[0], e3[0]} == {e1[0], e2[0], e3[0]} == set(G.nodes()):
  435. return "030C"
  436. # e3 == (e1[0], e2[1]) and e2 == (e1[1], e3[1]):
  437. return "030T"
  438. elif num_edges == 4:
  439. for e1, e2, e3, e4 in permutations(G.edges(), 4):
  440. if set(e1) == set(e2):
  441. # identify pair of symmetric edges (which necessarily exists)
  442. if set(e3) == set(e4):
  443. return "201"
  444. if {e3[0]} == {e4[0]} == set(e3).intersection(set(e4)):
  445. return "120D"
  446. if {e3[1]} == {e4[1]} == set(e3).intersection(set(e4)):
  447. return "120U"
  448. if e3[1] == e4[0]:
  449. return "120C"
  450. elif num_edges == 5:
  451. return "210"
  452. elif num_edges == 6:
  453. return "300"
  454. @not_implemented_for("undirected")
  455. @py_random_state(1)
  456. def random_triad(G, seed=None):
  457. """Returns a random triad from a directed graph.
  458. Parameters
  459. ----------
  460. G : digraph
  461. A NetworkX DiGraph
  462. seed : integer, random_state, or None (default)
  463. Indicator of random number generation state.
  464. See :ref:`Randomness<randomness>`.
  465. Returns
  466. -------
  467. G2 : subgraph
  468. A randomly selected triad (order-3 NetworkX DiGraph)
  469. Examples
  470. --------
  471. >>> G = nx.DiGraph([(1, 2), (1, 3), (2, 3), (3, 1), (5, 6), (5, 4), (6, 7)])
  472. >>> triad = nx.random_triad(G, seed=1)
  473. >>> triad.edges
  474. OutEdgeView([(1, 2)])
  475. """
  476. nodes = seed.sample(list(G.nodes()), 3)
  477. G2 = G.subgraph(nodes)
  478. return G2
  479. """
  480. @not_implemented_for('undirected')
  481. def triadic_closures(G):
  482. '''Returns a list of order-3 subgraphs of G that are triadic closures.
  483. Parameters
  484. ----------
  485. G : digraph
  486. A NetworkX DiGraph
  487. Returns
  488. -------
  489. closures : list
  490. List of triads of G that are triadic closures
  491. '''
  492. pass
  493. @not_implemented_for('undirected')
  494. def focal_closures(G, attr_name):
  495. '''Returns a list of order-3 subgraphs of G that are focally closed.
  496. Parameters
  497. ----------
  498. G : digraph
  499. A NetworkX DiGraph
  500. attr_name : str
  501. An attribute name
  502. Returns
  503. -------
  504. closures : list
  505. List of triads of G that are focally closed on attr_name
  506. '''
  507. pass
  508. @not_implemented_for('undirected')
  509. def balanced_triads(G, crit_func):
  510. '''Returns a list of order-3 subgraphs of G that are stable.
  511. Parameters
  512. ----------
  513. G : digraph
  514. A NetworkX DiGraph
  515. crit_func : function
  516. A function that determines if a triad (order-3 digraph) is stable
  517. Returns
  518. -------
  519. triads : list
  520. List of triads in G that are stable
  521. '''
  522. pass
  523. """