1964 lines
65 KiB
Python
1964 lines
65 KiB
Python
"""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
|
|
import networkx.convert as 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
|
|
OrderedGraph
|
|
|
|
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
|
|
|
|
Please see :mod:`~networkx.classes.ordered` for more examples of
|
|
creating graph subclasses by overwriting the base class `dict` with
|
|
a dictionary-like object.
|
|
"""
|
|
|
|
_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 matrix, 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 as provided by `nx.info`
|
|
|
|
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
|
|
|
|
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
|
|
|
|
"""
|
|
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
|
|
|
|
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)
|
|
[]
|
|
|
|
"""
|
|
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.
|
|
|
|
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")
|
|
"""
|
|
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.
|
|
|
|
Examples
|
|
--------
|
|
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
|
>>> G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5)])
|
|
"""
|
|
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
|