1"""Base class for undirected graphs.
2
3The Graph class allows any hashable object as a node
4and can associate key/value attribute pairs with each undirected edge.
5
6Self-loops are allowed but multiple edges are not (see MultiGraph).
7
8For directed graphs see DiGraph and MultiDiGraph.
9"""
10
11from copy import deepcopy
12from functools import cached_property
13
14import networkx as nx
15from networkx import convert
16from networkx.classes.coreviews import AdjacencyView
17from networkx.classes.reportviews import DegreeView, EdgeView, NodeView
18from networkx.exception import NetworkXError
19
20__all__ = ["Graph"]
21
22
23class _CachedPropertyResetterAdj:
24 """Data Descriptor class for _adj that resets ``adj`` cached_property when needed
25
26 This assumes that the ``cached_property`` ``G.adj`` should be reset whenever
27 ``G._adj`` is set to a new value.
28
29 This object sits on a class and ensures that any instance of that
30 class clears its cached property "adj" whenever the underlying
31 instance attribute "_adj" is set to a new object. It only affects
32 the set process of the obj._adj attribute. All get/del operations
33 act as they normally would.
34
35 For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
36 """
37
38 def __set__(self, obj, value):
39 od = obj.__dict__
40 od["_adj"] = value
41 # reset cached properties
42 props = ["adj", "edges", "degree"]
43 for prop in props:
44 if prop in od:
45 del od[prop]
46
47
48class _CachedPropertyResetterNode:
49 """Data Descriptor class for _node that resets ``nodes`` cached_property when needed
50
51 This assumes that the ``cached_property`` ``G.node`` should be reset whenever
52 ``G._node`` is set to a new value.
53
54 This object sits on a class and ensures that any instance of that
55 class clears its cached property "nodes" whenever the underlying
56 instance attribute "_node" is set to a new object. It only affects
57 the set process of the obj._adj attribute. All get/del operations
58 act as they normally would.
59
60 For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
61 """
62
63 def __set__(self, obj, value):
64 od = obj.__dict__
65 od["_node"] = value
66 # reset cached properties
67 if "nodes" in od:
68 del od["nodes"]
69
70
71class Graph:
72 """
73 Base class for undirected graphs.
74
75 A Graph stores nodes and edges with optional data, or attributes.
76
77 Graphs hold undirected edges. Self loops are allowed but multiple
78 (parallel) edges are not.
79
80 Nodes can be arbitrary (hashable) Python objects with optional
81 key/value attributes, except that `None` is not allowed as a node.
82
83 Edges are represented as links between nodes with optional
84 key/value attributes.
85
86 Parameters
87 ----------
88 incoming_graph_data : input graph (optional, default: None)
89 Data to initialize graph. If None (default) an empty
90 graph is created. The data can be any format that is supported
91 by the to_networkx_graph() function, currently including edge list,
92 dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
93 sparse matrix, or PyGraphviz graph.
94
95 attr : keyword arguments, optional (default= no attributes)
96 Attributes to add to graph as key=value pairs.
97
98 See Also
99 --------
100 DiGraph
101 MultiGraph
102 MultiDiGraph
103
104 Examples
105 --------
106 Create an empty graph structure (a "null graph") with no nodes and
107 no edges.
108
109 >>> G = nx.Graph()
110
111 G can be grown in several ways.
112
113 **Nodes:**
114
115 Add one node at a time:
116
117 >>> G.add_node(1)
118
119 Add the nodes from any container (a list, dict, set or
120 even the lines from a file or the nodes from another graph).
121
122 >>> G.add_nodes_from([2, 3])
123 >>> G.add_nodes_from(range(100, 110))
124 >>> H = nx.path_graph(10)
125 >>> G.add_nodes_from(H)
126
127 In addition to strings and integers any hashable Python object
128 (except None) can represent a node, e.g. a customized node object,
129 or even another Graph.
130
131 >>> G.add_node(H)
132
133 **Edges:**
134
135 G can also be grown by adding edges.
136
137 Add one edge,
138
139 >>> G.add_edge(1, 2)
140
141 a list of edges,
142
143 >>> G.add_edges_from([(1, 2), (1, 3)])
144
145 or a collection of edges,
146
147 >>> G.add_edges_from(H.edges)
148
149 If some edges connect nodes not yet in the graph, the nodes
150 are added automatically. There are no errors when adding
151 nodes or edges that already exist.
152
153 **Attributes:**
154
155 Each graph, node, and edge can hold key/value attribute pairs
156 in an associated attribute dictionary (the keys must be hashable).
157 By default these are empty, but can be added or changed using
158 add_edge, add_node or direct manipulation of the attribute
159 dictionaries named graph, node and edge respectively.
160
161 >>> G = nx.Graph(day="Friday")
162 >>> G.graph
163 {'day': 'Friday'}
164
165 Add node attributes using add_node(), add_nodes_from() or G.nodes
166
167 >>> G.add_node(1, time="5pm")
168 >>> G.add_nodes_from([3], time="2pm")
169 >>> G.nodes[1]
170 {'time': '5pm'}
171 >>> G.nodes[1]["room"] = 714 # node must exist already to use G.nodes
172 >>> del G.nodes[1]["room"] # remove attribute
173 >>> list(G.nodes(data=True))
174 [(1, {'time': '5pm'}), (3, {'time': '2pm'})]
175
176 Add edge attributes using add_edge(), add_edges_from(), subscript
177 notation, or G.edges.
178
179 >>> G.add_edge(1, 2, weight=4.7)
180 >>> G.add_edges_from([(3, 4), (4, 5)], color="red")
181 >>> G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
182 >>> G[1][2]["weight"] = 4.7
183 >>> G.edges[1, 2]["weight"] = 4
184
185 Warning: we protect the graph data structure by making `G.edges` a
186 read-only dict-like structure. However, you can assign to attributes
187 in e.g. `G.edges[1, 2]`. Thus, use 2 sets of brackets to add/change
188 data attributes: `G.edges[1, 2]['weight'] = 4`
189 (For multigraphs: `MG.edges[u, v, key][name] = value`).
190
191 **Shortcuts:**
192
193 Many common graph features allow python syntax to speed reporting.
194
195 >>> 1 in G # check if node in graph
196 True
197 >>> [n for n in G if n < 3] # iterate through nodes
198 [1, 2]
199 >>> len(G) # number of nodes in graph
200 5
201
202 Often the best way to traverse all edges of a graph is via the neighbors.
203 The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`
204
205 >>> for n, nbrsdict in G.adjacency():
206 ... for nbr, eattr in nbrsdict.items():
207 ... if "weight" in eattr:
208 ... # Do something useful with the edges
209 ... pass
210
211 But the edges() method is often more convenient:
212
213 >>> for u, v, weight in G.edges.data("weight"):
214 ... if weight is not None:
215 ... # Do something useful with the edges
216 ... pass
217
218 **Reporting:**
219
220 Simple graph information is obtained using object-attributes and methods.
221 Reporting typically provides views instead of containers to reduce memory
222 usage. The views update as the graph is updated similarly to dict-views.
223 The objects `nodes`, `edges` and `adj` provide access to data attributes
224 via lookup (e.g. `nodes[n]`, `edges[u, v]`, `adj[u][v]`) and iteration
225 (e.g. `nodes.items()`, `nodes.data('color')`,
226 `nodes.data('color', default='blue')` and similarly for `edges`)
227 Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
228
229 For details on these and other miscellaneous methods, see below.
230
231 **Subclasses (Advanced):**
232
233 The Graph class uses a dict-of-dict-of-dict data structure.
234 The outer dict (node_dict) holds adjacency information keyed by node.
235 The next dict (adjlist_dict) represents the adjacency information and holds
236 edge data keyed by neighbor. The inner dict (edge_attr_dict) represents
237 the edge data and holds edge attribute values keyed by attribute names.
238
239 Each of these three dicts can be replaced in a subclass by a user defined
240 dict-like object. In general, the dict-like features should be
241 maintained but extra features can be added. To replace one of the
242 dicts create a new graph class by changing the class(!) variable
243 holding the factory for that dict-like structure.
244
245 node_dict_factory : function, (default: dict)
246 Factory function to be used to create the dict containing node
247 attributes, keyed by node id.
248 It should require no arguments and return a dict-like object
249
250 node_attr_dict_factory: function, (default: dict)
251 Factory function to be used to create the node attribute
252 dict which holds attribute values keyed by attribute name.
253 It should require no arguments and return a dict-like object
254
255 adjlist_outer_dict_factory : function, (default: dict)
256 Factory function to be used to create the outer-most dict
257 in the data structure that holds adjacency info keyed by node.
258 It should require no arguments and return a dict-like object.
259
260 adjlist_inner_dict_factory : function, (default: dict)
261 Factory function to be used to create the adjacency list
262 dict which holds edge data keyed by neighbor.
263 It should require no arguments and return a dict-like object
264
265 edge_attr_dict_factory : function, (default: dict)
266 Factory function to be used to create the edge attribute
267 dict which holds attribute values keyed by attribute name.
268 It should require no arguments and return a dict-like object.
269
270 graph_attr_dict_factory : function, (default: dict)
271 Factory function to be used to create the graph attribute
272 dict which holds attribute values keyed by attribute name.
273 It should require no arguments and return a dict-like object.
274
275 Typically, if your extension doesn't impact the data structure all
276 methods will inherit without issue except: `to_directed/to_undirected`.
277 By default these methods create a DiGraph/Graph class and you probably
278 want them to create your extension of a DiGraph/Graph. To facilitate
279 this we define two class variables that you can set in your subclass.
280
281 to_directed_class : callable, (default: DiGraph or MultiDiGraph)
282 Class to create a new graph structure in the `to_directed` method.
283 If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
284
285 to_undirected_class : callable, (default: Graph or MultiGraph)
286 Class to create a new graph structure in the `to_undirected` method.
287 If `None`, a NetworkX class (Graph or MultiGraph) is used.
288
289 **Subclassing Example**
290
291 Create a low memory graph class that effectively disallows edge
292 attributes by using a single attribute dict for all edges.
293 This reduces the memory used, but you lose edge attributes.
294
295 >>> class ThinGraph(nx.Graph):
296 ... all_edge_dict = {"weight": 1}
297 ...
298 ... def single_edge_dict(self):
299 ... return self.all_edge_dict
300 ...
301 ... edge_attr_dict_factory = single_edge_dict
302 >>> G = ThinGraph()
303 >>> G.add_edge(2, 1)
304 >>> G[2][1]
305 {'weight': 1}
306 >>> G.add_edge(2, 2)
307 >>> G[2][1] is G[2][2]
308 True
309 """
310
311 __networkx_backend__ = "networkx"
312
313 _adj = _CachedPropertyResetterAdj()
314 _node = _CachedPropertyResetterNode()
315
316 node_dict_factory = dict
317 node_attr_dict_factory = dict
318 adjlist_outer_dict_factory = dict
319 adjlist_inner_dict_factory = dict
320 edge_attr_dict_factory = dict
321 graph_attr_dict_factory = dict
322
323 def to_directed_class(self):
324 """Returns the class to use for empty directed copies.
325
326 If you subclass the base classes, use this to designate
327 what directed class to use for `to_directed()` copies.
328 """
329 return nx.DiGraph
330
331 def to_undirected_class(self):
332 """Returns the class to use for empty undirected copies.
333
334 If you subclass the base classes, use this to designate
335 what directed class to use for `to_directed()` copies.
336 """
337 return Graph
338
339 def __init__(self, incoming_graph_data=None, **attr):
340 """Initialize a graph with edges, name, or graph attributes.
341
342 Parameters
343 ----------
344 incoming_graph_data : input graph (optional, default: None)
345 Data to initialize graph. If None (default) an empty
346 graph is created. The data can be an edge list, or any
347 NetworkX graph object. If the corresponding optional Python
348 packages are installed the data can also be a 2D NumPy array, a
349 SciPy sparse array, or a PyGraphviz graph.
350
351 attr : keyword arguments, optional (default= no attributes)
352 Attributes to add to graph as key=value pairs.
353
354 See Also
355 --------
356 convert
357
358 Examples
359 --------
360 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
361 >>> G = nx.Graph(name="my graph")
362 >>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
363 >>> G = nx.Graph(e)
364
365 Arbitrary graph attribute pairs (key=value) may be assigned
366
367 >>> G = nx.Graph(e, day="Friday")
368 >>> G.graph
369 {'day': 'Friday'}
370
371 """
372 self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes
373 self._node = self.node_dict_factory() # empty node attribute dict
374 self._adj = self.adjlist_outer_dict_factory() # empty adjacency dict
375 self.__networkx_cache__ = {}
376 # attempt to load graph with data
377 if incoming_graph_data is not None:
378 convert.to_networkx_graph(incoming_graph_data, create_using=self)
379 # load graph attributes (must be after convert)
380 self.graph.update(attr)
381
382 @cached_property
383 def adj(self):
384 """Graph adjacency object holding the neighbors of each node.
385
386 This object is a read-only dict-like structure with node keys
387 and neighbor-dict values. The neighbor-dict is keyed by neighbor
388 to the edge-data-dict. So `G.adj[3][2]['color'] = 'blue'` sets
389 the color of the edge `(3, 2)` to `"blue"`.
390
391 Iterating over G.adj behaves like a dict. Useful idioms include
392 `for nbr, datadict in G.adj[n].items():`.
393
394 The neighbor information is also provided by subscripting the graph.
395 So `for nbr, foovalue in G[node].data('foo', default=1):` works.
396
397 For directed graphs, `G.adj` holds outgoing (successor) info.
398 """
399 return AdjacencyView(self._adj)
400
401 @property
402 def name(self):
403 """String identifier of the graph.
404
405 This graph attribute appears in the attribute dict G.graph
406 keyed by the string `"name"`. as well as an attribute (technically
407 a property) `G.name`. This is entirely user controlled.
408 """
409 return self.graph.get("name", "")
410
411 @name.setter
412 def name(self, s):
413 self.graph["name"] = s
414 nx._clear_cache(self)
415
416 def __str__(self):
417 """Returns a short summary of the graph.
418
419 Returns
420 -------
421 info : string
422 Graph information including the graph name (if any), graph type, and the
423 number of nodes and edges.
424
425 Examples
426 --------
427 >>> G = nx.Graph(name="foo")
428 >>> str(G)
429 "Graph named 'foo' with 0 nodes and 0 edges"
430
431 >>> G = nx.path_graph(3)
432 >>> str(G)
433 'Graph with 3 nodes and 2 edges'
434
435 """
436 return "".join(
437 [
438 type(self).__name__,
439 f" named {self.name!r}" if self.name else "",
440 f" with {self.number_of_nodes()} nodes and {self.number_of_edges()} edges",
441 ]
442 )
443
444 def __iter__(self):
445 """Iterate over the nodes. Use: 'for n in G'.
446
447 Returns
448 -------
449 niter : iterator
450 An iterator over all nodes in the graph.
451
452 Examples
453 --------
454 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
455 >>> [n for n in G]
456 [0, 1, 2, 3]
457 >>> list(G)
458 [0, 1, 2, 3]
459 """
460 return iter(self._node)
461
462 def __contains__(self, n):
463 """Returns True if n is a node, False otherwise. Use: 'n in G'.
464
465 Examples
466 --------
467 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
468 >>> 1 in G
469 True
470 """
471 try:
472 return n in self._node
473 except TypeError:
474 return False
475
476 def __len__(self):
477 """Returns the number of nodes in the graph. Use: 'len(G)'.
478
479 Returns
480 -------
481 nnodes : int
482 The number of nodes in the graph.
483
484 See Also
485 --------
486 number_of_nodes: identical method
487 order: identical method
488
489 Examples
490 --------
491 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
492 >>> len(G)
493 4
494
495 """
496 return len(self._node)
497
498 def __getitem__(self, n):
499 """Returns a dict of neighbors of node n. Use: 'G[n]'.
500
501 Parameters
502 ----------
503 n : node
504 A node in the graph.
505
506 Returns
507 -------
508 adj_dict : dictionary
509 The adjacency dictionary for nodes connected to n.
510
511 Notes
512 -----
513 G[n] is the same as G.adj[n] and similar to G.neighbors(n)
514 (which is an iterator over G.adj[n])
515
516 Examples
517 --------
518 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
519 >>> G[0]
520 AtlasView({1: {}})
521 """
522 return self.adj[n]
523
524 def add_node(self, node_for_adding, **attr):
525 """Add a single node `node_for_adding` and update node attributes.
526
527 Parameters
528 ----------
529 node_for_adding : node
530 A node can be any hashable Python object except None.
531 attr : keyword arguments, optional
532 Set or change node attributes using key=value.
533
534 See Also
535 --------
536 add_nodes_from
537
538 Examples
539 --------
540 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
541 >>> G.add_node(1)
542 >>> G.add_node("Hello")
543 >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
544 >>> G.add_node(K3)
545 >>> G.number_of_nodes()
546 3
547
548 Use keywords set/change node attributes:
549
550 >>> G.add_node(1, size=10)
551 >>> G.add_node(3, weight=0.4, UTM=("13S", 382871, 3972649))
552
553 Notes
554 -----
555 A hashable object is one that can be used as a key in a Python
556 dictionary. This includes strings, numbers, tuples of strings
557 and numbers, etc.
558
559 On many platforms hashable items also include mutables such as
560 NetworkX Graphs, though one should be careful that the hash
561 doesn't change on mutables.
562 """
563 if node_for_adding not in self._node:
564 if node_for_adding is None:
565 raise ValueError("None cannot be a node")
566 self._adj[node_for_adding] = self.adjlist_inner_dict_factory()
567 attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory()
568 attr_dict.update(attr)
569 else: # update attr even if node already exists
570 self._node[node_for_adding].update(attr)
571 nx._clear_cache(self)
572
573 def add_nodes_from(self, nodes_for_adding, **attr):
574 """Add multiple nodes.
575
576 Parameters
577 ----------
578 nodes_for_adding : iterable container
579 A container of nodes (list, dict, set, etc.).
580 OR
581 A container of (node, attribute dict) tuples.
582 Node attributes are updated using the attribute dict.
583 attr : keyword arguments, optional (default= no attributes)
584 Update attributes for all nodes in nodes.
585 Node attributes specified in nodes as a tuple take
586 precedence over attributes specified via keyword arguments.
587
588 See Also
589 --------
590 add_node
591
592 Notes
593 -----
594 When adding nodes from an iterator over the graph you are changing,
595 a `RuntimeError` can be raised with message:
596 `RuntimeError: dictionary changed size during iteration`. This
597 happens when the graph's underlying dictionary is modified during
598 iteration. To avoid this error, evaluate the iterator into a separate
599 object, e.g. by using `list(iterator_of_nodes)`, and pass this
600 object to `G.add_nodes_from`.
601
602 Examples
603 --------
604 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
605 >>> G.add_nodes_from("Hello")
606 >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
607 >>> G.add_nodes_from(K3)
608 >>> sorted(G.nodes(), key=str)
609 [0, 1, 2, 'H', 'e', 'l', 'o']
610
611 Use keywords to update specific node attributes for every node.
612
613 >>> G.add_nodes_from([1, 2], size=10)
614 >>> G.add_nodes_from([3, 4], weight=0.4)
615
616 Use (node, attrdict) tuples to update attributes for specific nodes.
617
618 >>> G.add_nodes_from([(1, dict(size=11)), (2, {"color": "blue"})])
619 >>> G.nodes[1]["size"]
620 11
621 >>> H = nx.Graph()
622 >>> H.add_nodes_from(G.nodes(data=True))
623 >>> H.nodes[1]["size"]
624 11
625
626 Evaluate an iterator over a graph if using it to modify the same graph
627
628 >>> G = nx.Graph([(0, 1), (1, 2), (3, 4)])
629 >>> # wrong way - will raise RuntimeError
630 >>> # G.add_nodes_from(n + 1 for n in G.nodes)
631 >>> # correct way
632 >>> G.add_nodes_from(list(n + 1 for n in G.nodes))
633 """
634 for n in nodes_for_adding:
635 try:
636 newnode = n not in self._node
637 newdict = attr
638 except TypeError:
639 n, ndict = n
640 newnode = n not in self._node
641 newdict = attr.copy()
642 newdict.update(ndict)
643 if newnode:
644 if n is None:
645 raise ValueError("None cannot be a node")
646 self._adj[n] = self.adjlist_inner_dict_factory()
647 self._node[n] = self.node_attr_dict_factory()
648 self._node[n].update(newdict)
649 nx._clear_cache(self)
650
651 def remove_node(self, n):
652 """Remove node n.
653
654 Removes the node n and all adjacent edges.
655 Attempting to remove a nonexistent node will raise an exception.
656
657 Parameters
658 ----------
659 n : node
660 A node in the graph
661
662 Raises
663 ------
664 NetworkXError
665 If n is not in the graph.
666
667 See Also
668 --------
669 remove_nodes_from
670
671 Examples
672 --------
673 >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
674 >>> list(G.edges)
675 [(0, 1), (1, 2)]
676 >>> G.remove_node(1)
677 >>> list(G.edges)
678 []
679
680 """
681 adj = self._adj
682 try:
683 nbrs = list(adj[n]) # list handles self-loops (allows mutation)
684 del self._node[n]
685 except KeyError as err: # NetworkXError if n not in self
686 raise NetworkXError(f"The node {n} is not in the graph.") from err
687 for u in nbrs:
688 del adj[u][n] # remove all edges n-u in graph
689 del adj[n] # now remove node
690 nx._clear_cache(self)
691
692 def remove_nodes_from(self, nodes):
693 """Remove multiple nodes.
694
695 Parameters
696 ----------
697 nodes : iterable container
698 A container of nodes (list, dict, set, etc.). If a node
699 in the container is not in the graph it is silently
700 ignored.
701
702 See Also
703 --------
704 remove_node
705
706 Notes
707 -----
708 When removing nodes from an iterator over the graph you are changing,
709 a `RuntimeError` will be raised with message:
710 `RuntimeError: dictionary changed size during iteration`. This
711 happens when the graph's underlying dictionary is modified during
712 iteration. To avoid this error, evaluate the iterator into a separate
713 object, e.g. by using `list(iterator_of_nodes)`, and pass this
714 object to `G.remove_nodes_from`.
715
716 Examples
717 --------
718 >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
719 >>> e = list(G.nodes)
720 >>> e
721 [0, 1, 2]
722 >>> G.remove_nodes_from(e)
723 >>> list(G.nodes)
724 []
725
726 Evaluate an iterator over a graph if using it to modify the same graph
727
728 >>> G = nx.Graph([(0, 1), (1, 2), (3, 4)])
729 >>> # this command will fail, as the graph's dict is modified during iteration
730 >>> # G.remove_nodes_from(n for n in G.nodes if n < 2)
731 >>> # this command will work, since the dictionary underlying graph is not modified
732 >>> G.remove_nodes_from(list(n for n in G.nodes if n < 2))
733 """
734 adj = self._adj
735 for n in nodes:
736 try:
737 del self._node[n]
738 for u in list(adj[n]): # list handles self-loops
739 del adj[u][n] # (allows mutation of dict in loop)
740 del adj[n]
741 except KeyError:
742 pass
743 nx._clear_cache(self)
744
745 @cached_property
746 def nodes(self):
747 """A NodeView of the Graph as G.nodes or G.nodes().
748
749 Can be used as `G.nodes` for data lookup and for set-like operations.
750 Can also be used as `G.nodes(data='color', default=None)` to return a
751 NodeDataView which reports specific node data but no set operations.
752 It presents a dict-like interface as well with `G.nodes.items()`
753 iterating over `(node, nodedata)` 2-tuples and `G.nodes[3]['foo']`
754 providing the value of the `foo` attribute for node `3`. In addition,
755 a view `G.nodes.data('foo')` provides a dict-like interface to the
756 `foo` attribute of each node. `G.nodes.data('foo', default=1)`
757 provides a default for nodes that do not have attribute `foo`.
758
759 Parameters
760 ----------
761 data : string or bool, optional (default=False)
762 The node attribute returned in 2-tuple (n, ddict[data]).
763 If True, return entire node attribute dict as (n, ddict).
764 If False, return just the nodes n.
765
766 default : value, optional (default=None)
767 Value used for nodes that don't have the requested attribute.
768 Only relevant if data is not True or False.
769
770 Returns
771 -------
772 NodeView
773 Allows set-like operations over the nodes as well as node
774 attribute dict lookup and calling to get a NodeDataView.
775 A NodeDataView iterates over `(n, data)` and has no set operations.
776 A NodeView iterates over `n` and includes set operations.
777
778 When called, if data is False, an iterator over nodes.
779 Otherwise an iterator of 2-tuples (node, attribute value)
780 where the attribute is specified in `data`.
781 If data is True then the attribute becomes the
782 entire data dictionary.
783
784 Notes
785 -----
786 If your node data is not needed, it is simpler and equivalent
787 to use the expression ``for n in G``, or ``list(G)``.
788
789 Examples
790 --------
791 There are two simple ways of getting a list of all nodes in the graph:
792
793 >>> G = nx.path_graph(3)
794 >>> list(G.nodes)
795 [0, 1, 2]
796 >>> list(G)
797 [0, 1, 2]
798
799 To get the node data along with the nodes:
800
801 >>> G.add_node(1, time="5pm")
802 >>> G.nodes[0]["foo"] = "bar"
803 >>> list(G.nodes(data=True))
804 [(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
805 >>> list(G.nodes.data())
806 [(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
807
808 >>> list(G.nodes(data="foo"))
809 [(0, 'bar'), (1, None), (2, None)]
810 >>> list(G.nodes.data("foo"))
811 [(0, 'bar'), (1, None), (2, None)]
812
813 >>> list(G.nodes(data="time"))
814 [(0, None), (1, '5pm'), (2, None)]
815 >>> list(G.nodes.data("time"))
816 [(0, None), (1, '5pm'), (2, None)]
817
818 >>> list(G.nodes(data="time", default="Not Available"))
819 [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]
820 >>> list(G.nodes.data("time", default="Not Available"))
821 [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]
822
823 If some of your nodes have an attribute and the rest are assumed
824 to have a default attribute value you can create a dictionary
825 from node/attribute pairs using the `default` keyword argument
826 to guarantee the value is never None::
827
828 >>> G = nx.Graph()
829 >>> G.add_node(0)
830 >>> G.add_node(1, weight=2)
831 >>> G.add_node(2, weight=3)
832 >>> dict(G.nodes(data="weight", default=1))
833 {0: 1, 1: 2, 2: 3}
834
835 """
836 return NodeView(self)
837
838 def number_of_nodes(self):
839 """Returns the number of nodes in the graph.
840
841 Returns
842 -------
843 nnodes : int
844 The number of nodes in the graph.
845
846 See Also
847 --------
848 order: identical method
849 __len__: identical method
850
851 Examples
852 --------
853 >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
854 >>> G.number_of_nodes()
855 3
856 """
857 return len(self._node)
858
859 def order(self):
860 """Returns the number of nodes in the graph.
861
862 Returns
863 -------
864 nnodes : int
865 The number of nodes in the graph.
866
867 See Also
868 --------
869 number_of_nodes: identical method
870 __len__: identical method
871
872 Examples
873 --------
874 >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
875 >>> G.order()
876 3
877 """
878 return len(self._node)
879
880 def has_node(self, n):
881 """Returns True if the graph contains the node n.
882
883 Identical to `n in G`
884
885 Parameters
886 ----------
887 n : node
888
889 Examples
890 --------
891 >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
892 >>> G.has_node(0)
893 True
894
895 It is more readable and simpler to use
896
897 >>> 0 in G
898 True
899
900 """
901 try:
902 return n in self._node
903 except TypeError:
904 return False
905
906 def add_edge(self, u_of_edge, v_of_edge, **attr):
907 """Add an edge between u and v.
908
909 The nodes u and v will be automatically added if they are
910 not already in the graph.
911
912 Edge attributes can be specified with keywords or by directly
913 accessing the edge's attribute dictionary. See examples below.
914
915 Parameters
916 ----------
917 u_of_edge, v_of_edge : nodes
918 Nodes can be, for example, strings or numbers.
919 Nodes must be hashable (and not None) Python objects.
920 attr : keyword arguments, optional
921 Edge data (or labels or objects) can be assigned using
922 keyword arguments.
923
924 See Also
925 --------
926 add_edges_from : add a collection of edges
927
928 Notes
929 -----
930 Adding an edge that already exists updates the edge data.
931
932 Many NetworkX algorithms designed for weighted graphs use
933 an edge attribute (by default `weight`) to hold a numerical value.
934
935 Examples
936 --------
937 The following all add the edge e=(1, 2) to graph G:
938
939 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
940 >>> e = (1, 2)
941 >>> G.add_edge(1, 2) # explicit two-node form
942 >>> G.add_edge(*e) # single edge as tuple of two nodes
943 >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
944
945 Associate data to edges using keywords:
946
947 >>> G.add_edge(1, 2, weight=3)
948 >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
949
950 For non-string attribute keys, use subscript notation.
951
952 >>> G.add_edge(1, 2)
953 >>> G[1][2].update({0: 5})
954 >>> G.edges[1, 2].update({0: 5})
955 """
956 u, v = u_of_edge, v_of_edge
957 # add nodes
958 if u not in self._node:
959 if u is None:
960 raise ValueError("None cannot be a node")
961 self._adj[u] = self.adjlist_inner_dict_factory()
962 self._node[u] = self.node_attr_dict_factory()
963 if v not in self._node:
964 if v is None:
965 raise ValueError("None cannot be a node")
966 self._adj[v] = self.adjlist_inner_dict_factory()
967 self._node[v] = self.node_attr_dict_factory()
968 # add the edge
969 datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
970 datadict.update(attr)
971 self._adj[u][v] = datadict
972 self._adj[v][u] = datadict
973 nx._clear_cache(self)
974
975 def add_edges_from(self, ebunch_to_add, **attr):
976 """Add all the edges in ebunch_to_add.
977
978 Parameters
979 ----------
980 ebunch_to_add : container of edges
981 Each edge given in the container will be added to the
982 graph. The edges must be given as 2-tuples (u, v) or
983 3-tuples (u, v, d) where d is a dictionary containing edge data.
984 attr : keyword arguments, optional
985 Edge data (or labels or objects) can be assigned using
986 keyword arguments.
987
988 See Also
989 --------
990 add_edge : add a single edge
991 add_weighted_edges_from : convenient way to add weighted edges
992
993 Notes
994 -----
995 Adding the same edge twice has no effect but any edge data
996 will be updated when each duplicate edge is added.
997
998 Edge attributes specified in an ebunch take precedence over
999 attributes specified via keyword arguments.
1000
1001 When adding edges from an iterator over the graph you are changing,
1002 a `RuntimeError` can be raised with message:
1003 `RuntimeError: dictionary changed size during iteration`. This
1004 happens when the graph's underlying dictionary is modified during
1005 iteration. To avoid this error, evaluate the iterator into a separate
1006 object, e.g. by using `list(iterator_of_edges)`, and pass this
1007 object to `G.add_edges_from`.
1008
1009 Examples
1010 --------
1011 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1012 >>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
1013 >>> e = zip(range(0, 3), range(1, 4))
1014 >>> G.add_edges_from(e) # Add the path graph 0-1-2-3
1015
1016 Associate data to edges
1017
1018 >>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
1019 >>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
1020
1021 Evaluate an iterator over a graph if using it to modify the same graph
1022
1023 >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
1024 >>> # Grow graph by one new node, adding edges to all existing nodes.
1025 >>> # wrong way - will raise RuntimeError
1026 >>> # G.add_edges_from(((5, n) for n in G.nodes))
1027 >>> # correct way - note that there will be no self-edge for node 5
1028 >>> G.add_edges_from(list((5, n) for n in G.nodes))
1029 """
1030 for e in ebunch_to_add:
1031 ne = len(e)
1032 if ne == 3:
1033 u, v, dd = e
1034 elif ne == 2:
1035 u, v = e
1036 dd = {} # doesn't need edge_attr_dict_factory
1037 else:
1038 raise NetworkXError(f"Edge tuple {e} must be a 2-tuple or 3-tuple.")
1039 if u not in self._node:
1040 if u is None:
1041 raise ValueError("None cannot be a node")
1042 self._adj[u] = self.adjlist_inner_dict_factory()
1043 self._node[u] = self.node_attr_dict_factory()
1044 if v not in self._node:
1045 if v is None:
1046 raise ValueError("None cannot be a node")
1047 self._adj[v] = self.adjlist_inner_dict_factory()
1048 self._node[v] = self.node_attr_dict_factory()
1049 datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
1050 datadict.update(attr)
1051 datadict.update(dd)
1052 self._adj[u][v] = datadict
1053 self._adj[v][u] = datadict
1054 nx._clear_cache(self)
1055
1056 def add_weighted_edges_from(self, ebunch_to_add, weight="weight", **attr):
1057 """Add weighted edges in `ebunch_to_add` with specified weight attr
1058
1059 Parameters
1060 ----------
1061 ebunch_to_add : container of edges
1062 Each edge given in the list or container will be added
1063 to the graph. The edges must be given as 3-tuples (u, v, w)
1064 where w is a number.
1065 weight : string, optional (default= 'weight')
1066 The attribute name for the edge weights to be added.
1067 attr : keyword arguments, optional (default= no attributes)
1068 Edge attributes to add/update for all edges.
1069
1070 See Also
1071 --------
1072 add_edge : add a single edge
1073 add_edges_from : add multiple edges
1074
1075 Notes
1076 -----
1077 Adding the same edge twice for Graph/DiGraph simply updates
1078 the edge data. For MultiGraph/MultiDiGraph, duplicate edges
1079 are stored.
1080
1081 When adding edges from an iterator over the graph you are changing,
1082 a `RuntimeError` can be raised with message:
1083 `RuntimeError: dictionary changed size during iteration`. This
1084 happens when the graph's underlying dictionary is modified during
1085 iteration. To avoid this error, evaluate the iterator into a separate
1086 object, e.g. by using `list(iterator_of_edges)`, and pass this
1087 object to `G.add_weighted_edges_from`.
1088
1089 Examples
1090 --------
1091 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1092 >>> G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5)])
1093
1094 Evaluate an iterator over edges before passing it
1095
1096 >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
1097 >>> weight = 0.1
1098 >>> # Grow graph by one new node, adding edges to all existing nodes.
1099 >>> # wrong way - will raise RuntimeError
1100 >>> # G.add_weighted_edges_from(((5, n, weight) for n in G.nodes))
1101 >>> # correct way - note that there will be no self-edge for node 5
1102 >>> G.add_weighted_edges_from(list((5, n, weight) for n in G.nodes))
1103 """
1104 self.add_edges_from(((u, v, {weight: d}) for u, v, d in ebunch_to_add), **attr)
1105 nx._clear_cache(self)
1106
1107 def remove_edge(self, u, v):
1108 """Remove the edge between u and v.
1109
1110 Parameters
1111 ----------
1112 u, v : nodes
1113 Remove the edge between nodes u and v.
1114
1115 Raises
1116 ------
1117 NetworkXError
1118 If there is not an edge between u and v.
1119
1120 See Also
1121 --------
1122 remove_edges_from : remove a collection of edges
1123
1124 Examples
1125 --------
1126 >>> G = nx.path_graph(4) # or DiGraph, etc
1127 >>> G.remove_edge(0, 1)
1128 >>> e = (1, 2)
1129 >>> G.remove_edge(*e) # unpacks e from an edge tuple
1130 >>> e = (2, 3, {"weight": 7}) # an edge with attribute data
1131 >>> G.remove_edge(*e[:2]) # select first part of edge tuple
1132 """
1133 try:
1134 del self._adj[u][v]
1135 if u != v: # self-loop needs only one entry removed
1136 del self._adj[v][u]
1137 except KeyError as err:
1138 raise NetworkXError(f"The edge {u}-{v} is not in the graph") from err
1139 nx._clear_cache(self)
1140
1141 def remove_edges_from(self, ebunch):
1142 """Remove all edges specified in ebunch.
1143
1144 Parameters
1145 ----------
1146 ebunch: list or container of edge tuples
1147 Each edge given in the list or container will be removed
1148 from the graph. The edges can be:
1149
1150 - 2-tuples (u, v) edge between u and v.
1151 - 3-tuples (u, v, k) where k is ignored.
1152
1153 See Also
1154 --------
1155 remove_edge : remove a single edge
1156
1157 Notes
1158 -----
1159 Will fail silently if an edge in ebunch is not in the graph.
1160
1161 Examples
1162 --------
1163 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1164 >>> ebunch = [(1, 2), (2, 3)]
1165 >>> G.remove_edges_from(ebunch)
1166 """
1167 adj = self._adj
1168 for e in ebunch:
1169 u, v = e[:2] # ignore edge data if present
1170 if u in adj and v in adj[u]:
1171 del adj[u][v]
1172 if u != v: # self loop needs only one entry removed
1173 del adj[v][u]
1174 nx._clear_cache(self)
1175
1176 def update(self, edges=None, nodes=None):
1177 """Update the graph using nodes/edges/graphs as input.
1178
1179 Like dict.update, this method takes a graph as input, adding the
1180 graph's nodes and edges to this graph. It can also take two inputs:
1181 edges and nodes. Finally it can take either edges or nodes.
1182 To specify only nodes the keyword `nodes` must be used.
1183
1184 The collections of edges and nodes are treated similarly to
1185 the add_edges_from/add_nodes_from methods. When iterated, they
1186 should yield 2-tuples (u, v) or 3-tuples (u, v, datadict).
1187
1188 Parameters
1189 ----------
1190 edges : Graph object, collection of edges, or None
1191 The first parameter can be a graph or some edges. If it has
1192 attributes `nodes` and `edges`, then it is taken to be a
1193 Graph-like object and those attributes are used as collections
1194 of nodes and edges to be added to the graph.
1195 If the first parameter does not have those attributes, it is
1196 treated as a collection of edges and added to the graph.
1197 If the first argument is None, no edges are added.
1198 nodes : collection of nodes, or None
1199 The second parameter is treated as a collection of nodes
1200 to be added to the graph unless it is None.
1201 If `edges is None` and `nodes is None` an exception is raised.
1202 If the first parameter is a Graph, then `nodes` is ignored.
1203
1204 Examples
1205 --------
1206 >>> G = nx.path_graph(5)
1207 >>> G.update(nx.complete_graph(range(4, 10)))
1208 >>> from itertools import combinations
1209 >>> edges = (
1210 ... (u, v, {"power": u * v})
1211 ... for u, v in combinations(range(10, 20), 2)
1212 ... if u * v < 225
1213 ... )
1214 >>> nodes = [1000] # for singleton, use a container
1215 >>> G.update(edges, nodes)
1216
1217 Notes
1218 -----
1219 It you want to update the graph using an adjacency structure
1220 it is straightforward to obtain the edges/nodes from adjacency.
1221 The following examples provide common cases, your adjacency may
1222 be slightly different and require tweaks of these examples::
1223
1224 >>> # dict-of-set/list/tuple
1225 >>> adj = {1: {2, 3}, 2: {1, 3}, 3: {1, 2}}
1226 >>> e = [(u, v) for u, nbrs in adj.items() for v in nbrs]
1227 >>> G.update(edges=e, nodes=adj)
1228
1229 >>> DG = nx.DiGraph()
1230 >>> # dict-of-dict-of-attribute
1231 >>> adj = {1: {2: 1.3, 3: 0.7}, 2: {1: 1.4}, 3: {1: 0.7}}
1232 >>> e = [
1233 ... (u, v, {"weight": d})
1234 ... for u, nbrs in adj.items()
1235 ... for v, d in nbrs.items()
1236 ... ]
1237 >>> DG.update(edges=e, nodes=adj)
1238
1239 >>> # dict-of-dict-of-dict
1240 >>> adj = {1: {2: {"weight": 1.3}, 3: {"color": 0.7, "weight": 1.2}}}
1241 >>> e = [
1242 ... (u, v, {"weight": d})
1243 ... for u, nbrs in adj.items()
1244 ... for v, d in nbrs.items()
1245 ... ]
1246 >>> DG.update(edges=e, nodes=adj)
1247
1248 >>> # predecessor adjacency (dict-of-set)
1249 >>> pred = {1: {2, 3}, 2: {3}, 3: {3}}
1250 >>> e = [(v, u) for u, nbrs in pred.items() for v in nbrs]
1251
1252 >>> # MultiGraph dict-of-dict-of-dict-of-attribute
1253 >>> MDG = nx.MultiDiGraph()
1254 >>> adj = {
1255 ... 1: {2: {0: {"weight": 1.3}, 1: {"weight": 1.2}}},
1256 ... 3: {2: {0: {"weight": 0.7}}},
1257 ... }
1258 >>> e = [
1259 ... (u, v, ekey, d)
1260 ... for u, nbrs in adj.items()
1261 ... for v, keydict in nbrs.items()
1262 ... for ekey, d in keydict.items()
1263 ... ]
1264 >>> MDG.update(edges=e)
1265
1266 See Also
1267 --------
1268 add_edges_from: add multiple edges to a graph
1269 add_nodes_from: add multiple nodes to a graph
1270 """
1271 if edges is not None:
1272 if nodes is not None:
1273 self.add_nodes_from(nodes)
1274 self.add_edges_from(edges)
1275 else:
1276 # check if edges is a Graph object
1277 try:
1278 graph_nodes = edges.nodes
1279 graph_edges = edges.edges
1280 except AttributeError:
1281 # edge not Graph-like
1282 self.add_edges_from(edges)
1283 else: # edges is Graph-like
1284 self.add_nodes_from(graph_nodes.data())
1285 self.add_edges_from(graph_edges.data())
1286 self.graph.update(edges.graph)
1287 elif nodes is not None:
1288 self.add_nodes_from(nodes)
1289 else:
1290 raise NetworkXError("update needs nodes or edges input")
1291
1292 def has_edge(self, u, v):
1293 """Returns True if the edge (u, v) is in the graph.
1294
1295 This is the same as `v in G[u]` without KeyError exceptions.
1296
1297 Parameters
1298 ----------
1299 u, v : nodes
1300 Nodes can be, for example, strings or numbers.
1301 Nodes must be hashable (and not None) Python objects.
1302
1303 Returns
1304 -------
1305 edge_ind : bool
1306 True if edge is in the graph, False otherwise.
1307
1308 Examples
1309 --------
1310 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1311 >>> G.has_edge(0, 1) # using two nodes
1312 True
1313 >>> e = (0, 1)
1314 >>> G.has_edge(*e) # e is a 2-tuple (u, v)
1315 True
1316 >>> e = (0, 1, {"weight": 7})
1317 >>> G.has_edge(*e[:2]) # e is a 3-tuple (u, v, data_dictionary)
1318 True
1319
1320 The following syntax are equivalent:
1321
1322 >>> G.has_edge(0, 1)
1323 True
1324 >>> 1 in G[0] # though this gives KeyError if 0 not in G
1325 True
1326
1327 """
1328 try:
1329 return v in self._adj[u]
1330 except KeyError:
1331 return False
1332
1333 def neighbors(self, n):
1334 """Returns an iterator over all neighbors of node n.
1335
1336 This is identical to `iter(G[n])`
1337
1338 Parameters
1339 ----------
1340 n : node
1341 A node in the graph
1342
1343 Returns
1344 -------
1345 neighbors : iterator
1346 An iterator over all neighbors of node n
1347
1348 Raises
1349 ------
1350 NetworkXError
1351 If the node n is not in the graph.
1352
1353 Examples
1354 --------
1355 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1356 >>> [n for n in G.neighbors(0)]
1357 [1]
1358
1359 Notes
1360 -----
1361 Alternate ways to access the neighbors are ``G.adj[n]`` or ``G[n]``:
1362
1363 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1364 >>> G.add_edge("a", "b", weight=7)
1365 >>> G["a"]
1366 AtlasView({'b': {'weight': 7}})
1367 >>> G = nx.path_graph(4)
1368 >>> [n for n in G[0]]
1369 [1]
1370 """
1371 try:
1372 return iter(self._adj[n])
1373 except KeyError as err:
1374 raise NetworkXError(f"The node {n} is not in the graph.") from err
1375
1376 @cached_property
1377 def edges(self):
1378 """An EdgeView of the Graph as G.edges or G.edges().
1379
1380 edges(self, nbunch=None, data=False, default=None)
1381
1382 The EdgeView provides set-like operations on the edge-tuples
1383 as well as edge attribute lookup. When called, it also provides
1384 an EdgeDataView object which allows control of access to edge
1385 attributes (but does not provide set-like operations).
1386 Hence, `G.edges[u, v]['color']` provides the value of the color
1387 attribute for edge `(u, v)` while
1388 `for (u, v, c) in G.edges.data('color', default='red'):`
1389 iterates through all the edges yielding the color attribute
1390 with default `'red'` if no color attribute exists.
1391
1392 Parameters
1393 ----------
1394 nbunch : single node, container, or all nodes (default= all nodes)
1395 The view will only report edges from these nodes.
1396 data : string or bool, optional (default=False)
1397 The edge attribute returned in 3-tuple (u, v, ddict[data]).
1398 If True, return edge attribute dict in 3-tuple (u, v, ddict).
1399 If False, return 2-tuple (u, v).
1400 default : value, optional (default=None)
1401 Value used for edges that don't have the requested attribute.
1402 Only relevant if data is not True or False.
1403
1404 Returns
1405 -------
1406 edges : EdgeView
1407 A view of edge attributes, usually it iterates over (u, v)
1408 or (u, v, d) tuples of edges, but can also be used for
1409 attribute lookup as `edges[u, v]['foo']`.
1410
1411 Notes
1412 -----
1413 Nodes in nbunch that are not in the graph will be (quietly) ignored.
1414 For directed graphs this returns the out-edges.
1415
1416 Examples
1417 --------
1418 >>> G = nx.path_graph(3) # or MultiGraph, etc
1419 >>> G.add_edge(2, 3, weight=5)
1420 >>> [e for e in G.edges]
1421 [(0, 1), (1, 2), (2, 3)]
1422 >>> G.edges.data() # default data is {} (empty dict)
1423 EdgeDataView([(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})])
1424 >>> G.edges.data("weight", default=1)
1425 EdgeDataView([(0, 1, 1), (1, 2, 1), (2, 3, 5)])
1426 >>> G.edges([0, 3]) # only edges from these nodes
1427 EdgeDataView([(0, 1), (3, 2)])
1428 >>> G.edges(0) # only edges from node 0
1429 EdgeDataView([(0, 1)])
1430 """
1431 return EdgeView(self)
1432
1433 def get_edge_data(self, u, v, default=None):
1434 """Returns the attribute dictionary associated with edge (u, v).
1435
1436 This is identical to `G[u][v]` except the default is returned
1437 instead of an exception if the edge doesn't exist.
1438
1439 Parameters
1440 ----------
1441 u, v : nodes
1442 default: any Python object (default=None)
1443 Value to return if the edge (u, v) is not found.
1444
1445 Returns
1446 -------
1447 edge_dict : dictionary
1448 The edge attribute dictionary.
1449
1450 Examples
1451 --------
1452 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1453 >>> G[0][1]
1454 {}
1455
1456 Warning: Assigning to `G[u][v]` is not permitted.
1457 But it is safe to assign attributes `G[u][v]['foo']`
1458
1459 >>> G[0][1]["weight"] = 7
1460 >>> G[0][1]["weight"]
1461 7
1462 >>> G[1][0]["weight"]
1463 7
1464
1465 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1466 >>> G.get_edge_data(0, 1) # default edge data is {}
1467 {}
1468 >>> e = (0, 1)
1469 >>> G.get_edge_data(*e) # tuple form
1470 {}
1471 >>> G.get_edge_data("a", "b", default=0) # edge not in graph, return 0
1472 0
1473 """
1474 try:
1475 return self._adj[u][v]
1476 except KeyError:
1477 return default
1478
1479 def adjacency(self):
1480 """Returns an iterator over (node, adjacency dict) tuples for all nodes.
1481
1482 For directed graphs, only outgoing neighbors/adjacencies are included.
1483
1484 Returns
1485 -------
1486 adj_iter : iterator
1487 An iterator over (node, adjacency dictionary) for all nodes in
1488 the graph.
1489
1490 Examples
1491 --------
1492 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1493 >>> [(n, nbrdict) for n, nbrdict in G.adjacency()]
1494 [(0, {1: {}}), (1, {0: {}, 2: {}}), (2, {1: {}, 3: {}}), (3, {2: {}})]
1495
1496 """
1497 return iter(self._adj.items())
1498
1499 @cached_property
1500 def degree(self):
1501 """A DegreeView for the Graph as G.degree or G.degree().
1502
1503 The node degree is the number of edges adjacent to the node.
1504 The weighted node degree is the sum of the edge weights for
1505 edges incident to that node.
1506
1507 This object provides an iterator for (node, degree) as well as
1508 lookup for the degree for a single node.
1509
1510 Parameters
1511 ----------
1512 nbunch : single node, container, or all nodes (default= all nodes)
1513 The view will only report edges incident to these nodes.
1514
1515 weight : string or None, optional (default=None)
1516 The name of an edge attribute that holds the numerical value used
1517 as a weight. If None, then each edge has weight 1.
1518 The degree is the sum of the edge weights adjacent to the node.
1519
1520 Returns
1521 -------
1522 DegreeView or int
1523 If multiple nodes are requested (the default), returns a `DegreeView`
1524 mapping nodes to their degree.
1525 If a single node is requested, returns the degree of the node as an integer.
1526
1527 Examples
1528 --------
1529 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1530 >>> G.degree[0] # node 0 has degree 1
1531 1
1532 >>> list(G.degree([0, 1, 2]))
1533 [(0, 1), (1, 2), (2, 2)]
1534 """
1535 return DegreeView(self)
1536
1537 def clear(self):
1538 """Remove all nodes and edges from the graph.
1539
1540 This also removes the name, and all graph, node, and edge attributes.
1541
1542 Examples
1543 --------
1544 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1545 >>> G.clear()
1546 >>> list(G.nodes)
1547 []
1548 >>> list(G.edges)
1549 []
1550
1551 """
1552 self._adj.clear()
1553 self._node.clear()
1554 self.graph.clear()
1555 nx._clear_cache(self)
1556
1557 def clear_edges(self):
1558 """Remove all edges from the graph without altering nodes.
1559
1560 Examples
1561 --------
1562 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1563 >>> G.clear_edges()
1564 >>> list(G.nodes)
1565 [0, 1, 2, 3]
1566 >>> list(G.edges)
1567 []
1568 """
1569 for nbr_dict in self._adj.values():
1570 nbr_dict.clear()
1571 nx._clear_cache(self)
1572
1573 def is_multigraph(self):
1574 """Returns True if graph is a multigraph, False otherwise."""
1575 return False
1576
1577 def is_directed(self):
1578 """Returns True if graph is directed, False otherwise."""
1579 return False
1580
1581 def copy(self, as_view=False):
1582 """Returns a copy of the graph.
1583
1584 The copy method by default returns an independent shallow copy
1585 of the graph and attributes. That is, if an attribute is a
1586 container, that container is shared by the original an the copy.
1587 Use Python's `copy.deepcopy` for new containers.
1588
1589 If `as_view` is True then a view is returned instead of a copy.
1590
1591 Notes
1592 -----
1593 All copies reproduce the graph structure, but data attributes
1594 may be handled in different ways. There are four types of copies
1595 of a graph that people might want.
1596
1597 Deepcopy -- A "deepcopy" copies the graph structure as well as
1598 all data attributes and any objects they might contain.
1599 The entire graph object is new so that changes in the copy
1600 do not affect the original object. (see Python's copy.deepcopy)
1601
1602 Data Reference (Shallow) -- For a shallow copy the graph structure
1603 is copied but the edge, node and graph attribute dicts are
1604 references to those in the original graph. This saves
1605 time and memory but could cause confusion if you change an attribute
1606 in one graph and it changes the attribute in the other.
1607 NetworkX does not provide this level of shallow copy.
1608
1609 Independent Shallow -- This copy creates new independent attribute
1610 dicts and then does a shallow copy of the attributes. That is, any
1611 attributes that are containers are shared between the new graph
1612 and the original. This is exactly what `dict.copy()` provides.
1613 You can obtain this style copy using:
1614
1615 >>> G = nx.path_graph(5)
1616 >>> H = G.copy()
1617 >>> H = G.copy(as_view=False)
1618 >>> H = nx.Graph(G)
1619 >>> H = G.__class__(G)
1620
1621 Fresh Data -- For fresh data, the graph structure is copied while
1622 new empty data attribute dicts are created. The resulting graph
1623 is independent of the original and it has no edge, node or graph
1624 attributes. Fresh copies are not enabled. Instead use:
1625
1626 >>> H = G.__class__()
1627 >>> H.add_nodes_from(G)
1628 >>> H.add_edges_from(G.edges)
1629
1630 View -- Inspired by dict-views, graph-views act like read-only
1631 versions of the original graph, providing a copy of the original
1632 structure without requiring any memory for copying the information.
1633
1634 See the Python copy module for more information on shallow
1635 and deep copies, https://docs.python.org/3/library/copy.html.
1636
1637 Parameters
1638 ----------
1639 as_view : bool, optional (default=False)
1640 If True, the returned graph-view provides a read-only view
1641 of the original graph without actually copying any data.
1642
1643 Returns
1644 -------
1645 G : Graph
1646 A copy of the graph.
1647
1648 See Also
1649 --------
1650 to_directed: return a directed copy of the graph.
1651
1652 Examples
1653 --------
1654 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1655 >>> H = G.copy()
1656
1657 """
1658 if as_view is True:
1659 return nx.graphviews.generic_graph_view(self)
1660 G = self.__class__()
1661 G.graph.update(self.graph)
1662 G.add_nodes_from((n, d.copy()) for n, d in self._node.items())
1663 G.add_edges_from(
1664 (u, v, datadict.copy())
1665 for u, nbrs in self._adj.items()
1666 for v, datadict in nbrs.items()
1667 )
1668 return G
1669
1670 def to_directed(self, as_view=False):
1671 """Returns a directed representation of the graph.
1672
1673 Returns
1674 -------
1675 G : DiGraph
1676 A directed graph with the same name, same nodes, and with
1677 each edge (u, v, data) replaced by two directed edges
1678 (u, v, data) and (v, u, data).
1679
1680 Notes
1681 -----
1682 This returns a "deepcopy" of the edge, node, and
1683 graph attributes which attempts to completely copy
1684 all of the data and references.
1685
1686 This is in contrast to the similar D=DiGraph(G) which returns a
1687 shallow copy of the data.
1688
1689 See the Python copy module for more information on shallow
1690 and deep copies, https://docs.python.org/3/library/copy.html.
1691
1692 Warning: If you have subclassed Graph to use dict-like objects
1693 in the data structure, those changes do not transfer to the
1694 DiGraph created by this method.
1695
1696 Examples
1697 --------
1698 >>> G = nx.Graph() # or MultiGraph, etc
1699 >>> G.add_edge(0, 1)
1700 >>> H = G.to_directed()
1701 >>> list(H.edges)
1702 [(0, 1), (1, 0)]
1703
1704 If already directed, return a (deep) copy
1705
1706 >>> G = nx.DiGraph() # or MultiDiGraph, etc
1707 >>> G.add_edge(0, 1)
1708 >>> H = G.to_directed()
1709 >>> list(H.edges)
1710 [(0, 1)]
1711 """
1712 graph_class = self.to_directed_class()
1713 if as_view is True:
1714 return nx.graphviews.generic_graph_view(self, graph_class)
1715 # deepcopy when not a view
1716 G = graph_class()
1717 G.graph.update(deepcopy(self.graph))
1718 G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1719 G.add_edges_from(
1720 (u, v, deepcopy(data))
1721 for u, nbrs in self._adj.items()
1722 for v, data in nbrs.items()
1723 )
1724 return G
1725
1726 def to_undirected(self, as_view=False):
1727 """Returns an undirected copy of the graph.
1728
1729 Parameters
1730 ----------
1731 as_view : bool (optional, default=False)
1732 If True return a view of the original undirected graph.
1733
1734 Returns
1735 -------
1736 G : Graph/MultiGraph
1737 A deepcopy of the graph.
1738
1739 See Also
1740 --------
1741 Graph, copy, add_edge, add_edges_from
1742
1743 Notes
1744 -----
1745 This returns a "deepcopy" of the edge, node, and
1746 graph attributes which attempts to completely copy
1747 all of the data and references.
1748
1749 This is in contrast to the similar `G = nx.DiGraph(D)` which returns a
1750 shallow copy of the data.
1751
1752 See the Python copy module for more information on shallow
1753 and deep copies, https://docs.python.org/3/library/copy.html.
1754
1755 Warning: If you have subclassed DiGraph to use dict-like objects
1756 in the data structure, those changes do not transfer to the
1757 Graph created by this method.
1758
1759 Examples
1760 --------
1761 >>> G = nx.path_graph(2) # or MultiGraph, etc
1762 >>> H = G.to_directed()
1763 >>> list(H.edges)
1764 [(0, 1), (1, 0)]
1765 >>> G2 = H.to_undirected()
1766 >>> list(G2.edges)
1767 [(0, 1)]
1768 """
1769 graph_class = self.to_undirected_class()
1770 if as_view is True:
1771 return nx.graphviews.generic_graph_view(self, graph_class)
1772 # deepcopy when not a view
1773 G = graph_class()
1774 G.graph.update(deepcopy(self.graph))
1775 G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1776 G.add_edges_from(
1777 (u, v, deepcopy(d))
1778 for u, nbrs in self._adj.items()
1779 for v, d in nbrs.items()
1780 )
1781 return G
1782
1783 def subgraph(self, nodes):
1784 """Returns a SubGraph view of the subgraph induced on `nodes`.
1785
1786 The induced subgraph of the graph contains the nodes in `nodes`
1787 and the edges between those nodes.
1788
1789 Parameters
1790 ----------
1791 nodes : list, iterable
1792 A container of nodes which will be iterated through once.
1793
1794 Returns
1795 -------
1796 G : SubGraph View
1797 A subgraph view of the graph. The graph structure cannot be
1798 changed but node/edge attributes can and are shared with the
1799 original graph.
1800
1801 Notes
1802 -----
1803 The graph, edge and node attributes are shared with the original graph.
1804 Changes to the graph structure is ruled out by the view, but changes
1805 to attributes are reflected in the original graph.
1806
1807 To create a subgraph with its own copy of the edge/node attributes use:
1808 G.subgraph(nodes).copy()
1809
1810 For an inplace reduction of a graph to a subgraph you can remove nodes:
1811 G.remove_nodes_from([n for n in G if n not in set(nodes)])
1812
1813 Subgraph views are sometimes NOT what you want. In most cases where
1814 you want to do more than simply look at the induced edges, it makes
1815 more sense to just create the subgraph as its own graph with code like:
1816
1817 ::
1818
1819 # Create a subgraph SG based on a (possibly multigraph) G
1820 SG = G.__class__()
1821 SG.add_nodes_from((n, G.nodes[n]) for n in largest_wcc)
1822 if SG.is_multigraph():
1823 SG.add_edges_from(
1824 (n, nbr, key, d)
1825 for n, nbrs in G.adj.items()
1826 if n in largest_wcc
1827 for nbr, keydict in nbrs.items()
1828 if nbr in largest_wcc
1829 for key, d in keydict.items()
1830 )
1831 else:
1832 SG.add_edges_from(
1833 (n, nbr, d)
1834 for n, nbrs in G.adj.items()
1835 if n in largest_wcc
1836 for nbr, d in nbrs.items()
1837 if nbr in largest_wcc
1838 )
1839 SG.graph.update(G.graph)
1840
1841 Subgraphs are not guaranteed to preserve the order of nodes or edges
1842 as they appear in the original graph. For example:
1843
1844 >>> G = nx.Graph()
1845 >>> G.add_nodes_from(reversed(range(10)))
1846 >>> list(G)
1847 [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
1848 >>> list(G.subgraph([1, 3, 2]))
1849 [1, 2, 3]
1850
1851 Examples
1852 --------
1853 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1854 >>> H = G.subgraph([0, 1, 2])
1855 >>> list(H.edges)
1856 [(0, 1), (1, 2)]
1857 """
1858 induced_nodes = nx.filters.show_nodes(self.nbunch_iter(nodes))
1859 # if already a subgraph, don't make a chain
1860 subgraph = nx.subgraph_view
1861 if hasattr(self, "_NODE_OK"):
1862 return subgraph(
1863 self._graph, filter_node=induced_nodes, filter_edge=self._EDGE_OK
1864 )
1865 return subgraph(self, filter_node=induced_nodes)
1866
1867 def edge_subgraph(self, edges):
1868 """Returns the subgraph induced by the specified edges.
1869
1870 The induced subgraph contains each edge in `edges` and each
1871 node incident to any one of those edges.
1872
1873 Parameters
1874 ----------
1875 edges : iterable
1876 An iterable of edges in this graph.
1877
1878 Returns
1879 -------
1880 G : Graph
1881 An edge-induced subgraph of this graph with the same edge
1882 attributes.
1883
1884 Notes
1885 -----
1886 The graph, edge, and node attributes in the returned subgraph
1887 view are references to the corresponding attributes in the original
1888 graph. The view is read-only.
1889
1890 To create a full graph version of the subgraph with its own copy
1891 of the edge or node attributes, use::
1892
1893 G.edge_subgraph(edges).copy()
1894
1895 Examples
1896 --------
1897 >>> G = nx.path_graph(5)
1898 >>> H = G.edge_subgraph([(0, 1), (3, 4)])
1899 >>> list(H.nodes)
1900 [0, 1, 3, 4]
1901 >>> list(H.edges)
1902 [(0, 1), (3, 4)]
1903
1904 """
1905 return nx.edge_subgraph(self, edges)
1906
1907 def size(self, weight=None):
1908 """Returns the number of edges or total of all edge weights.
1909
1910 Parameters
1911 ----------
1912 weight : string or None, optional (default=None)
1913 The edge attribute that holds the numerical value used
1914 as a weight. If None, then each edge has weight 1.
1915
1916 Returns
1917 -------
1918 size : numeric
1919 The number of edges or
1920 (if weight keyword is provided) the total weight sum.
1921
1922 If weight is None, returns an int. Otherwise a float
1923 (or more general numeric if the weights are more general).
1924
1925 See Also
1926 --------
1927 number_of_edges
1928
1929 Examples
1930 --------
1931 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1932 >>> G.size()
1933 3
1934
1935 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1936 >>> G.add_edge("a", "b", weight=2)
1937 >>> G.add_edge("b", "c", weight=4)
1938 >>> G.size()
1939 2
1940 >>> G.size(weight="weight")
1941 6.0
1942 """
1943 s = sum(d for v, d in self.degree(weight=weight))
1944 # If `weight` is None, the sum of the degrees is guaranteed to be
1945 # even, so we can perform integer division and hence return an
1946 # integer. Otherwise, the sum of the weighted degrees is not
1947 # guaranteed to be an integer, so we perform "real" division.
1948 return s // 2 if weight is None else s / 2
1949
1950 def number_of_edges(self, u=None, v=None):
1951 """Returns the number of edges between two nodes.
1952
1953 Parameters
1954 ----------
1955 u, v : nodes, optional (default=all edges)
1956 If u and v are specified, return the number of edges between
1957 u and v. Otherwise return the total number of all edges.
1958
1959 Returns
1960 -------
1961 nedges : int
1962 The number of edges in the graph. If nodes `u` and `v` are
1963 specified return the number of edges between those nodes. If
1964 the graph is directed, this only returns the number of edges
1965 from `u` to `v`.
1966
1967 See Also
1968 --------
1969 size
1970
1971 Examples
1972 --------
1973 For undirected graphs, this method counts the total number of
1974 edges in the graph:
1975
1976 >>> G = nx.path_graph(4)
1977 >>> G.number_of_edges()
1978 3
1979
1980 If you specify two nodes, this counts the total number of edges
1981 joining the two nodes:
1982
1983 >>> G.number_of_edges(0, 1)
1984 1
1985
1986 For directed graphs, this method can count the total number of
1987 directed edges from `u` to `v`:
1988
1989 >>> G = nx.DiGraph()
1990 >>> G.add_edge(0, 1)
1991 >>> G.add_edge(1, 0)
1992 >>> G.number_of_edges(0, 1)
1993 1
1994
1995 """
1996 if u is None:
1997 return int(self.size())
1998 if v in self._adj[u]:
1999 return 1
2000 return 0
2001
2002 def nbunch_iter(self, nbunch=None):
2003 """Returns an iterator over nodes contained in nbunch that are
2004 also in the graph.
2005
2006 The nodes in an iterable nbunch are checked for membership in the graph
2007 and if not are silently ignored.
2008
2009 Parameters
2010 ----------
2011 nbunch : single node, container, or all nodes (default= all nodes)
2012 The view will only report edges incident to these nodes.
2013
2014 Returns
2015 -------
2016 niter : iterator
2017 An iterator over nodes in nbunch that are also in the graph.
2018 If nbunch is None, iterate over all nodes in the graph.
2019
2020 Raises
2021 ------
2022 NetworkXError
2023 If nbunch is not a node or sequence of nodes.
2024 If a node in nbunch is not hashable.
2025
2026 See Also
2027 --------
2028 Graph.__iter__
2029
2030 Notes
2031 -----
2032 When nbunch is an iterator, the returned iterator yields values
2033 directly from nbunch, becoming exhausted when nbunch is exhausted.
2034
2035 To test whether nbunch is a single node, one can use
2036 "if nbunch in self:", even after processing with this routine.
2037
2038 If nbunch is not a node or a (possibly empty) sequence/iterator
2039 or None, a :exc:`NetworkXError` is raised. Also, if any object in
2040 nbunch is not hashable, a :exc:`NetworkXError` is raised.
2041 """
2042 if nbunch is None: # include all nodes via iterator
2043 bunch = iter(self._adj)
2044 elif nbunch in self: # if nbunch is a single node
2045 bunch = iter([nbunch])
2046 else: # if nbunch is a sequence of nodes
2047
2048 def bunch_iter(nlist, adj):
2049 try:
2050 for n in nlist:
2051 if n in adj:
2052 yield n
2053 except TypeError as err:
2054 exc, message = err, err.args[0]
2055 # capture error for non-sequence/iterator nbunch.
2056 if "iter" in message:
2057 exc = NetworkXError(
2058 "nbunch is not a node or a sequence of nodes."
2059 )
2060 # capture single nodes that are not in the graph.
2061 if "object is not iterable" in message:
2062 exc = NetworkXError(f"Node {nbunch} is not in the graph.")
2063 # capture error for unhashable node.
2064 if "hashable" in message:
2065 exc = NetworkXError(
2066 f"Node {n} in sequence nbunch is not a valid node."
2067 )
2068 raise exc
2069
2070 bunch = bunch_iter(nbunch, self._adj)
2071 return bunch