123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028 |
- """Base class for undirected graphs.
- The Graph class allows any hashable object as a node
- and can associate key/value attribute pairs with each undirected edge.
- Self-loops are allowed but multiple edges are not (see MultiGraph).
- For directed graphs see DiGraph and MultiDiGraph.
- """
- from copy import deepcopy
- from functools import cached_property
- import networkx as nx
- from networkx import convert
- from networkx.classes.coreviews import AdjacencyView
- from networkx.classes.reportviews import DegreeView, EdgeView, NodeView
- from networkx.exception import NetworkXError
- __all__ = ["Graph"]
- class _CachedPropertyResetterAdj:
- """Data Descriptor class for _adj that resets ``adj`` cached_property when needed
- This assumes that the ``cached_property`` ``G.adj`` should be reset whenever
- ``G._adj`` is set to a new value.
- This object sits on a class and ensures that any instance of that
- class clears its cached property "adj" whenever the underlying
- instance attribute "_adj" is set to a new object. It only affects
- the set process of the obj._adj attribute. All get/del operations
- act as they normally would.
- For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
- """
- def __set__(self, obj, value):
- od = obj.__dict__
- od["_adj"] = value
- if "adj" in od:
- del od["adj"]
- class _CachedPropertyResetterNode:
- """Data Descriptor class for _node that resets ``nodes`` cached_property when needed
- This assumes that the ``cached_property`` ``G.node`` should be reset whenever
- ``G._node`` is set to a new value.
- This object sits on a class and ensures that any instance of that
- class clears its cached property "nodes" whenever the underlying
- instance attribute "_node" is set to a new object. It only affects
- the set process of the obj._adj attribute. All get/del operations
- act as they normally would.
- For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
- """
- def __set__(self, obj, value):
- od = obj.__dict__
- od["_node"] = value
- if "nodes" in od:
- del od["nodes"]
- class Graph:
- """
- Base class for undirected graphs.
- A Graph stores nodes and edges with optional data, or attributes.
- Graphs hold undirected edges. Self loops are allowed but multiple
- (parallel) edges are not.
- Nodes can be arbitrary (hashable) Python objects with optional
- key/value attributes, except that `None` is not allowed as a node.
- Edges are represented as links between nodes with optional
- key/value attributes.
- Parameters
- ----------
- incoming_graph_data : input graph (optional, default: None)
- Data to initialize graph. If None (default) an empty
- graph is created. The data can be any format that is supported
- by the to_networkx_graph() function, currently including edge list,
- dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
- sparse matrix, or PyGraphviz graph.
- attr : keyword arguments, optional (default= no attributes)
- Attributes to add to graph as key=value pairs.
- See Also
- --------
- DiGraph
- MultiGraph
- MultiDiGraph
- Examples
- --------
- Create an empty graph structure (a "null graph") with no nodes and
- no edges.
- >>> G = nx.Graph()
- G can be grown in several ways.
- **Nodes:**
- Add one node at a time:
- >>> G.add_node(1)
- Add the nodes from any container (a list, dict, set or
- even the lines from a file or the nodes from another graph).
- >>> G.add_nodes_from([2, 3])
- >>> G.add_nodes_from(range(100, 110))
- >>> H = nx.path_graph(10)
- >>> G.add_nodes_from(H)
- In addition to strings and integers any hashable Python object
- (except None) can represent a node, e.g. a customized node object,
- or even another Graph.
- >>> G.add_node(H)
- **Edges:**
- G can also be grown by adding edges.
- Add one edge,
- >>> G.add_edge(1, 2)
- a list of edges,
- >>> G.add_edges_from([(1, 2), (1, 3)])
- or a collection of edges,
- >>> G.add_edges_from(H.edges)
- If some edges connect nodes not yet in the graph, the nodes
- are added automatically. There are no errors when adding
- nodes or edges that already exist.
- **Attributes:**
- Each graph, node, and edge can hold key/value attribute pairs
- in an associated attribute dictionary (the keys must be hashable).
- By default these are empty, but can be added or changed using
- add_edge, add_node or direct manipulation of the attribute
- dictionaries named graph, node and edge respectively.
- >>> G = nx.Graph(day="Friday")
- >>> G.graph
- {'day': 'Friday'}
- Add node attributes using add_node(), add_nodes_from() or G.nodes
- >>> G.add_node(1, time="5pm")
- >>> G.add_nodes_from([3], time="2pm")
- >>> G.nodes[1]
- {'time': '5pm'}
- >>> G.nodes[1]["room"] = 714 # node must exist already to use G.nodes
- >>> del G.nodes[1]["room"] # remove attribute
- >>> list(G.nodes(data=True))
- [(1, {'time': '5pm'}), (3, {'time': '2pm'})]
- Add edge attributes using add_edge(), add_edges_from(), subscript
- notation, or G.edges.
- >>> G.add_edge(1, 2, weight=4.7)
- >>> G.add_edges_from([(3, 4), (4, 5)], color="red")
- >>> G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
- >>> G[1][2]["weight"] = 4.7
- >>> G.edges[1, 2]["weight"] = 4
- Warning: we protect the graph data structure by making `G.edges` a
- read-only dict-like structure. However, you can assign to attributes
- in e.g. `G.edges[1, 2]`. Thus, use 2 sets of brackets to add/change
- data attributes: `G.edges[1, 2]['weight'] = 4`
- (For multigraphs: `MG.edges[u, v, key][name] = value`).
- **Shortcuts:**
- Many common graph features allow python syntax to speed reporting.
- >>> 1 in G # check if node in graph
- True
- >>> [n for n in G if n < 3] # iterate through nodes
- [1, 2]
- >>> len(G) # number of nodes in graph
- 5
- Often the best way to traverse all edges of a graph is via the neighbors.
- The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`
- >>> for n, nbrsdict in G.adjacency():
- ... for nbr, eattr in nbrsdict.items():
- ... if "weight" in eattr:
- ... # Do something useful with the edges
- ... pass
- But the edges() method is often more convenient:
- >>> for u, v, weight in G.edges.data("weight"):
- ... if weight is not None:
- ... # Do something useful with the edges
- ... pass
- **Reporting:**
- Simple graph information is obtained using object-attributes and methods.
- Reporting typically provides views instead of containers to reduce memory
- usage. The views update as the graph is updated similarly to dict-views.
- The objects `nodes`, `edges` and `adj` provide access to data attributes
- via lookup (e.g. `nodes[n]`, `edges[u, v]`, `adj[u][v]`) and iteration
- (e.g. `nodes.items()`, `nodes.data('color')`,
- `nodes.data('color', default='blue')` and similarly for `edges`)
- Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
- For details on these and other miscellaneous methods, see below.
- **Subclasses (Advanced):**
- The Graph class uses a dict-of-dict-of-dict data structure.
- The outer dict (node_dict) holds adjacency information keyed by node.
- The next dict (adjlist_dict) represents the adjacency information and holds
- edge data keyed by neighbor. The inner dict (edge_attr_dict) represents
- the edge data and holds edge attribute values keyed by attribute names.
- Each of these three dicts can be replaced in a subclass by a user defined
- dict-like object. In general, the dict-like features should be
- maintained but extra features can be added. To replace one of the
- dicts create a new graph class by changing the class(!) variable
- holding the factory for that dict-like structure.
- node_dict_factory : function, (default: dict)
- Factory function to be used to create the dict containing node
- attributes, keyed by node id.
- It should require no arguments and return a dict-like object
- node_attr_dict_factory: function, (default: dict)
- Factory function to be used to create the node attribute
- dict which holds attribute values keyed by attribute name.
- It should require no arguments and return a dict-like object
- adjlist_outer_dict_factory : function, (default: dict)
- Factory function to be used to create the outer-most dict
- in the data structure that holds adjacency info keyed by node.
- It should require no arguments and return a dict-like object.
- adjlist_inner_dict_factory : function, (default: dict)
- Factory function to be used to create the adjacency list
- dict which holds edge data keyed by neighbor.
- It should require no arguments and return a dict-like object
- edge_attr_dict_factory : function, (default: dict)
- Factory function to be used to create the edge attribute
- dict which holds attribute values keyed by attribute name.
- It should require no arguments and return a dict-like object.
- graph_attr_dict_factory : function, (default: dict)
- Factory function to be used to create the graph attribute
- dict which holds attribute values keyed by attribute name.
- It should require no arguments and return a dict-like object.
- Typically, if your extension doesn't impact the data structure all
- methods will inherit without issue except: `to_directed/to_undirected`.
- By default these methods create a DiGraph/Graph class and you probably
- want them to create your extension of a DiGraph/Graph. To facilitate
- this we define two class variables that you can set in your subclass.
- to_directed_class : callable, (default: DiGraph or MultiDiGraph)
- Class to create a new graph structure in the `to_directed` method.
- If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
- to_undirected_class : callable, (default: Graph or MultiGraph)
- Class to create a new graph structure in the `to_undirected` method.
- If `None`, a NetworkX class (Graph or MultiGraph) is used.
- **Subclassing Example**
- Create a low memory graph class that effectively disallows edge
- attributes by using a single attribute dict for all edges.
- This reduces the memory used, but you lose edge attributes.
- >>> class ThinGraph(nx.Graph):
- ... all_edge_dict = {"weight": 1}
- ...
- ... def single_edge_dict(self):
- ... return self.all_edge_dict
- ...
- ... edge_attr_dict_factory = single_edge_dict
- >>> G = ThinGraph()
- >>> G.add_edge(2, 1)
- >>> G[2][1]
- {'weight': 1}
- >>> G.add_edge(2, 2)
- >>> G[2][1] is G[2][2]
- True
- """
- _adj = _CachedPropertyResetterAdj()
- _node = _CachedPropertyResetterNode()
- node_dict_factory = dict
- node_attr_dict_factory = dict
- adjlist_outer_dict_factory = dict
- adjlist_inner_dict_factory = dict
- edge_attr_dict_factory = dict
- graph_attr_dict_factory = dict
- def to_directed_class(self):
- """Returns the class to use for empty directed copies.
- If you subclass the base classes, use this to designate
- what directed class to use for `to_directed()` copies.
- """
- return nx.DiGraph
- def to_undirected_class(self):
- """Returns the class to use for empty undirected copies.
- If you subclass the base classes, use this to designate
- what directed class to use for `to_directed()` copies.
- """
- return Graph
- def __init__(self, incoming_graph_data=None, **attr):
- """Initialize a graph with edges, name, or graph attributes.
- Parameters
- ----------
- incoming_graph_data : input graph (optional, default: None)
- Data to initialize graph. If None (default) an empty
- graph is created. The data can be an edge list, or any
- NetworkX graph object. If the corresponding optional Python
- packages are installed the data can also be a 2D NumPy array, a
- SciPy sparse array, or a PyGraphviz graph.
- attr : keyword arguments, optional (default= no attributes)
- Attributes to add to graph as key=value pairs.
- See Also
- --------
- convert
- Examples
- --------
- >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G = nx.Graph(name="my graph")
- >>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
- >>> G = nx.Graph(e)
- Arbitrary graph attribute pairs (key=value) may be assigned
- >>> G = nx.Graph(e, day="Friday")
- >>> G.graph
- {'day': 'Friday'}
- """
- self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes
- self._node = self.node_dict_factory() # empty node attribute dict
- self._adj = self.adjlist_outer_dict_factory() # empty adjacency dict
- # attempt to load graph with data
- if incoming_graph_data is not None:
- convert.to_networkx_graph(incoming_graph_data, create_using=self)
- # load graph attributes (must be after convert)
- self.graph.update(attr)
- @cached_property
- def adj(self):
- """Graph adjacency object holding the neighbors of each node.
- This object is a read-only dict-like structure with node keys
- and neighbor-dict values. The neighbor-dict is keyed by neighbor
- to the edge-data-dict. So `G.adj[3][2]['color'] = 'blue'` sets
- the color of the edge `(3, 2)` to `"blue"`.
- Iterating over G.adj behaves like a dict. Useful idioms include
- `for nbr, datadict in G.adj[n].items():`.
- The neighbor information is also provided by subscripting the graph.
- So `for nbr, foovalue in G[node].data('foo', default=1):` works.
- For directed graphs, `G.adj` holds outgoing (successor) info.
- """
- return AdjacencyView(self._adj)
- @property
- def name(self):
- """String identifier of the graph.
- This graph attribute appears in the attribute dict G.graph
- keyed by the string `"name"`. as well as an attribute (technically
- a property) `G.name`. This is entirely user controlled.
- """
- return self.graph.get("name", "")
- @name.setter
- def name(self, s):
- self.graph["name"] = s
- def __str__(self):
- """Returns a short summary of the graph.
- Returns
- -------
- info : string
- Graph information including the graph name (if any), graph type, and the
- number of nodes and edges.
- Examples
- --------
- >>> G = nx.Graph(name="foo")
- >>> str(G)
- "Graph named 'foo' with 0 nodes and 0 edges"
- >>> G = nx.path_graph(3)
- >>> str(G)
- 'Graph with 3 nodes and 2 edges'
- """
- return "".join(
- [
- type(self).__name__,
- f" named {self.name!r}" if self.name else "",
- f" with {self.number_of_nodes()} nodes and {self.number_of_edges()} edges",
- ]
- )
- def __iter__(self):
- """Iterate over the nodes. Use: 'for n in G'.
- Returns
- -------
- niter : iterator
- An iterator over all nodes in the graph.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> [n for n in G]
- [0, 1, 2, 3]
- >>> list(G)
- [0, 1, 2, 3]
- """
- return iter(self._node)
- def __contains__(self, n):
- """Returns True if n is a node, False otherwise. Use: 'n in G'.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> 1 in G
- True
- """
- try:
- return n in self._node
- except TypeError:
- return False
- def __len__(self):
- """Returns the number of nodes in the graph. Use: 'len(G)'.
- Returns
- -------
- nnodes : int
- The number of nodes in the graph.
- See Also
- --------
- number_of_nodes: identical method
- order: identical method
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> len(G)
- 4
- """
- return len(self._node)
- def __getitem__(self, n):
- """Returns a dict of neighbors of node n. Use: 'G[n]'.
- Parameters
- ----------
- n : node
- A node in the graph.
- Returns
- -------
- adj_dict : dictionary
- The adjacency dictionary for nodes connected to n.
- Notes
- -----
- G[n] is the same as G.adj[n] and similar to G.neighbors(n)
- (which is an iterator over G.adj[n])
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G[0]
- AtlasView({1: {}})
- """
- return self.adj[n]
- def add_node(self, node_for_adding, **attr):
- """Add a single node `node_for_adding` and update node attributes.
- Parameters
- ----------
- node_for_adding : node
- A node can be any hashable Python object except None.
- attr : keyword arguments, optional
- Set or change node attributes using key=value.
- See Also
- --------
- add_nodes_from
- Examples
- --------
- >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.add_node(1)
- >>> G.add_node("Hello")
- >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
- >>> G.add_node(K3)
- >>> G.number_of_nodes()
- 3
- Use keywords set/change node attributes:
- >>> G.add_node(1, size=10)
- >>> G.add_node(3, weight=0.4, UTM=("13S", 382871, 3972649))
- Notes
- -----
- A hashable object is one that can be used as a key in a Python
- dictionary. This includes strings, numbers, tuples of strings
- and numbers, etc.
- On many platforms hashable items also include mutables such as
- NetworkX Graphs, though one should be careful that the hash
- doesn't change on mutables.
- """
- if node_for_adding not in self._node:
- if node_for_adding is None:
- raise ValueError("None cannot be a node")
- self._adj[node_for_adding] = self.adjlist_inner_dict_factory()
- attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory()
- attr_dict.update(attr)
- else: # update attr even if node already exists
- self._node[node_for_adding].update(attr)
- def add_nodes_from(self, nodes_for_adding, **attr):
- """Add multiple nodes.
- Parameters
- ----------
- nodes_for_adding : iterable container
- A container of nodes (list, dict, set, etc.).
- OR
- A container of (node, attribute dict) tuples.
- Node attributes are updated using the attribute dict.
- attr : keyword arguments, optional (default= no attributes)
- Update attributes for all nodes in nodes.
- Node attributes specified in nodes as a tuple take
- precedence over attributes specified via keyword arguments.
- See Also
- --------
- add_node
- Notes
- -----
- When adding nodes from an iterator over the graph you are changing,
- a `RuntimeError` can be raised with message:
- `RuntimeError: dictionary changed size during iteration`. This
- happens when the graph's underlying dictionary is modified during
- iteration. To avoid this error, evaluate the iterator into a separate
- object, e.g. by using `list(iterator_of_nodes)`, and pass this
- object to `G.add_nodes_from`.
- Examples
- --------
- >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.add_nodes_from("Hello")
- >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
- >>> G.add_nodes_from(K3)
- >>> sorted(G.nodes(), key=str)
- [0, 1, 2, 'H', 'e', 'l', 'o']
- Use keywords to update specific node attributes for every node.
- >>> G.add_nodes_from([1, 2], size=10)
- >>> G.add_nodes_from([3, 4], weight=0.4)
- Use (node, attrdict) tuples to update attributes for specific nodes.
- >>> G.add_nodes_from([(1, dict(size=11)), (2, {"color": "blue"})])
- >>> G.nodes[1]["size"]
- 11
- >>> H = nx.Graph()
- >>> H.add_nodes_from(G.nodes(data=True))
- >>> H.nodes[1]["size"]
- 11
- Evaluate an iterator over a graph if using it to modify the same graph
- >>> G = nx.Graph([(0, 1), (1, 2), (3, 4)])
- >>> # wrong way - will raise RuntimeError
- >>> # G.add_nodes_from(n + 1 for n in G.nodes)
- >>> # correct way
- >>> G.add_nodes_from(list(n + 1 for n in G.nodes))
- """
- for n in nodes_for_adding:
- try:
- newnode = n not in self._node
- newdict = attr
- except TypeError:
- n, ndict = n
- newnode = n not in self._node
- newdict = attr.copy()
- newdict.update(ndict)
- if newnode:
- if n is None:
- raise ValueError("None cannot be a node")
- self._adj[n] = self.adjlist_inner_dict_factory()
- self._node[n] = self.node_attr_dict_factory()
- self._node[n].update(newdict)
- def remove_node(self, n):
- """Remove node n.
- Removes the node n and all adjacent edges.
- Attempting to remove a non-existent node will raise an exception.
- Parameters
- ----------
- n : node
- A node in the graph
- Raises
- ------
- NetworkXError
- If n is not in the graph.
- See Also
- --------
- remove_nodes_from
- Examples
- --------
- >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> list(G.edges)
- [(0, 1), (1, 2)]
- >>> G.remove_node(1)
- >>> list(G.edges)
- []
- """
- adj = self._adj
- try:
- nbrs = list(adj[n]) # list handles self-loops (allows mutation)
- del self._node[n]
- except KeyError as err: # NetworkXError if n not in self
- raise NetworkXError(f"The node {n} is not in the graph.") from err
- for u in nbrs:
- del adj[u][n] # remove all edges n-u in graph
- del adj[n] # now remove node
- def remove_nodes_from(self, nodes):
- """Remove multiple nodes.
- Parameters
- ----------
- nodes : iterable container
- A container of nodes (list, dict, set, etc.). If a node
- in the container is not in the graph it is silently
- ignored.
- See Also
- --------
- remove_node
- Notes
- -----
- When removing nodes from an iterator over the graph you are changing,
- a `RuntimeError` will be raised with message:
- `RuntimeError: dictionary changed size during iteration`. This
- happens when the graph's underlying dictionary is modified during
- iteration. To avoid this error, evaluate the iterator into a separate
- object, e.g. by using `list(iterator_of_nodes)`, and pass this
- object to `G.remove_nodes_from`.
- Examples
- --------
- >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> e = list(G.nodes)
- >>> e
- [0, 1, 2]
- >>> G.remove_nodes_from(e)
- >>> list(G.nodes)
- []
- Evaluate an iterator over a graph if using it to modify the same graph
- >>> G = nx.Graph([(0, 1), (1, 2), (3, 4)])
- >>> # this command will fail, as the graph's dict is modified during iteration
- >>> # G.remove_nodes_from(n for n in G.nodes if n < 2)
- >>> # this command will work, since the dictionary underlying graph is not modified
- >>> G.remove_nodes_from(list(n for n in G.nodes if n < 2))
- """
- adj = self._adj
- for n in nodes:
- try:
- del self._node[n]
- for u in list(adj[n]): # list handles self-loops
- del adj[u][n] # (allows mutation of dict in loop)
- del adj[n]
- except KeyError:
- pass
- @cached_property
- def nodes(self):
- """A NodeView of the Graph as G.nodes or G.nodes().
- Can be used as `G.nodes` for data lookup and for set-like operations.
- Can also be used as `G.nodes(data='color', default=None)` to return a
- NodeDataView which reports specific node data but no set operations.
- It presents a dict-like interface as well with `G.nodes.items()`
- iterating over `(node, nodedata)` 2-tuples and `G.nodes[3]['foo']`
- providing the value of the `foo` attribute for node `3`. In addition,
- a view `G.nodes.data('foo')` provides a dict-like interface to the
- `foo` attribute of each node. `G.nodes.data('foo', default=1)`
- provides a default for nodes that do not have attribute `foo`.
- Parameters
- ----------
- data : string or bool, optional (default=False)
- The node attribute returned in 2-tuple (n, ddict[data]).
- If True, return entire node attribute dict as (n, ddict).
- If False, return just the nodes n.
- default : value, optional (default=None)
- Value used for nodes that don't have the requested attribute.
- Only relevant if data is not True or False.
- Returns
- -------
- NodeView
- Allows set-like operations over the nodes as well as node
- attribute dict lookup and calling to get a NodeDataView.
- A NodeDataView iterates over `(n, data)` and has no set operations.
- A NodeView iterates over `n` and includes set operations.
- When called, if data is False, an iterator over nodes.
- Otherwise an iterator of 2-tuples (node, attribute value)
- where the attribute is specified in `data`.
- If data is True then the attribute becomes the
- entire data dictionary.
- Notes
- -----
- If your node data is not needed, it is simpler and equivalent
- to use the expression ``for n in G``, or ``list(G)``.
- Examples
- --------
- There are two simple ways of getting a list of all nodes in the graph:
- >>> G = nx.path_graph(3)
- >>> list(G.nodes)
- [0, 1, 2]
- >>> list(G)
- [0, 1, 2]
- To get the node data along with the nodes:
- >>> G.add_node(1, time="5pm")
- >>> G.nodes[0]["foo"] = "bar"
- >>> list(G.nodes(data=True))
- [(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
- >>> list(G.nodes.data())
- [(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
- >>> list(G.nodes(data="foo"))
- [(0, 'bar'), (1, None), (2, None)]
- >>> list(G.nodes.data("foo"))
- [(0, 'bar'), (1, None), (2, None)]
- >>> list(G.nodes(data="time"))
- [(0, None), (1, '5pm'), (2, None)]
- >>> list(G.nodes.data("time"))
- [(0, None), (1, '5pm'), (2, None)]
- >>> list(G.nodes(data="time", default="Not Available"))
- [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]
- >>> list(G.nodes.data("time", default="Not Available"))
- [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]
- If some of your nodes have an attribute and the rest are assumed
- to have a default attribute value you can create a dictionary
- from node/attribute pairs using the `default` keyword argument
- to guarantee the value is never None::
- >>> G = nx.Graph()
- >>> G.add_node(0)
- >>> G.add_node(1, weight=2)
- >>> G.add_node(2, weight=3)
- >>> dict(G.nodes(data="weight", default=1))
- {0: 1, 1: 2, 2: 3}
- """
- return NodeView(self)
- def number_of_nodes(self):
- """Returns the number of nodes in the graph.
- Returns
- -------
- nnodes : int
- The number of nodes in the graph.
- See Also
- --------
- order: identical method
- __len__: identical method
- Examples
- --------
- >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.number_of_nodes()
- 3
- """
- return len(self._node)
- def order(self):
- """Returns the number of nodes in the graph.
- Returns
- -------
- nnodes : int
- The number of nodes in the graph.
- See Also
- --------
- number_of_nodes: identical method
- __len__: identical method
- Examples
- --------
- >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.order()
- 3
- """
- return len(self._node)
- def has_node(self, n):
- """Returns True if the graph contains the node n.
- Identical to `n in G`
- Parameters
- ----------
- n : node
- Examples
- --------
- >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.has_node(0)
- True
- It is more readable and simpler to use
- >>> 0 in G
- True
- """
- try:
- return n in self._node
- except TypeError:
- return False
- def add_edge(self, u_of_edge, v_of_edge, **attr):
- """Add an edge between u and v.
- The nodes u and v will be automatically added if they are
- not already in the graph.
- Edge attributes can be specified with keywords or by directly
- accessing the edge's attribute dictionary. See examples below.
- Parameters
- ----------
- u_of_edge, v_of_edge : nodes
- Nodes can be, for example, strings or numbers.
- Nodes must be hashable (and not None) Python objects.
- attr : keyword arguments, optional
- Edge data (or labels or objects) can be assigned using
- keyword arguments.
- See Also
- --------
- add_edges_from : add a collection of edges
- Notes
- -----
- Adding an edge that already exists updates the edge data.
- Many NetworkX algorithms designed for weighted graphs use
- an edge attribute (by default `weight`) to hold a numerical value.
- Examples
- --------
- The following all add the edge e=(1, 2) to graph G:
- >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> e = (1, 2)
- >>> G.add_edge(1, 2) # explicit two-node form
- >>> G.add_edge(*e) # single edge as tuple of two nodes
- >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
- Associate data to edges using keywords:
- >>> G.add_edge(1, 2, weight=3)
- >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
- For non-string attribute keys, use subscript notation.
- >>> G.add_edge(1, 2)
- >>> G[1][2].update({0: 5})
- >>> G.edges[1, 2].update({0: 5})
- """
- u, v = u_of_edge, v_of_edge
- # add nodes
- if u not in self._node:
- if u is None:
- raise ValueError("None cannot be a node")
- self._adj[u] = self.adjlist_inner_dict_factory()
- self._node[u] = self.node_attr_dict_factory()
- if v not in self._node:
- if v is None:
- raise ValueError("None cannot be a node")
- self._adj[v] = self.adjlist_inner_dict_factory()
- self._node[v] = self.node_attr_dict_factory()
- # add the edge
- datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
- datadict.update(attr)
- self._adj[u][v] = datadict
- self._adj[v][u] = datadict
- def add_edges_from(self, ebunch_to_add, **attr):
- """Add all the edges in ebunch_to_add.
- Parameters
- ----------
- ebunch_to_add : container of edges
- Each edge given in the container will be added to the
- graph. The edges must be given as 2-tuples (u, v) or
- 3-tuples (u, v, d) where d is a dictionary containing edge data.
- attr : keyword arguments, optional
- Edge data (or labels or objects) can be assigned using
- keyword arguments.
- See Also
- --------
- add_edge : add a single edge
- add_weighted_edges_from : convenient way to add weighted edges
- Notes
- -----
- Adding the same edge twice has no effect but any edge data
- will be updated when each duplicate edge is added.
- Edge attributes specified in an ebunch take precedence over
- attributes specified via keyword arguments.
- When adding edges from an iterator over the graph you are changing,
- a `RuntimeError` can be raised with message:
- `RuntimeError: dictionary changed size during iteration`. This
- happens when the graph's underlying dictionary is modified during
- iteration. To avoid this error, evaluate the iterator into a separate
- object, e.g. by using `list(iterator_of_edges)`, and pass this
- object to `G.add_edges_from`.
- Examples
- --------
- >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
- >>> e = zip(range(0, 3), range(1, 4))
- >>> G.add_edges_from(e) # Add the path graph 0-1-2-3
- Associate data to edges
- >>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
- >>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
- Evaluate an iterator over a graph if using it to modify the same graph
- >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
- >>> # Grow graph by one new node, adding edges to all existing nodes.
- >>> # wrong way - will raise RuntimeError
- >>> # G.add_edges_from(((5, n) for n in G.nodes))
- >>> # correct way - note that there will be no self-edge for node 5
- >>> G.add_edges_from(list((5, n) for n in G.nodes))
- """
- for e in ebunch_to_add:
- ne = len(e)
- if ne == 3:
- u, v, dd = e
- elif ne == 2:
- u, v = e
- dd = {} # doesn't need edge_attr_dict_factory
- else:
- raise NetworkXError(f"Edge tuple {e} must be a 2-tuple or 3-tuple.")
- if u not in self._node:
- if u is None:
- raise ValueError("None cannot be a node")
- self._adj[u] = self.adjlist_inner_dict_factory()
- self._node[u] = self.node_attr_dict_factory()
- if v not in self._node:
- if v is None:
- raise ValueError("None cannot be a node")
- self._adj[v] = self.adjlist_inner_dict_factory()
- self._node[v] = self.node_attr_dict_factory()
- datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
- datadict.update(attr)
- datadict.update(dd)
- self._adj[u][v] = datadict
- self._adj[v][u] = datadict
- def add_weighted_edges_from(self, ebunch_to_add, weight="weight", **attr):
- """Add weighted edges in `ebunch_to_add` with specified weight attr
- Parameters
- ----------
- ebunch_to_add : container of edges
- Each edge given in the list or container will be added
- to the graph. The edges must be given as 3-tuples (u, v, w)
- where w is a number.
- weight : string, optional (default= 'weight')
- The attribute name for the edge weights to be added.
- attr : keyword arguments, optional (default= no attributes)
- Edge attributes to add/update for all edges.
- See Also
- --------
- add_edge : add a single edge
- add_edges_from : add multiple edges
- Notes
- -----
- Adding the same edge twice for Graph/DiGraph simply updates
- the edge data. For MultiGraph/MultiDiGraph, duplicate edges
- are stored.
- When adding edges from an iterator over the graph you are changing,
- a `RuntimeError` can be raised with message:
- `RuntimeError: dictionary changed size during iteration`. This
- happens when the graph's underlying dictionary is modified during
- iteration. To avoid this error, evaluate the iterator into a separate
- object, e.g. by using `list(iterator_of_edges)`, and pass this
- object to `G.add_weighted_edges_from`.
- Examples
- --------
- >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5)])
- Evaluate an iterator over edges before passing it
- >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
- >>> weight = 0.1
- >>> # Grow graph by one new node, adding edges to all existing nodes.
- >>> # wrong way - will raise RuntimeError
- >>> # G.add_weighted_edges_from(((5, n, weight) for n in G.nodes))
- >>> # correct way - note that there will be no self-edge for node 5
- >>> G.add_weighted_edges_from(list((5, n, weight) for n in G.nodes))
- """
- self.add_edges_from(((u, v, {weight: d}) for u, v, d in ebunch_to_add), **attr)
- def remove_edge(self, u, v):
- """Remove the edge between u and v.
- Parameters
- ----------
- u, v : nodes
- Remove the edge between nodes u and v.
- Raises
- ------
- NetworkXError
- If there is not an edge between u and v.
- See Also
- --------
- remove_edges_from : remove a collection of edges
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, etc
- >>> G.remove_edge(0, 1)
- >>> e = (1, 2)
- >>> G.remove_edge(*e) # unpacks e from an edge tuple
- >>> e = (2, 3, {"weight": 7}) # an edge with attribute data
- >>> G.remove_edge(*e[:2]) # select first part of edge tuple
- """
- try:
- del self._adj[u][v]
- if u != v: # self-loop needs only one entry removed
- del self._adj[v][u]
- except KeyError as err:
- raise NetworkXError(f"The edge {u}-{v} is not in the graph") from err
- def remove_edges_from(self, ebunch):
- """Remove all edges specified in ebunch.
- Parameters
- ----------
- ebunch: list or container of edge tuples
- Each edge given in the list or container will be removed
- from the graph. The edges can be:
- - 2-tuples (u, v) edge between u and v.
- - 3-tuples (u, v, k) where k is ignored.
- See Also
- --------
- remove_edge : remove a single edge
- Notes
- -----
- Will fail silently if an edge in ebunch is not in the graph.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> ebunch = [(1, 2), (2, 3)]
- >>> G.remove_edges_from(ebunch)
- """
- adj = self._adj
- for e in ebunch:
- u, v = e[:2] # ignore edge data if present
- if u in adj and v in adj[u]:
- del adj[u][v]
- if u != v: # self loop needs only one entry removed
- del adj[v][u]
- def update(self, edges=None, nodes=None):
- """Update the graph using nodes/edges/graphs as input.
- Like dict.update, this method takes a graph as input, adding the
- graph's nodes and edges to this graph. It can also take two inputs:
- edges and nodes. Finally it can take either edges or nodes.
- To specify only nodes the keyword `nodes` must be used.
- The collections of edges and nodes are treated similarly to
- the add_edges_from/add_nodes_from methods. When iterated, they
- should yield 2-tuples (u, v) or 3-tuples (u, v, datadict).
- Parameters
- ----------
- edges : Graph object, collection of edges, or None
- The first parameter can be a graph or some edges. If it has
- attributes `nodes` and `edges`, then it is taken to be a
- Graph-like object and those attributes are used as collections
- of nodes and edges to be added to the graph.
- If the first parameter does not have those attributes, it is
- treated as a collection of edges and added to the graph.
- If the first argument is None, no edges are added.
- nodes : collection of nodes, or None
- The second parameter is treated as a collection of nodes
- to be added to the graph unless it is None.
- If `edges is None` and `nodes is None` an exception is raised.
- If the first parameter is a Graph, then `nodes` is ignored.
- Examples
- --------
- >>> G = nx.path_graph(5)
- >>> G.update(nx.complete_graph(range(4, 10)))
- >>> from itertools import combinations
- >>> edges = (
- ... (u, v, {"power": u * v})
- ... for u, v in combinations(range(10, 20), 2)
- ... if u * v < 225
- ... )
- >>> nodes = [1000] # for singleton, use a container
- >>> G.update(edges, nodes)
- Notes
- -----
- It you want to update the graph using an adjacency structure
- it is straightforward to obtain the edges/nodes from adjacency.
- The following examples provide common cases, your adjacency may
- be slightly different and require tweaks of these examples::
- >>> # dict-of-set/list/tuple
- >>> adj = {1: {2, 3}, 2: {1, 3}, 3: {1, 2}}
- >>> e = [(u, v) for u, nbrs in adj.items() for v in nbrs]
- >>> G.update(edges=e, nodes=adj)
- >>> DG = nx.DiGraph()
- >>> # dict-of-dict-of-attribute
- >>> adj = {1: {2: 1.3, 3: 0.7}, 2: {1: 1.4}, 3: {1: 0.7}}
- >>> e = [
- ... (u, v, {"weight": d})
- ... for u, nbrs in adj.items()
- ... for v, d in nbrs.items()
- ... ]
- >>> DG.update(edges=e, nodes=adj)
- >>> # dict-of-dict-of-dict
- >>> adj = {1: {2: {"weight": 1.3}, 3: {"color": 0.7, "weight": 1.2}}}
- >>> e = [
- ... (u, v, {"weight": d})
- ... for u, nbrs in adj.items()
- ... for v, d in nbrs.items()
- ... ]
- >>> DG.update(edges=e, nodes=adj)
- >>> # predecessor adjacency (dict-of-set)
- >>> pred = {1: {2, 3}, 2: {3}, 3: {3}}
- >>> e = [(v, u) for u, nbrs in pred.items() for v in nbrs]
- >>> # MultiGraph dict-of-dict-of-dict-of-attribute
- >>> MDG = nx.MultiDiGraph()
- >>> adj = {
- ... 1: {2: {0: {"weight": 1.3}, 1: {"weight": 1.2}}},
- ... 3: {2: {0: {"weight": 0.7}}},
- ... }
- >>> e = [
- ... (u, v, ekey, d)
- ... for u, nbrs in adj.items()
- ... for v, keydict in nbrs.items()
- ... for ekey, d in keydict.items()
- ... ]
- >>> MDG.update(edges=e)
- See Also
- --------
- add_edges_from: add multiple edges to a graph
- add_nodes_from: add multiple nodes to a graph
- """
- if edges is not None:
- if nodes is not None:
- self.add_nodes_from(nodes)
- self.add_edges_from(edges)
- else:
- # check if edges is a Graph object
- try:
- graph_nodes = edges.nodes
- graph_edges = edges.edges
- except AttributeError:
- # edge not Graph-like
- self.add_edges_from(edges)
- else: # edges is Graph-like
- self.add_nodes_from(graph_nodes.data())
- self.add_edges_from(graph_edges.data())
- self.graph.update(edges.graph)
- elif nodes is not None:
- self.add_nodes_from(nodes)
- else:
- raise NetworkXError("update needs nodes or edges input")
- def has_edge(self, u, v):
- """Returns True if the edge (u, v) is in the graph.
- This is the same as `v in G[u]` without KeyError exceptions.
- Parameters
- ----------
- u, v : nodes
- Nodes can be, for example, strings or numbers.
- Nodes must be hashable (and not None) Python objects.
- Returns
- -------
- edge_ind : bool
- True if edge is in the graph, False otherwise.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.has_edge(0, 1) # using two nodes
- True
- >>> e = (0, 1)
- >>> G.has_edge(*e) # e is a 2-tuple (u, v)
- True
- >>> e = (0, 1, {"weight": 7})
- >>> G.has_edge(*e[:2]) # e is a 3-tuple (u, v, data_dictionary)
- True
- The following syntax are equivalent:
- >>> G.has_edge(0, 1)
- True
- >>> 1 in G[0] # though this gives KeyError if 0 not in G
- True
- """
- try:
- return v in self._adj[u]
- except KeyError:
- return False
- def neighbors(self, n):
- """Returns an iterator over all neighbors of node n.
- This is identical to `iter(G[n])`
- Parameters
- ----------
- n : node
- A node in the graph
- Returns
- -------
- neighbors : iterator
- An iterator over all neighbors of node n
- Raises
- ------
- NetworkXError
- If the node n is not in the graph.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> [n for n in G.neighbors(0)]
- [1]
- Notes
- -----
- Alternate ways to access the neighbors are ``G.adj[n]`` or ``G[n]``:
- >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.add_edge("a", "b", weight=7)
- >>> G["a"]
- AtlasView({'b': {'weight': 7}})
- >>> G = nx.path_graph(4)
- >>> [n for n in G[0]]
- [1]
- """
- try:
- return iter(self._adj[n])
- except KeyError as err:
- raise NetworkXError(f"The node {n} is not in the graph.") from err
- @cached_property
- def edges(self):
- """An EdgeView of the Graph as G.edges or G.edges().
- edges(self, nbunch=None, data=False, default=None)
- The EdgeView provides set-like operations on the edge-tuples
- as well as edge attribute lookup. When called, it also provides
- an EdgeDataView object which allows control of access to edge
- attributes (but does not provide set-like operations).
- Hence, `G.edges[u, v]['color']` provides the value of the color
- attribute for edge `(u, v)` while
- `for (u, v, c) in G.edges.data('color', default='red'):`
- iterates through all the edges yielding the color attribute
- with default `'red'` if no color attribute exists.
- Parameters
- ----------
- nbunch : single node, container, or all nodes (default= all nodes)
- The view will only report edges from these nodes.
- data : string or bool, optional (default=False)
- The edge attribute returned in 3-tuple (u, v, ddict[data]).
- If True, return edge attribute dict in 3-tuple (u, v, ddict).
- If False, return 2-tuple (u, v).
- default : value, optional (default=None)
- Value used for edges that don't have the requested attribute.
- Only relevant if data is not True or False.
- Returns
- -------
- edges : EdgeView
- A view of edge attributes, usually it iterates over (u, v)
- or (u, v, d) tuples of edges, but can also be used for
- attribute lookup as `edges[u, v]['foo']`.
- Notes
- -----
- Nodes in nbunch that are not in the graph will be (quietly) ignored.
- For directed graphs this returns the out-edges.
- Examples
- --------
- >>> G = nx.path_graph(3) # or MultiGraph, etc
- >>> G.add_edge(2, 3, weight=5)
- >>> [e for e in G.edges]
- [(0, 1), (1, 2), (2, 3)]
- >>> G.edges.data() # default data is {} (empty dict)
- EdgeDataView([(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})])
- >>> G.edges.data("weight", default=1)
- EdgeDataView([(0, 1, 1), (1, 2, 1), (2, 3, 5)])
- >>> G.edges([0, 3]) # only edges from these nodes
- EdgeDataView([(0, 1), (3, 2)])
- >>> G.edges(0) # only edges from node 0
- EdgeDataView([(0, 1)])
- """
- return EdgeView(self)
- def get_edge_data(self, u, v, default=None):
- """Returns the attribute dictionary associated with edge (u, v).
- This is identical to `G[u][v]` except the default is returned
- instead of an exception if the edge doesn't exist.
- Parameters
- ----------
- u, v : nodes
- default: any Python object (default=None)
- Value to return if the edge (u, v) is not found.
- Returns
- -------
- edge_dict : dictionary
- The edge attribute dictionary.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G[0][1]
- {}
- Warning: Assigning to `G[u][v]` is not permitted.
- But it is safe to assign attributes `G[u][v]['foo']`
- >>> G[0][1]["weight"] = 7
- >>> G[0][1]["weight"]
- 7
- >>> G[1][0]["weight"]
- 7
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.get_edge_data(0, 1) # default edge data is {}
- {}
- >>> e = (0, 1)
- >>> G.get_edge_data(*e) # tuple form
- {}
- >>> G.get_edge_data("a", "b", default=0) # edge not in graph, return 0
- 0
- """
- try:
- return self._adj[u][v]
- except KeyError:
- return default
- def adjacency(self):
- """Returns an iterator over (node, adjacency dict) tuples for all nodes.
- For directed graphs, only outgoing neighbors/adjacencies are included.
- Returns
- -------
- adj_iter : iterator
- An iterator over (node, adjacency dictionary) for all nodes in
- the graph.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> [(n, nbrdict) for n, nbrdict in G.adjacency()]
- [(0, {1: {}}), (1, {0: {}, 2: {}}), (2, {1: {}, 3: {}}), (3, {2: {}})]
- """
- return iter(self._adj.items())
- @cached_property
- def degree(self):
- """A DegreeView for the Graph as G.degree or G.degree().
- The node degree is the number of edges adjacent to the node.
- The weighted node degree is the sum of the edge weights for
- edges incident to that node.
- This object provides an iterator for (node, degree) as well as
- lookup for the degree for a single node.
- Parameters
- ----------
- nbunch : single node, container, or all nodes (default= all nodes)
- The view will only report edges incident to these nodes.
- weight : string or None, optional (default=None)
- The name of an edge attribute that holds the numerical value used
- as a weight. If None, then each edge has weight 1.
- The degree is the sum of the edge weights adjacent to the node.
- Returns
- -------
- DegreeView or int
- If multiple nodes are requested (the default), returns a `DegreeView`
- mapping nodes to their degree.
- If a single node is requested, returns the degree of the node as an integer.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.degree[0] # node 0 has degree 1
- 1
- >>> list(G.degree([0, 1, 2]))
- [(0, 1), (1, 2), (2, 2)]
- """
- return DegreeView(self)
- def clear(self):
- """Remove all nodes and edges from the graph.
- This also removes the name, and all graph, node, and edge attributes.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.clear()
- >>> list(G.nodes)
- []
- >>> list(G.edges)
- []
- """
- self._adj.clear()
- self._node.clear()
- self.graph.clear()
- def clear_edges(self):
- """Remove all edges from the graph without altering nodes.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.clear_edges()
- >>> list(G.nodes)
- [0, 1, 2, 3]
- >>> list(G.edges)
- []
- """
- for neighbours_dict in self._adj.values():
- neighbours_dict.clear()
- def is_multigraph(self):
- """Returns True if graph is a multigraph, False otherwise."""
- return False
- def is_directed(self):
- """Returns True if graph is directed, False otherwise."""
- return False
- def copy(self, as_view=False):
- """Returns a copy of the graph.
- The copy method by default returns an independent shallow copy
- of the graph and attributes. That is, if an attribute is a
- container, that container is shared by the original an the copy.
- Use Python's `copy.deepcopy` for new containers.
- If `as_view` is True then a view is returned instead of a copy.
- Notes
- -----
- All copies reproduce the graph structure, but data attributes
- may be handled in different ways. There are four types of copies
- of a graph that people might want.
- Deepcopy -- A "deepcopy" copies the graph structure as well as
- all data attributes and any objects they might contain.
- The entire graph object is new so that changes in the copy
- do not affect the original object. (see Python's copy.deepcopy)
- Data Reference (Shallow) -- For a shallow copy the graph structure
- is copied but the edge, node and graph attribute dicts are
- references to those in the original graph. This saves
- time and memory but could cause confusion if you change an attribute
- in one graph and it changes the attribute in the other.
- NetworkX does not provide this level of shallow copy.
- Independent Shallow -- This copy creates new independent attribute
- dicts and then does a shallow copy of the attributes. That is, any
- attributes that are containers are shared between the new graph
- and the original. This is exactly what `dict.copy()` provides.
- You can obtain this style copy using:
- >>> G = nx.path_graph(5)
- >>> H = G.copy()
- >>> H = G.copy(as_view=False)
- >>> H = nx.Graph(G)
- >>> H = G.__class__(G)
- Fresh Data -- For fresh data, the graph structure is copied while
- new empty data attribute dicts are created. The resulting graph
- is independent of the original and it has no edge, node or graph
- attributes. Fresh copies are not enabled. Instead use:
- >>> H = G.__class__()
- >>> H.add_nodes_from(G)
- >>> H.add_edges_from(G.edges)
- View -- Inspired by dict-views, graph-views act like read-only
- versions of the original graph, providing a copy of the original
- structure without requiring any memory for copying the information.
- See the Python copy module for more information on shallow
- and deep copies, https://docs.python.org/3/library/copy.html.
- Parameters
- ----------
- as_view : bool, optional (default=False)
- If True, the returned graph-view provides a read-only view
- of the original graph without actually copying any data.
- Returns
- -------
- G : Graph
- A copy of the graph.
- See Also
- --------
- to_directed: return a directed copy of the graph.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> H = G.copy()
- """
- if as_view is True:
- return nx.graphviews.generic_graph_view(self)
- G = self.__class__()
- G.graph.update(self.graph)
- G.add_nodes_from((n, d.copy()) for n, d in self._node.items())
- G.add_edges_from(
- (u, v, datadict.copy())
- for u, nbrs in self._adj.items()
- for v, datadict in nbrs.items()
- )
- return G
- def to_directed(self, as_view=False):
- """Returns a directed representation of the graph.
- Returns
- -------
- G : DiGraph
- A directed graph with the same name, same nodes, and with
- each edge (u, v, data) replaced by two directed edges
- (u, v, data) and (v, u, data).
- Notes
- -----
- This returns a "deepcopy" of the edge, node, and
- graph attributes which attempts to completely copy
- all of the data and references.
- This is in contrast to the similar D=DiGraph(G) which returns a
- shallow copy of the data.
- See the Python copy module for more information on shallow
- and deep copies, https://docs.python.org/3/library/copy.html.
- Warning: If you have subclassed Graph to use dict-like objects
- in the data structure, those changes do not transfer to the
- DiGraph created by this method.
- Examples
- --------
- >>> G = nx.Graph() # or MultiGraph, etc
- >>> G.add_edge(0, 1)
- >>> H = G.to_directed()
- >>> list(H.edges)
- [(0, 1), (1, 0)]
- If already directed, return a (deep) copy
- >>> G = nx.DiGraph() # or MultiDiGraph, etc
- >>> G.add_edge(0, 1)
- >>> H = G.to_directed()
- >>> list(H.edges)
- [(0, 1)]
- """
- graph_class = self.to_directed_class()
- if as_view is True:
- return nx.graphviews.generic_graph_view(self, graph_class)
- # deepcopy when not a view
- G = graph_class()
- G.graph.update(deepcopy(self.graph))
- G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
- G.add_edges_from(
- (u, v, deepcopy(data))
- for u, nbrs in self._adj.items()
- for v, data in nbrs.items()
- )
- return G
- def to_undirected(self, as_view=False):
- """Returns an undirected copy of the graph.
- Parameters
- ----------
- as_view : bool (optional, default=False)
- If True return a view of the original undirected graph.
- Returns
- -------
- G : Graph/MultiGraph
- A deepcopy of the graph.
- See Also
- --------
- Graph, copy, add_edge, add_edges_from
- Notes
- -----
- This returns a "deepcopy" of the edge, node, and
- graph attributes which attempts to completely copy
- all of the data and references.
- This is in contrast to the similar `G = nx.DiGraph(D)` which returns a
- shallow copy of the data.
- See the Python copy module for more information on shallow
- and deep copies, https://docs.python.org/3/library/copy.html.
- Warning: If you have subclassed DiGraph to use dict-like objects
- in the data structure, those changes do not transfer to the
- Graph created by this method.
- Examples
- --------
- >>> G = nx.path_graph(2) # or MultiGraph, etc
- >>> H = G.to_directed()
- >>> list(H.edges)
- [(0, 1), (1, 0)]
- >>> G2 = H.to_undirected()
- >>> list(G2.edges)
- [(0, 1)]
- """
- graph_class = self.to_undirected_class()
- if as_view is True:
- return nx.graphviews.generic_graph_view(self, graph_class)
- # deepcopy when not a view
- G = graph_class()
- G.graph.update(deepcopy(self.graph))
- G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
- G.add_edges_from(
- (u, v, deepcopy(d))
- for u, nbrs in self._adj.items()
- for v, d in nbrs.items()
- )
- return G
- def subgraph(self, nodes):
- """Returns a SubGraph view of the subgraph induced on `nodes`.
- The induced subgraph of the graph contains the nodes in `nodes`
- and the edges between those nodes.
- Parameters
- ----------
- nodes : list, iterable
- A container of nodes which will be iterated through once.
- Returns
- -------
- G : SubGraph View
- A subgraph view of the graph. The graph structure cannot be
- changed but node/edge attributes can and are shared with the
- original graph.
- Notes
- -----
- The graph, edge and node attributes are shared with the original graph.
- Changes to the graph structure is ruled out by the view, but changes
- to attributes are reflected in the original graph.
- To create a subgraph with its own copy of the edge/node attributes use:
- G.subgraph(nodes).copy()
- For an inplace reduction of a graph to a subgraph you can remove nodes:
- G.remove_nodes_from([n for n in G if n not in set(nodes)])
- Subgraph views are sometimes NOT what you want. In most cases where
- you want to do more than simply look at the induced edges, it makes
- more sense to just create the subgraph as its own graph with code like:
- ::
- # Create a subgraph SG based on a (possibly multigraph) G
- SG = G.__class__()
- SG.add_nodes_from((n, G.nodes[n]) for n in largest_wcc)
- if SG.is_multigraph():
- SG.add_edges_from((n, nbr, key, d)
- for n, nbrs in G.adj.items() if n in largest_wcc
- for nbr, keydict in nbrs.items() if nbr in largest_wcc
- for key, d in keydict.items())
- else:
- SG.add_edges_from((n, nbr, d)
- for n, nbrs in G.adj.items() if n in largest_wcc
- for nbr, d in nbrs.items() if nbr in largest_wcc)
- SG.graph.update(G.graph)
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> H = G.subgraph([0, 1, 2])
- >>> list(H.edges)
- [(0, 1), (1, 2)]
- """
- induced_nodes = nx.filters.show_nodes(self.nbunch_iter(nodes))
- # if already a subgraph, don't make a chain
- subgraph = nx.graphviews.subgraph_view
- if hasattr(self, "_NODE_OK"):
- return subgraph(self._graph, induced_nodes, self._EDGE_OK)
- return subgraph(self, induced_nodes)
- def edge_subgraph(self, edges):
- """Returns the subgraph induced by the specified edges.
- The induced subgraph contains each edge in `edges` and each
- node incident to any one of those edges.
- Parameters
- ----------
- edges : iterable
- An iterable of edges in this graph.
- Returns
- -------
- G : Graph
- An edge-induced subgraph of this graph with the same edge
- attributes.
- Notes
- -----
- The graph, edge, and node attributes in the returned subgraph
- view are references to the corresponding attributes in the original
- graph. The view is read-only.
- To create a full graph version of the subgraph with its own copy
- of the edge or node attributes, use::
- G.edge_subgraph(edges).copy()
- Examples
- --------
- >>> G = nx.path_graph(5)
- >>> H = G.edge_subgraph([(0, 1), (3, 4)])
- >>> list(H.nodes)
- [0, 1, 3, 4]
- >>> list(H.edges)
- [(0, 1), (3, 4)]
- """
- return nx.edge_subgraph(self, edges)
- def size(self, weight=None):
- """Returns the number of edges or total of all edge weights.
- Parameters
- ----------
- weight : string or None, optional (default=None)
- The edge attribute that holds the numerical value used
- as a weight. If None, then each edge has weight 1.
- Returns
- -------
- size : numeric
- The number of edges or
- (if weight keyword is provided) the total weight sum.
- If weight is None, returns an int. Otherwise a float
- (or more general numeric if the weights are more general).
- See Also
- --------
- number_of_edges
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.size()
- 3
- >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.add_edge("a", "b", weight=2)
- >>> G.add_edge("b", "c", weight=4)
- >>> G.size()
- 2
- >>> G.size(weight="weight")
- 6.0
- """
- s = sum(d for v, d in self.degree(weight=weight))
- # If `weight` is None, the sum of the degrees is guaranteed to be
- # even, so we can perform integer division and hence return an
- # integer. Otherwise, the sum of the weighted degrees is not
- # guaranteed to be an integer, so we perform "real" division.
- return s // 2 if weight is None else s / 2
- def number_of_edges(self, u=None, v=None):
- """Returns the number of edges between two nodes.
- Parameters
- ----------
- u, v : nodes, optional (default=all edges)
- If u and v are specified, return the number of edges between
- u and v. Otherwise return the total number of all edges.
- Returns
- -------
- nedges : int
- The number of edges in the graph. If nodes `u` and `v` are
- specified return the number of edges between those nodes. If
- the graph is directed, this only returns the number of edges
- from `u` to `v`.
- See Also
- --------
- size
- Examples
- --------
- For undirected graphs, this method counts the total number of
- edges in the graph:
- >>> G = nx.path_graph(4)
- >>> G.number_of_edges()
- 3
- If you specify two nodes, this counts the total number of edges
- joining the two nodes:
- >>> G.number_of_edges(0, 1)
- 1
- For directed graphs, this method can count the total number of
- directed edges from `u` to `v`:
- >>> G = nx.DiGraph()
- >>> G.add_edge(0, 1)
- >>> G.add_edge(1, 0)
- >>> G.number_of_edges(0, 1)
- 1
- """
- if u is None:
- return int(self.size())
- if v in self._adj[u]:
- return 1
- return 0
- def nbunch_iter(self, nbunch=None):
- """Returns an iterator over nodes contained in nbunch that are
- also in the graph.
- The nodes in nbunch are checked for membership in the graph
- and if not are silently ignored.
- Parameters
- ----------
- nbunch : single node, container, or all nodes (default= all nodes)
- The view will only report edges incident to these nodes.
- Returns
- -------
- niter : iterator
- An iterator over nodes in nbunch that are also in the graph.
- If nbunch is None, iterate over all nodes in the graph.
- Raises
- ------
- NetworkXError
- If nbunch is not a node or sequence of nodes.
- If a node in nbunch is not hashable.
- See Also
- --------
- Graph.__iter__
- Notes
- -----
- When nbunch is an iterator, the returned iterator yields values
- directly from nbunch, becoming exhausted when nbunch is exhausted.
- To test whether nbunch is a single node, one can use
- "if nbunch in self:", even after processing with this routine.
- If nbunch is not a node or a (possibly empty) sequence/iterator
- or None, a :exc:`NetworkXError` is raised. Also, if any object in
- nbunch is not hashable, a :exc:`NetworkXError` is raised.
- """
- if nbunch is None: # include all nodes via iterator
- bunch = iter(self._adj)
- elif nbunch in self: # if nbunch is a single node
- bunch = iter([nbunch])
- else: # if nbunch is a sequence of nodes
- def bunch_iter(nlist, adj):
- try:
- for n in nlist:
- if n in adj:
- yield n
- except TypeError as err:
- exc, message = err, err.args[0]
- # capture error for non-sequence/iterator nbunch.
- if "iter" in message:
- exc = NetworkXError(
- "nbunch is not a node or a sequence of nodes."
- )
- # capture error for unhashable node.
- if "hashable" in message:
- exc = NetworkXError(
- f"Node {n} in sequence nbunch is not a valid node."
- )
- raise exc
- bunch = bunch_iter(nbunch, self._adj)
- return bunch
|