function.py 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549
  1. """Functional interface to graph methods and assorted utilities."""
  2. from collections import Counter
  3. from itertools import chain
  4. import networkx as nx
  5. from networkx.utils import not_implemented_for, pairwise
  6. __all__ = [
  7. "nodes",
  8. "edges",
  9. "degree",
  10. "degree_histogram",
  11. "neighbors",
  12. "number_of_nodes",
  13. "number_of_edges",
  14. "density",
  15. "is_directed",
  16. "freeze",
  17. "is_frozen",
  18. "subgraph",
  19. "induced_subgraph",
  20. "edge_subgraph",
  21. "restricted_view",
  22. "to_directed",
  23. "to_undirected",
  24. "add_star",
  25. "add_path",
  26. "add_cycle",
  27. "create_empty_copy",
  28. "set_node_attributes",
  29. "get_node_attributes",
  30. "remove_node_attributes",
  31. "set_edge_attributes",
  32. "get_edge_attributes",
  33. "remove_edge_attributes",
  34. "all_neighbors",
  35. "non_neighbors",
  36. "non_edges",
  37. "common_neighbors",
  38. "is_weighted",
  39. "is_negatively_weighted",
  40. "is_empty",
  41. "selfloop_edges",
  42. "nodes_with_selfloops",
  43. "number_of_selfloops",
  44. "path_weight",
  45. "is_path",
  46. "describe",
  47. ]
  48. def nodes(G):
  49. """Returns a NodeView over the graph nodes.
  50. This function wraps the :func:`G.nodes <networkx.Graph.nodes>` property.
  51. """
  52. return G.nodes()
  53. def edges(G, nbunch=None):
  54. """Returns an edge view of edges incident to nodes in nbunch.
  55. Return all edges if nbunch is unspecified or nbunch=None.
  56. For digraphs, edges=out_edges
  57. This function wraps the :func:`G.edges <networkx.Graph.edges>` property.
  58. """
  59. return G.edges(nbunch)
  60. def degree(G, nbunch=None, weight=None):
  61. """Returns a degree view of single node or of nbunch of nodes.
  62. If nbunch is omitted, then return degrees of *all* nodes.
  63. This function wraps the :func:`G.degree <networkx.Graph.degree>` property.
  64. """
  65. return G.degree(nbunch, weight)
  66. def neighbors(G, n):
  67. """Returns an iterator over all neighbors of node n.
  68. This function wraps the :func:`G.neighbors <networkx.Graph.neighbors>` function.
  69. """
  70. return G.neighbors(n)
  71. def number_of_nodes(G):
  72. """Returns the number of nodes in the graph.
  73. This function wraps the :func:`G.number_of_nodes <networkx.Graph.number_of_nodes>` function.
  74. """
  75. return G.number_of_nodes()
  76. def number_of_edges(G):
  77. """Returns the number of edges in the graph.
  78. This function wraps the :func:`G.number_of_edges <networkx.Graph.number_of_edges>` function.
  79. """
  80. return G.number_of_edges()
  81. def density(G):
  82. r"""Returns the density of a graph.
  83. The density for undirected graphs is
  84. .. math::
  85. d = \frac{2m}{n(n-1)},
  86. and for directed graphs is
  87. .. math::
  88. d = \frac{m}{n(n-1)},
  89. where `n` is the number of nodes and `m` is the number of edges in `G`.
  90. Notes
  91. -----
  92. The density is 0 for a graph without edges and 1 for a complete graph.
  93. The density of multigraphs can be higher than 1.
  94. Self loops are counted in the total number of edges so graphs with self
  95. loops can have density higher than 1.
  96. """
  97. n = number_of_nodes(G)
  98. m = number_of_edges(G)
  99. if m == 0 or n <= 1:
  100. return 0
  101. d = m / (n * (n - 1))
  102. if not G.is_directed():
  103. d *= 2
  104. return d
  105. def degree_histogram(G):
  106. """Returns a list of the frequency of each degree value.
  107. Parameters
  108. ----------
  109. G : Networkx graph
  110. A graph
  111. Returns
  112. -------
  113. hist : list
  114. A list of frequencies of degrees.
  115. The degree values are the index in the list.
  116. Notes
  117. -----
  118. Note: the bins are width one, hence len(list) can be large
  119. (Order(number_of_edges))
  120. """
  121. counts = Counter(d for n, d in G.degree())
  122. return [counts.get(i, 0) for i in range(max(counts) + 1 if counts else 0)]
  123. def is_directed(G):
  124. """Return True if graph is directed."""
  125. return G.is_directed()
  126. def frozen(*args, **kwargs):
  127. """Dummy method for raising errors when trying to modify frozen graphs"""
  128. raise nx.NetworkXError("Frozen graph can't be modified")
  129. def freeze(G):
  130. """Modify graph to prevent further change by adding or removing
  131. nodes or edges.
  132. Node and edge data can still be modified.
  133. Parameters
  134. ----------
  135. G : graph
  136. A NetworkX graph
  137. Examples
  138. --------
  139. >>> G = nx.path_graph(4)
  140. >>> G = nx.freeze(G)
  141. >>> try:
  142. ... G.add_edge(4, 5)
  143. ... except nx.NetworkXError as err:
  144. ... print(str(err))
  145. Frozen graph can't be modified
  146. Notes
  147. -----
  148. To "unfreeze" a graph you must make a copy by creating a new graph object:
  149. >>> graph = nx.path_graph(4)
  150. >>> frozen_graph = nx.freeze(graph)
  151. >>> unfrozen_graph = nx.Graph(frozen_graph)
  152. >>> nx.is_frozen(unfrozen_graph)
  153. False
  154. See Also
  155. --------
  156. is_frozen
  157. """
  158. G.add_node = frozen
  159. G.add_nodes_from = frozen
  160. G.remove_node = frozen
  161. G.remove_nodes_from = frozen
  162. G.add_edge = frozen
  163. G.add_edges_from = frozen
  164. G.add_weighted_edges_from = frozen
  165. G.remove_edge = frozen
  166. G.remove_edges_from = frozen
  167. G.clear = frozen
  168. G.clear_edges = frozen
  169. G.frozen = True
  170. return G
  171. def is_frozen(G):
  172. """Returns True if graph is frozen.
  173. Parameters
  174. ----------
  175. G : graph
  176. A NetworkX graph
  177. See Also
  178. --------
  179. freeze
  180. """
  181. try:
  182. return G.frozen
  183. except AttributeError:
  184. return False
  185. def add_star(G_to_add_to, nodes_for_star, **attr):
  186. """Add a star to Graph G_to_add_to.
  187. The first node in `nodes_for_star` is the middle of the star.
  188. It is connected to all other nodes.
  189. Parameters
  190. ----------
  191. G_to_add_to : graph
  192. A NetworkX graph
  193. nodes_for_star : iterable container
  194. A container of nodes.
  195. attr : keyword arguments, optional (default= no attributes)
  196. Attributes to add to every edge in star.
  197. See Also
  198. --------
  199. add_path, add_cycle
  200. Examples
  201. --------
  202. >>> G = nx.Graph()
  203. >>> nx.add_star(G, [0, 1, 2, 3])
  204. >>> nx.add_star(G, [10, 11, 12], weight=2)
  205. """
  206. nlist = iter(nodes_for_star)
  207. try:
  208. v = next(nlist)
  209. except StopIteration:
  210. return
  211. G_to_add_to.add_node(v)
  212. edges = ((v, n) for n in nlist)
  213. G_to_add_to.add_edges_from(edges, **attr)
  214. def add_path(G_to_add_to, nodes_for_path, **attr):
  215. """Add a path to the Graph G_to_add_to.
  216. Parameters
  217. ----------
  218. G_to_add_to : graph
  219. A NetworkX graph
  220. nodes_for_path : iterable container
  221. A container of nodes. A path will be constructed from
  222. the nodes (in order) and added to the graph.
  223. attr : keyword arguments, optional (default= no attributes)
  224. Attributes to add to every edge in path.
  225. See Also
  226. --------
  227. add_star, add_cycle
  228. Examples
  229. --------
  230. >>> G = nx.Graph()
  231. >>> nx.add_path(G, [0, 1, 2, 3])
  232. >>> nx.add_path(G, [10, 11, 12], weight=7)
  233. """
  234. nlist = iter(nodes_for_path)
  235. try:
  236. first_node = next(nlist)
  237. except StopIteration:
  238. return
  239. G_to_add_to.add_node(first_node)
  240. G_to_add_to.add_edges_from(pairwise(chain((first_node,), nlist)), **attr)
  241. def add_cycle(G_to_add_to, nodes_for_cycle, **attr):
  242. """Add a cycle to the Graph G_to_add_to.
  243. Parameters
  244. ----------
  245. G_to_add_to : graph
  246. A NetworkX graph
  247. nodes_for_cycle: iterable container
  248. A container of nodes. A cycle will be constructed from
  249. the nodes (in order) and added to the graph.
  250. attr : keyword arguments, optional (default= no attributes)
  251. Attributes to add to every edge in cycle.
  252. See Also
  253. --------
  254. add_path, add_star
  255. Examples
  256. --------
  257. >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
  258. >>> nx.add_cycle(G, [0, 1, 2, 3])
  259. >>> nx.add_cycle(G, [10, 11, 12], weight=7)
  260. """
  261. nlist = iter(nodes_for_cycle)
  262. try:
  263. first_node = next(nlist)
  264. except StopIteration:
  265. return
  266. G_to_add_to.add_node(first_node)
  267. G_to_add_to.add_edges_from(
  268. pairwise(chain((first_node,), nlist), cyclic=True), **attr
  269. )
  270. def subgraph(G, nbunch):
  271. """Returns the subgraph induced on nodes in nbunch.
  272. Parameters
  273. ----------
  274. G : graph
  275. A NetworkX graph
  276. nbunch : list, iterable
  277. A container of nodes that will be iterated through once (thus
  278. it should be an iterator or be iterable). Each element of the
  279. container should be a valid node type: any hashable type except
  280. None. If nbunch is None, return all edges data in the graph.
  281. Nodes in nbunch that are not in the graph will be (quietly)
  282. ignored.
  283. Notes
  284. -----
  285. subgraph(G) calls G.subgraph()
  286. """
  287. return G.subgraph(nbunch)
  288. def induced_subgraph(G, nbunch):
  289. """Returns a SubGraph view of `G` showing only nodes in nbunch.
  290. The induced subgraph of a graph on a set of nodes N is the
  291. graph with nodes N and edges from G which have both ends in N.
  292. Parameters
  293. ----------
  294. G : NetworkX Graph
  295. nbunch : node, container of nodes or None (for all nodes)
  296. Returns
  297. -------
  298. subgraph : SubGraph View
  299. A read-only view of the subgraph in `G` induced by the nodes.
  300. Changes to the graph `G` will be reflected in the view.
  301. Notes
  302. -----
  303. To create a mutable subgraph with its own copies of nodes
  304. edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
  305. For an inplace reduction of a graph to a subgraph you can remove nodes:
  306. `G.remove_nodes_from(n in G if n not in set(nbunch))`
  307. If you are going to compute subgraphs of your subgraphs you could
  308. end up with a chain of views that can be very slow once the chain
  309. has about 15 views in it. If they are all induced subgraphs, you
  310. can short-cut the chain by making them all subgraphs of the original
  311. graph. The graph class method `G.subgraph` does this when `G` is
  312. a subgraph. In contrast, this function allows you to choose to build
  313. chains or not, as you wish. The returned subgraph is a view on `G`.
  314. Examples
  315. --------
  316. >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
  317. >>> H = nx.induced_subgraph(G, [0, 1, 3])
  318. >>> list(H.edges)
  319. [(0, 1)]
  320. >>> list(H.nodes)
  321. [0, 1, 3]
  322. """
  323. induced_nodes = nx.filters.show_nodes(G.nbunch_iter(nbunch))
  324. return nx.subgraph_view(G, filter_node=induced_nodes)
  325. def edge_subgraph(G, edges):
  326. """Returns a view of the subgraph induced by the specified edges.
  327. The induced subgraph contains each edge in `edges` and each
  328. node incident to any of those edges.
  329. Parameters
  330. ----------
  331. G : NetworkX Graph
  332. edges : iterable
  333. An iterable of edges. Edges not present in `G` are ignored.
  334. Returns
  335. -------
  336. subgraph : SubGraph View
  337. A read-only edge-induced subgraph of `G`.
  338. Changes to `G` are reflected in the view.
  339. Notes
  340. -----
  341. To create a mutable subgraph with its own copies of nodes
  342. edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
  343. If you create a subgraph of a subgraph recursively you can end up
  344. with a chain of subgraphs that becomes very slow with about 15
  345. nested subgraph views. Luckily the edge_subgraph filter nests
  346. nicely so you can use the original graph as G in this function
  347. to avoid chains. We do not rule out chains programmatically so
  348. that odd cases like an `edge_subgraph` of a `restricted_view`
  349. can be created.
  350. Examples
  351. --------
  352. >>> G = nx.path_graph(5)
  353. >>> H = G.edge_subgraph([(0, 1), (3, 4)])
  354. >>> list(H.nodes)
  355. [0, 1, 3, 4]
  356. >>> list(H.edges)
  357. [(0, 1), (3, 4)]
  358. """
  359. nxf = nx.filters
  360. edges = set(edges)
  361. nodes = set()
  362. for e in edges:
  363. nodes.update(e[:2])
  364. induced_nodes = nxf.show_nodes(nodes)
  365. if G.is_multigraph():
  366. if G.is_directed():
  367. induced_edges = nxf.show_multidiedges(edges)
  368. else:
  369. induced_edges = nxf.show_multiedges(edges)
  370. else:
  371. if G.is_directed():
  372. induced_edges = nxf.show_diedges(edges)
  373. else:
  374. induced_edges = nxf.show_edges(edges)
  375. return nx.subgraph_view(G, filter_node=induced_nodes, filter_edge=induced_edges)
  376. def restricted_view(G, nodes, edges):
  377. """Returns a view of `G` with hidden nodes and edges.
  378. The resulting subgraph filters out node `nodes` and edges `edges`.
  379. Filtered out nodes also filter out any of their edges.
  380. Parameters
  381. ----------
  382. G : NetworkX Graph
  383. nodes : iterable
  384. An iterable of nodes. Nodes not present in `G` are ignored.
  385. edges : iterable
  386. An iterable of edges. Edges not present in `G` are ignored.
  387. Returns
  388. -------
  389. subgraph : SubGraph View
  390. A read-only restricted view of `G` filtering out nodes and edges.
  391. Changes to `G` are reflected in the view.
  392. Notes
  393. -----
  394. To create a mutable subgraph with its own copies of nodes
  395. edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
  396. If you create a subgraph of a subgraph recursively you may end up
  397. with a chain of subgraph views. Such chains can get quite slow
  398. for lengths near 15. To avoid long chains, try to make your subgraph
  399. based on the original graph. We do not rule out chains programmatically
  400. so that odd cases like an `edge_subgraph` of a `restricted_view`
  401. can be created.
  402. Examples
  403. --------
  404. >>> G = nx.path_graph(5)
  405. >>> H = nx.restricted_view(G, [0], [(1, 2), (3, 4)])
  406. >>> list(H.nodes)
  407. [1, 2, 3, 4]
  408. >>> list(H.edges)
  409. [(2, 3)]
  410. """
  411. nxf = nx.filters
  412. hide_nodes = nxf.hide_nodes(nodes)
  413. if G.is_multigraph():
  414. if G.is_directed():
  415. hide_edges = nxf.hide_multidiedges(edges)
  416. else:
  417. hide_edges = nxf.hide_multiedges(edges)
  418. else:
  419. if G.is_directed():
  420. hide_edges = nxf.hide_diedges(edges)
  421. else:
  422. hide_edges = nxf.hide_edges(edges)
  423. return nx.subgraph_view(G, filter_node=hide_nodes, filter_edge=hide_edges)
  424. def to_directed(graph):
  425. """Returns a directed view of the graph `graph`.
  426. Identical to graph.to_directed(as_view=True)
  427. Note that graph.to_directed defaults to `as_view=False`
  428. while this function always provides a view.
  429. """
  430. return graph.to_directed(as_view=True)
  431. def to_undirected(graph):
  432. """Returns an undirected view of the graph `graph`.
  433. Identical to graph.to_undirected(as_view=True)
  434. Note that graph.to_undirected defaults to `as_view=False`
  435. while this function always provides a view.
  436. """
  437. return graph.to_undirected(as_view=True)
  438. def create_empty_copy(G, with_data=True):
  439. """Returns a copy of the graph G with all of the edges removed.
  440. Parameters
  441. ----------
  442. G : graph
  443. A NetworkX graph
  444. with_data : bool (default=True)
  445. Propagate Graph and Nodes data to the new graph.
  446. See Also
  447. --------
  448. empty_graph
  449. """
  450. H = G.__class__()
  451. H.add_nodes_from(G.nodes(data=with_data))
  452. if with_data:
  453. H.graph.update(G.graph)
  454. return H
  455. @nx._dispatchable(preserve_node_attrs=True, mutates_input=True)
  456. def set_node_attributes(G, values, name=None):
  457. """Sets node attributes from a given value or dictionary of values.
  458. .. Warning:: The call order of arguments `values` and `name`
  459. switched between v1.x & v2.x.
  460. Parameters
  461. ----------
  462. G : NetworkX Graph
  463. values : scalar value, dict-like
  464. What the node attribute should be set to. If `values` is
  465. not a dictionary, then it is treated as a single attribute value
  466. that is then applied to every node in `G`. This means that if
  467. you provide a mutable object, like a list, updates to that object
  468. will be reflected in the node attribute for every node.
  469. The attribute name will be `name`.
  470. If `values` is a dict or a dict of dict, it should be keyed
  471. by node to either an attribute value or a dict of attribute key/value
  472. pairs used to update the node's attributes.
  473. name : string (optional, default=None)
  474. Name of the node attribute to set if values is a scalar.
  475. Examples
  476. --------
  477. After computing some property of the nodes of a graph, you may want
  478. to assign a node attribute to store the value of that property for
  479. each node::
  480. >>> G = nx.path_graph(3)
  481. >>> bb = nx.betweenness_centrality(G)
  482. >>> isinstance(bb, dict)
  483. True
  484. >>> nx.set_node_attributes(G, bb, "betweenness")
  485. >>> G.nodes[1]["betweenness"]
  486. 1.0
  487. If you provide a list as the second argument, updates to the list
  488. will be reflected in the node attribute for each node::
  489. >>> G = nx.path_graph(3)
  490. >>> labels = []
  491. >>> nx.set_node_attributes(G, labels, "labels")
  492. >>> labels.append("foo")
  493. >>> G.nodes[0]["labels"]
  494. ['foo']
  495. >>> G.nodes[1]["labels"]
  496. ['foo']
  497. >>> G.nodes[2]["labels"]
  498. ['foo']
  499. If you provide a dictionary of dictionaries as the second argument,
  500. the outer dictionary is assumed to be keyed by node to an inner
  501. dictionary of node attributes for that node::
  502. >>> G = nx.path_graph(3)
  503. >>> attrs = {0: {"attr1": 20, "attr2": "nothing"}, 1: {"attr2": 3}}
  504. >>> nx.set_node_attributes(G, attrs)
  505. >>> G.nodes[0]["attr1"]
  506. 20
  507. >>> G.nodes[0]["attr2"]
  508. 'nothing'
  509. >>> G.nodes[1]["attr2"]
  510. 3
  511. >>> G.nodes[2]
  512. {}
  513. Note that if the dictionary contains nodes that are not in `G`, the
  514. values are silently ignored::
  515. >>> G = nx.Graph()
  516. >>> G.add_node(0)
  517. >>> nx.set_node_attributes(G, {0: "red", 1: "blue"}, name="color")
  518. >>> G.nodes[0]["color"]
  519. 'red'
  520. >>> 1 in G.nodes
  521. False
  522. """
  523. # Set node attributes based on type of `values`
  524. if name is not None: # `values` must not be a dict of dict
  525. try: # `values` is a dict
  526. for n, v in values.items():
  527. try:
  528. G.nodes[n][name] = values[n]
  529. except KeyError:
  530. pass
  531. except AttributeError: # `values` is a constant
  532. for n in G:
  533. G.nodes[n][name] = values
  534. else: # `values` must be dict of dict
  535. for n, d in values.items():
  536. try:
  537. G.nodes[n].update(d)
  538. except KeyError:
  539. pass
  540. nx._clear_cache(G)
  541. @nx._dispatchable(node_attrs={"name": "default"})
  542. def get_node_attributes(G, name, default=None):
  543. """Get node attributes from graph
  544. Parameters
  545. ----------
  546. G : NetworkX Graph
  547. name : string
  548. Attribute name
  549. default: object (default=None)
  550. Default value of the node attribute if there is no value set for that
  551. node in graph. If `None` then nodes without this attribute are not
  552. included in the returned dict.
  553. Returns
  554. -------
  555. Dictionary of attributes keyed by node.
  556. Examples
  557. --------
  558. >>> G = nx.Graph()
  559. >>> G.add_nodes_from([1, 2, 3], color="red")
  560. >>> color = nx.get_node_attributes(G, "color")
  561. >>> color[1]
  562. 'red'
  563. >>> G.add_node(4)
  564. >>> color = nx.get_node_attributes(G, "color", default="yellow")
  565. >>> color[4]
  566. 'yellow'
  567. """
  568. if default is not None:
  569. return {n: d.get(name, default) for n, d in G.nodes.items()}
  570. return {n: d[name] for n, d in G.nodes.items() if name in d}
  571. @nx._dispatchable(preserve_node_attrs=True, mutates_input=True)
  572. def remove_node_attributes(G, *attr_names, nbunch=None):
  573. """Remove node attributes from all nodes in the graph.
  574. Parameters
  575. ----------
  576. G : NetworkX Graph
  577. *attr_names : List of Strings
  578. The attribute names to remove from the graph.
  579. nbunch : List of Nodes
  580. Remove the node attributes only from the nodes in this list.
  581. Examples
  582. --------
  583. >>> G = nx.Graph()
  584. >>> G.add_nodes_from([1, 2, 3], color="blue")
  585. >>> nx.get_node_attributes(G, "color")
  586. {1: 'blue', 2: 'blue', 3: 'blue'}
  587. >>> nx.remove_node_attributes(G, "color")
  588. >>> nx.get_node_attributes(G, "color")
  589. {}
  590. """
  591. if nbunch is None:
  592. nbunch = G.nodes()
  593. for attr in attr_names:
  594. for n, d in G.nodes(data=True):
  595. if n in nbunch:
  596. try:
  597. del d[attr]
  598. except KeyError:
  599. pass
  600. @nx._dispatchable(preserve_edge_attrs=True, mutates_input=True)
  601. def set_edge_attributes(G, values, name=None):
  602. """Sets edge attributes from a given value or dictionary of values.
  603. .. Warning:: The call order of arguments `values` and `name`
  604. switched between v1.x & v2.x.
  605. Parameters
  606. ----------
  607. G : NetworkX Graph
  608. values : scalar value, dict-like
  609. What the edge attribute should be set to. If `values` is
  610. not a dictionary, then it is treated as a single attribute value
  611. that is then applied to every edge in `G`. This means that if
  612. you provide a mutable object, like a list, updates to that object
  613. will be reflected in the edge attribute for each edge. The attribute
  614. name will be `name`.
  615. If `values` is a dict or a dict of dict, it should be keyed
  616. by edge tuple to either an attribute value or a dict of attribute
  617. key/value pairs used to update the edge's attributes.
  618. For multigraphs, the edge tuples must be of the form ``(u, v, key)``,
  619. where `u` and `v` are nodes and `key` is the edge key.
  620. For non-multigraphs, the keys must be tuples of the form ``(u, v)``.
  621. name : string (optional, default=None)
  622. Name of the edge attribute to set if values is a scalar.
  623. Examples
  624. --------
  625. After computing some property of the edges of a graph, you may want
  626. to assign a edge attribute to store the value of that property for
  627. each edge::
  628. >>> G = nx.path_graph(3)
  629. >>> bb = nx.edge_betweenness_centrality(G, normalized=False)
  630. >>> nx.set_edge_attributes(G, bb, "betweenness")
  631. >>> G.edges[1, 2]["betweenness"]
  632. 2.0
  633. If you provide a list as the second argument, updates to the list
  634. will be reflected in the edge attribute for each edge::
  635. >>> labels = []
  636. >>> nx.set_edge_attributes(G, labels, "labels")
  637. >>> labels.append("foo")
  638. >>> G.edges[0, 1]["labels"]
  639. ['foo']
  640. >>> G.edges[1, 2]["labels"]
  641. ['foo']
  642. If you provide a dictionary of dictionaries as the second argument,
  643. the entire dictionary will be used to update edge attributes::
  644. >>> G = nx.path_graph(3)
  645. >>> attrs = {(0, 1): {"attr1": 20, "attr2": "nothing"}, (1, 2): {"attr2": 3}}
  646. >>> nx.set_edge_attributes(G, attrs)
  647. >>> G[0][1]["attr1"]
  648. 20
  649. >>> G[0][1]["attr2"]
  650. 'nothing'
  651. >>> G[1][2]["attr2"]
  652. 3
  653. The attributes of one Graph can be used to set those of another.
  654. >>> H = nx.path_graph(3)
  655. >>> nx.set_edge_attributes(H, G.edges)
  656. Note that if the dict contains edges that are not in `G`, they are
  657. silently ignored::
  658. >>> G = nx.Graph([(0, 1)])
  659. >>> nx.set_edge_attributes(G, {(1, 2): {"weight": 2.0}})
  660. >>> (1, 2) in G.edges()
  661. False
  662. For multigraphs, the `values` dict is expected to be keyed by 3-tuples
  663. including the edge key::
  664. >>> MG = nx.MultiGraph()
  665. >>> edges = [(0, 1), (0, 1)]
  666. >>> MG.add_edges_from(edges) # Returns list of edge keys
  667. [0, 1]
  668. >>> attributes = {(0, 1, 0): {"cost": 21}, (0, 1, 1): {"cost": 7}}
  669. >>> nx.set_edge_attributes(MG, attributes)
  670. >>> MG[0][1][0]["cost"]
  671. 21
  672. >>> MG[0][1][1]["cost"]
  673. 7
  674. If MultiGraph attributes are desired for a Graph, you must convert the 3-tuple
  675. multiedge to a 2-tuple edge and the last multiedge's attribute value will
  676. overwrite the previous values. Continuing from the previous case we get::
  677. >>> H = nx.path_graph([0, 1, 2])
  678. >>> nx.set_edge_attributes(H, {(u, v): ed for u, v, ed in MG.edges.data()})
  679. >>> nx.get_edge_attributes(H, "cost")
  680. {(0, 1): 7}
  681. """
  682. if name is not None:
  683. # `values` does not contain attribute names
  684. try:
  685. # if `values` is a dict using `.items()` => {edge: value}
  686. if G.is_multigraph():
  687. for (u, v, key), value in values.items():
  688. try:
  689. G._adj[u][v][key][name] = value
  690. except KeyError:
  691. pass
  692. else:
  693. for (u, v), value in values.items():
  694. try:
  695. G._adj[u][v][name] = value
  696. except KeyError:
  697. pass
  698. except AttributeError:
  699. # treat `values` as a constant
  700. for u, v, data in G.edges(data=True):
  701. data[name] = values
  702. else:
  703. # `values` consists of doct-of-dict {edge: {attr: value}} shape
  704. if G.is_multigraph():
  705. for (u, v, key), d in values.items():
  706. try:
  707. G._adj[u][v][key].update(d)
  708. except KeyError:
  709. pass
  710. else:
  711. for (u, v), d in values.items():
  712. try:
  713. G._adj[u][v].update(d)
  714. except KeyError:
  715. pass
  716. nx._clear_cache(G)
  717. @nx._dispatchable(edge_attrs={"name": "default"})
  718. def get_edge_attributes(G, name, default=None):
  719. """Get edge attributes from graph
  720. Parameters
  721. ----------
  722. G : NetworkX Graph
  723. name : string
  724. Attribute name
  725. default: object (default=None)
  726. Default value of the edge attribute if there is no value set for that
  727. edge in graph. If `None` then edges without this attribute are not
  728. included in the returned dict.
  729. Returns
  730. -------
  731. Dictionary of attributes keyed by edge. For (di)graphs, the keys are
  732. 2-tuples of the form: (u, v). For multi(di)graphs, the keys are 3-tuples of
  733. the form: (u, v, key).
  734. Examples
  735. --------
  736. >>> G = nx.Graph()
  737. >>> nx.add_path(G, [1, 2, 3], color="red")
  738. >>> color = nx.get_edge_attributes(G, "color")
  739. >>> color[(1, 2)]
  740. 'red'
  741. >>> G.add_edge(3, 4)
  742. >>> color = nx.get_edge_attributes(G, "color", default="yellow")
  743. >>> color[(3, 4)]
  744. 'yellow'
  745. """
  746. if G.is_multigraph():
  747. edges = G.edges(keys=True, data=True)
  748. else:
  749. edges = G.edges(data=True)
  750. if default is not None:
  751. return {x[:-1]: x[-1].get(name, default) for x in edges}
  752. return {x[:-1]: x[-1][name] for x in edges if name in x[-1]}
  753. @nx._dispatchable(preserve_edge_attrs=True, mutates_input=True)
  754. def remove_edge_attributes(G, *attr_names, ebunch=None):
  755. """Remove edge attributes from all edges in the graph.
  756. Parameters
  757. ----------
  758. G : NetworkX Graph
  759. *attr_names : List of Strings
  760. The attribute names to remove from the graph.
  761. Examples
  762. --------
  763. >>> G = nx.path_graph(3)
  764. >>> nx.set_edge_attributes(G, {(u, v): u + v for u, v in G.edges()}, name="weight")
  765. >>> nx.get_edge_attributes(G, "weight")
  766. {(0, 1): 1, (1, 2): 3}
  767. >>> remove_edge_attributes(G, "weight")
  768. >>> nx.get_edge_attributes(G, "weight")
  769. {}
  770. """
  771. if ebunch is None:
  772. ebunch = G.edges(keys=True) if G.is_multigraph() else G.edges()
  773. for attr in attr_names:
  774. edges = (
  775. G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True)
  776. )
  777. for *e, d in edges:
  778. if tuple(e) in ebunch:
  779. try:
  780. del d[attr]
  781. except KeyError:
  782. pass
  783. def all_neighbors(graph, node):
  784. """Returns all of the neighbors of a node in the graph.
  785. If the graph is directed returns predecessors as well as successors.
  786. Parameters
  787. ----------
  788. graph : NetworkX graph
  789. Graph to find neighbors.
  790. node : node
  791. The node whose neighbors will be returned.
  792. Returns
  793. -------
  794. neighbors : iterator
  795. Iterator of neighbors
  796. Raises
  797. ------
  798. NetworkXError
  799. If `node` is not in the graph.
  800. Examples
  801. --------
  802. For undirected graphs, this function is equivalent to ``G.neighbors(node)``.
  803. >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
  804. >>> list(nx.all_neighbors(G, 1))
  805. [0, 2]
  806. For directed graphs, this function returns both predecessors and successors,
  807. which may include duplicates if a node is both a predecessor and successor
  808. (e.g., in bidirectional edges or self-loops).
  809. >>> DG = nx.DiGraph([(0, 1), (1, 2), (2, 1)])
  810. >>> list(nx.all_neighbors(DG, 1))
  811. [0, 2, 2]
  812. Notes
  813. -----
  814. This function iterates over all neighbors (both predecessors and successors).
  815. See Also
  816. --------
  817. Graph.neighbors : Returns successors for both Graph and DiGraph
  818. DiGraph.predecessors : Returns predecessors for directed graphs only
  819. DiGraph.successors : Returns successors for directed graphs only
  820. """
  821. if graph.is_directed():
  822. values = chain(graph.predecessors(node), graph.successors(node))
  823. else:
  824. values = graph.neighbors(node)
  825. return values
  826. def non_neighbors(graph, node):
  827. """Returns the non-neighbors of the node in the graph.
  828. Parameters
  829. ----------
  830. graph : NetworkX graph
  831. Graph to find neighbors.
  832. node : node
  833. The node whose neighbors will be returned.
  834. Returns
  835. -------
  836. non_neighbors : set
  837. Set of nodes in the graph that are not neighbors of the node.
  838. """
  839. return graph._adj.keys() - graph._adj[node].keys() - {node}
  840. def non_edges(graph):
  841. """Returns the nonexistent edges in the graph.
  842. Parameters
  843. ----------
  844. graph : NetworkX graph.
  845. Graph to find nonexistent edges.
  846. Returns
  847. -------
  848. non_edges : iterator
  849. Iterator of edges that are not in the graph.
  850. """
  851. if graph.is_directed():
  852. for u in graph:
  853. for v in non_neighbors(graph, u):
  854. yield (u, v)
  855. else:
  856. nodes = set(graph)
  857. while nodes:
  858. u = nodes.pop()
  859. for v in nodes - set(graph[u]):
  860. yield (u, v)
  861. @not_implemented_for("directed")
  862. def common_neighbors(G, u, v):
  863. """Returns the common neighbors of two nodes in a graph.
  864. Parameters
  865. ----------
  866. G : graph
  867. A NetworkX undirected graph.
  868. u, v : nodes
  869. Nodes in the graph.
  870. Returns
  871. -------
  872. cnbors : set
  873. Set of common neighbors of u and v in the graph.
  874. Raises
  875. ------
  876. NetworkXError
  877. If u or v is not a node in the graph.
  878. Examples
  879. --------
  880. >>> G = nx.complete_graph(5)
  881. >>> sorted(nx.common_neighbors(G, 0, 1))
  882. [2, 3, 4]
  883. """
  884. if u not in G:
  885. raise nx.NetworkXError("u is not in the graph.")
  886. if v not in G:
  887. raise nx.NetworkXError("v is not in the graph.")
  888. return G._adj[u].keys() & G._adj[v].keys() - {u, v}
  889. @nx._dispatchable(preserve_edge_attrs=True)
  890. def is_weighted(G, edge=None, weight="weight"):
  891. """Returns True if `G` has weighted edges.
  892. Parameters
  893. ----------
  894. G : graph
  895. A NetworkX graph.
  896. edge : tuple, optional
  897. A 2-tuple specifying the only edge in `G` that will be tested. If
  898. None, then every edge in `G` is tested.
  899. weight: string, optional
  900. The attribute name used to query for edge weights.
  901. Returns
  902. -------
  903. bool
  904. A boolean signifying if `G`, or the specified edge, is weighted.
  905. Raises
  906. ------
  907. NetworkXError
  908. If the specified edge does not exist.
  909. Examples
  910. --------
  911. >>> G = nx.path_graph(4)
  912. >>> nx.is_weighted(G)
  913. False
  914. >>> nx.is_weighted(G, (2, 3))
  915. False
  916. >>> G = nx.DiGraph()
  917. >>> G.add_edge(1, 2, weight=1)
  918. >>> nx.is_weighted(G)
  919. True
  920. """
  921. if edge is not None:
  922. data = G.get_edge_data(*edge)
  923. if data is None:
  924. msg = f"Edge {edge!r} does not exist."
  925. raise nx.NetworkXError(msg)
  926. return weight in data
  927. if is_empty(G):
  928. # Special handling required since: all([]) == True
  929. return False
  930. return all(weight in data for u, v, data in G.edges(data=True))
  931. @nx._dispatchable(edge_attrs="weight")
  932. def is_negatively_weighted(G, edge=None, weight="weight"):
  933. """Returns True if `G` has negatively weighted edges.
  934. Parameters
  935. ----------
  936. G : graph
  937. A NetworkX graph.
  938. edge : tuple, optional
  939. A 2-tuple specifying the only edge in `G` that will be tested. If
  940. None, then every edge in `G` is tested.
  941. weight: string, optional
  942. The attribute name used to query for edge weights.
  943. Returns
  944. -------
  945. bool
  946. A boolean signifying if `G`, or the specified edge, is negatively
  947. weighted.
  948. Raises
  949. ------
  950. NetworkXError
  951. If the specified edge does not exist.
  952. Examples
  953. --------
  954. >>> G = nx.Graph()
  955. >>> G.add_edges_from([(1, 3), (2, 4), (2, 6)])
  956. >>> G.add_edge(1, 2, weight=4)
  957. >>> nx.is_negatively_weighted(G, (1, 2))
  958. False
  959. >>> G[2][4]["weight"] = -2
  960. >>> nx.is_negatively_weighted(G)
  961. True
  962. >>> G = nx.DiGraph()
  963. >>> edges = [("0", "3", 3), ("0", "1", -5), ("1", "0", -2)]
  964. >>> G.add_weighted_edges_from(edges)
  965. >>> nx.is_negatively_weighted(G)
  966. True
  967. """
  968. if edge is not None:
  969. data = G.get_edge_data(*edge)
  970. if data is None:
  971. msg = f"Edge {edge!r} does not exist."
  972. raise nx.NetworkXError(msg)
  973. return weight in data and data[weight] < 0
  974. return any(weight in data and data[weight] < 0 for u, v, data in G.edges(data=True))
  975. @nx._dispatchable
  976. def is_empty(G):
  977. """Returns True if `G` has no edges.
  978. Parameters
  979. ----------
  980. G : graph
  981. A NetworkX graph.
  982. Returns
  983. -------
  984. bool
  985. True if `G` has no edges, and False otherwise.
  986. Notes
  987. -----
  988. An empty graph can have nodes but not edges. The empty graph with zero
  989. nodes is known as the null graph. This is an $O(n)$ operation where n
  990. is the number of nodes in the graph.
  991. """
  992. return not any(G._adj.values())
  993. def nodes_with_selfloops(G):
  994. """Returns an iterator over nodes with self loops.
  995. A node with a self loop has an edge with both ends adjacent
  996. to that node.
  997. Returns
  998. -------
  999. nodelist : iterator
  1000. A iterator over nodes with self loops.
  1001. See Also
  1002. --------
  1003. selfloop_edges, number_of_selfloops
  1004. Examples
  1005. --------
  1006. >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
  1007. >>> G.add_edge(1, 1)
  1008. >>> G.add_edge(1, 2)
  1009. >>> list(nx.nodes_with_selfloops(G))
  1010. [1]
  1011. """
  1012. return (n for n, nbrs in G._adj.items() if n in nbrs)
  1013. def selfloop_edges(G, data=False, keys=False, default=None):
  1014. """Returns an iterator over selfloop edges.
  1015. A selfloop edge has the same node at both ends.
  1016. Parameters
  1017. ----------
  1018. G : graph
  1019. A NetworkX graph.
  1020. data : string or bool, optional (default=False)
  1021. Return selfloop edges as two tuples (u, v) (data=False)
  1022. or three-tuples (u, v, datadict) (data=True)
  1023. or three-tuples (u, v, datavalue) (data='attrname')
  1024. keys : bool, optional (default=False)
  1025. If True, return edge keys with each edge.
  1026. default : value, optional (default=None)
  1027. Value used for edges that don't have the requested attribute.
  1028. Only relevant if data is not True or False.
  1029. Returns
  1030. -------
  1031. edgeiter : iterator over edge tuples
  1032. An iterator over all selfloop edges.
  1033. See Also
  1034. --------
  1035. nodes_with_selfloops, number_of_selfloops
  1036. Examples
  1037. --------
  1038. >>> G = nx.MultiGraph() # or Graph, DiGraph, MultiDiGraph, etc
  1039. >>> ekey = G.add_edge(1, 1)
  1040. >>> ekey = G.add_edge(1, 2)
  1041. >>> list(nx.selfloop_edges(G))
  1042. [(1, 1)]
  1043. >>> list(nx.selfloop_edges(G, data=True))
  1044. [(1, 1, {})]
  1045. >>> list(nx.selfloop_edges(G, keys=True))
  1046. [(1, 1, 0)]
  1047. >>> list(nx.selfloop_edges(G, keys=True, data=True))
  1048. [(1, 1, 0, {})]
  1049. """
  1050. if data is True:
  1051. if G.is_multigraph():
  1052. if keys is True:
  1053. return (
  1054. (n, n, k, d)
  1055. for n, nbrs in G._adj.items()
  1056. if n in nbrs
  1057. for k, d in nbrs[n].items()
  1058. )
  1059. else:
  1060. return (
  1061. (n, n, d)
  1062. for n, nbrs in G._adj.items()
  1063. if n in nbrs
  1064. for d in nbrs[n].values()
  1065. )
  1066. else:
  1067. return ((n, n, nbrs[n]) for n, nbrs in G._adj.items() if n in nbrs)
  1068. elif data is not False:
  1069. if G.is_multigraph():
  1070. if keys is True:
  1071. return (
  1072. (n, n, k, d.get(data, default))
  1073. for n, nbrs in G._adj.items()
  1074. if n in nbrs
  1075. for k, d in nbrs[n].items()
  1076. )
  1077. else:
  1078. return (
  1079. (n, n, d.get(data, default))
  1080. for n, nbrs in G._adj.items()
  1081. if n in nbrs
  1082. for d in nbrs[n].values()
  1083. )
  1084. else:
  1085. return (
  1086. (n, n, nbrs[n].get(data, default))
  1087. for n, nbrs in G._adj.items()
  1088. if n in nbrs
  1089. )
  1090. else:
  1091. if G.is_multigraph():
  1092. if keys is True:
  1093. return (
  1094. (n, n, k)
  1095. for n, nbrs in G._adj.items()
  1096. if n in nbrs
  1097. for k in nbrs[n]
  1098. )
  1099. else:
  1100. return (
  1101. (n, n)
  1102. for n, nbrs in G._adj.items()
  1103. if n in nbrs
  1104. for i in range(len(nbrs[n])) # for easy edge removal (#4068)
  1105. )
  1106. else:
  1107. return ((n, n) for n, nbrs in G._adj.items() if n in nbrs)
  1108. @nx._dispatchable
  1109. def number_of_selfloops(G):
  1110. """Returns the number of selfloop edges.
  1111. A selfloop edge has the same node at both ends.
  1112. Returns
  1113. -------
  1114. nloops : int
  1115. The number of selfloops.
  1116. See Also
  1117. --------
  1118. nodes_with_selfloops, selfloop_edges
  1119. Examples
  1120. --------
  1121. >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
  1122. >>> G.add_edge(1, 1)
  1123. >>> G.add_edge(1, 2)
  1124. >>> nx.number_of_selfloops(G)
  1125. 1
  1126. """
  1127. return sum(1 for _ in nx.selfloop_edges(G))
  1128. def is_path(G, path):
  1129. """Returns whether or not the specified path exists.
  1130. For it to return True, every node on the path must exist and
  1131. each consecutive pair must be connected via one or more edges.
  1132. Parameters
  1133. ----------
  1134. G : graph
  1135. A NetworkX graph.
  1136. path : list
  1137. A list of nodes which defines the path to traverse
  1138. Returns
  1139. -------
  1140. bool
  1141. True if `path` is a valid path in `G`
  1142. """
  1143. try:
  1144. return all(nbr in G._adj[node] for node, nbr in nx.utils.pairwise(path))
  1145. except (KeyError, TypeError):
  1146. return False
  1147. def path_weight(G, path, weight):
  1148. """Returns total cost associated with specified path and weight
  1149. Parameters
  1150. ----------
  1151. G : graph
  1152. A NetworkX graph.
  1153. path: list
  1154. A list of node labels which defines the path to traverse
  1155. weight: string
  1156. A string indicating which edge attribute to use for path cost
  1157. Returns
  1158. -------
  1159. cost: int or float
  1160. An integer or a float representing the total cost with respect to the
  1161. specified weight of the specified path
  1162. Raises
  1163. ------
  1164. NetworkXNoPath
  1165. If the specified edge does not exist.
  1166. """
  1167. multigraph = G.is_multigraph()
  1168. cost = 0
  1169. if not nx.is_path(G, path):
  1170. raise nx.NetworkXNoPath("path does not exist")
  1171. for node, nbr in nx.utils.pairwise(path):
  1172. if multigraph:
  1173. cost += min(v[weight] for v in G._adj[node][nbr].values())
  1174. else:
  1175. cost += G._adj[node][nbr][weight]
  1176. return cost
  1177. def describe(G, describe_hook=None):
  1178. """Prints a description of the graph G.
  1179. By default, the description includes some basic properties of the graph.
  1180. You can also provide additional functions to compute and include
  1181. more properties in the description.
  1182. Parameters
  1183. ----------
  1184. G : graph
  1185. A NetworkX graph.
  1186. describe_hook: callable, optional (default=None)
  1187. A function that takes a graph as input and returns a
  1188. dictionary of additional properties to include in the description.
  1189. The keys of the dictionary are the property names, and the values
  1190. are the corresponding property values.
  1191. Examples
  1192. --------
  1193. >>> G = nx.path_graph(5)
  1194. >>> nx.describe(G)
  1195. Number of nodes : 5
  1196. Number of edges : 4
  1197. Directed : False
  1198. Multigraph : False
  1199. Tree : True
  1200. Bipartite : True
  1201. Average degree (min, max) : 1.60 (1, 2)
  1202. Number of connected components : 1
  1203. >>> def augment_description(G):
  1204. ... return {"Average Shortest Path Length": nx.average_shortest_path_length(G)}
  1205. >>> nx.describe(G, describe_hook=augment_description)
  1206. Number of nodes : 5
  1207. Number of edges : 4
  1208. Directed : False
  1209. Multigraph : False
  1210. Tree : True
  1211. Bipartite : True
  1212. Average degree (min, max) : 1.60 (1, 2)
  1213. Number of connected components : 1
  1214. Average Shortest Path Length : 2.0
  1215. >>> G.name = "Path Graph of 5 nodes"
  1216. >>> nx.describe(G)
  1217. Name of Graph : Path Graph of 5 nodes
  1218. Number of nodes : 5
  1219. Number of edges : 4
  1220. Directed : False
  1221. Multigraph : False
  1222. Tree : True
  1223. Bipartite : True
  1224. Average degree (min, max) : 1.60 (1, 2)
  1225. Number of connected components : 1
  1226. """
  1227. info_dict = _create_describe_info_dict(G)
  1228. if describe_hook is not None:
  1229. additional_info = describe_hook(G)
  1230. info_dict.update(additional_info)
  1231. max_key_len = max(len(k) for k in info_dict)
  1232. for key, val in info_dict.items():
  1233. print(f"{key:<{max_key_len}} : {val}")
  1234. def _create_describe_info_dict(G):
  1235. info = {}
  1236. if G.name != "":
  1237. info["Name of Graph"] = G.name
  1238. info.update(
  1239. {
  1240. "Number of nodes": len(G),
  1241. "Number of edges": G.number_of_edges(),
  1242. "Directed": G.is_directed(),
  1243. "Multigraph": G.is_multigraph(),
  1244. "Tree": nx.is_tree(G),
  1245. "Bipartite": nx.is_bipartite(G),
  1246. }
  1247. )
  1248. if len(G) == 0:
  1249. return info
  1250. degree_values = dict(nx.degree(G)).values()
  1251. avg_degree = sum(degree_values) / len(G)
  1252. max_degree, min_degree = max(degree_values), min(degree_values)
  1253. info["Average degree (min, max)"] = f"{avg_degree:.2f} ({min_degree}, {max_degree})"
  1254. if G.is_directed():
  1255. info["Number of strongly connected components"] = (
  1256. nx.number_strongly_connected_components(G)
  1257. )
  1258. info["Number of weakly connected components"] = (
  1259. nx.number_weakly_connected_components(G)
  1260. )
  1261. else:
  1262. info["Number of connected components"] = nx.number_connected_components(G)
  1263. return info