1"""Functions to convert NetworkX graphs to and from other formats.
2
3The preferred way of converting data to a NetworkX graph is through the
4graph constructor. The constructor calls the to_networkx_graph() function
5which attempts to guess the input type and convert it automatically.
6
7Examples
8--------
9Create a graph with a single edge from a dictionary of dictionaries
10
11>>> d = {0: {1: 1}} # dict-of-dicts single edge (0,1)
12>>> G = nx.Graph(d)
13
14See Also
15--------
16nx_agraph, nx_pydot
17"""
18
19from collections.abc import Collection, Generator, Iterator
20
21import networkx as nx
22
23__all__ = [
24 "to_networkx_graph",
25 "from_dict_of_dicts",
26 "to_dict_of_dicts",
27 "from_dict_of_lists",
28 "to_dict_of_lists",
29 "from_edgelist",
30 "to_edgelist",
31]
32
33
34def to_networkx_graph(data, create_using=None, multigraph_input=False):
35 """Make a NetworkX graph from a known data structure.
36
37 The preferred way to call this is automatically
38 from the class constructor
39
40 >>> d = {0: {1: {"weight": 1}}} # dict-of-dicts single edge (0,1)
41 >>> G = nx.Graph(d)
42
43 instead of the equivalent
44
45 >>> G = nx.from_dict_of_dicts(d)
46
47 Parameters
48 ----------
49 data : object to be converted
50
51 Current known types are:
52 any NetworkX graph
53 dict-of-dicts
54 dict-of-lists
55 container (e.g. set, list, tuple) of edges
56 iterator (e.g. itertools.chain) that produces edges
57 generator of edges
58 Pandas DataFrame (row per edge)
59 2D numpy array
60 scipy sparse array
61 pygraphviz agraph
62
63 create_using : NetworkX graph constructor, optional (default=nx.Graph)
64 Graph type to create. If graph instance, then cleared before populated.
65
66 multigraph_input : bool (default False)
67 If True and data is a dict_of_dicts,
68 try to create a multigraph assuming dict_of_dict_of_lists.
69 If data and create_using are both multigraphs then create
70 a multigraph from a multigraph.
71
72 """
73 # NX graph
74 if hasattr(data, "adj"):
75 try:
76 result = from_dict_of_dicts(
77 data.adj,
78 create_using=create_using,
79 multigraph_input=data.is_multigraph(),
80 )
81 # data.graph should be dict-like
82 result.graph.update(data.graph)
83 # data.nodes should be dict-like
84 # result.add_node_from(data.nodes.items()) possible but
85 # for custom node_attr_dict_factory which may be hashable
86 # will be unexpected behavior
87 for n, dd in data.nodes.items():
88 result._node[n].update(dd)
89 return result
90 except Exception as err:
91 raise nx.NetworkXError("Input is not a correct NetworkX graph.") from err
92
93 # dict of dicts/lists
94 if isinstance(data, dict):
95 try:
96 return from_dict_of_dicts(
97 data, create_using=create_using, multigraph_input=multigraph_input
98 )
99 except Exception as err1:
100 if multigraph_input is True:
101 raise nx.NetworkXError(
102 f"converting multigraph_input raised:\n{type(err1)}: {err1}"
103 )
104 try:
105 return from_dict_of_lists(data, create_using=create_using)
106 except Exception as err2:
107 raise TypeError("Input is not known type.") from err2
108
109 # edgelists
110 if isinstance(data, list | tuple | nx.reportviews.EdgeViewABC | Iterator):
111 try:
112 return from_edgelist(data, create_using=create_using)
113 except:
114 pass
115
116 # pygraphviz agraph
117 if hasattr(data, "is_strict"):
118 try:
119 return nx.nx_agraph.from_agraph(data, create_using=create_using)
120 except Exception as err:
121 raise nx.NetworkXError("Input is not a correct pygraphviz graph.") from err
122
123 # Pandas DataFrame
124 try:
125 import pandas as pd
126
127 if isinstance(data, pd.DataFrame):
128 if data.shape[0] == data.shape[1]:
129 try:
130 return nx.from_pandas_adjacency(data, create_using=create_using)
131 except Exception as err:
132 msg = "Input is not a correct Pandas DataFrame adjacency matrix."
133 raise nx.NetworkXError(msg) from err
134 else:
135 try:
136 return nx.from_pandas_edgelist(
137 data, edge_attr=True, create_using=create_using
138 )
139 except Exception as err:
140 msg = "Input is not a correct Pandas DataFrame edge-list."
141 raise nx.NetworkXError(msg) from err
142 except ImportError:
143 pass
144
145 # numpy array
146 try:
147 import numpy as np
148
149 if isinstance(data, np.ndarray):
150 try:
151 return nx.from_numpy_array(data, create_using=create_using)
152 except Exception as err:
153 raise nx.NetworkXError(
154 f"Failed to interpret array as an adjacency matrix."
155 ) from err
156 except ImportError:
157 pass
158
159 # scipy sparse array - any format
160 try:
161 import scipy as sp
162
163 if hasattr(data, "format"):
164 try:
165 return nx.from_scipy_sparse_array(data, create_using=create_using)
166 except Exception as err:
167 raise nx.NetworkXError(
168 "Input is not a correct scipy sparse array type."
169 ) from err
170 except ImportError:
171 pass
172
173 # Note: most general check - should remain last in order of execution
174 # Includes containers (e.g. list, set, dict, etc.), generators, and
175 # iterators (e.g. itertools.chain) of edges
176
177 if isinstance(data, Collection | Generator | Iterator):
178 try:
179 return from_edgelist(data, create_using=create_using)
180 except Exception as err:
181 raise nx.NetworkXError("Input is not a valid edge list") from err
182
183 raise nx.NetworkXError("Input is not a known data type for conversion.")
184
185
186@nx._dispatchable
187def to_dict_of_lists(G, nodelist=None):
188 """Returns adjacency representation of graph as a dictionary of lists.
189
190 Parameters
191 ----------
192 G : graph
193 A NetworkX graph
194
195 nodelist : list
196 Use only nodes specified in nodelist
197
198 Notes
199 -----
200 Completely ignores edge data for MultiGraph and MultiDiGraph.
201
202 """
203 if nodelist is None:
204 nodelist = G
205
206 d = {}
207 for n in nodelist:
208 d[n] = [nbr for nbr in G.neighbors(n) if nbr in nodelist]
209 return d
210
211
212@nx._dispatchable(graphs=None, returns_graph=True)
213def from_dict_of_lists(d, create_using=None):
214 """Returns a graph from a dictionary of lists.
215
216 Parameters
217 ----------
218 d : dictionary of lists
219 A dictionary of lists adjacency representation.
220
221 create_using : NetworkX graph constructor, optional (default=nx.Graph)
222 Graph type to create. If graph instance, then cleared before populated.
223
224 Examples
225 --------
226 >>> dol = {0: [1]} # single edge (0,1)
227 >>> G = nx.from_dict_of_lists(dol)
228
229 or
230
231 >>> G = nx.Graph(dol) # use Graph constructor
232
233 """
234 G = nx.empty_graph(0, create_using)
235 G.add_nodes_from(d)
236 if G.is_multigraph() and not G.is_directed():
237 # a dict_of_lists can't show multiedges. BUT for undirected graphs,
238 # each edge shows up twice in the dict_of_lists.
239 # So we need to treat this case separately.
240 seen = {}
241 for node, nbrlist in d.items():
242 for nbr in nbrlist:
243 if nbr not in seen:
244 G.add_edge(node, nbr)
245 seen[node] = 1 # don't allow reverse edge to show up
246 else:
247 G.add_edges_from(
248 ((node, nbr) for node, nbrlist in d.items() for nbr in nbrlist)
249 )
250 return G
251
252
253def to_dict_of_dicts(G, nodelist=None, edge_data=None):
254 """Returns adjacency representation of graph as a dictionary of dictionaries.
255
256 Parameters
257 ----------
258 G : graph
259 A NetworkX graph
260
261 nodelist : list
262 Use only nodes specified in nodelist. If None, all nodes in G.
263
264 edge_data : scalar, optional (default: the G edgedatadict for each edge)
265 If provided, the value of the dictionary will be set to `edge_data` for
266 all edges. Usual values could be `1` or `True`. If `edge_data` is
267 `None` (the default), the edgedata in `G` is used, resulting in a
268 dict-of-dict-of-dicts. If `G` is a MultiGraph, the result will be a
269 dict-of-dict-of-dict-of-dicts. See Notes for an approach to customize
270 handling edge data. `edge_data` should *not* be a container as it will
271 be the same container for all the edges.
272
273 Returns
274 -------
275 dod : dict
276 A nested dictionary representation of `G`. Note that the level of
277 nesting depends on the type of `G` and the value of `edge_data`
278 (see Examples).
279
280 See Also
281 --------
282 from_dict_of_dicts, to_dict_of_lists
283
284 Notes
285 -----
286 For a more custom approach to handling edge data, try::
287
288 dod = {
289 n: {nbr: custom(n, nbr, dd) for nbr, dd in nbrdict.items()}
290 for n, nbrdict in G.adj.items()
291 }
292
293 where `custom` returns the desired edge data for each edge between `n` and
294 `nbr`, given existing edge data `dd`.
295
296 Examples
297 --------
298 >>> G = nx.path_graph(3)
299 >>> nx.to_dict_of_dicts(G)
300 {0: {1: {}}, 1: {0: {}, 2: {}}, 2: {1: {}}}
301
302 Edge data is preserved by default (``edge_data=None``), resulting
303 in dict-of-dict-of-dicts where the innermost dictionary contains the
304 edge data:
305
306 >>> G = nx.Graph()
307 >>> G.add_edges_from(
308 ... [
309 ... (0, 1, {"weight": 1.0}),
310 ... (1, 2, {"weight": 2.0}),
311 ... (2, 0, {"weight": 1.0}),
312 ... ]
313 ... )
314 >>> d = nx.to_dict_of_dicts(G)
315 >>> d # doctest: +SKIP
316 {0: {1: {'weight': 1.0}, 2: {'weight': 1.0}},
317 1: {0: {'weight': 1.0}, 2: {'weight': 2.0}},
318 2: {1: {'weight': 2.0}, 0: {'weight': 1.0}}}
319 >>> d[1][2]["weight"]
320 2.0
321
322 If `edge_data` is not `None`, edge data in the original graph (if any) is
323 replaced:
324
325 >>> d = nx.to_dict_of_dicts(G, edge_data=1)
326 >>> d
327 {0: {1: 1, 2: 1}, 1: {0: 1, 2: 1}, 2: {1: 1, 0: 1}}
328 >>> d[1][2]
329 1
330
331 This also applies to MultiGraphs: edge data is preserved by default:
332
333 >>> G = nx.MultiGraph()
334 >>> G.add_edge(0, 1, key="a", weight=1.0)
335 'a'
336 >>> G.add_edge(0, 1, key="b", weight=5.0)
337 'b'
338 >>> d = nx.to_dict_of_dicts(G)
339 >>> d # doctest: +SKIP
340 {0: {1: {'a': {'weight': 1.0}, 'b': {'weight': 5.0}}},
341 1: {0: {'a': {'weight': 1.0}, 'b': {'weight': 5.0}}}}
342 >>> d[0][1]["b"]["weight"]
343 5.0
344
345 But multi edge data is lost if `edge_data` is not `None`:
346
347 >>> d = nx.to_dict_of_dicts(G, edge_data=10)
348 >>> d
349 {0: {1: 10}, 1: {0: 10}}
350 """
351 dod = {}
352 if nodelist is None:
353 if edge_data is None:
354 for u, nbrdict in G.adjacency():
355 dod[u] = nbrdict.copy()
356 else: # edge_data is not None
357 for u, nbrdict in G.adjacency():
358 dod[u] = dod.fromkeys(nbrdict, edge_data)
359 else: # nodelist is not None
360 if edge_data is None:
361 for u in nodelist:
362 dod[u] = {}
363 for v, data in ((v, data) for v, data in G[u].items() if v in nodelist):
364 dod[u][v] = data
365 else: # nodelist and edge_data are not None
366 for u in nodelist:
367 dod[u] = {}
368 for v in (v for v in G[u] if v in nodelist):
369 dod[u][v] = edge_data
370 return dod
371
372
373@nx._dispatchable(graphs=None, returns_graph=True)
374def from_dict_of_dicts(d, create_using=None, multigraph_input=False):
375 """Returns a graph from a dictionary of dictionaries.
376
377 Parameters
378 ----------
379 d : dictionary of dictionaries
380 A dictionary of dictionaries adjacency representation.
381
382 create_using : NetworkX graph constructor, optional (default=nx.Graph)
383 Graph type to create. If graph instance, then cleared before populated.
384
385 multigraph_input : bool (default False)
386 When True, the dict `d` is assumed
387 to be a dict-of-dict-of-dict-of-dict structure keyed by
388 node to neighbor to edge keys to edge data for multi-edges.
389 Otherwise this routine assumes dict-of-dict-of-dict keyed by
390 node to neighbor to edge data.
391
392 Examples
393 --------
394 >>> dod = {0: {1: {"weight": 1}}} # single edge (0,1)
395 >>> G = nx.from_dict_of_dicts(dod)
396
397 or
398
399 >>> G = nx.Graph(dod) # use Graph constructor
400
401 """
402 G = nx.empty_graph(0, create_using)
403 G.add_nodes_from(d)
404 # does dict d represent a MultiGraph or MultiDiGraph?
405 if multigraph_input:
406 if G.is_directed():
407 if G.is_multigraph():
408 G.add_edges_from(
409 (u, v, key, data)
410 for u, nbrs in d.items()
411 for v, datadict in nbrs.items()
412 for key, data in datadict.items()
413 )
414 else:
415 G.add_edges_from(
416 (u, v, data)
417 for u, nbrs in d.items()
418 for v, datadict in nbrs.items()
419 for key, data in datadict.items()
420 )
421 else: # Undirected
422 if G.is_multigraph():
423 seen = set() # don't add both directions of undirected graph
424 for u, nbrs in d.items():
425 for v, datadict in nbrs.items():
426 if (u, v) not in seen:
427 G.add_edges_from(
428 (u, v, key, data) for key, data in datadict.items()
429 )
430 seen.add((v, u))
431 else:
432 seen = set() # don't add both directions of undirected graph
433 for u, nbrs in d.items():
434 for v, datadict in nbrs.items():
435 if (u, v) not in seen:
436 G.add_edges_from(
437 (u, v, data) for key, data in datadict.items()
438 )
439 seen.add((v, u))
440
441 else: # not a multigraph to multigraph transfer
442 if G.is_multigraph() and not G.is_directed():
443 # d can have both representations u-v, v-u in dict. Only add one.
444 # We don't need this check for digraphs since we add both directions,
445 # or for Graph() since it is done implicitly (parallel edges not allowed)
446 seen = set()
447 for u, nbrs in d.items():
448 for v, data in nbrs.items():
449 if (u, v) not in seen:
450 G.add_edge(u, v, key=0)
451 G[u][v][0].update(data)
452 seen.add((v, u))
453 else:
454 G.add_edges_from(
455 ((u, v, data) for u, nbrs in d.items() for v, data in nbrs.items())
456 )
457 return G
458
459
460@nx._dispatchable(preserve_edge_attrs=True)
461def to_edgelist(G, nodelist=None):
462 """Returns a list of edges in the graph.
463
464 Parameters
465 ----------
466 G : graph
467 A NetworkX graph
468
469 nodelist : list
470 Use only nodes specified in nodelist
471
472 """
473 if nodelist is None:
474 return G.edges(data=True)
475 return G.edges(nodelist, data=True)
476
477
478@nx._dispatchable(graphs=None, returns_graph=True)
479def from_edgelist(edgelist, create_using=None):
480 """Returns a graph from a list of edges.
481
482 Parameters
483 ----------
484 edgelist : list or iterator
485 Edge tuples
486
487 create_using : NetworkX graph constructor, optional (default=nx.Graph)
488 Graph type to create. If graph instance, then cleared before populated.
489
490 Examples
491 --------
492 >>> edgelist = [(0, 1)] # single edge (0,1)
493 >>> G = nx.from_edgelist(edgelist)
494
495 or
496
497 >>> G = nx.Graph(edgelist) # use Graph constructor
498
499 """
500 G = nx.empty_graph(0, create_using)
501 G.add_edges_from(edgelist)
502 return G