| | """Functional interface to graph methods and assorted utilities.""" |
| |
|
| | from collections import Counter |
| | from itertools import chain |
| |
|
| | import networkx as nx |
| | from networkx.utils import not_implemented_for, pairwise |
| |
|
| | __all__ = [ |
| | "nodes", |
| | "edges", |
| | "degree", |
| | "degree_histogram", |
| | "neighbors", |
| | "number_of_nodes", |
| | "number_of_edges", |
| | "density", |
| | "is_directed", |
| | "freeze", |
| | "is_frozen", |
| | "subgraph", |
| | "induced_subgraph", |
| | "edge_subgraph", |
| | "restricted_view", |
| | "to_directed", |
| | "to_undirected", |
| | "add_star", |
| | "add_path", |
| | "add_cycle", |
| | "create_empty_copy", |
| | "set_node_attributes", |
| | "get_node_attributes", |
| | "remove_node_attributes", |
| | "set_edge_attributes", |
| | "get_edge_attributes", |
| | "remove_edge_attributes", |
| | "all_neighbors", |
| | "non_neighbors", |
| | "non_edges", |
| | "common_neighbors", |
| | "is_weighted", |
| | "is_negatively_weighted", |
| | "is_empty", |
| | "selfloop_edges", |
| | "nodes_with_selfloops", |
| | "number_of_selfloops", |
| | "path_weight", |
| | "is_path", |
| | "describe", |
| | ] |
| |
|
| |
|
| | def nodes(G): |
| | """Returns a NodeView over the graph nodes. |
| | |
| | This function wraps the :func:`G.nodes <networkx.Graph.nodes>` property. |
| | """ |
| | return G.nodes() |
| |
|
| |
|
| | def edges(G, nbunch=None): |
| | """Returns an edge view of edges incident to nodes in nbunch. |
| | |
| | Return all edges if nbunch is unspecified or nbunch=None. |
| | |
| | For digraphs, edges=out_edges |
| | |
| | This function wraps the :func:`G.edges <networkx.Graph.edges>` property. |
| | """ |
| | return G.edges(nbunch) |
| |
|
| |
|
| | def degree(G, nbunch=None, weight=None): |
| | """Returns a degree view of single node or of nbunch of nodes. |
| | If nbunch is omitted, then return degrees of *all* nodes. |
| | |
| | This function wraps the :func:`G.degree <networkx.Graph.degree>` property. |
| | """ |
| | return G.degree(nbunch, weight) |
| |
|
| |
|
| | def neighbors(G, n): |
| | """Returns an iterator over all neighbors of node n. |
| | |
| | This function wraps the :func:`G.neighbors <networkx.Graph.neighbors>` function. |
| | """ |
| | return G.neighbors(n) |
| |
|
| |
|
| | def number_of_nodes(G): |
| | """Returns the number of nodes in the graph. |
| | |
| | This function wraps the :func:`G.number_of_nodes <networkx.Graph.number_of_nodes>` function. |
| | """ |
| | return G.number_of_nodes() |
| |
|
| |
|
| | def number_of_edges(G): |
| | """Returns the number of edges in the graph. |
| | |
| | This function wraps the :func:`G.number_of_edges <networkx.Graph.number_of_edges>` function. |
| | """ |
| | return G.number_of_edges() |
| |
|
| |
|
| | def density(G): |
| | r"""Returns the density of a graph. |
| | |
| | The density for undirected graphs is |
| | |
| | .. math:: |
| | |
| | d = \frac{2m}{n(n-1)}, |
| | |
| | and for directed graphs is |
| | |
| | .. math:: |
| | |
| | d = \frac{m}{n(n-1)}, |
| | |
| | where `n` is the number of nodes and `m` is the number of edges in `G`. |
| | |
| | Notes |
| | ----- |
| | The density is 0 for a graph without edges and 1 for a complete graph. |
| | The density of multigraphs can be higher than 1. |
| | |
| | Self loops are counted in the total number of edges so graphs with self |
| | loops can have density higher than 1. |
| | """ |
| | n = number_of_nodes(G) |
| | m = number_of_edges(G) |
| | if m == 0 or n <= 1: |
| | return 0 |
| | d = m / (n * (n - 1)) |
| | if not G.is_directed(): |
| | d *= 2 |
| | return d |
| |
|
| |
|
| | def degree_histogram(G): |
| | """Returns a list of the frequency of each degree value. |
| | |
| | Parameters |
| | ---------- |
| | G : Networkx graph |
| | A graph |
| | |
| | Returns |
| | ------- |
| | hist : list |
| | A list of frequencies of degrees. |
| | The degree values are the index in the list. |
| | |
| | Notes |
| | ----- |
| | Note: the bins are width one, hence len(list) can be large |
| | (Order(number_of_edges)) |
| | """ |
| | counts = Counter(d for n, d in G.degree()) |
| | return [counts.get(i, 0) for i in range(max(counts) + 1 if counts else 0)] |
| |
|
| |
|
| | def is_directed(G): |
| | """Return True if graph is directed.""" |
| | return G.is_directed() |
| |
|
| |
|
| | def frozen(*args, **kwargs): |
| | """Dummy method for raising errors when trying to modify frozen graphs""" |
| | raise nx.NetworkXError("Frozen graph can't be modified") |
| |
|
| |
|
| | def freeze(G): |
| | """Modify graph to prevent further change by adding or removing |
| | nodes or edges. |
| | |
| | Node and edge data can still be modified. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.path_graph(4) |
| | >>> G = nx.freeze(G) |
| | >>> try: |
| | ... G.add_edge(4, 5) |
| | ... except nx.NetworkXError as err: |
| | ... print(str(err)) |
| | Frozen graph can't be modified |
| | |
| | Notes |
| | ----- |
| | To "unfreeze" a graph you must make a copy by creating a new graph object: |
| | |
| | >>> graph = nx.path_graph(4) |
| | >>> frozen_graph = nx.freeze(graph) |
| | >>> unfrozen_graph = nx.Graph(frozen_graph) |
| | >>> nx.is_frozen(unfrozen_graph) |
| | False |
| | |
| | See Also |
| | -------- |
| | is_frozen |
| | """ |
| | G.add_node = frozen |
| | G.add_nodes_from = frozen |
| | G.remove_node = frozen |
| | G.remove_nodes_from = frozen |
| | G.add_edge = frozen |
| | G.add_edges_from = frozen |
| | G.add_weighted_edges_from = frozen |
| | G.remove_edge = frozen |
| | G.remove_edges_from = frozen |
| | G.clear = frozen |
| | G.clear_edges = frozen |
| | G.frozen = True |
| | return G |
| |
|
| |
|
| | def is_frozen(G): |
| | """Returns True if graph is frozen. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph |
| | |
| | See Also |
| | -------- |
| | freeze |
| | """ |
| | try: |
| | return G.frozen |
| | except AttributeError: |
| | return False |
| |
|
| |
|
| | def add_star(G_to_add_to, nodes_for_star, **attr): |
| | """Add a star to Graph G_to_add_to. |
| | |
| | The first node in `nodes_for_star` is the middle of the star. |
| | It is connected to all other nodes. |
| | |
| | Parameters |
| | ---------- |
| | G_to_add_to : graph |
| | A NetworkX graph |
| | nodes_for_star : iterable container |
| | A container of nodes. |
| | attr : keyword arguments, optional (default= no attributes) |
| | Attributes to add to every edge in star. |
| | |
| | See Also |
| | -------- |
| | add_path, add_cycle |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.Graph() |
| | >>> nx.add_star(G, [0, 1, 2, 3]) |
| | >>> nx.add_star(G, [10, 11, 12], weight=2) |
| | """ |
| | nlist = iter(nodes_for_star) |
| | try: |
| | v = next(nlist) |
| | except StopIteration: |
| | return |
| | G_to_add_to.add_node(v) |
| | edges = ((v, n) for n in nlist) |
| | G_to_add_to.add_edges_from(edges, **attr) |
| |
|
| |
|
| | def add_path(G_to_add_to, nodes_for_path, **attr): |
| | """Add a path to the Graph G_to_add_to. |
| | |
| | Parameters |
| | ---------- |
| | G_to_add_to : graph |
| | A NetworkX graph |
| | nodes_for_path : iterable container |
| | A container of nodes. A path will be constructed from |
| | the nodes (in order) and added to the graph. |
| | attr : keyword arguments, optional (default= no attributes) |
| | Attributes to add to every edge in path. |
| | |
| | See Also |
| | -------- |
| | add_star, add_cycle |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.Graph() |
| | >>> nx.add_path(G, [0, 1, 2, 3]) |
| | >>> nx.add_path(G, [10, 11, 12], weight=7) |
| | """ |
| | nlist = iter(nodes_for_path) |
| | try: |
| | first_node = next(nlist) |
| | except StopIteration: |
| | return |
| | G_to_add_to.add_node(first_node) |
| | G_to_add_to.add_edges_from(pairwise(chain((first_node,), nlist)), **attr) |
| |
|
| |
|
| | def add_cycle(G_to_add_to, nodes_for_cycle, **attr): |
| | """Add a cycle to the Graph G_to_add_to. |
| | |
| | Parameters |
| | ---------- |
| | G_to_add_to : graph |
| | A NetworkX graph |
| | nodes_for_cycle: iterable container |
| | A container of nodes. A cycle will be constructed from |
| | the nodes (in order) and added to the graph. |
| | attr : keyword arguments, optional (default= no attributes) |
| | Attributes to add to every edge in cycle. |
| | |
| | See Also |
| | -------- |
| | add_path, add_star |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc |
| | >>> nx.add_cycle(G, [0, 1, 2, 3]) |
| | >>> nx.add_cycle(G, [10, 11, 12], weight=7) |
| | """ |
| | nlist = iter(nodes_for_cycle) |
| | try: |
| | first_node = next(nlist) |
| | except StopIteration: |
| | return |
| | G_to_add_to.add_node(first_node) |
| | G_to_add_to.add_edges_from( |
| | pairwise(chain((first_node,), nlist), cyclic=True), **attr |
| | ) |
| |
|
| |
|
| | def subgraph(G, nbunch): |
| | """Returns the subgraph induced on nodes in nbunch. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph |
| | |
| | nbunch : list, iterable |
| | A container of nodes that will be iterated through once (thus |
| | it should be an iterator or be iterable). Each element of the |
| | container should be a valid node type: any hashable type except |
| | None. If nbunch is None, return all edges data in the graph. |
| | Nodes in nbunch that are not in the graph will be (quietly) |
| | ignored. |
| | |
| | Notes |
| | ----- |
| | subgraph(G) calls G.subgraph() |
| | """ |
| | return G.subgraph(nbunch) |
| |
|
| |
|
| | def induced_subgraph(G, nbunch): |
| | """Returns a SubGraph view of `G` showing only nodes in nbunch. |
| | |
| | The induced subgraph of a graph on a set of nodes N is the |
| | graph with nodes N and edges from G which have both ends in N. |
| | |
| | Parameters |
| | ---------- |
| | G : NetworkX Graph |
| | nbunch : node, container of nodes or None (for all nodes) |
| | |
| | Returns |
| | ------- |
| | subgraph : SubGraph View |
| | A read-only view of the subgraph in `G` induced by the nodes. |
| | Changes to the graph `G` will be reflected in the view. |
| | |
| | Notes |
| | ----- |
| | To create a mutable subgraph with its own copies of nodes |
| | edges and attributes use `subgraph.copy()` or `Graph(subgraph)` |
| | |
| | For an inplace reduction of a graph to a subgraph you can remove nodes: |
| | `G.remove_nodes_from(n in G if n not in set(nbunch))` |
| | |
| | If you are going to compute subgraphs of your subgraphs you could |
| | end up with a chain of views that can be very slow once the chain |
| | has about 15 views in it. If they are all induced subgraphs, you |
| | can short-cut the chain by making them all subgraphs of the original |
| | graph. The graph class method `G.subgraph` does this when `G` is |
| | a subgraph. In contrast, this function allows you to choose to build |
| | chains or not, as you wish. The returned subgraph is a view on `G`. |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc |
| | >>> H = nx.induced_subgraph(G, [0, 1, 3]) |
| | >>> list(H.edges) |
| | [(0, 1)] |
| | >>> list(H.nodes) |
| | [0, 1, 3] |
| | """ |
| | induced_nodes = nx.filters.show_nodes(G.nbunch_iter(nbunch)) |
| | return nx.subgraph_view(G, filter_node=induced_nodes) |
| |
|
| |
|
| | def edge_subgraph(G, edges): |
| | """Returns a view of the subgraph induced by the specified edges. |
| | |
| | The induced subgraph contains each edge in `edges` and each |
| | node incident to any of those edges. |
| | |
| | Parameters |
| | ---------- |
| | G : NetworkX Graph |
| | edges : iterable |
| | An iterable of edges. Edges not present in `G` are ignored. |
| | |
| | Returns |
| | ------- |
| | subgraph : SubGraph View |
| | A read-only edge-induced subgraph of `G`. |
| | Changes to `G` are reflected in the view. |
| | |
| | Notes |
| | ----- |
| | To create a mutable subgraph with its own copies of nodes |
| | edges and attributes use `subgraph.copy()` or `Graph(subgraph)` |
| | |
| | If you create a subgraph of a subgraph recursively you can end up |
| | with a chain of subgraphs that becomes very slow with about 15 |
| | nested subgraph views. Luckily the edge_subgraph filter nests |
| | nicely so you can use the original graph as G in this function |
| | to avoid chains. We do not rule out chains programmatically so |
| | that odd cases like an `edge_subgraph` of a `restricted_view` |
| | can be created. |
| | |
| | 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)] |
| | """ |
| | nxf = nx.filters |
| | edges = set(edges) |
| | nodes = set() |
| | for e in edges: |
| | nodes.update(e[:2]) |
| | induced_nodes = nxf.show_nodes(nodes) |
| | if G.is_multigraph(): |
| | if G.is_directed(): |
| | induced_edges = nxf.show_multidiedges(edges) |
| | else: |
| | induced_edges = nxf.show_multiedges(edges) |
| | else: |
| | if G.is_directed(): |
| | induced_edges = nxf.show_diedges(edges) |
| | else: |
| | induced_edges = nxf.show_edges(edges) |
| | return nx.subgraph_view(G, filter_node=induced_nodes, filter_edge=induced_edges) |
| |
|
| |
|
| | def restricted_view(G, nodes, edges): |
| | """Returns a view of `G` with hidden nodes and edges. |
| | |
| | The resulting subgraph filters out node `nodes` and edges `edges`. |
| | Filtered out nodes also filter out any of their edges. |
| | |
| | Parameters |
| | ---------- |
| | G : NetworkX Graph |
| | nodes : iterable |
| | An iterable of nodes. Nodes not present in `G` are ignored. |
| | edges : iterable |
| | An iterable of edges. Edges not present in `G` are ignored. |
| | |
| | Returns |
| | ------- |
| | subgraph : SubGraph View |
| | A read-only restricted view of `G` filtering out nodes and edges. |
| | Changes to `G` are reflected in the view. |
| | |
| | Notes |
| | ----- |
| | To create a mutable subgraph with its own copies of nodes |
| | edges and attributes use `subgraph.copy()` or `Graph(subgraph)` |
| | |
| | If you create a subgraph of a subgraph recursively you may end up |
| | with a chain of subgraph views. Such chains can get quite slow |
| | for lengths near 15. To avoid long chains, try to make your subgraph |
| | based on the original graph. We do not rule out chains programmatically |
| | so that odd cases like an `edge_subgraph` of a `restricted_view` |
| | can be created. |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.path_graph(5) |
| | >>> H = nx.restricted_view(G, [0], [(1, 2), (3, 4)]) |
| | >>> list(H.nodes) |
| | [1, 2, 3, 4] |
| | >>> list(H.edges) |
| | [(2, 3)] |
| | """ |
| | nxf = nx.filters |
| | hide_nodes = nxf.hide_nodes(nodes) |
| | if G.is_multigraph(): |
| | if G.is_directed(): |
| | hide_edges = nxf.hide_multidiedges(edges) |
| | else: |
| | hide_edges = nxf.hide_multiedges(edges) |
| | else: |
| | if G.is_directed(): |
| | hide_edges = nxf.hide_diedges(edges) |
| | else: |
| | hide_edges = nxf.hide_edges(edges) |
| | return nx.subgraph_view(G, filter_node=hide_nodes, filter_edge=hide_edges) |
| |
|
| |
|
| | def to_directed(graph): |
| | """Returns a directed view of the graph `graph`. |
| | |
| | Identical to graph.to_directed(as_view=True) |
| | Note that graph.to_directed defaults to `as_view=False` |
| | while this function always provides a view. |
| | """ |
| | return graph.to_directed(as_view=True) |
| |
|
| |
|
| | def to_undirected(graph): |
| | """Returns an undirected view of the graph `graph`. |
| | |
| | Identical to graph.to_undirected(as_view=True) |
| | Note that graph.to_undirected defaults to `as_view=False` |
| | while this function always provides a view. |
| | """ |
| | return graph.to_undirected(as_view=True) |
| |
|
| |
|
| | def create_empty_copy(G, with_data=True): |
| | """Returns a copy of the graph G with all of the edges removed. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph |
| | |
| | with_data : bool (default=True) |
| | Propagate Graph and Nodes data to the new graph. |
| | |
| | See Also |
| | -------- |
| | empty_graph |
| | |
| | """ |
| | H = G.__class__() |
| | H.add_nodes_from(G.nodes(data=with_data)) |
| | if with_data: |
| | H.graph.update(G.graph) |
| | return H |
| |
|
| |
|
| | @nx._dispatchable(preserve_node_attrs=True, mutates_input=True) |
| | def set_node_attributes(G, values, name=None): |
| | """Sets node attributes from a given value or dictionary of values. |
| | |
| | .. Warning:: The call order of arguments `values` and `name` |
| | switched between v1.x & v2.x. |
| | |
| | Parameters |
| | ---------- |
| | G : NetworkX Graph |
| | |
| | values : scalar value, dict-like |
| | What the node attribute should be set to. If `values` is |
| | not a dictionary, then it is treated as a single attribute value |
| | that is then applied to every node in `G`. This means that if |
| | you provide a mutable object, like a list, updates to that object |
| | will be reflected in the node attribute for every node. |
| | The attribute name will be `name`. |
| | |
| | If `values` is a dict or a dict of dict, it should be keyed |
| | by node to either an attribute value or a dict of attribute key/value |
| | pairs used to update the node's attributes. |
| | |
| | name : string (optional, default=None) |
| | Name of the node attribute to set if values is a scalar. |
| | |
| | Examples |
| | -------- |
| | After computing some property of the nodes of a graph, you may want |
| | to assign a node attribute to store the value of that property for |
| | each node:: |
| | |
| | >>> G = nx.path_graph(3) |
| | >>> bb = nx.betweenness_centrality(G) |
| | >>> isinstance(bb, dict) |
| | True |
| | >>> nx.set_node_attributes(G, bb, "betweenness") |
| | >>> G.nodes[1]["betweenness"] |
| | 1.0 |
| | |
| | If you provide a list as the second argument, updates to the list |
| | will be reflected in the node attribute for each node:: |
| | |
| | >>> G = nx.path_graph(3) |
| | >>> labels = [] |
| | >>> nx.set_node_attributes(G, labels, "labels") |
| | >>> labels.append("foo") |
| | >>> G.nodes[0]["labels"] |
| | ['foo'] |
| | >>> G.nodes[1]["labels"] |
| | ['foo'] |
| | >>> G.nodes[2]["labels"] |
| | ['foo'] |
| | |
| | If you provide a dictionary of dictionaries as the second argument, |
| | the outer dictionary is assumed to be keyed by node to an inner |
| | dictionary of node attributes for that node:: |
| | |
| | >>> G = nx.path_graph(3) |
| | >>> attrs = {0: {"attr1": 20, "attr2": "nothing"}, 1: {"attr2": 3}} |
| | >>> nx.set_node_attributes(G, attrs) |
| | >>> G.nodes[0]["attr1"] |
| | 20 |
| | >>> G.nodes[0]["attr2"] |
| | 'nothing' |
| | >>> G.nodes[1]["attr2"] |
| | 3 |
| | >>> G.nodes[2] |
| | {} |
| | |
| | Note that if the dictionary contains nodes that are not in `G`, the |
| | values are silently ignored:: |
| | |
| | >>> G = nx.Graph() |
| | >>> G.add_node(0) |
| | >>> nx.set_node_attributes(G, {0: "red", 1: "blue"}, name="color") |
| | >>> G.nodes[0]["color"] |
| | 'red' |
| | >>> 1 in G.nodes |
| | False |
| | |
| | """ |
| | |
| | if name is not None: |
| | try: |
| | for n, v in values.items(): |
| | try: |
| | G.nodes[n][name] = values[n] |
| | except KeyError: |
| | pass |
| | except AttributeError: |
| | for n in G: |
| | G.nodes[n][name] = values |
| | else: |
| | for n, d in values.items(): |
| | try: |
| | G.nodes[n].update(d) |
| | except KeyError: |
| | pass |
| | nx._clear_cache(G) |
| |
|
| |
|
| | @nx._dispatchable(node_attrs={"name": "default"}) |
| | def get_node_attributes(G, name, default=None): |
| | """Get node attributes from graph |
| | |
| | Parameters |
| | ---------- |
| | G : NetworkX Graph |
| | |
| | name : string |
| | Attribute name |
| | |
| | default: object (default=None) |
| | Default value of the node attribute if there is no value set for that |
| | node in graph. If `None` then nodes without this attribute are not |
| | included in the returned dict. |
| | |
| | Returns |
| | ------- |
| | Dictionary of attributes keyed by node. |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.Graph() |
| | >>> G.add_nodes_from([1, 2, 3], color="red") |
| | >>> color = nx.get_node_attributes(G, "color") |
| | >>> color[1] |
| | 'red' |
| | >>> G.add_node(4) |
| | >>> color = nx.get_node_attributes(G, "color", default="yellow") |
| | >>> color[4] |
| | 'yellow' |
| | """ |
| | if default is not None: |
| | return {n: d.get(name, default) for n, d in G.nodes.items()} |
| | return {n: d[name] for n, d in G.nodes.items() if name in d} |
| |
|
| |
|
| | @nx._dispatchable(preserve_node_attrs=True, mutates_input=True) |
| | def remove_node_attributes(G, *attr_names, nbunch=None): |
| | """Remove node attributes from all nodes in the graph. |
| | |
| | Parameters |
| | ---------- |
| | G : NetworkX Graph |
| | |
| | *attr_names : List of Strings |
| | The attribute names to remove from the graph. |
| | |
| | nbunch : List of Nodes |
| | Remove the node attributes only from the nodes in this list. |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.Graph() |
| | >>> G.add_nodes_from([1, 2, 3], color="blue") |
| | >>> nx.get_node_attributes(G, "color") |
| | {1: 'blue', 2: 'blue', 3: 'blue'} |
| | >>> nx.remove_node_attributes(G, "color") |
| | >>> nx.get_node_attributes(G, "color") |
| | {} |
| | """ |
| |
|
| | if nbunch is None: |
| | nbunch = G.nodes() |
| |
|
| | for attr in attr_names: |
| | for n, d in G.nodes(data=True): |
| | if n in nbunch: |
| | try: |
| | del d[attr] |
| | except KeyError: |
| | pass |
| |
|
| |
|
| | @nx._dispatchable(preserve_edge_attrs=True, mutates_input=True) |
| | def set_edge_attributes(G, values, name=None): |
| | """Sets edge attributes from a given value or dictionary of values. |
| | |
| | .. Warning:: The call order of arguments `values` and `name` |
| | switched between v1.x & v2.x. |
| | |
| | Parameters |
| | ---------- |
| | G : NetworkX Graph |
| | |
| | values : scalar value, dict-like |
| | What the edge attribute should be set to. If `values` is |
| | not a dictionary, then it is treated as a single attribute value |
| | that is then applied to every edge in `G`. This means that if |
| | you provide a mutable object, like a list, updates to that object |
| | will be reflected in the edge attribute for each edge. The attribute |
| | name will be `name`. |
| | |
| | If `values` is a dict or a dict of dict, it should be keyed |
| | by edge tuple to either an attribute value or a dict of attribute |
| | key/value pairs used to update the edge's attributes. |
| | For multigraphs, the edge tuples must be of the form ``(u, v, key)``, |
| | where `u` and `v` are nodes and `key` is the edge key. |
| | For non-multigraphs, the keys must be tuples of the form ``(u, v)``. |
| | |
| | name : string (optional, default=None) |
| | Name of the edge attribute to set if values is a scalar. |
| | |
| | Examples |
| | -------- |
| | After computing some property of the edges of a graph, you may want |
| | to assign a edge attribute to store the value of that property for |
| | each edge:: |
| | |
| | >>> G = nx.path_graph(3) |
| | >>> bb = nx.edge_betweenness_centrality(G, normalized=False) |
| | >>> nx.set_edge_attributes(G, bb, "betweenness") |
| | >>> G.edges[1, 2]["betweenness"] |
| | 2.0 |
| | |
| | If you provide a list as the second argument, updates to the list |
| | will be reflected in the edge attribute for each edge:: |
| | |
| | >>> labels = [] |
| | >>> nx.set_edge_attributes(G, labels, "labels") |
| | >>> labels.append("foo") |
| | >>> G.edges[0, 1]["labels"] |
| | ['foo'] |
| | >>> G.edges[1, 2]["labels"] |
| | ['foo'] |
| | |
| | If you provide a dictionary of dictionaries as the second argument, |
| | the entire dictionary will be used to update edge attributes:: |
| | |
| | >>> G = nx.path_graph(3) |
| | >>> attrs = {(0, 1): {"attr1": 20, "attr2": "nothing"}, (1, 2): {"attr2": 3}} |
| | >>> nx.set_edge_attributes(G, attrs) |
| | >>> G[0][1]["attr1"] |
| | 20 |
| | >>> G[0][1]["attr2"] |
| | 'nothing' |
| | >>> G[1][2]["attr2"] |
| | 3 |
| | |
| | The attributes of one Graph can be used to set those of another. |
| | |
| | >>> H = nx.path_graph(3) |
| | >>> nx.set_edge_attributes(H, G.edges) |
| | |
| | Note that if the dict contains edges that are not in `G`, they are |
| | silently ignored:: |
| | |
| | >>> G = nx.Graph([(0, 1)]) |
| | >>> nx.set_edge_attributes(G, {(1, 2): {"weight": 2.0}}) |
| | >>> (1, 2) in G.edges() |
| | False |
| | |
| | For multigraphs, the `values` dict is expected to be keyed by 3-tuples |
| | including the edge key:: |
| | |
| | >>> MG = nx.MultiGraph() |
| | >>> edges = [(0, 1), (0, 1)] |
| | >>> MG.add_edges_from(edges) # Returns list of edge keys |
| | [0, 1] |
| | >>> attributes = {(0, 1, 0): {"cost": 21}, (0, 1, 1): {"cost": 7}} |
| | >>> nx.set_edge_attributes(MG, attributes) |
| | >>> MG[0][1][0]["cost"] |
| | 21 |
| | >>> MG[0][1][1]["cost"] |
| | 7 |
| | |
| | If MultiGraph attributes are desired for a Graph, you must convert the 3-tuple |
| | multiedge to a 2-tuple edge and the last multiedge's attribute value will |
| | overwrite the previous values. Continuing from the previous case we get:: |
| | |
| | >>> H = nx.path_graph([0, 1, 2]) |
| | >>> nx.set_edge_attributes(H, {(u, v): ed for u, v, ed in MG.edges.data()}) |
| | >>> nx.get_edge_attributes(H, "cost") |
| | {(0, 1): 7} |
| | |
| | """ |
| | if name is not None: |
| | |
| | try: |
| | |
| | if G.is_multigraph(): |
| | for (u, v, key), value in values.items(): |
| | try: |
| | G._adj[u][v][key][name] = value |
| | except KeyError: |
| | pass |
| | else: |
| | for (u, v), value in values.items(): |
| | try: |
| | G._adj[u][v][name] = value |
| | except KeyError: |
| | pass |
| | except AttributeError: |
| | |
| | for u, v, data in G.edges(data=True): |
| | data[name] = values |
| | else: |
| | |
| | if G.is_multigraph(): |
| | for (u, v, key), d in values.items(): |
| | try: |
| | G._adj[u][v][key].update(d) |
| | except KeyError: |
| | pass |
| | else: |
| | for (u, v), d in values.items(): |
| | try: |
| | G._adj[u][v].update(d) |
| | except KeyError: |
| | pass |
| | nx._clear_cache(G) |
| |
|
| |
|
| | @nx._dispatchable(edge_attrs={"name": "default"}) |
| | def get_edge_attributes(G, name, default=None): |
| | """Get edge attributes from graph |
| | |
| | Parameters |
| | ---------- |
| | G : NetworkX Graph |
| | |
| | name : string |
| | Attribute name |
| | |
| | default: object (default=None) |
| | Default value of the edge attribute if there is no value set for that |
| | edge in graph. If `None` then edges without this attribute are not |
| | included in the returned dict. |
| | |
| | Returns |
| | ------- |
| | Dictionary of attributes keyed by edge. For (di)graphs, the keys are |
| | 2-tuples of the form: (u, v). For multi(di)graphs, the keys are 3-tuples of |
| | the form: (u, v, key). |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.Graph() |
| | >>> nx.add_path(G, [1, 2, 3], color="red") |
| | >>> color = nx.get_edge_attributes(G, "color") |
| | >>> color[(1, 2)] |
| | 'red' |
| | >>> G.add_edge(3, 4) |
| | >>> color = nx.get_edge_attributes(G, "color", default="yellow") |
| | >>> color[(3, 4)] |
| | 'yellow' |
| | """ |
| | if G.is_multigraph(): |
| | edges = G.edges(keys=True, data=True) |
| | else: |
| | edges = G.edges(data=True) |
| | if default is not None: |
| | return {x[:-1]: x[-1].get(name, default) for x in edges} |
| | return {x[:-1]: x[-1][name] for x in edges if name in x[-1]} |
| |
|
| |
|
| | @nx._dispatchable(preserve_edge_attrs=True, mutates_input=True) |
| | def remove_edge_attributes(G, *attr_names, ebunch=None): |
| | """Remove edge attributes from all edges in the graph. |
| | |
| | Parameters |
| | ---------- |
| | G : NetworkX Graph |
| | |
| | *attr_names : List of Strings |
| | The attribute names to remove from the graph. |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.path_graph(3) |
| | >>> nx.set_edge_attributes(G, {(u, v): u + v for u, v in G.edges()}, name="weight") |
| | >>> nx.get_edge_attributes(G, "weight") |
| | {(0, 1): 1, (1, 2): 3} |
| | >>> remove_edge_attributes(G, "weight") |
| | >>> nx.get_edge_attributes(G, "weight") |
| | {} |
| | """ |
| | if ebunch is None: |
| | ebunch = G.edges(keys=True) if G.is_multigraph() else G.edges() |
| |
|
| | for attr in attr_names: |
| | edges = ( |
| | G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True) |
| | ) |
| | for *e, d in edges: |
| | if tuple(e) in ebunch: |
| | try: |
| | del d[attr] |
| | except KeyError: |
| | pass |
| |
|
| |
|
| | def all_neighbors(graph, node): |
| | """Returns all of the neighbors of a node in the graph. |
| | |
| | If the graph is directed returns predecessors as well as successors. |
| | |
| | Parameters |
| | ---------- |
| | graph : NetworkX graph |
| | Graph to find neighbors. |
| | node : node |
| | The node whose neighbors will be returned. |
| | |
| | Returns |
| | ------- |
| | neighbors : iterator |
| | Iterator of neighbors |
| | |
| | Raises |
| | ------ |
| | NetworkXError |
| | If `node` is not in the graph. |
| | |
| | Examples |
| | -------- |
| | For undirected graphs, this function is equivalent to ``G.neighbors(node)``. |
| | |
| | >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc |
| | >>> list(nx.all_neighbors(G, 1)) |
| | [0, 2] |
| | |
| | For directed graphs, this function returns both predecessors and successors, |
| | which may include duplicates if a node is both a predecessor and successor |
| | (e.g., in bidirectional edges or self-loops). |
| | |
| | >>> DG = nx.DiGraph([(0, 1), (1, 2), (2, 1)]) |
| | >>> list(nx.all_neighbors(DG, 1)) |
| | [0, 2, 2] |
| | |
| | Notes |
| | ----- |
| | This function iterates over all neighbors (both predecessors and successors). |
| | |
| | See Also |
| | -------- |
| | Graph.neighbors : Returns successors for both Graph and DiGraph |
| | DiGraph.predecessors : Returns predecessors for directed graphs only |
| | DiGraph.successors : Returns successors for directed graphs only |
| | """ |
| | if graph.is_directed(): |
| | values = chain(graph.predecessors(node), graph.successors(node)) |
| | else: |
| | values = graph.neighbors(node) |
| | return values |
| |
|
| |
|
| | def non_neighbors(graph, node): |
| | """Returns the non-neighbors of the node in the graph. |
| | |
| | Parameters |
| | ---------- |
| | graph : NetworkX graph |
| | Graph to find neighbors. |
| | |
| | node : node |
| | The node whose neighbors will be returned. |
| | |
| | Returns |
| | ------- |
| | non_neighbors : set |
| | Set of nodes in the graph that are not neighbors of the node. |
| | """ |
| | return graph._adj.keys() - graph._adj[node].keys() - {node} |
| |
|
| |
|
| | def non_edges(graph): |
| | """Returns the nonexistent edges in the graph. |
| | |
| | Parameters |
| | ---------- |
| | graph : NetworkX graph. |
| | Graph to find nonexistent edges. |
| | |
| | Returns |
| | ------- |
| | non_edges : iterator |
| | Iterator of edges that are not in the graph. |
| | """ |
| | if graph.is_directed(): |
| | for u in graph: |
| | for v in non_neighbors(graph, u): |
| | yield (u, v) |
| | else: |
| | nodes = set(graph) |
| | while nodes: |
| | u = nodes.pop() |
| | for v in nodes - set(graph[u]): |
| | yield (u, v) |
| |
|
| |
|
| | @not_implemented_for("directed") |
| | def common_neighbors(G, u, v): |
| | """Returns the common neighbors of two nodes in a graph. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX undirected graph. |
| | |
| | u, v : nodes |
| | Nodes in the graph. |
| | |
| | Returns |
| | ------- |
| | cnbors : set |
| | Set of common neighbors of u and v in the graph. |
| | |
| | Raises |
| | ------ |
| | NetworkXError |
| | If u or v is not a node in the graph. |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.complete_graph(5) |
| | >>> sorted(nx.common_neighbors(G, 0, 1)) |
| | [2, 3, 4] |
| | """ |
| | if u not in G: |
| | raise nx.NetworkXError("u is not in the graph.") |
| | if v not in G: |
| | raise nx.NetworkXError("v is not in the graph.") |
| |
|
| | return G._adj[u].keys() & G._adj[v].keys() - {u, v} |
| |
|
| |
|
| | @nx._dispatchable(preserve_edge_attrs=True) |
| | def is_weighted(G, edge=None, weight="weight"): |
| | """Returns True if `G` has weighted edges. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph. |
| | |
| | edge : tuple, optional |
| | A 2-tuple specifying the only edge in `G` that will be tested. If |
| | None, then every edge in `G` is tested. |
| | |
| | weight: string, optional |
| | The attribute name used to query for edge weights. |
| | |
| | Returns |
| | ------- |
| | bool |
| | A boolean signifying if `G`, or the specified edge, is weighted. |
| | |
| | Raises |
| | ------ |
| | NetworkXError |
| | If the specified edge does not exist. |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.path_graph(4) |
| | >>> nx.is_weighted(G) |
| | False |
| | >>> nx.is_weighted(G, (2, 3)) |
| | False |
| | |
| | >>> G = nx.DiGraph() |
| | >>> G.add_edge(1, 2, weight=1) |
| | >>> nx.is_weighted(G) |
| | True |
| | |
| | """ |
| | if edge is not None: |
| | data = G.get_edge_data(*edge) |
| | if data is None: |
| | msg = f"Edge {edge!r} does not exist." |
| | raise nx.NetworkXError(msg) |
| | return weight in data |
| |
|
| | if is_empty(G): |
| | |
| | return False |
| |
|
| | return all(weight in data for u, v, data in G.edges(data=True)) |
| |
|
| |
|
| | @nx._dispatchable(edge_attrs="weight") |
| | def is_negatively_weighted(G, edge=None, weight="weight"): |
| | """Returns True if `G` has negatively weighted edges. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph. |
| | |
| | edge : tuple, optional |
| | A 2-tuple specifying the only edge in `G` that will be tested. If |
| | None, then every edge in `G` is tested. |
| | |
| | weight: string, optional |
| | The attribute name used to query for edge weights. |
| | |
| | Returns |
| | ------- |
| | bool |
| | A boolean signifying if `G`, or the specified edge, is negatively |
| | weighted. |
| | |
| | Raises |
| | ------ |
| | NetworkXError |
| | If the specified edge does not exist. |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.Graph() |
| | >>> G.add_edges_from([(1, 3), (2, 4), (2, 6)]) |
| | >>> G.add_edge(1, 2, weight=4) |
| | >>> nx.is_negatively_weighted(G, (1, 2)) |
| | False |
| | >>> G[2][4]["weight"] = -2 |
| | >>> nx.is_negatively_weighted(G) |
| | True |
| | >>> G = nx.DiGraph() |
| | >>> edges = [("0", "3", 3), ("0", "1", -5), ("1", "0", -2)] |
| | >>> G.add_weighted_edges_from(edges) |
| | >>> nx.is_negatively_weighted(G) |
| | True |
| | |
| | """ |
| | if edge is not None: |
| | data = G.get_edge_data(*edge) |
| | if data is None: |
| | msg = f"Edge {edge!r} does not exist." |
| | raise nx.NetworkXError(msg) |
| | return weight in data and data[weight] < 0 |
| |
|
| | return any(weight in data and data[weight] < 0 for u, v, data in G.edges(data=True)) |
| |
|
| |
|
| | @nx._dispatchable |
| | def is_empty(G): |
| | """Returns True if `G` has no edges. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph. |
| | |
| | Returns |
| | ------- |
| | bool |
| | True if `G` has no edges, and False otherwise. |
| | |
| | Notes |
| | ----- |
| | An empty graph can have nodes but not edges. The empty graph with zero |
| | nodes is known as the null graph. This is an $O(n)$ operation where n |
| | is the number of nodes in the graph. |
| | |
| | """ |
| | return not any(G._adj.values()) |
| |
|
| |
|
| | def nodes_with_selfloops(G): |
| | """Returns an iterator over nodes with self loops. |
| | |
| | A node with a self loop has an edge with both ends adjacent |
| | to that node. |
| | |
| | Returns |
| | ------- |
| | nodelist : iterator |
| | A iterator over nodes with self loops. |
| | |
| | See Also |
| | -------- |
| | selfloop_edges, number_of_selfloops |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc |
| | >>> G.add_edge(1, 1) |
| | >>> G.add_edge(1, 2) |
| | >>> list(nx.nodes_with_selfloops(G)) |
| | [1] |
| | |
| | """ |
| | return (n for n, nbrs in G._adj.items() if n in nbrs) |
| |
|
| |
|
| | def selfloop_edges(G, data=False, keys=False, default=None): |
| | """Returns an iterator over selfloop edges. |
| | |
| | A selfloop edge has the same node at both ends. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph. |
| | data : string or bool, optional (default=False) |
| | Return selfloop edges as two tuples (u, v) (data=False) |
| | or three-tuples (u, v, datadict) (data=True) |
| | or three-tuples (u, v, datavalue) (data='attrname') |
| | keys : bool, optional (default=False) |
| | If True, return edge keys with each edge. |
| | 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 |
| | ------- |
| | edgeiter : iterator over edge tuples |
| | An iterator over all selfloop edges. |
| | |
| | See Also |
| | -------- |
| | nodes_with_selfloops, number_of_selfloops |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.MultiGraph() # or Graph, DiGraph, MultiDiGraph, etc |
| | >>> ekey = G.add_edge(1, 1) |
| | >>> ekey = G.add_edge(1, 2) |
| | >>> list(nx.selfloop_edges(G)) |
| | [(1, 1)] |
| | >>> list(nx.selfloop_edges(G, data=True)) |
| | [(1, 1, {})] |
| | >>> list(nx.selfloop_edges(G, keys=True)) |
| | [(1, 1, 0)] |
| | >>> list(nx.selfloop_edges(G, keys=True, data=True)) |
| | [(1, 1, 0, {})] |
| | """ |
| | if data is True: |
| | if G.is_multigraph(): |
| | if keys is True: |
| | return ( |
| | (n, n, k, d) |
| | for n, nbrs in G._adj.items() |
| | if n in nbrs |
| | for k, d in nbrs[n].items() |
| | ) |
| | else: |
| | return ( |
| | (n, n, d) |
| | for n, nbrs in G._adj.items() |
| | if n in nbrs |
| | for d in nbrs[n].values() |
| | ) |
| | else: |
| | return ((n, n, nbrs[n]) for n, nbrs in G._adj.items() if n in nbrs) |
| | elif data is not False: |
| | if G.is_multigraph(): |
| | if keys is True: |
| | return ( |
| | (n, n, k, d.get(data, default)) |
| | for n, nbrs in G._adj.items() |
| | if n in nbrs |
| | for k, d in nbrs[n].items() |
| | ) |
| | else: |
| | return ( |
| | (n, n, d.get(data, default)) |
| | for n, nbrs in G._adj.items() |
| | if n in nbrs |
| | for d in nbrs[n].values() |
| | ) |
| | else: |
| | return ( |
| | (n, n, nbrs[n].get(data, default)) |
| | for n, nbrs in G._adj.items() |
| | if n in nbrs |
| | ) |
| | else: |
| | if G.is_multigraph(): |
| | if keys is True: |
| | return ( |
| | (n, n, k) |
| | for n, nbrs in G._adj.items() |
| | if n in nbrs |
| | for k in nbrs[n] |
| | ) |
| | else: |
| | return ( |
| | (n, n) |
| | for n, nbrs in G._adj.items() |
| | if n in nbrs |
| | for i in range(len(nbrs[n])) |
| | ) |
| | else: |
| | return ((n, n) for n, nbrs in G._adj.items() if n in nbrs) |
| |
|
| |
|
| | @nx._dispatchable |
| | def number_of_selfloops(G): |
| | """Returns the number of selfloop edges. |
| | |
| | A selfloop edge has the same node at both ends. |
| | |
| | Returns |
| | ------- |
| | nloops : int |
| | The number of selfloops. |
| | |
| | See Also |
| | -------- |
| | nodes_with_selfloops, selfloop_edges |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc |
| | >>> G.add_edge(1, 1) |
| | >>> G.add_edge(1, 2) |
| | >>> nx.number_of_selfloops(G) |
| | 1 |
| | """ |
| | return sum(1 for _ in nx.selfloop_edges(G)) |
| |
|
| |
|
| | def is_path(G, path): |
| | """Returns whether or not the specified path exists. |
| | |
| | For it to return True, every node on the path must exist and |
| | each consecutive pair must be connected via one or more edges. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph. |
| | |
| | path : list |
| | A list of nodes which defines the path to traverse |
| | |
| | Returns |
| | ------- |
| | bool |
| | True if `path` is a valid path in `G` |
| | |
| | """ |
| | try: |
| | return all(nbr in G._adj[node] for node, nbr in nx.utils.pairwise(path)) |
| | except (KeyError, TypeError): |
| | return False |
| |
|
| |
|
| | def path_weight(G, path, weight): |
| | """Returns total cost associated with specified path and weight |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph. |
| | |
| | path: list |
| | A list of node labels which defines the path to traverse |
| | |
| | weight: string |
| | A string indicating which edge attribute to use for path cost |
| | |
| | Returns |
| | ------- |
| | cost: int or float |
| | An integer or a float representing the total cost with respect to the |
| | specified weight of the specified path |
| | |
| | Raises |
| | ------ |
| | NetworkXNoPath |
| | If the specified edge does not exist. |
| | """ |
| | multigraph = G.is_multigraph() |
| | cost = 0 |
| |
|
| | if not nx.is_path(G, path): |
| | raise nx.NetworkXNoPath("path does not exist") |
| | for node, nbr in nx.utils.pairwise(path): |
| | if multigraph: |
| | cost += min(v[weight] for v in G._adj[node][nbr].values()) |
| | else: |
| | cost += G._adj[node][nbr][weight] |
| | return cost |
| |
|
| |
|
| | def describe(G, describe_hook=None): |
| | """Prints a description of the graph G. |
| | |
| | By default, the description includes some basic properties of the graph. |
| | You can also provide additional functions to compute and include |
| | more properties in the description. |
| | |
| | Parameters |
| | ---------- |
| | G : graph |
| | A NetworkX graph. |
| | |
| | describe_hook: callable, optional (default=None) |
| | A function that takes a graph as input and returns a |
| | dictionary of additional properties to include in the description. |
| | The keys of the dictionary are the property names, and the values |
| | are the corresponding property values. |
| | |
| | Examples |
| | -------- |
| | >>> G = nx.path_graph(5) |
| | >>> nx.describe(G) |
| | Number of nodes : 5 |
| | Number of edges : 4 |
| | Directed : False |
| | Multigraph : False |
| | Tree : True |
| | Bipartite : True |
| | Average degree (min, max) : 1.60 (1, 2) |
| | Number of connected components : 1 |
| | |
| | >>> def augment_description(G): |
| | ... return {"Average Shortest Path Length": nx.average_shortest_path_length(G)} |
| | >>> nx.describe(G, describe_hook=augment_description) |
| | Number of nodes : 5 |
| | Number of edges : 4 |
| | Directed : False |
| | Multigraph : False |
| | Tree : True |
| | Bipartite : True |
| | Average degree (min, max) : 1.60 (1, 2) |
| | Number of connected components : 1 |
| | Average Shortest Path Length : 2.0 |
| | |
| | >>> G.name = "Path Graph of 5 nodes" |
| | >>> nx.describe(G) |
| | Name of Graph : Path Graph of 5 nodes |
| | Number of nodes : 5 |
| | Number of edges : 4 |
| | Directed : False |
| | Multigraph : False |
| | Tree : True |
| | Bipartite : True |
| | Average degree (min, max) : 1.60 (1, 2) |
| | Number of connected components : 1 |
| | |
| | """ |
| | info_dict = _create_describe_info_dict(G) |
| |
|
| | if describe_hook is not None: |
| | additional_info = describe_hook(G) |
| | info_dict.update(additional_info) |
| |
|
| | max_key_len = max(len(k) for k in info_dict) |
| | for key, val in info_dict.items(): |
| | print(f"{key:<{max_key_len}} : {val}") |
| |
|
| |
|
| | def _create_describe_info_dict(G): |
| | info = {} |
| | if G.name != "": |
| | info["Name of Graph"] = G.name |
| | info.update( |
| | { |
| | "Number of nodes": len(G), |
| | "Number of edges": G.number_of_edges(), |
| | "Directed": G.is_directed(), |
| | "Multigraph": G.is_multigraph(), |
| | "Tree": nx.is_tree(G), |
| | "Bipartite": nx.is_bipartite(G), |
| | } |
| | ) |
| | if len(G) == 0: |
| | return info |
| |
|
| | degree_values = dict(nx.degree(G)).values() |
| | avg_degree = sum(degree_values) / len(G) |
| | max_degree, min_degree = max(degree_values), min(degree_values) |
| | info["Average degree (min, max)"] = f"{avg_degree:.2f} ({min_degree}, {max_degree})" |
| |
|
| | if G.is_directed(): |
| | info["Number of strongly connected components"] = ( |
| | nx.number_strongly_connected_components(G) |
| | ) |
| | info["Number of weakly connected components"] = ( |
| | nx.number_weakly_connected_components(G) |
| | ) |
| | else: |
| | info["Number of connected components"] = nx.number_connected_components(G) |
| | return info |
| |
|