convert.py 16 KB

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