def to_networkx_graph(data, create_using=None, multigraph_input=False): """Make a NetworkX graph from a known data structure. The preferred way to call this is automatically from the class constructor >>> d = {0: {1: {'weight':1}}} # dict-of-dicts single edge (0,1) >>> G = nx.Graph(d) instead of the equivalent >>> G = nx.from_dict_of_dicts(d) Parameters ---------- data : object to be converted Current known types are: any NetworkX graph dict-of-dicts dict-of-lists list of edges Pandas DataFrame (row per edge) numpy matrix numpy ndarray scipy sparse matrix pygraphviz agraph create_using : NetworkX graph constructor, optional (default=nx.Graph) Graph type to create. If graph instance, then cleared before populated. multigraph_input : bool (default False) If True and data is a dict_of_dicts, try to create a multigraph assuming dict_of_dict_of_lists. If data and create_using are both multigraphs then create a multigraph from a multigraph. """ # NX graph if hasattr(data, "adj"): try: result = from_dict_of_dicts(data.adj, create_using=create_using, multigraph_input=data.is_multigraph()) if hasattr(data, 'graph'): # data.graph should be dict-like result.graph.update(data.graph) if hasattr(data, 'nodes'): # data.nodes should be dict-like # result.add_node_from(data.nodes.items()) possible but # for custom node_attr_dict_factory which may be hashable # will be unexpected behavior for n, dd in data.nodes.items(): result._node[n].update(dd) return result except: raise nx.NetworkXError("Input is not a correct NetworkX graph.") # pygraphviz agraph if hasattr(data, "is_strict"): try: return nx.nx_agraph.from_agraph(data, create_using=create_using) except: raise nx.NetworkXError("Input is not a correct pygraphviz graph.") # dict of dicts/lists if isinstance(data, dict): try: return from_dict_of_dicts(data, create_using=create_using, multigraph_input=multigraph_input) except: try: return from_dict_of_lists(data, create_using=create_using) except: raise TypeError("Input is not known type.") # list or generator of edges if (isinstance(data, (list, tuple)) or any(hasattr(data, attr) for attr in ['_adjdict', 'next', '__next__'])): try: return from_edgelist(data, create_using=create_using) except: raise nx.NetworkXError("Input is not a valid edge list") # Pandas DataFrame try: import pandas as pd if isinstance(data, pd.DataFrame): if data.shape[0] == data.shape[1]: try: return nx.from_pandas_adjacency(data, create_using=create_using) except: msg = "Input is not a correct Pandas DataFrame adjacency matrix." raise nx.NetworkXError(msg) else: try: return nx.from_pandas_edgelist(data, edge_attr=True, create_using=create_using) except: msg = "Input is not a correct Pandas DataFrame edge-list." raise nx.NetworkXError(msg) except ImportError: msg = 'pandas not found, skipping conversion test.' warnings.warn(msg, ImportWarning) # numpy matrix or ndarray try: import numpy if isinstance(data, (numpy.matrix, numpy.ndarray)): try: return nx.from_numpy_matrix(data, create_using=create_using) except: raise nx.NetworkXError( "Input is not a correct numpy matrix or array.") except ImportError: warnings.warn('numpy not found, skipping conversion test.', ImportWarning) # scipy sparse matrix - any format try: import scipy if hasattr(data, "format"): try: return nx.from_scipy_sparse_matrix(data, create_using=create_using) except: raise nx.NetworkXError( "Input is not a correct scipy sparse matrix type.") except ImportError: warnings.warn('scipy not found, skipping conversion test.', ImportWarning) raise nx.NetworkXError( "Input is not a known data type for conversion.")
def extended_barabasi_albert_graph(n, m, p, q, seed=None): """Returns an extended Barabási–Albert model graph. An extended Barabási–Albert model graph is a random graph constructed using preferential attachment. The extended model allows new egdes, rewired edges or new nodes. Based on the probabilities `p` and `q` with `p + q < 1`, the growing behavior of the graph is determined as: 1) With `p` probability, `m` new edges are added to the graph, starting from randomly chosen existing nodes and attached preferentially at the other end. 2) With `q` probability, `m` existing edges are rewired by randomly chosing an edge and rewiring one end to a preferentially chosen node. 3) With `(1 - p - q)` probability, `m` new nodes are added to the graph with edges attached preferentially. When `p = q = 0`, the model behaves just like the Barabási–Alber mo Parameters ---------- n : int Number of nodes m : int Number of edges with which a new node attaches to existing nodes p : float Probability value for adding an edge between existing nodes. p + q < 1 q : float Probability value of rewiring of existing edges. p + q < 1 seed : int (optional, default: None) Seed for random number generator Returns ------- G : Graph Raises ------ NetworkXError If `m` does not satisfy ``1 <= m < n`` or ``1 >= p + q`` References ---------- .. [1] Albert, R., & Barabási, A. L. (2000) Topology of evolving networks: local events and universality Physical review letters, 85(24), 5234. """ if m < 1 or m >= n: msg = "Extended Barabasi-Albert network needs m>=1 and m<n, m=%d, n=%d" raise nx.NetworkXError(msg % (m, n)) if p + q >= 1: msg = "Extended Barabasi-Albert network needs p + q <= 1, p=%d, q=%d" raise nx.NetworkXError(msg % (p, q)) if seed is not None: random.seed(seed) # Add m initial nodes (m0 in barabasi-speak) G = empty_graph(m) # List of nodes to represent the preferential attachment random selection. # At the creation of the graph, all nodes are added to the list # so that even nodes that are not connected have a chance to get selected, # for rewiring and adding of edges. # With each new edge, nodes at the ends of the edge are added to the list. attachment_preference = [] attachment_preference.extend(range(m)) # Start adding the other n-m nodes. The first node is m. new_node = m while new_node < n: a_probability = random.random() # Total number of edges of a Clique of all the nodes clique_degree = len(G) - 1 clique_size = (len(G) * clique_degree) / 2 # Adding m new edges, if there is room to add them if a_probability < p and G.size() <= clique_size - m: # Select the nodes where an edge can be added elligible_nodes = [ nd for nd, deg in G.degree() if deg < clique_degree ] for i in range(m): # Choosing a random source node from elligible_nodes src_node = random.choice(elligible_nodes) # Picking a possible node that is not 'src_node' or # neighbor with 'src_node', with preferential attachment prohibited_nodes = list(G[src_node]) prohibited_nodes.append(src_node) # This will raise an exception if the sequence is empty dest_node = random.choice([ nd for nd in attachment_preference if nd not in prohibited_nodes ]) # Adding the new edge G.add_edge(src_node, dest_node) # Appending both nodes to add to their preferential attachment attachment_preference.append(src_node) attachment_preference.append(dest_node) # Adjusting the elligible nodes. Degree may be saturated. if G.degree(src_node) == clique_degree: elligible_nodes.remove(src_node) if G.degree(dest_node) == clique_degree \ and dest_node in elligible_nodes: elligible_nodes.remove(dest_node) # Rewiring m edges, if there are enough edges elif p <= a_probability < (p + q) and m <= G.size() < clique_size: # Selecting nodes that have at least 1 edge but that are not # fully connected to ALL other nodes (center of star). # These nodes are the pivot nodes of the edges to rewire elligible_nodes = [ nd for nd, deg in G.degree() if 0 < deg < clique_degree ] for i in range(m): # Choosing a random source node node = random.choice(elligible_nodes) # The available nodes do have a neighbor at least. neighbor_nodes = list(G[node]) # Choosing the other end that will get dettached src_node = random.choice(neighbor_nodes) # Picking a target node that is not 'node' or # neighbor with 'node', with preferential attachment neighbor_nodes.append(node) dest_node = random.choice([ nd for nd in attachment_preference if nd not in neighbor_nodes ]) # Rewire G.remove_edge(node, src_node) G.add_edge(node, dest_node) # Adjusting the preferential attachment list attachment_preference.remove(src_node) attachment_preference.append(dest_node) # Adjusting the elligible nodes. # nodes may be saturated or isolated. if G.degree(src_node) == 0 and src_node in elligible_nodes: elligible_nodes.remove(src_node) if dest_node in elligible_nodes: if G.degree(dest_node) == clique_degree: elligible_nodes.remove(dest_node) else: if G.degree(dest_node) == 1: elligible_nodes.append(dest_node) # Adding new node with m edges else: # Select the edges' nodes by preferential attachment targets = _random_subset(attachment_preference, m) G.add_edges_from(zip([new_node] * m, targets)) # Add one node to the list for each new edge just created. attachment_preference.extend(targets) # The new node has m edges to it, plus itself: m + 1 attachment_preference.extend([new_node] * (m + 1)) new_node += 1 return G
def find_induced_nodes(G, s, t, treewidth_bound=sys.maxsize): """Returns the set of induced nodes in the path from s to t. Parameters ---------- G : graph A chordal NetworkX graph s : node Source node to look for induced nodes t : node Destination node to look for induced nodes treewith_bound: float Maximum treewidth acceptable for the graph H. The search for induced nodes will end as soon as the treewidth_bound is exceeded. Returns ------- Induced_nodes : Set of nodes The set of induced nodes in the path from s to t in G Raises ------ NetworkXError The algorithm does not support DiGraph, MultiGraph and MultiDiGraph. If the input graph is an instance of one of these classes, a :exc:`NetworkXError` is raised. The algorithm can only be applied to chordal graphs. If the input graph is found to be non-chordal, a :exc:`NetworkXError` is raised. Examples -------- >>> import networkx as nx >>> G=nx.Graph() >>> G = nx.generators.classic.path_graph(10) >>> Induced_nodes = nx.find_induced_nodes(G,1,9,2) >>> sorted(Induced_nodes) [1, 2, 3, 4, 5, 6, 7, 8, 9] Notes ----- G must be a chordal graph and (s,t) an edge that is not in G. If a treewidth_bound is provided, the search for induced nodes will end as soon as the treewidth_bound is exceeded. The algorithm is inspired by Algorithm 4 in [1]_. A formal definition of induced node can also be found on that reference. References ---------- .. [1] Learning Bounded Treewidth Bayesian Networks. Gal Elidan, Stephen Gould; JMLR, 9(Dec):2699--2731, 2008. http://jmlr.csail.mit.edu/papers/volume9/elidan08a/elidan08a.pdf """ if not is_chordal(G): raise nx.NetworkXError("Input graph is not chordal.") H = nx.Graph(G) H.add_edge(s, t) Induced_nodes = set() triplet = _find_chordality_breaker(H, s, treewidth_bound) while triplet: (u, v, w) = triplet Induced_nodes.update(triplet) for n in triplet: if n != s: H.add_edge(s, n) triplet = _find_chordality_breaker(H, s, treewidth_bound) if Induced_nodes: # Add t and the second node in the induced path from s to t. Induced_nodes.add(t) for u in G[s]: if len(Induced_nodes & set(G[u])) == 2: Induced_nodes.add(u) break return Induced_nodes
def union(G, H, rename=(None, None), name=None): """ Return the union of graphs G and H. Graphs G and H must be disjoint, otherwise an exception is raised. Parameters ---------- G,H : graph A NetworkX graph rename : bool , default=(None, None) Node names of G and H can be changed by specifying the tuple rename=('G-','H-') (for example). Node "u" in G is then renamed "G-u" and "v" in H is renamed "H-v". name : string Specify the name for the union graph Returns ------- U : A union graph with the same type as G. Notes ----- To force a disjoint union with node relabeling, use disjoint_union(G,H) or convert_node_labels_to integers(). Graph, edge, and node attributes are propagated from G and H to the union graph. If a graph attribute is present in both G and H the value from H is used. See Also -------- disjoint_union """ if not G.is_multigraph() == H.is_multigraph(): raise nx.NetworkXError('G and H must both be graphs or multigraphs.') # Union is the same type as G R = G.__class__() # add graph attributes, H attributes take precedent over G attributes R.graph.update(G.graph) R.graph.update(H.graph) # rename graph to obtain disjoint node labels def add_prefix(graph, prefix): if prefix is None: return graph def label(x): if is_string_like(x): name = prefix + x else: name = prefix + repr(x) return name return nx.relabel_nodes(graph, label) G = add_prefix(G, rename[0]) H = add_prefix(H, rename[1]) if set(G) & set(H): raise nx.NetworkXError( 'The node sets of G and H are not disjoint.', 'Use appropriate rename=(Gprefix,Hprefix)' 'or use disjoint_union(G,H).') if G.is_multigraph(): G_edges = G.edges(keys=True, data=True) else: G_edges = G.edges(data=True) if H.is_multigraph(): H_edges = H.edges(keys=True, data=True) else: H_edges = H.edges(data=True) # add nodes R.add_nodes_from(G) R.add_edges_from(G_edges) # add edges R.add_nodes_from(H) R.add_edges_from(H_edges) # add node attributes for n in G: R.nodes[n].update(G.nodes[n]) for n in H: R.nodes[n].update(H.nodes[n]) return R
def watts_strogatz_graph(n, k, p, seed=None): """Return a Watts–Strogatz small-world graph. Parameters ---------- n : int The number of nodes k : int Each node is joined with its `k` nearest neighbors in a ring topology. p : float The probability of rewiring each edge seed : int, optional Seed for random number generator (default=None) See Also -------- newman_watts_strogatz_graph() connected_watts_strogatz_graph() Notes ----- First create a ring over `n` nodes. Then each node in the ring is joined to its `k` nearest neighbors (or `k - 1` neighbors if `k` is odd). Then shortcuts are created by replacing some edges as follows: for each edge `(u, v)` in the underlying "`n`-ring with `k` nearest neighbors" with probability :math:`p` replace it with a new edge `(u, w)` with uniformly random choice of existing node `w`. In contrast with :func:`newman_watts_strogatz_graph`, the random rewiring does not increase the number of edges. The rewired graph is not guaranteed to be connected as in :func:`connected_watts_strogatz_graph`. References ---------- .. [1] Duncan J. Watts and Steven H. Strogatz, Collective dynamics of small-world networks, Nature, 393, pp. 440--442, 1998. """ if k >= n: raise nx.NetworkXError("k>=n, choose smaller k or larger n") if seed is not None: random.seed(seed) G = nx.Graph() nodes = list(range(n)) # nodes are labeled 0 to n-1 # connect each node to k/2 neighbors for j in range(1, k // 2 + 1): targets = nodes[j:] + nodes[0:j] # first j nodes are now last in list G.add_edges_from(zip(nodes, targets)) # rewire edges from each node # loop over all nodes in order (label) and neighbors in order (distance) # no self loops or multiple edges allowed for j in range(1, k // 2 + 1): # outer loop is neighbors targets = nodes[j:] + nodes[0:j] # first j nodes are now last in list # inner loop in node order for u, v in zip(nodes, targets): if random.random() < p: w = random.choice(nodes) # Enforce no self-loops or multiple edges while w == u or G.has_edge(u, w): w = random.choice(nodes) if G.degree(u) >= n - 1: break # skip this rewiring else: G.remove_edge(u, v) G.add_edge(u, w) return G
def from_pandas_edgelist( df, source="source", target="target", edge_attr=None, create_using=None, edge_key=None, ): """Returns a graph from Pandas DataFrame containing an edge list. The Pandas DataFrame should contain at least two columns of node names and zero or more columns of edge attributes. Each row will be processed as one edge instance. Note: This function iterates over DataFrame.values, which is not guaranteed to retain the data type across columns in the row. This is only a problem if your row is entirely numeric and a mix of ints and floats. In that case, all values will be returned as floats. See the DataFrame.iterrows documentation for an example. Parameters ---------- df : Pandas DataFrame An edge list representation of a graph source : str or int A valid column name (string or integer) for the source nodes (for the directed case). target : str or int A valid column name (string or integer) for the target nodes (for the directed case). edge_attr : str or int, iterable, True, or None A valid column name (str or int) or iterable of column names that are used to retrieve items and add them to the graph as edge attributes. If `True`, all of the remaining columns will be added. If `None`, no edge attributes are added to the graph. create_using : NetworkX graph constructor, optional (default=nx.Graph) Graph type to create. If graph instance, then cleared before populated. edge_key : str or None, optional (default=None) A valid column name for the edge keys (for a MultiGraph). The values in this column are used for the edge keys when adding edges if create_using is a multigraph. See Also -------- to_pandas_edgelist Examples -------- Simple integer weights on edges: >>> import pandas as pd >>> pd.options.display.max_columns = 20 >>> import numpy as np >>> rng = np.random.RandomState(seed=5) >>> ints = rng.randint(1, 11, size=(3, 2)) >>> a = ["A", "B", "C"] >>> b = ["D", "A", "E"] >>> df = pd.DataFrame(ints, columns=["weight", "cost"]) >>> df[0] = a >>> df["b"] = b >>> df[["weight", "cost", 0, "b"]] weight cost 0 b 0 4 7 A D 1 7 1 B A 2 10 9 C E >>> G = nx.from_pandas_edgelist(df, 0, "b", ["weight", "cost"]) >>> G["E"]["C"]["weight"] 10 >>> G["E"]["C"]["cost"] 9 >>> edges = pd.DataFrame( ... { ... "source": [0, 1, 2], ... "target": [2, 2, 3], ... "weight": [3, 4, 5], ... "color": ["red", "blue", "blue"], ... } ... ) >>> G = nx.from_pandas_edgelist(edges, edge_attr=True) >>> G[0][2]["color"] 'red' Build multigraph with custom keys: >>> edges = pd.DataFrame( ... { ... "source": [0, 1, 2, 0], ... "target": [2, 2, 3, 2], ... "my_edge_key": ["A", "B", "C", "D"], ... "weight": [3, 4, 5, 6], ... "color": ["red", "blue", "blue", "blue"], ... } ... ) >>> G = nx.from_pandas_edgelist( ... edges, ... edge_key="my_edge_key", ... edge_attr=["weight", "color"], ... create_using=nx.MultiGraph(), ... ) >>> G[0][2] AtlasView({'A': {'weight': 3, 'color': 'red'}, 'D': {'weight': 6, 'color': 'blue'}}) """ g = nx.empty_graph(0, create_using) if edge_attr is None: g.add_edges_from(zip(df[source], df[target])) return g reserved_columns = [source, target] # Additional columns requested attr_col_headings = [] attribute_data = [] if edge_attr is True: attr_col_headings = [ c for c in df.columns if c not in reserved_columns ] elif isinstance(edge_attr, (list, tuple)): attr_col_headings = edge_attr else: attr_col_headings = [edge_attr] if len(attr_col_headings) == 0: raise nx.NetworkXError( f"Invalid edge_attr argument: No columns found with name: {attr_col_headings}" ) try: attribute_data = zip(*[df[col] for col in attr_col_headings]) except (KeyError, TypeError) as e: msg = f"Invalid edge_attr argument: {edge_attr}" raise nx.NetworkXError(msg) from e if g.is_multigraph(): # => append the edge keys from the df to the bundled data if edge_key is not None: try: multigraph_edge_keys = df[edge_key] attribute_data = zip(attribute_data, multigraph_edge_keys) except (KeyError, TypeError) as e: msg = f"Invalid edge_key argument: {edge_key}" raise nx.NetworkXError(msg) from e for s, t, attrs in zip(df[source], df[target], attribute_data): if edge_key is not None: attrs, multigraph_edge_key = attrs key = g.add_edge(s, t, key=multigraph_edge_key) else: key = g.add_edge(s, t) g[s][t][key].update(zip(attr_col_headings, attrs)) else: for s, t, attrs in zip(df[source], df[target], attribute_data): g.add_edge(s, t) g[s][t].update(zip(attr_col_headings, attrs)) return g
def to_scipy_sparse_matrix(G, nodelist=None, dtype=None, weight="weight", format="csr"): """Returns the graph adjacency matrix as a SciPy sparse matrix. Parameters ---------- G : graph The NetworkX graph used to construct the sparse matrix. nodelist : list, optional The rows and columns are ordered according to the nodes in `nodelist`. If `nodelist` is None, then the ordering is produced by G.nodes(). dtype : NumPy data-type, optional A valid NumPy dtype used to initialize the array. If None, then the NumPy default is used. weight : string or None optional (default='weight') The edge attribute that holds the numerical value used for the edge weight. If None then all edge weights are 1. format : str in {'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'} The type of the matrix to be returned (default 'csr'). For some algorithms different implementations of sparse matrices can perform better. See [1]_ for details. Returns ------- M : SciPy sparse matrix Graph adjacency matrix. Notes ----- For directed graphs, matrix entry i,j corresponds to an edge from i to j. The matrix entries are populated using the edge attribute held in parameter weight. When an edge does not have that attribute, the value of the entry is 1. For multiple edges the matrix values are the sums of the edge weights. When `nodelist` does not contain every node in `G`, the adjacency matrix is built from the subgraph of `G` that is induced by the nodes in `nodelist`. The convention used for self-loop edges in graphs is to assign the diagonal matrix entry value to the weight attribute of the edge (or the number 1 if the edge has no weight attribute). If the alternate convention of doubling the edge weight is desired the resulting Scipy sparse matrix can be modified as follows: >>> G = nx.Graph([(1, 1)]) >>> A = nx.to_scipy_sparse_matrix(G) >>> print(A.todense()) [[1]] >>> A.setdiag(A.diagonal() * 2) >>> print(A.todense()) [[2]] Examples -------- >>> G = nx.MultiDiGraph() >>> G.add_edge(0, 1, weight=2) 0 >>> G.add_edge(1, 0) 0 >>> G.add_edge(2, 2, weight=3) 0 >>> G.add_edge(2, 2) 1 >>> S = nx.to_scipy_sparse_matrix(G, nodelist=[0, 1, 2]) >>> print(S.todense()) [[0 2 0] [1 0 0] [0 0 4]] References ---------- .. [1] Scipy Dev. References, "Sparse Matrices", https://docs.scipy.org/doc/scipy/reference/sparse.html """ import scipy as sp import scipy.sparse # call as sp.sparse if len(G) == 0: raise nx.NetworkXError("Graph has no nodes or edges") if nodelist is None: nodelist = list(G) nlen = len(G) else: nlen = len(nodelist) if nlen == 0: raise nx.NetworkXError("nodelist has no nodes") nodeset = set(G.nbunch_iter(nodelist)) if nlen != len(nodeset): for n in nodelist: if n not in G: raise nx.NetworkXError(f"Node {n} in nodelist is not in G") raise nx.NetworkXError("nodelist contains duplicates.") if nlen < len(G): G = G.subgraph(nodelist) index = dict(zip(nodelist, range(nlen))) coefficients = zip(*((index[u], index[v], wt) for u, v, wt in G.edges(data=weight, default=1))) try: row, col, data = coefficients except ValueError: # there is no edge in the subgraph row, col, data = [], [], [] if G.is_directed(): M = sp.sparse.coo_matrix((data, (row, col)), shape=(nlen, nlen), dtype=dtype) else: # symmetrize matrix d = data + data r = row + col c = col + row # selfloop entries get double counted when symmetrizing # so we subtract the data on the diagonal selfloops = list(nx.selfloop_edges(G, data=weight, default=1)) if selfloops: diag_index, diag_data = zip(*((index[u], -wt) for u, v, wt in selfloops)) d += diag_data r += diag_index c += diag_index M = sp.sparse.coo_matrix((d, (r, c)), shape=(nlen, nlen), dtype=dtype) try: return M.asformat(format) # From Scipy 1.1.0, asformat will throw a ValueError instead of an # AttributeError if the format if not recognized. except (AttributeError, ValueError) as e: raise nx.NetworkXError( f"Unknown sparse matrix format: {format}") from e
def average_shortest_path_length(G, weight=None, method=None): r"""Returns the average shortest path length. The average shortest path length is .. math:: a =\sum_{s,t \in V} \frac{d(s, t)}{n(n-1)} where `V` is the set of nodes in `G`, `d(s, t)` is the shortest path from `s` to `t`, and `n` is the number of nodes in `G`. Parameters ---------- G : NetworkX graph weight : None or string, optional (default = None) If None, every edge has weight/distance/cost 1. If a string, use this edge attribute as the edge weight. Any edge attribute not present defaults to 1. method : string, optional (default = 'unweighted' or 'djikstra') The algorithm to use to compute the path lengths. Supported options are 'unweighted', 'dijkstra', 'bellman-ford', 'floyd-warshall' and 'floyd-warshall-numpy'. Other method values produce a ValueError. The default method is 'unweighted' if `weight` is None, otherwise the default method is 'dijkstra'. Raises ------ NetworkXPointlessConcept If `G` is the null graph (that is, the graph on zero nodes). NetworkXError If `G` is not connected (or not weakly connected, in the case of a directed graph). ValueError If `method` is not among the supported options. Examples -------- >>> G = nx.path_graph(5) >>> nx.average_shortest_path_length(G) 2.0 For disconnected graphs, you can compute the average shortest path length for each component >>> G = nx.Graph([(1, 2), (3, 4)]) >>> for C in (G.subgraph(c).copy() for c in connected_components(G)): ... print(nx.average_shortest_path_length(C)) 1.0 1.0 """ single_source_methods = ['unweighted', 'dijkstra', 'bellman-ford'] all_pairs_methods = ['floyd-warshall', 'floyd-warshall-numpy'] supported_methods = single_source_methods + all_pairs_methods if method is None: method = 'unweighted' if weight is None else 'dijkstra' if method not in supported_methods: raise ValueError(f'method not supported: {method}') n = len(G) # For the special case of the null graph, raise an exception, since # there are no paths in the null graph. if n == 0: msg = ('the null graph has no paths, thus there is no average' 'shortest path length') raise nx.NetworkXPointlessConcept(msg) # For the special case of the trivial graph, return zero immediately. if n == 1: return 0 # Shortest path length is undefined if the graph is disconnected. if G.is_directed() and not nx.is_weakly_connected(G): raise nx.NetworkXError("Graph is not weakly connected.") if not G.is_directed() and not nx.is_connected(G): raise nx.NetworkXError("Graph is not connected.") # Compute all-pairs shortest paths. def path_length(v): if method == 'unweighted': return nx.single_source_shortest_path_length(G, v) elif method == 'dijkstra': return nx.single_source_dijkstra_path_length(G, v, weight=weight) elif method == 'bellman-ford': return nx.single_source_bellman_ford_path_length(G, v, weight=weight) if method in single_source_methods: # Sum the distances for each (ordered) pair of source and target node. s = sum(l for u in G for l in path_length(u).values()) else: if method == 'floyd-warshall': all_pairs = nx.floyd_warshall(G, weight=weight) s = sum([sum(t.values()) for t in all_pairs.values()]) elif method == 'floyd-warshall-numpy': s = nx.floyd_warshall_numpy(G, weight=weight).sum() return s / (n * (n - 1))
def _fruchterman_reingold(A, k=None, pos=None, fixed=None, iterations=50, threshold=1e-4, dim=2, seed=None): # Position nodes in adjacency matrix A using Fruchterman-Reingold # Entry point for NetworkX graph is fruchterman_reingold_layout() import numpy as np try: nnodes, _ = A.shape except AttributeError as e: msg = "fruchterman_reingold() takes an adjacency matrix as input" raise nx.NetworkXError(msg) from e if pos is None: # random initial positions pos = np.asarray(seed.rand(nnodes, dim), dtype=A.dtype) else: # make sure positions are of same type as matrix pos = pos.astype(A.dtype) # optimal distance between nodes if k is None: k = np.sqrt(1.0 / nnodes) # the initial "temperature" is about .1 of domain area (=1x1) # this is the largest step allowed in the dynamics. # We need to calculate this in case our fixed positions force our domain # to be much bigger than 1x1 t = max(max(pos.T[0]) - min(pos.T[0]), max(pos.T[1]) - min(pos.T[1])) * 0.1 # simple cooling scheme. # linearly step down by dt on each iteration so last iteration is size dt. dt = t / float(iterations + 1) delta = np.zeros((pos.shape[0], pos.shape[0], pos.shape[1]), dtype=A.dtype) # the inscrutable (but fast) version # this is still O(V^2) # could use multilevel methods to speed this up significantly for iteration in range(iterations): # matrix of difference between points delta = pos[:, np.newaxis, :] - pos[np.newaxis, :, :] # distance between points distance = np.linalg.norm(delta, axis=-1) # enforce minimum distance of 0.01 np.clip(distance, 0.01, None, out=distance) # displacement "force" displacement = np.einsum("ijk,ij->ik", delta, (k * k / distance**2 - A * distance / k)) # update positions length = np.linalg.norm(displacement, axis=-1) length = np.where(length < 0.01, 0.1, length) delta_pos = np.einsum("ij,i->ij", displacement, t / length) if fixed is not None: # don't change positions of fixed nodes delta_pos[fixed] = 0.0 pos += delta_pos # cool temperature t -= dt err = np.linalg.norm(delta_pos) / nnodes if err < threshold: break return pos
def complete_multipartite_graph(*block_sizes): """Returns the complete multipartite graph with the specified block sizes. Parameters ---------- block_sizes : tuple of integers or tuple of node iterables The arguments can either all be integer number of nodes or they can all be iterables of nodes. If integers, they represent the number of vertices in each block of the multipartite graph. If iterables, each is used to create the nodes for that block. The length of block_sizes is the number of blocks. Returns ------- G : NetworkX Graph Returns the complete multipartite graph with the specified blocks. For each node, the node attribute 'block' is an integer indicating which block contains the node. Examples -------- Creating a complete tripartite graph, with blocks of one, two, and three vertices, respectively. >>> import networkx as nx >>> G = nx.complete_multipartite_graph(1, 2, 3) >>> [G.node[u]['block'] for u in G] [0, 1, 1, 2, 2, 2] >>> list(G.edges(0)) [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)] >>> list(G.edges(2)) [(2, 0), (2, 3), (2, 4), (2, 5)] >>> list(G.edges(4)) [(4, 0), (4, 1), (4, 2)] >>> G = nx.complete_multipartite_graph('a', 'bc', 'def') >>> [G.node[u]['block'] for u in sorted(G)] [0, 1, 1, 2, 2, 2] Notes ----- This function generalizes several other graph generator functions. - If no block sizes are given, this returns the null graph. - If a single block size `n` is given, this returns the empty graph on `n` nodes. - If two block sizes `m` and `n` are given, this returns the complete bipartite graph on `m + n` nodes. - If block sizes `1` and `n` are given, this returns the star graph on `n + 1` nodes. See also -------- complete_bipartite_graph """ # The complete multipartite graph is an undirected simple graph. G = nx.Graph() G.name = 'complete_multiparite_graph{}'.format(block_sizes) if len(block_sizes) == 0: return G # set up blocks of nodes try: extents = pairwise(accumulate((0, ) + block_sizes)) blocks = [range(start, end) for start, end in extents] except TypeError: blocks = block_sizes # add nodes with block attribute # while checking that ints are not mixed with iterables try: for (i, block) in enumerate(blocks): G.add_nodes_from(block, block=i) except TypeError: raise nx.NetworkXError("Arguments must be all ints or all iterables") # Across blocks, all vertices should be adjacent. # We can use itertools.combinations() because undirected. for block1, block2 in itertools.combinations(blocks, 2): G.add_edges_from(itertools.product(block1, block2)) return G
def frozen(*args): """Dummy method for raising errors when trying to modify frozen graphs""" raise nx.NetworkXError("Frozen graph can't be modified")
def prim_mst_edges_sparse(G, weight='weight', data=True): """Generate edges in a minimum spanning forest of an undirected weighted graph. A minimum spanning tree is a subgraph of the graph (a tree) with the minimum sum of edge weights. A spanning forest is a union of the spanning trees for each connected component of the graph. Parameters ---------- G : NetworkX Graph weight : string Edge data key to use for weight (default 'weight'). data : bool, optional If True yield the edge data along with the edge. Returns ------- edges : iterator A generator that produces edges in the minimum spanning tree. The edges are three-tuples (u,v,w) where w is the weight. Examples -------- >> G=nx.cycle_graph(4) >> G.add_edge(0,3,weight=2) # assign weight 2 to edge 0-3 >> mst=nx.prim_mst_edges(G,data=False) # a generator of MST edges >> edgelist=list(mst) # make a list of the edges >> print(sorted(edgelist)) [(0, 1), (1, 2), (2, 3)] Notes ----- Uses Prim's algorithm. If the graph edges do not have a weight attribute a default weight of 1 will be used. """ if G.is_directed(): raise nx.NetworkXError( "Mimimum spanning tree not defined for directed graphs.") push = heappush pop = heappop nodes = list(G.nodes()) c = count() while nodes: u = nodes.pop(0) frontier = [] visited = [u] for u, v in G.edges(u): push(frontier, (G[u][v].get(weight, 1), next(c), u, v)) while frontier: W, _, u, v = pop(frontier) if v in visited: continue visited.append(v) nodes.remove(v) for v, w in G.edges(v): if not w in visited: push(frontier, (G[v][w].get(weight, 1), next(c), v, w)) if data: yield (u, v, G[u][v]) else: yield (u, v)
def draw_networkx_nodes(G, pos, nodelist=None, node_size=300, node_color='r', node_shape='o', alpha=1.0, cmap=None, vmin=None, vmax=None, ax=None, linewidths=None, label=None, **kwds): """Draw the nodes of the graph G. This draws only the nodes of the graph G. Parameters ---------- G : graph A networkx graph pos : dictionary A dictionary with nodes as keys and positions as values. Positions should be sequences of length 2. ax : Matplotlib Axes object, optional Draw the graph in the specified Matplotlib axes. nodelist : list, optional Draw only specified nodes (default G.nodes()) node_size : scalar or array Size of nodes (default=300). If an array is specified it must be the same length as nodelist. node_color : color string, or array of floats Node color. Can be a single color format string (default='r'), or a sequence of colors with the same length as nodelist. If numeric values are specified they will be mapped to colors using the cmap and vmin,vmax parameters. See matplotlib.scatter for more details. node_shape : string The shape of the node. Specification is as matplotlib.scatter marker, one of 'so^>v<dph8' (default='o'). alpha : float The node transparency (default=1.0) cmap : Matplotlib colormap Colormap for mapping intensities of nodes (default=None) vmin,vmax : floats Minimum and maximum for node colormap scaling (default=None) linewidths : [None | scalar | sequence] Line width of symbol border (default =1.0) label : [None| string] Label for legend Returns ------- matplotlib.collections.PathCollection `PathCollection` of the nodes. Examples -------- >>> G=nx.dodecahedral_graph() >>> nodes=nx.draw_networkx_nodes(G,pos=nx.spring_layout(G)) Also see the NetworkX drawing examples at http://networkx.lanl.gov/gallery.html See Also -------- draw() draw_networkx() draw_networkx_edges() draw_networkx_labels() draw_networkx_edge_labels() """ try: import matplotlib.pyplot as plt import numpy except ImportError: raise ImportError("Matplotlib required for draw()") except RuntimeError: print("Matplotlib unable to open display") raise if ax is None: ax = plt.gca() if nodelist is None: nodelist = G.nodes() if not nodelist or len(nodelist) == 0: # empty nodelist, no drawing return None try: xy = numpy.asarray([pos[v] for v in nodelist]) except KeyError as e: raise nx.NetworkXError('Node %s has no position.' % e) except ValueError: raise nx.NetworkXError('Bad value in node positions.') node_collection = ax.scatter(xy[:, 0], xy[:, 1], s=node_size, c=node_color, marker=node_shape, cmap=cmap, vmin=vmin, vmax=vmax, alpha=alpha, linewidths=linewidths, label=label) node_collection.set_zorder(2) return node_collection
def min(self): if self._root is None: raise nx.NetworkXError('heap is empty.') return (self._root.key, self._root.value)
def from_numpy_array(A, parallel_edges=False, create_using=None): """Returns a graph from a 2D NumPy array. The 2D NumPy array is interpreted as an adjacency matrix for the graph. Parameters ---------- A : a 2D numpy.ndarray An adjacency matrix representation of a graph parallel_edges : Boolean If this is True, `create_using` is a multigraph, and `A` is an integer array, then entry *(i, j)* in the array is interpreted as the number of parallel edges joining vertices *i* and *j* in the graph. If it is False, then the entries in the array are interpreted as the weight of a single edge joining the vertices. create_using : NetworkX graph constructor, optional (default=nx.Graph) Graph type to create. If graph instance, then cleared before populated. Notes ----- For directed graphs, explicitly mention create_using=nx.DiGraph, and entry i,j of A corresponds to an edge from i to j. If `create_using` is :class:`networkx.MultiGraph` or :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the entries of `A` are of type :class:`int`, then this function returns a multigraph (of the same type as `create_using`) with parallel edges. If `create_using` indicates an undirected multigraph, then only the edges indicated by the upper triangle of the array `A` will be added to the graph. If the NumPy array has a single data type for each array entry it will be converted to an appropriate Python data type. If the NumPy array has a user-specified compound data type the names of the data fields will be used as attribute keys in the resulting NetworkX graph. See Also -------- to_numpy_array Examples -------- Simple integer weights on edges: >>> import numpy as np >>> A = np.array([[1, 1], [2, 1]]) >>> G = nx.from_numpy_array(A) >>> G.edges(data=True) EdgeDataView([(0, 0, {'weight': 1}), (0, 1, {'weight': 2}), (1, 1, {'weight': 1})]) If `create_using` indicates a multigraph and the array has only integer entries and `parallel_edges` is False, then the entries will be treated as weights for edges joining the nodes (without creating parallel edges): >>> A = np.array([[1, 1], [1, 2]]) >>> G = nx.from_numpy_array(A, create_using=nx.MultiGraph) >>> G[1][1] AtlasView({0: {'weight': 2}}) If `create_using` indicates a multigraph and the array has only integer entries and `parallel_edges` is True, then the entries will be treated as the number of parallel edges joining those two vertices: >>> A = np.array([[1, 1], [1, 2]]) >>> temp = nx.MultiGraph() >>> G = nx.from_numpy_array(A, parallel_edges=True, create_using=temp) >>> G[1][1] AtlasView({0: {'weight': 1}, 1: {'weight': 1}}) User defined compound data type on edges: >>> dt = [("weight", float), ("cost", int)] >>> A = np.array([[(1.0, 2)]], dtype=dt) >>> G = nx.from_numpy_array(A) >>> G.edges() EdgeView([(0, 0)]) >>> G[0][0]["cost"] 2 >>> G[0][0]["weight"] 1.0 """ kind_to_python_type = { "f": float, "i": int, "u": int, "b": bool, "c": complex, "S": str, "U": str, "V": "void", } G = nx.empty_graph(0, create_using) if A.ndim != 2: raise nx.NetworkXError(f"Input array must be 2D, not {A.ndim}") n, m = A.shape if n != m: raise nx.NetworkXError(f"Adjacency matrix not square: nx,ny={A.shape}") dt = A.dtype try: python_type = kind_to_python_type[dt.kind] except Exception as e: raise TypeError(f"Unknown numpy data type: {dt}") from e # Make sure we get even the isolated nodes of the graph. G.add_nodes_from(range(n)) # Get a list of all the entries in the array with nonzero entries. These # coordinates become edges in the graph. (convert to int from np.int64) edges = ((int(e[0]), int(e[1])) for e in zip(*A.nonzero())) # handle numpy constructed data type if python_type == "void": # Sort the fields by their offset, then by dtype, then by name. fields = sorted((offset, dtype, name) for name, (dtype, offset) in A.dtype.fields.items()) triples = (( u, v, { name: kind_to_python_type[dtype.kind](val) for (_, dtype, name), val in zip(fields, A[u, v]) }, ) for u, v in edges) # If the entries in the adjacency matrix are integers, the graph is a # multigraph, and parallel_edges is True, then create parallel edges, each # with weight 1, for each entry in the adjacency matrix. Otherwise, create # one edge for each positive entry in the adjacency matrix and set the # weight of that edge to be the entry in the matrix. elif python_type is int and G.is_multigraph() and parallel_edges: chain = itertools.chain.from_iterable # The following line is equivalent to: # # for (u, v) in edges: # for d in range(A[u, v]): # G.add_edge(u, v, weight=1) # triples = chain(((u, v, { "weight": 1 }) for d in range(A[u, v])) for (u, v) in edges) else: # basic data type triples = ((u, v, dict(weight=python_type(A[u, v]))) for u, v in edges) # If we are creating an undirected multigraph, only add the edges from the # upper triangle of the matrix. Otherwise, add all the edges. This relies # on the fact that the vertices created in the # `_generated_weighted_edges()` function are actually the row/column # indices for the matrix `A`. # # Without this check, we run into a problem where each edge is added twice # when `G.add_edges_from()` is invoked below. if G.is_multigraph() and not G.is_directed(): triples = ((u, v, d) for u, v, d in triples if u <= v) G.add_edges_from(triples) return G
def _sparse_fruchterman_reingold(A, k=None, pos=None, fixed=None, iterations=50, threshold=1e-4, dim=2, seed=None): # Position nodes in adjacency matrix A using Fruchterman-Reingold # Entry point for NetworkX graph is fruchterman_reingold_layout() # Sparse version import numpy as np try: nnodes, _ = A.shape except AttributeError as e: msg = "fruchterman_reingold() takes an adjacency matrix as input" raise nx.NetworkXError(msg) from e try: from scipy.sparse import coo_matrix except ImportError as e: msg = "_sparse_fruchterman_reingold() scipy numpy: http://scipy.org/ " raise ImportError(msg) from e # make sure we have a LIst of Lists representation try: A = A.tolil() except AttributeError: A = (coo_matrix(A)).tolil() if pos is None: # random initial positions pos = np.asarray(seed.rand(nnodes, dim), dtype=A.dtype) else: # make sure positions are of same type as matrix pos = pos.astype(A.dtype) # no fixed nodes if fixed is None: fixed = [] # optimal distance between nodes if k is None: k = np.sqrt(1.0 / nnodes) # the initial "temperature" is about .1 of domain area (=1x1) # this is the largest step allowed in the dynamics. t = max(max(pos.T[0]) - min(pos.T[0]), max(pos.T[1]) - min(pos.T[1])) * 0.1 # simple cooling scheme. # linearly step down by dt on each iteration so last iteration is size dt. dt = t / float(iterations + 1) displacement = np.zeros((dim, nnodes)) for iteration in range(iterations): displacement *= 0 # loop over rows for i in range(A.shape[0]): if i in fixed: continue # difference between this row's node position and all others delta = (pos[i] - pos).T # distance between points distance = np.sqrt((delta**2).sum(axis=0)) # enforce minimum distance of 0.01 distance = np.where(distance < 0.01, 0.01, distance) # the adjacency matrix row Ai = np.asarray(A.getrowview(i).toarray()) # displacement "force" displacement[:, i] += (delta * (k * k / distance**2 - Ai * distance / k)).sum( axis=1) # update positions length = np.sqrt((displacement**2).sum(axis=0)) length = np.where(length < 0.01, 0.1, length) delta_pos = (displacement * t / length).T pos += delta_pos # cool temperature t -= dt err = np.linalg.norm(delta_pos) / nnodes if err < threshold: break return pos
def to_pandas_edgelist( G, source="source", target="target", nodelist=None, dtype=None, order=None, edge_key=None, ): """Returns the graph edge list as a Pandas DataFrame. Parameters ---------- G : graph The NetworkX graph used to construct the Pandas DataFrame. source : str or int, optional A valid column name (string or integer) for the source nodes (for the directed case). target : str or int, optional A valid column name (string or integer) for the target nodes (for the directed case). nodelist : list, optional Use only nodes specified in nodelist dtype : dtype, default None Use to create the DataFrame. Data type to force. Only a single dtype is allowed. If None, infer. order : None An unused parameter mistakenly included in the function. .. deprecated:: 2.6 This is deprecated and will be removed in NetworkX v3.0. edge_key : str or int or None, optional (default=None) A valid column name (string or integer) for the edge keys (for the multigraph case). If None, edge keys are not stored in the DataFrame. Returns ------- df : Pandas DataFrame Graph edge list Examples -------- >>> G = nx.Graph( ... [ ... ("A", "B", {"cost": 1, "weight": 7}), ... ("C", "E", {"cost": 9, "weight": 10}), ... ] ... ) >>> df = nx.to_pandas_edgelist(G, nodelist=["A", "C"]) >>> df[["source", "target", "cost", "weight"]] source target cost weight 0 A B 1 7 1 C E 9 10 >>> G = nx.MultiGraph([('A', 'B', {'cost': 1}), ('A', 'B', {'cost': 9})]) >>> df = nx.to_pandas_edgelist(G, nodelist=['A', 'C'], edge_key='ekey') >>> df[['source', 'target', 'cost', 'ekey']] source target cost ekey 0 A B 1 0 1 A B 9 1 """ import pandas as pd if nodelist is None: edgelist = G.edges(data=True) else: edgelist = G.edges(nodelist, data=True) source_nodes = [s for s, _, _ in edgelist] target_nodes = [t for _, t, _ in edgelist] all_attrs = set().union(*(d.keys() for _, _, d in edgelist)) if source in all_attrs: raise nx.NetworkXError(f"Source name {source!r} is an edge attr name") if target in all_attrs: raise nx.NetworkXError(f"Target name {target!r} is an edge attr name") nan = float("nan") edge_attr = {k: [d.get(k, nan) for _, _, d in edgelist] for k in all_attrs} if G.is_multigraph() and edge_key is not None: if edge_key in all_attrs: raise nx.NetworkXError( f"Edge key name {edge_key!r} is an edge attr name") edge_keys = [k for _, _, k in G.edges(keys=True)] edgelistdict = { source: source_nodes, target: target_nodes, edge_key: edge_keys } else: edgelistdict = {source: source_nodes, target: target_nodes} edgelistdict.update(edge_attr) return pd.DataFrame(edgelistdict, dtype=dtype)
def shortest_path_length(G,source=None,target=None,weighted=False): """Compute shortest path lengths in the graph. This function can compute the single source shortest path lengths by specifying only the source or all pairs shortest path lengths by specifying neither the source or target. Parameters ---------- G : NetworkX graph source : node, optional Starting node for path. If not specified compute shortest pats lenghts for all connected node pairs. target : node, optional Ending node for path. If not specified compute shortest path lenghts for every node reachable from the source. weighted : bool, optional If True consider weighted edges when finding shortest path length. Returns ------- length : number, or container of numbers If the source and target are both specified return a single number for the shortest path. If only the source is specified return a dictionary keyed by targets with a the shortest path as keys. If neither the source or target is specified return a dictionary of dictionaries with length[source][target]=value. Raises ------ NetworkXNoPath If no path exists between source and target. Examples -------- >>> G=nx.path_graph(5) >>> print(nx.shortest_path_length(G,source=0,target=4)) 4 >>> p=nx.shortest_path_length(G,source=0) # target not specified >>> p[4] 4 >>> p=nx.shortest_path_length(G) # source,target not specified >>> p[0][4] 4 Notes ----- If weighted=True and the graph has no 'weight' edge attribute the value 1 will be used. For digraphs this returns the shortest directed path. To find path lengths in the reverse direction use G.reverse(copy=False) first to flip the edge orientation. """ if source is None: if target is None: if weighted: paths=nx.all_pairs_dijkstra_path_length(G) else: paths=nx.all_pairs_shortest_path_length(G) else: raise nx.NetworkXError(\ "Target given but no source specified.") else: # source specified if target is None: if weighted: paths=nx.single_source_dijkstra_path_length(G,source) else: paths=nx.single_source_shortest_path_length(G,source) else: # shortest source-target path if weighted: paths=nx.dijkstra_path_length(G,source,target) else: p=nx.bidirectional_shortest_path(G,source,target) paths=len(p)-1 return paths
def to_numpy_recarray(G, nodelist=None, dtype=None, order=None): """Returns the graph adjacency matrix as a NumPy recarray. Parameters ---------- G : graph The NetworkX graph used to construct the NumPy recarray. nodelist : list, optional The rows and columns are ordered according to the nodes in `nodelist`. If `nodelist` is None, then the ordering is produced by G.nodes(). dtype : NumPy data-type, optional A valid NumPy named dtype used to initialize the NumPy recarray. The data type names are assumed to be keys in the graph edge attribute dictionary. order : {'C', 'F'}, optional Whether to store multidimensional data in C- or Fortran-contiguous (row- or column-wise) order in memory. If None, then the NumPy default is used. Returns ------- M : NumPy recarray The graph with specified edge data as a Numpy recarray Notes ----- When `nodelist` does not contain every node in `G`, the adjacency matrix is built from the subgraph of `G` that is induced by the nodes in `nodelist`. Examples -------- >>> G = nx.Graph() >>> G.add_edge(1, 2, weight=7.0, cost=5) >>> A = nx.to_numpy_recarray(G, dtype=[("weight", float), ("cost", int)]) >>> print(A.weight) [[0. 7.] [7. 0.]] >>> print(A.cost) [[0 5] [5 0]] """ import numpy as np if dtype is None: dtype = [("weight", float)] if nodelist is None: nodelist = list(G) nodeset = G nlen = len(G) else: nlen = len(nodelist) nodeset = set(G.nbunch_iter(nodelist)) if nlen != len(nodeset): for n in nodelist: if n not in G: raise nx.NetworkXError(f"Node {n} in nodelist is not in G") raise nx.NetworkXError("nodelist contains duplicates.") undirected = not G.is_directed() index = dict(zip(nodelist, range(nlen))) M = np.zeros((nlen, nlen), dtype=dtype, order=order) names = M.dtype.names for u, v, attrs in G.edges(data=True): if (u in nodeset) and (v in nodeset): i, j = index[u], index[v] values = tuple([attrs[n] for n in names]) M[i, j] = values if undirected: M[j, i] = M[i, j] return M.view(np.recarray)
def average_shortest_path_length(G,weighted=False): """ Return the average shortest path length. The average shortest path length is .. math:: a =\\sum_{s,t \\in V} \\frac{d(s, t)}{n(n-1)} where :math:`V` is the set of nodes in :math:`G`, :math:`d(s, t)` is the shortest path from :math:`s` to :math:`t`, and :math:`n` is the number of nodes in :math:`G`. Parameters ---------- G : NetworkX graph weighted : bool, optional, default=False If True use edge weights on path. Raises ------ NetworkXError: if the graph is not connected. Examples -------- >>> G=nx.path_graph(5) >>> print(nx.average_shortest_path_length(G)) 2.0 For disconnected graphs you can compute the average shortest path length for each component: >>> G=nx.Graph([(1,2),(3,4)]) >>> for g in nx.connected_component_subgraphs(G): ... print(nx.average_shortest_path_length(g)) 1.0 1.0 Notes ----- If weighted=True and the graph has no 'weight' edge attribute the value 1 will be used. """ if weighted: path_length=nx.single_source_dijkstra_path_length else: path_length=nx.single_source_shortest_path_length if G.is_directed(): if not nx.is_weakly_connected(G): raise nx.NetworkXError("Graph is not connected.") else: if not nx.is_connected(G): raise nx.NetworkXError("Graph is not connected.") avg=0.0 n=len(G) for node in G: l=list(path_length(G,node).values()) avg+=sum(l) return avg/(n*(n-1))
def from_scipy_sparse_matrix(A, parallel_edges=False, create_using=None, edge_attribute="weight"): """Creates a new graph from an adjacency matrix given as a SciPy sparse matrix. Parameters ---------- A: scipy sparse matrix An adjacency matrix representation of a graph parallel_edges : Boolean If this is True, `create_using` is a multigraph, and `A` is an integer matrix, then entry *(i, j)* in the matrix is interpreted as the number of parallel edges joining vertices *i* and *j* in the graph. If it is False, then the entries in the matrix are interpreted as the weight of a single edge joining the vertices. create_using : NetworkX graph constructor, optional (default=nx.Graph) Graph type to create. If graph instance, then cleared before populated. edge_attribute: string Name of edge attribute to store matrix numeric value. The data will have the same type as the matrix entry (int, float, (real,imag)). Notes ----- For directed graphs, explicitly mention create_using=nx.DiGraph, and entry i,j of A corresponds to an edge from i to j. If `create_using` is :class:`networkx.MultiGraph` or :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the entries of `A` are of type :class:`int`, then this function returns a multigraph (constructed from `create_using`) with parallel edges. In this case, `edge_attribute` will be ignored. If `create_using` indicates an undirected multigraph, then only the edges indicated by the upper triangle of the matrix `A` will be added to the graph. Examples -------- >>> import scipy as sp >>> import scipy.sparse # call as sp.sparse >>> A = sp.sparse.eye(2, 2, 1) >>> G = nx.from_scipy_sparse_matrix(A) If `create_using` indicates a multigraph and the matrix has only integer entries and `parallel_edges` is False, then the entries will be treated as weights for edges joining the nodes (without creating parallel edges): >>> A = sp.sparse.csr_matrix([[1, 1], [1, 2]]) >>> G = nx.from_scipy_sparse_matrix(A, create_using=nx.MultiGraph) >>> G[1][1] AtlasView({0: {'weight': 2}}) If `create_using` indicates a multigraph and the matrix has only integer entries and `parallel_edges` is True, then the entries will be treated as the number of parallel edges joining those two vertices: >>> A = sp.sparse.csr_matrix([[1, 1], [1, 2]]) >>> G = nx.from_scipy_sparse_matrix( ... A, parallel_edges=True, create_using=nx.MultiGraph ... ) >>> G[1][1] AtlasView({0: {'weight': 1}, 1: {'weight': 1}}) """ G = nx.empty_graph(0, create_using) n, m = A.shape if n != m: raise nx.NetworkXError(f"Adjacency matrix not square: nx,ny={A.shape}") # Make sure we get even the isolated nodes of the graph. G.add_nodes_from(range(n)) # Create an iterable over (u, v, w) triples and for each triple, add an # edge from u to v with weight w. triples = _generate_weighted_edges(A) # If the entries in the adjacency matrix are integers, the graph is a # multigraph, and parallel_edges is True, then create parallel edges, each # with weight 1, for each entry in the adjacency matrix. Otherwise, create # one edge for each positive entry in the adjacency matrix and set the # weight of that edge to be the entry in the matrix. if A.dtype.kind in ("i", "u") and G.is_multigraph() and parallel_edges: chain = itertools.chain.from_iterable # The following line is equivalent to: # # for (u, v) in edges: # for d in range(A[u, v]): # G.add_edge(u, v, weight=1) # triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples) # If we are creating an undirected multigraph, only add the edges from the # upper triangle of the matrix. Otherwise, add all the edges. This relies # on the fact that the vertices created in the # `_generated_weighted_edges()` function are actually the row/column # indices for the matrix `A`. # # Without this check, we run into a problem where each edge is added twice # when `G.add_weighted_edges_from()` is invoked below. if G.is_multigraph() and not G.is_directed(): triples = ((u, v, d) for u, v, d in triples if u <= v) G.add_weighted_edges_from(triples, weight=edge_attribute) return G
def shortest_path(G,source=None,target=None,weighted=False): """Compute shortest paths in the graph. Parameters ---------- G : NetworkX graph source : node, optional Starting node for path. If not specified compute shortest paths for all connected node pairs. target : node, optional Ending node for path. If not specified compute shortest paths for every node reachable from the source. weighted : bool, optional If True consider weighted edges when finding shortest path. Returns ------- path: list or dictionary If the source and target are both specified return a single list of nodes in a shortest path. If only the source is specified return a dictionary keyed by targets with a list of nodes in a shortest path. If neither the source or target is specified return a dictionary of dictionaries with path[source][target]=[list of nodes in path]. Examples -------- >>> G=nx.path_graph(5) >>> print(nx.shortest_path(G,source=0,target=4)) [0, 1, 2, 3, 4] >>> p=nx.shortest_path(G,source=0) # target not specified >>> p[4] [0, 1, 2, 3, 4] >>> p=nx.shortest_path(G) # source,target not specified >>> p[0][4] [0, 1, 2, 3, 4] Notes ----- There may be more than one shortest path between a source and target. This returns only one of them. If weighted=True and the graph has no 'weight' edge attribute the value 1 will be used. For digraphs this returns a shortest directed path. To find paths in the reverse direction use G.reverse(copy=False) first to flip the edge orientation. """ if source is None: if target is None: if weighted: paths=nx.all_pairs_dijkstra_path(G) else: paths=nx.all_pairs_shortest_path(G) else: raise nx.NetworkXError(\ "Target given but no source specified.") else: # source specified if target is None: if weighted: paths=nx.single_source_dijkstra_path(G,source) else: paths=nx.single_source_shortest_path(G,source) else: # shortest source-target path if weighted: paths=nx.dijkstra_path(G,source,target) else: paths=nx.bidirectional_shortest_path(G,source,target) return paths
def newman_watts_strogatz_graph(n, k, p, seed=None): """Return a Newman–Watts–Strogatz small-world graph. Parameters ---------- n : int The number of nodes. k : int Each node is joined with its `k` nearest neighbors in a ring topology. p : float The probability of adding a new edge for each edge. seed : int, optional The seed for the random number generator (the default is None). Notes ----- First create a ring over `n` nodes. Then each node in the ring is connected with its `k` nearest neighbors (or `k - 1` neighbors if `k` is odd). Then shortcuts are created by adding new edges as follows: for each edge `(u, v)` in the underlying "`n`-ring with `k` nearest neighbors" with probability :math:`p` add a new edge `(u, w)` with randomly-chosen existing node `w`. In contrast with :func:`watts_strogatz_graph`, no edges are removed. See Also -------- watts_strogatz_graph() References ---------- .. [1] M. E. J. Newman and D. J. Watts, Renormalization group analysis of the small-world network model, Physics Letters A, 263, 341, 1999. http://dx.doi.org/10.1016/S0375-9601(99)00757-4 """ if seed is not None: random.seed(seed) if k >= n: raise nx.NetworkXError("k>=n, choose smaller k or larger n") G = empty_graph(n) nlist = list(G.nodes()) fromv = nlist # connect the k/2 neighbors for j in range(1, k // 2 + 1): tov = fromv[j:] + fromv[0:j] # the first j are now last for i in range(len(fromv)): G.add_edge(fromv[i], tov[i]) # for each edge u-v, with probability p, randomly select existing # node w and add new edge u-w e = list(G.edges()) for (u, v) in e: if random.random() < p: w = random.choice(nlist) # no self-loops and reject if edge u-w exists # is that the correct NWS model? while w == u or G.has_edge(u, w): w = random.choice(nlist) if G.degree(u) >= n - 1: break # skip this rewiring else: G.add_edge(u, w) return G
def to_networkx_graph(data, create_using=None, multigraph_input=False): """Make a NetworkX graph from a known data structure. The preferred way to call this is automatically from the class constructor >>> d={0: {1: {'weight':1}}} # dict-of-dicts single edge (0,1) >>> G=nx.Graph(d) instead of the equivalent >>> G=nx.from_dict_of_dicts(d) Parameters ---------- data : a object to be converted Current known types are: any NetworkX graph dict-of-dicts dist-of-lists list of edges numpy matrix numpy ndarray scipy sparse matrix pygraphviz agraph create_using : NetworkX graph Use specified graph for result. Otherwise a new graph is created. multigraph_input : bool (default False) If True and data is a dict_of_dicts, try to create a multigraph assuming dict_of_dict_of_lists. If data and create_using are both multigraphs then create a multigraph from a multigraph. """ # NX graph if hasattr(data, "adj"): try: result= from_dict_of_dicts(data.adj,\ create_using=create_using,\ multigraph_input=data.is_multigraph()) if hasattr(data, 'graph') and isinstance(data.graph, dict): result.graph = data.graph.copy() if hasattr(data, 'node') and isinstance(data.node, dict): result.node = dict( (n, dd.copy()) for n, dd in data.node.items()) return result except: raise nx.NetworkXError("Input is not a correct NetworkX graph.") # pygraphviz agraph if hasattr(data, "is_strict"): try: return nx.from_agraph(data, create_using=create_using) except: raise nx.NetworkXError("Input is not a correct pygraphviz graph.") # dict of dicts/lists if isinstance(data, dict): try: return from_dict_of_dicts(data,create_using=create_using,\ multigraph_input=multigraph_input) except: try: return from_dict_of_lists(data, create_using=create_using) except: raise TypeError("Input is not known type.") # list or generator of edges if (isinstance(data, list) or isinstance(data, tuple) or hasattr(data, 'next') or hasattr(data, '__next__')): try: return from_edgelist(data, create_using=create_using) except: raise nx.NetworkXError("Input is not a valid edge list") # Pandas DataFrame try: import pandas as pd if isinstance(data, pd.DataFrame): try: return nx.from_pandas_dataframe(data, create_using=create_using) except: msg = "Input is not a correct Pandas DataFrame." raise nx.NetworkXError(msg) except ImportError: msg = 'pandas not found, skipping conversion test.' warnings.warn(msg, ImportWarning) # numpy matrix or ndarray try: import numpy if isinstance(data,numpy.matrix) or \ isinstance(data,numpy.ndarray): try: return nx.from_numpy_matrix(data, create_using=create_using) except: raise nx.NetworkXError(\ "Input is not a correct numpy matrix or array.") except ImportError: warnings.warn('numpy not found, skipping conversion test.', ImportWarning) # scipy sparse matrix - any format try: import scipy if hasattr(data, "format"): try: return nx.from_scipy_sparse_matrix(data, create_using=create_using) except: raise nx.NetworkXError(\ "Input is not a correct scipy sparse matrix type.") except ImportError: warnings.warn('scipy not found, skipping conversion test.', ImportWarning) raise nx.NetworkXError(\ "Input is not a known data type for conversion.") return
def random_regular_graph(d, n, seed=None): """Returns a random `d`-regular graph on `n` nodes. The resulting graph has no self-loops or parallel edges. Parameters ---------- d : int The degree of each node. n : integer The number of nodes. The value of :math:`n * d` must be even. seed : hashable object The seed for random number generator. Notes ----- The nodes are numbered from `0` to `n - 1`. Kim and Vu's paper [2]_ shows that this algorithm samples in an asymptotically uniform way from the space of random graphs when `d = O(n^{1 / 3 - \epsilon})`. Raises ------ NetworkXError If :math:`n * d` is odd or `d` is greater than or equal to `n`. References ---------- .. [1] A. Steger and N. Wormald, Generating random regular graphs quickly, Probability and Computing 8 (1999), 377-396, 1999. http://citeseer.ist.psu.edu/steger99generating.html .. [2] Jeong Han Kim and Van H. Vu, Generating random regular graphs, Proceedings of the thirty-fifth ACM symposium on Theory of computing, San Diego, CA, USA, pp 213--222, 2003. http://portal.acm.org/citation.cfm?id=780542.780576 """ if (n * d) % 2 != 0: raise nx.NetworkXError("n * d must be even") if not 0 <= d < n: raise nx.NetworkXError("the 0 <= d < n inequality must be satisfied") if d == 0: return empty_graph(n) if seed is not None: random.seed(seed) def _suitable(edges, potential_edges): # Helper subroutine to check if there are suitable edges remaining # If False, the generation of the graph has failed if not potential_edges: return True for s1 in potential_edges: for s2 in potential_edges: # Two iterators on the same dictionary are guaranteed # to visit it in the same order if there are no # intervening modifications. if s1 == s2: # Only need to consider s1-s2 pair one time break if s1 > s2: s1, s2 = s2, s1 if (s1, s2) not in edges: return True return False def _try_creation(): # Attempt to create an edge set edges = set() stubs = list(range(n)) * d while stubs: potential_edges = defaultdict(lambda: 0) random.shuffle(stubs) stubiter = iter(stubs) for s1, s2 in zip(stubiter, stubiter): if s1 > s2: s1, s2 = s2, s1 if s1 != s2 and ((s1, s2) not in edges): edges.add((s1, s2)) else: potential_edges[s1] += 1 potential_edges[s2] += 1 if not _suitable(edges, potential_edges): return None # failed to find suitable edge set stubs = [ node for node, potential in potential_edges.items() for _ in range(potential) ] return edges # Even though a suitable edge set exists, # the generation of such a set is not guaranteed. # Try repeatedly to find one. edges = _try_creation() while edges is None: edges = _try_creation() G = nx.Graph() G.add_edges_from(edges) return G
def power(G, k): """Returns the specified power of a graph. The `k`-th power of a simple graph `G = (V, E)` is the graph `G^k` whose vertex set is `V`, two distinct vertices `u,v` are adjacent in `G^k` if and only if the shortest path distance between `u` and `v` in `G` is at most `k`. Parameters ---------- G: graph A NetworkX simple graph object. k: positive integer The power to which to raise the graph `G`. Returns ------- NetworkX simple graph `G` to the `k`-th power. Raises ------ :exc:`ValueError` If the exponent `k` is not positive. NetworkXError If G is not a simple graph. Examples -------- >>> G = nx.path_graph(4) >>> nx.power(G,2).edges() [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)] >>> nx.power(G,3).edges() [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] A complete graph of order n is returned if *k* is greater than equal to n/2 for a cycle graph of even order n, and if *k* is greater than equal to (n-1)/2 for a cycle graph of odd order. >>> G = nx.cycle_graph(5) >>> nx.power(G,2).edges() == nx.complete_graph(5).edges() True >>> G = nx.cycle_graph(8) >>> nx.power(G,4).edges() == nx.complete_graph(8).edges() True References ---------- .. [1] J. A. Bondy, U. S. R. Murty, Graph Theory. Springer, 2008. Notes ----- Exercise 3.1.6 of *Graph Theory* by J. A. Bondy and U. S. R. Murty [1]_. """ if k <= 0: raise ValueError('k must be a positive integer') if G.is_multigraph() or G.is_directed(): raise nx.NetworkXError("G should be a simple graph") H = nx.Graph() # update BFS code to ignore self loops. for n in G: seen = {} # level (number of hops) when seen in BFS level = 1 # the current level nextlevel = G[n] while nextlevel: thislevel = nextlevel # advance to next level nextlevel = {} # and start a new list (fringe) for v in thislevel: if v == n: # avoid self loop continue if v not in seen: seen[v] = level # set the level of vertex v nextlevel.update(G[v]) # add neighbors of v if (k <= level): break level = level + 1 H.add_edges_from((n, nbr) for nbr in seen) return H
def powerlaw_cluster_graph(n, m, p, seed=None): """Holme and Kim algorithm for growing graphs with powerlaw degree distribution and approximate average clustering. Parameters ---------- n : int the number of nodes m : int the number of random edges to add for each new node p : float, Probability of adding a triangle after adding a random edge seed : int, optional Seed for random number generator (default=None). Notes ----- The average clustering has a hard time getting above a certain cutoff that depends on `m`. This cutoff is often quite low. The transitivity (fraction of triangles to possible triangles) seems to decrease with network size. It is essentially the Barabási–Albert (BA) growth model with an extra step that each random edge is followed by a chance of making an edge to one of its neighbors too (and thus a triangle). This algorithm improves on BA in the sense that it enables a higher average clustering to be attained if desired. It seems possible to have a disconnected graph with this algorithm since the initial `m` nodes may not be all linked to a new node on the first iteration like the BA model. Raises ------ NetworkXError If `m` does not satisfy ``1 <= m <= n`` or `p` does not satisfy ``0 <= p <= 1``. References ---------- .. [1] P. Holme and B. J. Kim, "Growing scale-free networks with tunable clustering", Phys. Rev. E, 65, 026107, 2002. """ if m < 1 or n < m: raise nx.NetworkXError(\ "NetworkXError must have m>1 and m<n, m=%d,n=%d"%(m,n)) if p > 1 or p < 0: raise nx.NetworkXError(\ "NetworkXError p must be in [0,1], p=%f"%(p)) if seed is not None: random.seed(seed) G = empty_graph(m) # add m initial nodes (m0 in barabasi-speak) repeated_nodes = list(G.nodes()) # list of existing nodes to sample from # with nodes repeated once for each adjacent edge source = m # next node is m while source < n: # Now add the other n-1 nodes possible_targets = _random_subset(repeated_nodes, m) # do one preferential attachment for new node target = possible_targets.pop() G.add_edge(source, target) repeated_nodes.append(target) # add one node to list for each new link count = 1 while count < m: # add m-1 more new links if random.random() < p: # clustering step: add triangle neighborhood=[nbr for nbr in G.neighbors(target) \ if not G.has_edge(source,nbr) \ and not nbr==source] if neighborhood: # if there is a neighbor without a link nbr = random.choice(neighborhood) G.add_edge(source, nbr) # add triangle repeated_nodes.append(nbr) count = count + 1 continue # go to top of while loop # else do preferential attachment step if above fails target = possible_targets.pop() G.add_edge(source, target) repeated_nodes.append(target) count = count + 1 repeated_nodes.extend([source] * m) # add source node to list m times source += 1 return G
def to_numpy_array( G, nodelist=None, dtype=None, order=None, multigraph_weight=sum, weight="weight", nonedge=0.0, ): """Returns the graph adjacency matrix as a NumPy array. Parameters ---------- G : graph The NetworkX graph used to construct the NumPy array. nodelist : list, optional The rows and columns are ordered according to the nodes in `nodelist`. If `nodelist` is None, then the ordering is produced by G.nodes(). dtype : NumPy data type, optional A valid single NumPy data type used to initialize the array. This must be a simple type such as int or numpy.float64 and not a compound data type (see to_numpy_recarray) If None, then the NumPy default is used. order : {'C', 'F'}, optional Whether to store multidimensional data in C- or Fortran-contiguous (row- or column-wise) order in memory. If None, then the NumPy default is used. multigraph_weight : {sum, min, max}, optional An operator that determines how weights in multigraphs are handled. The default is to sum the weights of the multiple edges. weight : string or None optional (default = 'weight') The edge attribute that holds the numerical value used for the edge weight. If an edge does not have that attribute, then the value 1 is used instead. nonedge : float (default = 0.0) The array values corresponding to nonedges are typically set to zero. However, this could be undesirable if there are array values corresponding to actual edges that also have the value zero. If so, one might prefer nonedges to have some other value, such as nan. Returns ------- A : NumPy ndarray Graph adjacency matrix See Also -------- from_numpy_array Notes ----- For directed graphs, entry i,j corresponds to an edge from i to j. Entries in the adjacency matrix are assigned to the weight edge attribute. When an edge does not have a weight attribute, the value of the entry is set to the number 1. For multiple (parallel) edges, the values of the entries are determined by the `multigraph_weight` parameter. The default is to sum the weight attributes for each of the parallel edges. When `nodelist` does not contain every node in `G`, the adjacency matrix is built from the subgraph of `G` that is induced by the nodes in `nodelist`. The convention used for self-loop edges in graphs is to assign the diagonal array entry value to the weight attribute of the edge (or the number 1 if the edge has no weight attribute). If the alternate convention of doubling the edge weight is desired the resulting NumPy array can be modified as follows: >>> import numpy as np >>> G = nx.Graph([(1, 1)]) >>> A = nx.to_numpy_array(G) >>> A array([[1.]]) >>> A[np.diag_indices_from(A)] *= 2 >>> A array([[2.]]) Examples -------- >>> G = nx.MultiDiGraph() >>> G.add_edge(0, 1, weight=2) 0 >>> G.add_edge(1, 0) 0 >>> G.add_edge(2, 2, weight=3) 0 >>> G.add_edge(2, 2) 1 >>> nx.to_numpy_array(G, nodelist=[0, 1, 2]) array([[0., 2., 0.], [1., 0., 0.], [0., 0., 4.]]) """ import numpy as np if nodelist is None: nodelist = list(G) nodeset = G nlen = len(G) else: nlen = len(nodelist) nodeset = set(G.nbunch_iter(nodelist)) if nlen != len(nodeset): for n in nodelist: if n not in G: raise nx.NetworkXError(f"Node {n} in nodelist is not in G") raise nx.NetworkXError("nodelist contains duplicates.") undirected = not G.is_directed() index = dict(zip(nodelist, range(nlen))) # Initially, we start with an array of nans. Then we populate the array # using data from the graph. Afterwards, any leftover nans will be # converted to the value of `nonedge`. Note, we use nans initially, # instead of zero, for two reasons: # # 1) It can be important to distinguish a real edge with the value 0 # from a nonedge with the value 0. # # 2) When working with multi(di)graphs, we must combine the values of all # edges between any two nodes in some manner. This often takes the # form of a sum, min, or max. Using the value 0 for a nonedge would # have undesirable effects with min and max, but using nanmin and # nanmax with initially nan values is not problematic at all. # # That said, there are still some drawbacks to this approach. Namely, if # a real edge is nan, then that value is a) not distinguishable from # nonedges and b) is ignored by the default combinator (nansum, nanmin, # nanmax) functions used for multi(di)graphs. If this becomes an issue, # an alternative approach is to use masked arrays. Initially, every # element is masked and set to some `initial` value. As we populate the # graph, elements are unmasked (automatically) when we combine the initial # value with the values given by real edges. At the end, we convert all # masked values to `nonedge`. Using masked arrays fully addresses reason 1, # but for reason 2, we would still have the issue with min and max if the # initial values were 0.0. Note: an initial value of +inf is appropriate # for min, while an initial value of -inf is appropriate for max. When # working with sum, an initial value of zero is appropriate. Ideally then, # we'd want to allow users to specify both a value for nonedges and also # an initial value. For multi(di)graphs, the choice of the initial value # will, in general, depend on the combinator function---sensible defaults # can be provided. if G.is_multigraph(): # Handle MultiGraphs and MultiDiGraphs A = np.full((nlen, nlen), np.nan, order=order) # use numpy nan-aware operations operator = {sum: np.nansum, min: np.nanmin, max: np.nanmax} try: op = operator[multigraph_weight] except Exception as e: raise ValueError( "multigraph_weight must be sum, min, or max") from e for u, v, attrs in G.edges(data=True): if (u in nodeset) and (v in nodeset): i, j = index[u], index[v] e_weight = attrs.get(weight, 1) A[i, j] = op([e_weight, A[i, j]]) if undirected: A[j, i] = A[i, j] else: # Graph or DiGraph, this is much faster than above A = np.full((nlen, nlen), np.nan, order=order) for u, nbrdict in G.adjacency(): for v, d in nbrdict.items(): try: A[index[u], index[v]] = d.get(weight, 1) except KeyError: # This occurs when there are fewer desired nodes than # there are nodes in the graph: len(nodelist) < len(G) pass A[np.isnan(A)] = nonedge A = np.asarray(A, dtype=dtype) return A
def steiner_tree(G, terminal_nodes, root=None, weight='weight'): """ Return an approximation to the minimum Steiner tree of a graph. Parameters ---------- G : NetworkX graph terminal_nodes : list A list of terminal nodes for which minimum steiner tree is to be found. root : node Must be specified if G is directed Returns ------- NetworkX graph Approximation to the minimum steiner tree of `G` induced by `terminal_nodes` and `root` (if specified). Notes ----- UNDIRECTED GRAPHS: Steiner tree can be approximated by computing the minimum spanning tree of the subgraph of the metric closure of the graph induced by the terminal nodes, where the metric closure of *G* is the complete graph in which each edge is weighted by the shortest path distance between the nodes in *G* . This algorithm produces a tree whose weight is within a (2 - (2 / t)) factor of the weight of the optimal Steiner tree where *t* is number of terminal nodes. DIRECTED GRAPHS: This is a generalization of the NP-hard Set Cover problem and so is known to be inapproximable within O(log(t)). The simplest approximation is to join the shortest paths to each of the terminals, which approximates within t. This is the approach this implementation takes. """ if root is None: if G.is_directed(): raise nx.NetworkXError("root must be specified for Steiner arborescence of digraphs!") elif not G.is_directed(): terminal_nodes = terminal_nodes + [root] # TODO: enhance this algorithm for DiGraphs by taking the same metric closure approach. # However, this would require modifying the Edmonds algorithm / minimum_spanning_arborescence # implementations to accept an explicit root node. if G.is_directed(): res = set() for term in terminal_nodes: path = nx.shortest_path(G, root, term, weight=weight) path_edges = zip(path, path[1:]) for e in path_edges: res.add(e) res = G.edge_subgraph(e for e in res) assert all(t in res for t in terminal_nodes) and all(nx.has_path(res, root, t) for t in terminal_nodes) assert nx.is_directed_acyclic_graph(res) return res else: # M is the subgraph of the metric closure induced by the terminal nodes of # G. M = metric_closure(G, relevant_nodes=terminal_nodes, weight=weight) # Use the 'distance' attribute of each edge provided by the metric closure # graph. mst_edges = nx.minimum_spanning_edges(M, weight='distance', data=True) # Create an iterator over each edge in each shortest path; repeats are okay edges = chain.from_iterable(pairwise(d['path']) for u, v, d in mst_edges) T = G.edge_subgraph(edges) return T
def eigenvector_centrality(G, max_iter=100, tol=1.0e-6, nstart=None, weight=None): r"""Compute the eigenvector centrality for the graph `G`. Eigenvector centrality computes the centrality for a node based on the centrality of its neighbors. The eigenvector centrality for node $i$ is the $i$-th element of the vector $x$ defined by the equation .. math:: Ax = \lambda x where $A$ is the adjacency matrix of the graph `G` with eigenvalue $\lambda$. By virtue of the Perron–Frobenius theorem, there is a unique solution $x$, all of whose entries are positive, if $\lambda$ is the largest eigenvalue of the adjacency matrix $A$ ([2]_). Parameters ---------- G : graph A networkx graph max_iter : integer, optional (default=100) Maximum number of iterations in power method. tol : float, optional (default=1.0e-6) Error tolerance used to check convergence in power method iteration. nstart : dictionary, optional (default=None) Starting value of eigenvector iteration for each node. weight : None or string, optional (default=None) If None, all edge weights are considered equal. Otherwise holds the name of the edge attribute used as weight. In this measure the weight is interpreted as the connection strength. Returns ------- nodes : dictionary Dictionary of nodes with eigenvector centrality as the value. Examples -------- >>> G = nx.path_graph(4) >>> centrality = nx.eigenvector_centrality(G) >>> sorted((v, f"{c:0.2f}") for v, c in centrality.items()) [(0, '0.37'), (1, '0.60'), (2, '0.60'), (3, '0.37')] Raises ------ NetworkXPointlessConcept If the graph `G` is the null graph. NetworkXError If each value in `nstart` is zero. PowerIterationFailedConvergence If the algorithm fails to converge to the specified tolerance within the specified number of iterations of the power iteration method. See Also -------- eigenvector_centrality_numpy pagerank hits Notes ----- The measure was introduced by [1]_ and is discussed in [2]_. The power iteration method is used to compute the eigenvector and convergence is **not** guaranteed. Our method stops after ``max_iter`` iterations or when the change in the computed vector between two iterations is smaller than an error tolerance of ``G.number_of_nodes() * tol``. This implementation uses ($A + I$) rather than the adjacency matrix $A$ because it shifts the spectrum to enable discerning the correct eigenvector even for networks with multiple dominant eigenvalues. For directed graphs this is "left" eigenvector centrality which corresponds to the in-edges in the graph. For out-edges eigenvector centrality first reverse the graph with ``G.reverse()``. References ---------- .. [1] Phillip Bonacich. "Power and Centrality: A Family of Measures." *American Journal of Sociology* 92(5):1170–1182, 1986 <http://www.leonidzhukov.net/hse/2014/socialnetworks/papers/Bonacich-Centrality.pdf> .. [2] Mark E. J. Newman. *Networks: An Introduction.* Oxford University Press, USA, 2010, pp. 169. """ if len(G) == 0: raise nx.NetworkXPointlessConcept( "cannot compute centrality for the null graph") # If no initial vector is provided, start with the all-ones vector. if nstart is None: nstart = {v: 1 for v in G} if all(v == 0 for v in nstart.values()): raise nx.NetworkXError("initial vector cannot have all zero values") # Normalize the initial vector so that each entry is in [0, 1]. This is # guaranteed to never have a divide-by-zero error by the previous line. nstart_sum = sum(nstart.values()) x = {k: v / nstart_sum for k, v in nstart.items()} nnodes = G.number_of_nodes() # make up to max_iter iterations for i in range(max_iter): xlast = x x = xlast.copy() # Start with xlast times I to iterate with (A+I) # do the multiplication y^T = x^T A (left eigenvector) for n in x: for nbr in G[n]: w = G[n][nbr].get(weight, 1) if weight else 1 x[nbr] += xlast[n] * w # Normalize the vector. The normalization denominator `norm` # should never be zero by the Perron--Frobenius # theorem. However, in case it is due to numerical error, we # assume the norm to be one instead. norm = math.hypot(*x.values()) or 1 x = {k: v / norm for k, v in x.items()} # Check for convergence (in the L_1 norm). if sum(abs(x[n] - xlast[n]) for n in x) < nnodes * tol: return x raise nx.PowerIterationFailedConvergence(max_iter)