123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491 |
- """Functions to convert NetworkX graphs to and from other formats.
- The preferred way of converting data to a NetworkX graph is through the
- graph constructor. The constructor calls the to_networkx_graph() function
- which attempts to guess the input type and convert it automatically.
- Examples
- --------
- Create a graph with a single edge from a dictionary of dictionaries
- >>> d = {0: {1: 1}} # dict-of-dicts single edge (0,1)
- >>> G = nx.Graph(d)
- See Also
- --------
- nx_agraph, nx_pydot
- """
- import warnings
- from collections.abc import Collection, Generator, Iterator
- import networkx as nx
- __all__ = [
- "to_networkx_graph",
- "from_dict_of_dicts",
- "to_dict_of_dicts",
- "from_dict_of_lists",
- "to_dict_of_lists",
- "from_edgelist",
- "to_edgelist",
- ]
- 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
- container (e.g. set, list, tuple) of edges
- iterator (e.g. itertools.chain) that produces edges
- generator of edges
- Pandas DataFrame (row per edge)
- 2D numpy array
- scipy sparse array
- 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(),
- )
- # data.graph should be dict-like
- result.graph.update(data.graph)
- # 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 Exception as err:
- raise nx.NetworkXError("Input is not a correct NetworkX graph.") from err
- # pygraphviz agraph
- if hasattr(data, "is_strict"):
- try:
- return nx.nx_agraph.from_agraph(data, create_using=create_using)
- except Exception as err:
- raise nx.NetworkXError("Input is not a correct pygraphviz graph.") from err
- # dict of dicts/lists
- if isinstance(data, dict):
- try:
- return from_dict_of_dicts(
- data, create_using=create_using, multigraph_input=multigraph_input
- )
- except Exception as err1:
- if multigraph_input is True:
- raise nx.NetworkXError(
- f"converting multigraph_input raised:\n{type(err1)}: {err1}"
- )
- try:
- return from_dict_of_lists(data, create_using=create_using)
- except Exception as err2:
- raise TypeError("Input is not known type.") from err2
- # 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 Exception as err:
- msg = "Input is not a correct Pandas DataFrame adjacency matrix."
- raise nx.NetworkXError(msg) from err
- else:
- try:
- return nx.from_pandas_edgelist(
- data, edge_attr=True, create_using=create_using
- )
- except Exception as err:
- msg = "Input is not a correct Pandas DataFrame edge-list."
- raise nx.NetworkXError(msg) from err
- except ImportError:
- warnings.warn("pandas not found, skipping conversion test.", ImportWarning)
- # numpy array
- try:
- import numpy as np
- if isinstance(data, np.ndarray):
- try:
- return nx.from_numpy_array(data, create_using=create_using)
- except Exception as err:
- raise nx.NetworkXError(
- f"Failed to interpret array as an adjacency matrix."
- ) from err
- except ImportError:
- warnings.warn("numpy not found, skipping conversion test.", ImportWarning)
- # scipy sparse array - any format
- try:
- import scipy
- if hasattr(data, "format"):
- try:
- return nx.from_scipy_sparse_array(data, create_using=create_using)
- except Exception as err:
- raise nx.NetworkXError(
- "Input is not a correct scipy sparse array type."
- ) from err
- except ImportError:
- warnings.warn("scipy not found, skipping conversion test.", ImportWarning)
- # Note: most general check - should remain last in order of execution
- # Includes containers (e.g. list, set, dict, etc.), generators, and
- # iterators (e.g. itertools.chain) of edges
- if isinstance(data, (Collection, Generator, Iterator)):
- try:
- return from_edgelist(data, create_using=create_using)
- except Exception as err:
- raise nx.NetworkXError("Input is not a valid edge list") from err
- raise nx.NetworkXError("Input is not a known data type for conversion.")
- def to_dict_of_lists(G, nodelist=None):
- """Returns adjacency representation of graph as a dictionary of lists.
- Parameters
- ----------
- G : graph
- A NetworkX graph
- nodelist : list
- Use only nodes specified in nodelist
- Notes
- -----
- Completely ignores edge data for MultiGraph and MultiDiGraph.
- """
- if nodelist is None:
- nodelist = G
- d = {}
- for n in nodelist:
- d[n] = [nbr for nbr in G.neighbors(n) if nbr in nodelist]
- return d
- def from_dict_of_lists(d, create_using=None):
- """Returns a graph from a dictionary of lists.
- Parameters
- ----------
- d : dictionary of lists
- A dictionary of lists adjacency representation.
- create_using : NetworkX graph constructor, optional (default=nx.Graph)
- Graph type to create. If graph instance, then cleared before populated.
- Examples
- --------
- >>> dol = {0: [1]} # single edge (0,1)
- >>> G = nx.from_dict_of_lists(dol)
- or
- >>> G = nx.Graph(dol) # use Graph constructor
- """
- G = nx.empty_graph(0, create_using)
- G.add_nodes_from(d)
- if G.is_multigraph() and not G.is_directed():
- # a dict_of_lists can't show multiedges. BUT for undirected graphs,
- # each edge shows up twice in the dict_of_lists.
- # So we need to treat this case separately.
- seen = {}
- for node, nbrlist in d.items():
- for nbr in nbrlist:
- if nbr not in seen:
- G.add_edge(node, nbr)
- seen[node] = 1 # don't allow reverse edge to show up
- else:
- G.add_edges_from(
- ((node, nbr) for node, nbrlist in d.items() for nbr in nbrlist)
- )
- return G
- def to_dict_of_dicts(G, nodelist=None, edge_data=None):
- """Returns adjacency representation of graph as a dictionary of dictionaries.
- Parameters
- ----------
- G : graph
- A NetworkX graph
- nodelist : list
- Use only nodes specified in nodelist
- edge_data : scalar, optional
- If provided, the value of the dictionary will be set to `edge_data` for
- all edges. Usual values could be `1` or `True`. If `edge_data` is
- `None` (the default), the edgedata in `G` is used, resulting in a
- dict-of-dict-of-dicts. If `G` is a MultiGraph, the result will be a
- dict-of-dict-of-dict-of-dicts. See Notes for an approach to customize
- handling edge data. `edge_data` should *not* be a container.
- Returns
- -------
- dod : dict
- A nested dictionary representation of `G`. Note that the level of
- nesting depends on the type of `G` and the value of `edge_data`
- (see Examples).
- See Also
- --------
- from_dict_of_dicts, to_dict_of_lists
- Notes
- -----
- For a more custom approach to handling edge data, try::
- dod = {
- n: {
- nbr: custom(n, nbr, dd) for nbr, dd in nbrdict.items()
- }
- for n, nbrdict in G.adj.items()
- }
- where `custom` returns the desired edge data for each edge between `n` and
- `nbr`, given existing edge data `dd`.
- Examples
- --------
- >>> G = nx.path_graph(3)
- >>> nx.to_dict_of_dicts(G)
- {0: {1: {}}, 1: {0: {}, 2: {}}, 2: {1: {}}}
- Edge data is preserved by default (``edge_data=None``), resulting
- in dict-of-dict-of-dicts where the innermost dictionary contains the
- edge data:
- >>> G = nx.Graph()
- >>> G.add_edges_from(
- ... [
- ... (0, 1, {'weight': 1.0}),
- ... (1, 2, {'weight': 2.0}),
- ... (2, 0, {'weight': 1.0}),
- ... ]
- ... )
- >>> d = nx.to_dict_of_dicts(G)
- >>> d # doctest: +SKIP
- {0: {1: {'weight': 1.0}, 2: {'weight': 1.0}},
- 1: {0: {'weight': 1.0}, 2: {'weight': 2.0}},
- 2: {1: {'weight': 2.0}, 0: {'weight': 1.0}}}
- >>> d[1][2]['weight']
- 2.0
- If `edge_data` is not `None`, edge data in the original graph (if any) is
- replaced:
- >>> d = nx.to_dict_of_dicts(G, edge_data=1)
- >>> d
- {0: {1: 1, 2: 1}, 1: {0: 1, 2: 1}, 2: {1: 1, 0: 1}}
- >>> d[1][2]
- 1
- This also applies to MultiGraphs: edge data is preserved by default:
- >>> G = nx.MultiGraph()
- >>> G.add_edge(0, 1, key='a', weight=1.0)
- 'a'
- >>> G.add_edge(0, 1, key='b', weight=5.0)
- 'b'
- >>> d = nx.to_dict_of_dicts(G)
- >>> d # doctest: +SKIP
- {0: {1: {'a': {'weight': 1.0}, 'b': {'weight': 5.0}}},
- 1: {0: {'a': {'weight': 1.0}, 'b': {'weight': 5.0}}}}
- >>> d[0][1]['b']['weight']
- 5.0
- But multi edge data is lost if `edge_data` is not `None`:
- >>> d = nx.to_dict_of_dicts(G, edge_data=10)
- >>> d
- {0: {1: 10}, 1: {0: 10}}
- """
- dod = {}
- if nodelist is None:
- if edge_data is None:
- for u, nbrdict in G.adjacency():
- dod[u] = nbrdict.copy()
- else: # edge_data is not None
- for u, nbrdict in G.adjacency():
- dod[u] = dod.fromkeys(nbrdict, edge_data)
- else: # nodelist is not None
- if edge_data is None:
- for u in nodelist:
- dod[u] = {}
- for v, data in ((v, data) for v, data in G[u].items() if v in nodelist):
- dod[u][v] = data
- else: # nodelist and edge_data are not None
- for u in nodelist:
- dod[u] = {}
- for v in (v for v in G[u] if v in nodelist):
- dod[u][v] = edge_data
- return dod
- def from_dict_of_dicts(d, create_using=None, multigraph_input=False):
- """Returns a graph from a dictionary of dictionaries.
- Parameters
- ----------
- d : dictionary of dictionaries
- A dictionary of dictionaries adjacency representation.
- 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)
- When True, the dict `d` is assumed
- to be a dict-of-dict-of-dict-of-dict structure keyed by
- node to neighbor to edge keys to edge data for multi-edges.
- Otherwise this routine assumes dict-of-dict-of-dict keyed by
- node to neighbor to edge data.
- Examples
- --------
- >>> dod = {0: {1: {"weight": 1}}} # single edge (0,1)
- >>> G = nx.from_dict_of_dicts(dod)
- or
- >>> G = nx.Graph(dod) # use Graph constructor
- """
- G = nx.empty_graph(0, create_using)
- G.add_nodes_from(d)
- # does dict d represent a MultiGraph or MultiDiGraph?
- if multigraph_input:
- if G.is_directed():
- if G.is_multigraph():
- G.add_edges_from(
- (u, v, key, data)
- for u, nbrs in d.items()
- for v, datadict in nbrs.items()
- for key, data in datadict.items()
- )
- else:
- G.add_edges_from(
- (u, v, data)
- for u, nbrs in d.items()
- for v, datadict in nbrs.items()
- for key, data in datadict.items()
- )
- else: # Undirected
- if G.is_multigraph():
- seen = set() # don't add both directions of undirected graph
- for u, nbrs in d.items():
- for v, datadict in nbrs.items():
- if (u, v) not in seen:
- G.add_edges_from(
- (u, v, key, data) for key, data in datadict.items()
- )
- seen.add((v, u))
- else:
- seen = set() # don't add both directions of undirected graph
- for u, nbrs in d.items():
- for v, datadict in nbrs.items():
- if (u, v) not in seen:
- G.add_edges_from(
- (u, v, data) for key, data in datadict.items()
- )
- seen.add((v, u))
- else: # not a multigraph to multigraph transfer
- if G.is_multigraph() and not G.is_directed():
- # d can have both representations u-v, v-u in dict. Only add one.
- # We don't need this check for digraphs since we add both directions,
- # or for Graph() since it is done implicitly (parallel edges not allowed)
- seen = set()
- for u, nbrs in d.items():
- for v, data in nbrs.items():
- if (u, v) not in seen:
- G.add_edge(u, v, key=0)
- G[u][v][0].update(data)
- seen.add((v, u))
- else:
- G.add_edges_from(
- ((u, v, data) for u, nbrs in d.items() for v, data in nbrs.items())
- )
- return G
- def to_edgelist(G, nodelist=None):
- """Returns a list of edges in the graph.
- Parameters
- ----------
- G : graph
- A NetworkX graph
- nodelist : list
- Use only nodes specified in nodelist
- """
- if nodelist is None:
- return G.edges(data=True)
- return G.edges(nodelist, data=True)
- def from_edgelist(edgelist, create_using=None):
- """Returns a graph from a list of edges.
- Parameters
- ----------
- edgelist : list or iterator
- Edge tuples
- create_using : NetworkX graph constructor, optional (default=nx.Graph)
- Graph type to create. If graph instance, then cleared before populated.
- Examples
- --------
- >>> edgelist = [(0, 1)] # single edge (0,1)
- >>> G = nx.from_edgelist(edgelist)
- or
- >>> G = nx.Graph(edgelist) # use Graph constructor
- """
- G = nx.empty_graph(0, create_using)
- G.add_edges_from(edgelist)
- return G
|