convert_matrix.py 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314
  1. """Functions to convert NetworkX graphs to and from common data containers
  2. like numpy arrays, scipy sparse arrays, and pandas DataFrames.
  3. The preferred way of converting data to a NetworkX graph is through the
  4. graph constructor. The constructor calls the `~networkx.convert.to_networkx_graph`
  5. function which attempts to guess the input type and convert it automatically.
  6. Examples
  7. --------
  8. Create a 10 node random graph from a numpy array
  9. >>> import numpy as np
  10. >>> rng = np.random.default_rng()
  11. >>> a = rng.integers(low=0, high=2, size=(10, 10))
  12. >>> DG = nx.from_numpy_array(a, create_using=nx.DiGraph)
  13. or equivalently:
  14. >>> DG = nx.DiGraph(a)
  15. which calls `from_numpy_array` internally based on the type of ``a``.
  16. See Also
  17. --------
  18. nx_agraph, nx_pydot
  19. """
  20. import itertools
  21. from collections import defaultdict
  22. import networkx as nx
  23. __all__ = [
  24. "from_pandas_adjacency",
  25. "to_pandas_adjacency",
  26. "from_pandas_edgelist",
  27. "to_pandas_edgelist",
  28. "from_scipy_sparse_array",
  29. "to_scipy_sparse_array",
  30. "from_numpy_array",
  31. "to_numpy_array",
  32. ]
  33. @nx._dispatchable(edge_attrs="weight")
  34. def to_pandas_adjacency(
  35. G,
  36. nodelist=None,
  37. dtype=None,
  38. order=None,
  39. multigraph_weight=sum,
  40. weight="weight",
  41. nonedge=0.0,
  42. ):
  43. """Returns the graph adjacency matrix as a Pandas DataFrame.
  44. Parameters
  45. ----------
  46. G : graph
  47. The NetworkX graph used to construct the Pandas DataFrame.
  48. nodelist : list, optional
  49. The rows and columns are ordered according to the nodes in `nodelist`.
  50. If `nodelist` is None, then the ordering is produced by G.nodes().
  51. multigraph_weight : {sum, min, max}, optional
  52. An operator that determines how weights in multigraphs are handled.
  53. The default is to sum the weights of the multiple edges.
  54. weight : string or None, optional
  55. The edge attribute that holds the numerical value used for
  56. the edge weight. If an edge does not have that attribute, then the
  57. value 1 is used instead.
  58. nonedge : float, optional
  59. The matrix values corresponding to nonedges are typically set to zero.
  60. However, this could be undesirable if there are matrix values
  61. corresponding to actual edges that also have the value zero. If so,
  62. one might prefer nonedges to have some other value, such as nan.
  63. Returns
  64. -------
  65. df : Pandas DataFrame
  66. Graph adjacency matrix
  67. Notes
  68. -----
  69. For directed graphs, entry i,j corresponds to an edge from i to j.
  70. The DataFrame entries are assigned to the weight edge attribute. When
  71. an edge does not have a weight attribute, the value of the entry is set to
  72. the number 1. For multiple (parallel) edges, the values of the entries
  73. are determined by the 'multigraph_weight' parameter. The default is to
  74. sum the weight attributes for each of the parallel edges.
  75. When `nodelist` does not contain every node in `G`, the matrix is built
  76. from the subgraph of `G` that is induced by the nodes in `nodelist`.
  77. The convention used for self-loop edges in graphs is to assign the
  78. diagonal matrix entry value to the weight attribute of the edge
  79. (or the number 1 if the edge has no weight attribute). If the
  80. alternate convention of doubling the edge weight is desired the
  81. resulting Pandas DataFrame can be modified as follows::
  82. >>> import pandas as pd
  83. >>> G = nx.Graph([(1, 1), (2, 2)])
  84. >>> df = nx.to_pandas_adjacency(G)
  85. >>> df
  86. 1 2
  87. 1 1.0 0.0
  88. 2 0.0 1.0
  89. >>> diag_idx = list(range(len(df)))
  90. >>> df.iloc[diag_idx, diag_idx] *= 2
  91. >>> df
  92. 1 2
  93. 1 2.0 0.0
  94. 2 0.0 2.0
  95. Examples
  96. --------
  97. >>> G = nx.MultiDiGraph()
  98. >>> G.add_edge(0, 1, weight=2)
  99. 0
  100. >>> G.add_edge(1, 0)
  101. 0
  102. >>> G.add_edge(2, 2, weight=3)
  103. 0
  104. >>> G.add_edge(2, 2)
  105. 1
  106. >>> nx.to_pandas_adjacency(G, nodelist=[0, 1, 2], dtype=int)
  107. 0 1 2
  108. 0 0 2 0
  109. 1 1 0 0
  110. 2 0 0 4
  111. """
  112. import pandas as pd
  113. M = to_numpy_array(
  114. G,
  115. nodelist=nodelist,
  116. dtype=dtype,
  117. order=order,
  118. multigraph_weight=multigraph_weight,
  119. weight=weight,
  120. nonedge=nonedge,
  121. )
  122. if nodelist is None:
  123. nodelist = list(G)
  124. return pd.DataFrame(data=M, index=nodelist, columns=nodelist)
  125. @nx._dispatchable(graphs=None, returns_graph=True)
  126. def from_pandas_adjacency(df, create_using=None):
  127. r"""Returns a graph from Pandas DataFrame.
  128. The Pandas DataFrame is interpreted as an adjacency matrix for the graph.
  129. Parameters
  130. ----------
  131. df : Pandas DataFrame
  132. An adjacency matrix representation of a graph
  133. create_using : NetworkX graph constructor, optional (default=nx.Graph)
  134. Graph type to create. If graph instance, then cleared before populated.
  135. Notes
  136. -----
  137. For directed graphs, explicitly mention create_using=nx.DiGraph,
  138. and entry i,j of df corresponds to an edge from i to j.
  139. If `df` has a single data type for each entry it will be converted to an
  140. appropriate Python data type.
  141. If you have node attributes stored in a separate dataframe `df_nodes`,
  142. you can load those attributes to the graph `G` using the following code::
  143. df_nodes = pd.DataFrame({"node_id": [1, 2, 3], "attribute1": ["A", "B", "C"]})
  144. G.add_nodes_from((n, dict(d)) for n, d in df_nodes.iterrows())
  145. If `df` has a user-specified compound data type the names
  146. of the data fields will be used as attribute keys in the resulting
  147. NetworkX graph.
  148. See Also
  149. --------
  150. to_pandas_adjacency
  151. Examples
  152. --------
  153. Simple integer weights on edges:
  154. >>> import pandas as pd
  155. >>> pd.options.display.max_columns = 20
  156. >>> df = pd.DataFrame([[1, 1], [2, 1]])
  157. >>> df
  158. 0 1
  159. 0 1 1
  160. 1 2 1
  161. >>> G = nx.from_pandas_adjacency(df)
  162. >>> G.name = "Graph from pandas adjacency matrix"
  163. >>> print(G)
  164. Graph named 'Graph from pandas adjacency matrix' with 2 nodes and 3 edges
  165. """
  166. try:
  167. df = df[df.index]
  168. except Exception as err:
  169. missing = list(set(df.index).difference(set(df.columns)))
  170. msg = f"{missing} not in columns"
  171. raise nx.NetworkXError("Columns must match Indices.", msg) from err
  172. A = df.values
  173. G = from_numpy_array(A, create_using=create_using, nodelist=df.columns)
  174. return G
  175. @nx._dispatchable(preserve_edge_attrs=True)
  176. def to_pandas_edgelist(
  177. G,
  178. source="source",
  179. target="target",
  180. nodelist=None,
  181. dtype=None,
  182. edge_key=None,
  183. ):
  184. """Returns the graph edge list as a Pandas DataFrame.
  185. Parameters
  186. ----------
  187. G : graph
  188. The NetworkX graph used to construct the Pandas DataFrame.
  189. source : str or int, optional
  190. A valid column name (string or integer) for the source nodes (for the
  191. directed case).
  192. target : str or int, optional
  193. A valid column name (string or integer) for the target nodes (for the
  194. directed case).
  195. nodelist : list, optional
  196. Use only nodes specified in nodelist
  197. dtype : dtype, default None
  198. Use to create the DataFrame. Data type to force.
  199. Only a single dtype is allowed. If None, infer.
  200. edge_key : str or int or None, optional (default=None)
  201. A valid column name (string or integer) for the edge keys (for the
  202. multigraph case). If None, edge keys are not stored in the DataFrame.
  203. Returns
  204. -------
  205. df : Pandas DataFrame
  206. Graph edge list
  207. Examples
  208. --------
  209. >>> G = nx.Graph(
  210. ... [
  211. ... ("A", "B", {"cost": 1, "weight": 7}),
  212. ... ("C", "E", {"cost": 9, "weight": 10}),
  213. ... ]
  214. ... )
  215. >>> df = nx.to_pandas_edgelist(G, nodelist=["A", "C"])
  216. >>> df[["source", "target", "cost", "weight"]]
  217. source target cost weight
  218. 0 A B 1 7
  219. 1 C E 9 10
  220. >>> G = nx.MultiGraph([("A", "B", {"cost": 1}), ("A", "B", {"cost": 9})])
  221. >>> df = nx.to_pandas_edgelist(G, nodelist=["A", "C"], edge_key="ekey")
  222. >>> df[["source", "target", "cost", "ekey"]]
  223. source target cost ekey
  224. 0 A B 1 0
  225. 1 A B 9 1
  226. """
  227. import pandas as pd
  228. if nodelist is None:
  229. edgelist = G.edges(data=True)
  230. else:
  231. edgelist = G.edges(nodelist, data=True)
  232. source_nodes = [s for s, _, _ in edgelist]
  233. target_nodes = [t for _, t, _ in edgelist]
  234. all_attrs = set().union(*(d.keys() for _, _, d in edgelist))
  235. if source in all_attrs:
  236. raise nx.NetworkXError(f"Source name {source!r} is an edge attr name")
  237. if target in all_attrs:
  238. raise nx.NetworkXError(f"Target name {target!r} is an edge attr name")
  239. nan = float("nan")
  240. edge_attr = {k: [d.get(k, nan) for _, _, d in edgelist] for k in all_attrs}
  241. if G.is_multigraph() and edge_key is not None:
  242. if edge_key in all_attrs:
  243. raise nx.NetworkXError(f"Edge key name {edge_key!r} is an edge attr name")
  244. edge_keys = [k for _, _, k in G.edges(keys=True)]
  245. edgelistdict = {source: source_nodes, target: target_nodes, edge_key: edge_keys}
  246. else:
  247. edgelistdict = {source: source_nodes, target: target_nodes}
  248. edgelistdict.update(edge_attr)
  249. return pd.DataFrame(edgelistdict, dtype=dtype)
  250. @nx._dispatchable(graphs=None, returns_graph=True)
  251. def from_pandas_edgelist(
  252. df,
  253. source="source",
  254. target="target",
  255. edge_attr=None,
  256. create_using=None,
  257. edge_key=None,
  258. ):
  259. """Returns a graph from Pandas DataFrame containing an edge list.
  260. The Pandas DataFrame should contain at least two columns of node names and
  261. zero or more columns of edge attributes. Each row will be processed as one
  262. edge instance.
  263. Note: This function iterates over DataFrame.values, which is not
  264. guaranteed to retain the data type across columns in the row. This is only
  265. a problem if your row is entirely numeric and a mix of ints and floats. In
  266. that case, all values will be returned as floats. See the
  267. DataFrame.iterrows documentation for an example.
  268. Parameters
  269. ----------
  270. df : Pandas DataFrame
  271. An edge list representation of a graph
  272. source : str or int
  273. A valid column name (string or integer) for the source nodes (for the
  274. directed case).
  275. target : str or int
  276. A valid column name (string or integer) for the target nodes (for the
  277. directed case).
  278. edge_attr : str or int, iterable, True, or None
  279. A valid column name (str or int) or iterable of column names that are
  280. used to retrieve items and add them to the graph as edge attributes.
  281. If `True`, all columns will be added except `source`, `target` and `edge_key`.
  282. If `None`, no edge attributes are added to the graph.
  283. create_using : NetworkX graph constructor, optional (default=nx.Graph)
  284. Graph type to create. If graph instance, then cleared before populated.
  285. edge_key : str or None, optional (default=None)
  286. A valid column name for the edge keys (for a MultiGraph). The values in
  287. this column are used for the edge keys when adding edges if create_using
  288. is a multigraph.
  289. Notes
  290. -----
  291. If you have node attributes stored in a separate dataframe `df_nodes`,
  292. you can load those attributes to the graph `G` using the following code::
  293. df_nodes = pd.DataFrame({"node_id": [1, 2, 3], "attribute1": ["A", "B", "C"]})
  294. G.add_nodes_from((n, dict(d)) for n, d in df_nodes.iterrows())
  295. See Also
  296. --------
  297. to_pandas_edgelist
  298. Examples
  299. --------
  300. Simple integer weights on edges:
  301. >>> import pandas as pd
  302. >>> pd.options.display.max_columns = 20
  303. >>> import numpy as np
  304. >>> rng = np.random.RandomState(seed=5)
  305. >>> ints = rng.randint(1, 11, size=(3, 2))
  306. >>> a = ["A", "B", "C"]
  307. >>> b = ["D", "A", "E"]
  308. >>> df = pd.DataFrame(ints, columns=["weight", "cost"])
  309. >>> df[0] = a
  310. >>> df["b"] = b
  311. >>> df[["weight", "cost", 0, "b"]]
  312. weight cost 0 b
  313. 0 4 7 A D
  314. 1 7 1 B A
  315. 2 10 9 C E
  316. >>> G = nx.from_pandas_edgelist(df, 0, "b", ["weight", "cost"])
  317. >>> G["E"]["C"]["weight"]
  318. 10
  319. >>> G["E"]["C"]["cost"]
  320. 9
  321. >>> edges = pd.DataFrame(
  322. ... {
  323. ... "source": [0, 1, 2],
  324. ... "target": [2, 2, 3],
  325. ... "weight": [3, 4, 5],
  326. ... "color": ["red", "blue", "blue"],
  327. ... }
  328. ... )
  329. >>> G = nx.from_pandas_edgelist(edges, edge_attr=True)
  330. >>> G[0][2]["color"]
  331. 'red'
  332. Build multigraph with custom keys:
  333. >>> edges = pd.DataFrame(
  334. ... {
  335. ... "source": [0, 1, 2, 0],
  336. ... "target": [2, 2, 3, 2],
  337. ... "my_edge_key": ["A", "B", "C", "D"],
  338. ... "weight": [3, 4, 5, 6],
  339. ... "color": ["red", "blue", "blue", "blue"],
  340. ... }
  341. ... )
  342. >>> G = nx.from_pandas_edgelist(
  343. ... edges,
  344. ... edge_key="my_edge_key",
  345. ... edge_attr=["weight", "color"],
  346. ... create_using=nx.MultiGraph(),
  347. ... )
  348. >>> G[0][2]
  349. AtlasView({'A': {'weight': 3, 'color': 'red'}, 'D': {'weight': 6, 'color': 'blue'}})
  350. """
  351. g = nx.empty_graph(0, create_using)
  352. if edge_attr is None:
  353. if g.is_multigraph() and edge_key is not None:
  354. for u, v, k in zip(df[source], df[target], df[edge_key]):
  355. g.add_edge(u, v, k)
  356. else:
  357. g.add_edges_from(zip(df[source], df[target]))
  358. return g
  359. reserved_columns = [source, target]
  360. if g.is_multigraph() and edge_key is not None:
  361. reserved_columns.append(edge_key)
  362. # Additional columns requested
  363. attr_col_headings = []
  364. attribute_data = []
  365. if edge_attr is True:
  366. attr_col_headings = [c for c in df.columns if c not in reserved_columns]
  367. elif isinstance(edge_attr, list | tuple):
  368. attr_col_headings = edge_attr
  369. else:
  370. attr_col_headings = [edge_attr]
  371. if len(attr_col_headings) == 0:
  372. raise nx.NetworkXError(
  373. f"Invalid edge_attr argument: No columns found with name: {attr_col_headings}"
  374. )
  375. try:
  376. attribute_data = zip(*[df[col] for col in attr_col_headings])
  377. except (KeyError, TypeError) as err:
  378. msg = f"Invalid edge_attr argument: {edge_attr}"
  379. raise nx.NetworkXError(msg) from err
  380. if g.is_multigraph():
  381. # => append the edge keys from the df to the bundled data
  382. if edge_key is not None:
  383. try:
  384. multigraph_edge_keys = df[edge_key]
  385. attribute_data = zip(attribute_data, multigraph_edge_keys)
  386. except (KeyError, TypeError) as err:
  387. msg = f"Invalid edge_key argument: {edge_key}"
  388. raise nx.NetworkXError(msg) from err
  389. for s, t, attrs in zip(df[source], df[target], attribute_data):
  390. if edge_key is not None:
  391. attrs, multigraph_edge_key = attrs
  392. key = g.add_edge(s, t, key=multigraph_edge_key)
  393. else:
  394. key = g.add_edge(s, t)
  395. g[s][t][key].update(zip(attr_col_headings, attrs))
  396. else:
  397. for s, t, attrs in zip(df[source], df[target], attribute_data):
  398. g.add_edge(s, t)
  399. g[s][t].update(zip(attr_col_headings, attrs))
  400. return g
  401. @nx._dispatchable(edge_attrs="weight")
  402. def to_scipy_sparse_array(G, nodelist=None, dtype=None, weight="weight", format="csr"):
  403. """Returns the graph adjacency matrix as a SciPy sparse array.
  404. Parameters
  405. ----------
  406. G : graph
  407. The NetworkX graph used to construct the sparse array.
  408. nodelist : list, optional
  409. The rows and columns are ordered according to the nodes in `nodelist`.
  410. If `nodelist` is None, then the ordering is produced by ``G.nodes()``.
  411. dtype : NumPy data-type, optional
  412. A valid NumPy dtype used to initialize the array. If None, then the
  413. NumPy default is used.
  414. weight : string or None, optional (default='weight')
  415. The edge attribute that holds the numerical value used for
  416. the edge weight. If None then all edge weights are 1.
  417. format : str in {'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'}
  418. The format of the sparse array to be returned (default 'csr'). For
  419. some algorithms different implementations of sparse arrays
  420. can perform better. See [1]_ for details.
  421. Returns
  422. -------
  423. A : SciPy sparse array
  424. Graph adjacency matrix.
  425. Notes
  426. -----
  427. For directed graphs, matrix entry ``i, j`` corresponds to an edge from
  428. ``i`` to ``j``.
  429. The values of the adjacency matrix are populated using the edge attribute held in
  430. parameter `weight`. When an edge does not have that attribute, the
  431. value of the entry is 1.
  432. For multiple edges the matrix values are the sums of the edge weights.
  433. When `nodelist` does not contain every node in `G`, the adjacency matrix
  434. is built from the subgraph of `G` that is induced by the nodes in
  435. `nodelist`.
  436. The convention used for self-loop edges in graphs is to assign the
  437. diagonal matrix entry value to the weight attribute of the edge
  438. (or the number 1 if the edge has no weight attribute). If the
  439. alternate convention of doubling the edge weight is desired the
  440. resulting array can be modified as follows::
  441. >>> G = nx.Graph([(1, 1)])
  442. >>> A = nx.to_scipy_sparse_array(G)
  443. >>> A.toarray()
  444. array([[1]])
  445. >>> A.setdiag(A.diagonal() * 2)
  446. >>> A.toarray()
  447. array([[2]])
  448. Examples
  449. --------
  450. Basic usage:
  451. >>> G = nx.path_graph(4)
  452. >>> A = nx.to_scipy_sparse_array(G)
  453. >>> A # doctest: +SKIP
  454. <Compressed Sparse Row sparse array of dtype 'int64'
  455. with 6 stored elements and shape (4, 4)>
  456. >>> A.toarray()
  457. array([[0, 1, 0, 0],
  458. [1, 0, 1, 0],
  459. [0, 1, 0, 1],
  460. [0, 0, 1, 0]])
  461. .. note:: The `toarray` method is used in these examples to better visualize
  462. the adjacency matrix. For a dense representation of the adjaceny matrix,
  463. use `to_numpy_array` instead.
  464. Directed graphs:
  465. >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3)])
  466. >>> nx.to_scipy_sparse_array(G).toarray()
  467. array([[0, 1, 0, 0],
  468. [0, 0, 1, 0],
  469. [0, 0, 0, 1],
  470. [0, 0, 0, 0]])
  471. >>> H = G.reverse()
  472. >>> H.edges
  473. OutEdgeView([(1, 0), (2, 1), (3, 2)])
  474. >>> nx.to_scipy_sparse_array(H).toarray()
  475. array([[0, 0, 0, 0],
  476. [1, 0, 0, 0],
  477. [0, 1, 0, 0],
  478. [0, 0, 1, 0]])
  479. By default, the order of the rows/columns of the adjacency matrix is determined
  480. by the ordering of the nodes in `G`:
  481. >>> G = nx.Graph()
  482. >>> G.add_nodes_from([3, 5, 0, 1])
  483. >>> G.add_edges_from([(1, 3), (1, 5)])
  484. >>> nx.to_scipy_sparse_array(G).toarray()
  485. array([[0, 0, 0, 1],
  486. [0, 0, 0, 1],
  487. [0, 0, 0, 0],
  488. [1, 1, 0, 0]])
  489. The ordering of the rows can be changed with `nodelist`:
  490. >>> ordered = [0, 1, 3, 5]
  491. >>> nx.to_scipy_sparse_array(G, nodelist=ordered).toarray()
  492. array([[0, 0, 0, 0],
  493. [0, 0, 1, 1],
  494. [0, 1, 0, 0],
  495. [0, 1, 0, 0]])
  496. If `nodelist` contains a subset of the nodes in `G`, the adjacency matrix
  497. for the node-induced subgraph is produced:
  498. >>> nx.to_scipy_sparse_array(G, nodelist=[1, 3, 5]).toarray()
  499. array([[0, 1, 1],
  500. [1, 0, 0],
  501. [1, 0, 0]])
  502. The values of the adjacency matrix are drawn from the edge attribute
  503. specified by the `weight` parameter:
  504. >>> G = nx.path_graph(4)
  505. >>> nx.set_edge_attributes(
  506. ... G, values={(0, 1): 1, (1, 2): 10, (2, 3): 2}, name="weight"
  507. ... )
  508. >>> nx.set_edge_attributes(
  509. ... G, values={(0, 1): 50, (1, 2): 35, (2, 3): 10}, name="capacity"
  510. ... )
  511. >>> nx.to_scipy_sparse_array(G).toarray() # Default weight="weight"
  512. array([[ 0, 1, 0, 0],
  513. [ 1, 0, 10, 0],
  514. [ 0, 10, 0, 2],
  515. [ 0, 0, 2, 0]])
  516. >>> nx.to_scipy_sparse_array(G, weight="capacity").toarray()
  517. array([[ 0, 50, 0, 0],
  518. [50, 0, 35, 0],
  519. [ 0, 35, 0, 10],
  520. [ 0, 0, 10, 0]])
  521. Any edges that don't have a `weight` attribute default to 1:
  522. >>> G[1][2].pop("capacity")
  523. 35
  524. >>> nx.to_scipy_sparse_array(G, weight="capacity").toarray()
  525. array([[ 0, 50, 0, 0],
  526. [50, 0, 1, 0],
  527. [ 0, 1, 0, 10],
  528. [ 0, 0, 10, 0]])
  529. When `G` is a multigraph, the values in the adjacency matrix are given by
  530. the sum of the `weight` edge attribute over each edge key:
  531. >>> G = nx.MultiDiGraph([(0, 1), (0, 1), (0, 1), (2, 0)])
  532. >>> nx.to_scipy_sparse_array(G).toarray()
  533. array([[0, 3, 0],
  534. [0, 0, 0],
  535. [1, 0, 0]])
  536. References
  537. ----------
  538. .. [1] Scipy Dev. References, "Sparse Arrays",
  539. https://docs.scipy.org/doc/scipy/reference/sparse.html
  540. """
  541. import scipy as sp
  542. if len(G) == 0:
  543. raise nx.NetworkXError("Graph has no nodes or edges")
  544. if nodelist is None:
  545. nodelist = list(G)
  546. nlen = len(G)
  547. else:
  548. nlen = len(nodelist)
  549. if nlen == 0:
  550. raise nx.NetworkXError("nodelist has no nodes")
  551. nodeset = set(G.nbunch_iter(nodelist))
  552. if nlen != len(nodeset):
  553. for n in nodelist:
  554. if n not in G:
  555. raise nx.NetworkXError(f"Node {n} in nodelist is not in G")
  556. raise nx.NetworkXError("nodelist contains duplicates.")
  557. if nlen < len(G):
  558. G = G.subgraph(nodelist)
  559. index = dict(zip(nodelist, range(nlen)))
  560. coefficients = zip(
  561. *((index[u], index[v], wt) for u, v, wt in G.edges(data=weight, default=1))
  562. )
  563. try:
  564. row, col, data = coefficients
  565. except ValueError:
  566. # there is no edge in the subgraph
  567. row, col, data = [], [], []
  568. if G.is_directed():
  569. A = sp.sparse.coo_array((data, (row, col)), shape=(nlen, nlen), dtype=dtype)
  570. else:
  571. # symmetrize matrix
  572. d = data + data
  573. r = row + col
  574. c = col + row
  575. # selfloop entries get double counted when symmetrizing
  576. # so we subtract the data on the diagonal
  577. selfloops = list(nx.selfloop_edges(G, data=weight, default=1))
  578. if selfloops:
  579. diag_index, diag_data = zip(*((index[u], -wt) for u, v, wt in selfloops))
  580. d += diag_data
  581. r += diag_index
  582. c += diag_index
  583. A = sp.sparse.coo_array((d, (r, c)), shape=(nlen, nlen), dtype=dtype)
  584. try:
  585. return A.asformat(format)
  586. except ValueError as err:
  587. raise nx.NetworkXError(f"Unknown sparse matrix format: {format}") from err
  588. def _csr_gen_triples(A):
  589. """Converts a SciPy sparse array in **Compressed Sparse Row** format to
  590. an iterable of weighted edge triples.
  591. """
  592. nrows = A.shape[0]
  593. indptr, dst_indices, data = A.indptr, A.indices, A.data
  594. import numpy as np
  595. src_indices = np.repeat(np.arange(nrows), np.diff(indptr))
  596. return zip(src_indices.tolist(), dst_indices.tolist(), A.data.tolist())
  597. def _csc_gen_triples(A):
  598. """Converts a SciPy sparse array in **Compressed Sparse Column** format to
  599. an iterable of weighted edge triples.
  600. """
  601. ncols = A.shape[1]
  602. indptr, src_indices, data = A.indptr, A.indices, A.data
  603. import numpy as np
  604. dst_indices = np.repeat(np.arange(ncols), np.diff(indptr))
  605. return zip(src_indices.tolist(), dst_indices.tolist(), A.data.tolist())
  606. def _coo_gen_triples(A):
  607. """Converts a SciPy sparse array in **Coordinate** format to an iterable
  608. of weighted edge triples.
  609. """
  610. return zip(A.row.tolist(), A.col.tolist(), A.data.tolist())
  611. def _dok_gen_triples(A):
  612. """Converts a SciPy sparse array in **Dictionary of Keys** format to an
  613. iterable of weighted edge triples.
  614. """
  615. for (r, c), v in A.items():
  616. # Use `v.item()` to convert a NumPy scalar to the appropriate Python scalar
  617. yield int(r), int(c), v.item()
  618. def _generate_weighted_edges(A):
  619. """Returns an iterable over (u, v, w) triples, where u and v are adjacent
  620. vertices and w is the weight of the edge joining u and v.
  621. `A` is a SciPy sparse array (in any format).
  622. """
  623. if A.format == "csr":
  624. return _csr_gen_triples(A)
  625. if A.format == "csc":
  626. return _csc_gen_triples(A)
  627. if A.format == "dok":
  628. return _dok_gen_triples(A)
  629. # If A is in any other format (including COO), convert it to COO format.
  630. return _coo_gen_triples(A.tocoo())
  631. @nx._dispatchable(graphs=None, returns_graph=True)
  632. def from_scipy_sparse_array(
  633. A, parallel_edges=False, create_using=None, edge_attribute="weight"
  634. ):
  635. """Creates a new graph from an adjacency matrix given as a SciPy sparse
  636. array.
  637. Parameters
  638. ----------
  639. A: scipy.sparse array
  640. An adjacency matrix representation of a graph
  641. parallel_edges : Boolean
  642. If this is True, `create_using` is a multigraph, and `A` is an
  643. integer matrix, then entry *(i, j)* in the matrix is interpreted as the
  644. number of parallel edges joining vertices *i* and *j* in the graph.
  645. If it is False, then the entries in the matrix are interpreted as
  646. the weight of a single edge joining the vertices.
  647. create_using : NetworkX graph constructor, optional (default=nx.Graph)
  648. Graph type to create. If graph instance, then cleared before populated.
  649. edge_attribute: string
  650. Name of edge attribute to store matrix numeric value. The data will
  651. have the same type as the matrix entry (int, float, (real,imag)).
  652. Notes
  653. -----
  654. For directed graphs, explicitly mention create_using=nx.DiGraph,
  655. and entry i,j of A corresponds to an edge from i to j.
  656. If `create_using` is :class:`networkx.MultiGraph` or
  657. :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the
  658. entries of `A` are of type :class:`int`, then this function returns a
  659. multigraph (constructed from `create_using`) with parallel edges.
  660. In this case, `edge_attribute` will be ignored.
  661. If `create_using` indicates an undirected multigraph, then only the edges
  662. indicated by the upper triangle of the matrix `A` will be added to the
  663. graph.
  664. Examples
  665. --------
  666. >>> import scipy as sp
  667. >>> A = sp.sparse.eye(2, 2, 1)
  668. >>> G = nx.from_scipy_sparse_array(A)
  669. If `create_using` indicates a multigraph and the matrix has only integer
  670. entries and `parallel_edges` is False, then the entries will be treated
  671. as weights for edges joining the nodes (without creating parallel edges):
  672. >>> A = sp.sparse.csr_array([[1, 1], [1, 2]])
  673. >>> G = nx.from_scipy_sparse_array(A, create_using=nx.MultiGraph)
  674. >>> G[1][1]
  675. AtlasView({0: {'weight': 2}})
  676. If `create_using` indicates a multigraph and the matrix has only integer
  677. entries and `parallel_edges` is True, then the entries will be treated
  678. as the number of parallel edges joining those two vertices:
  679. >>> A = sp.sparse.csr_array([[1, 1], [1, 2]])
  680. >>> G = nx.from_scipy_sparse_array(
  681. ... A, parallel_edges=True, create_using=nx.MultiGraph
  682. ... )
  683. >>> G[1][1]
  684. AtlasView({0: {'weight': 1}, 1: {'weight': 1}})
  685. """
  686. G = nx.empty_graph(0, create_using)
  687. n, m = A.shape
  688. if n != m:
  689. raise nx.NetworkXError(f"Adjacency matrix not square: nx,ny={A.shape}")
  690. # Make sure we get even the isolated nodes of the graph.
  691. G.add_nodes_from(range(n))
  692. # Create an iterable over (u, v, w) triples and for each triple, add an
  693. # edge from u to v with weight w.
  694. triples = _generate_weighted_edges(A)
  695. # If the entries in the adjacency matrix are integers, the graph is a
  696. # multigraph, and parallel_edges is True, then create parallel edges, each
  697. # with weight 1, for each entry in the adjacency matrix. Otherwise, create
  698. # one edge for each positive entry in the adjacency matrix and set the
  699. # weight of that edge to be the entry in the matrix.
  700. if A.dtype.kind in ("i", "u") and G.is_multigraph() and parallel_edges:
  701. chain = itertools.chain.from_iterable
  702. # The following line is equivalent to:
  703. #
  704. # for (u, v) in edges:
  705. # for d in range(A[u, v]):
  706. # G.add_edge(u, v, weight=1)
  707. #
  708. triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples)
  709. # If we are creating an undirected multigraph, only add the edges from the
  710. # upper triangle of the matrix. Otherwise, add all the edges. This relies
  711. # on the fact that the vertices created in the
  712. # `_generated_weighted_edges()` function are actually the row/column
  713. # indices for the matrix `A`.
  714. #
  715. # Without this check, we run into a problem where each edge is added twice
  716. # when `G.add_weighted_edges_from()` is invoked below.
  717. if G.is_multigraph() and not G.is_directed():
  718. triples = ((u, v, d) for u, v, d in triples if u <= v)
  719. G.add_weighted_edges_from(triples, weight=edge_attribute)
  720. return G
  721. @nx._dispatchable(edge_attrs="weight") # edge attrs may also be obtained from `dtype`
  722. def to_numpy_array(
  723. G,
  724. nodelist=None,
  725. dtype=None,
  726. order=None,
  727. multigraph_weight=sum,
  728. weight="weight",
  729. nonedge=0.0,
  730. ):
  731. """Returns the graph adjacency matrix as a NumPy array.
  732. Parameters
  733. ----------
  734. G : graph
  735. The NetworkX graph used to construct the NumPy array.
  736. nodelist : list, optional
  737. The rows and columns are ordered according to the nodes in `nodelist`.
  738. If `nodelist` is ``None``, then the ordering is produced by ``G.nodes()``.
  739. dtype : NumPy data type, optional
  740. A NumPy data type used to initialize the array. If None, then the NumPy
  741. default is used. The dtype can be structured if `weight=None`, in which
  742. case the dtype field names are used to look up edge attributes. The
  743. result is a structured array where each named field in the dtype
  744. corresponds to the adjacency for that edge attribute. See examples for
  745. details.
  746. order : {'C', 'F'}, optional
  747. Whether to store multidimensional data in C- or Fortran-contiguous
  748. (row- or column-wise) order in memory. If None, then the NumPy default
  749. is used.
  750. multigraph_weight : callable, optional
  751. An function that determines how weights in multigraphs are handled.
  752. The function should accept a sequence of weights and return a single
  753. value. The default is to sum the weights of the multiple edges.
  754. weight : string or None optional (default = 'weight')
  755. The edge attribute that holds the numerical value used for
  756. the edge weight. If an edge does not have that attribute, then the
  757. value 1 is used instead. `weight` must be ``None`` if a structured
  758. dtype is used.
  759. nonedge : array_like (default = 0.0)
  760. The value used to represent non-edges in the adjacency matrix.
  761. The array values corresponding to nonedges are typically set to zero.
  762. However, this could be undesirable if there are array values
  763. corresponding to actual edges that also have the value zero. If so,
  764. one might prefer nonedges to have some other value, such as ``nan``.
  765. Returns
  766. -------
  767. A : NumPy ndarray
  768. Graph adjacency matrix
  769. Raises
  770. ------
  771. NetworkXError
  772. If `dtype` is a structured dtype and `G` is a multigraph
  773. ValueError
  774. If `dtype` is a structured dtype and `weight` is not `None`
  775. See Also
  776. --------
  777. from_numpy_array
  778. Notes
  779. -----
  780. For directed graphs, entry ``i, j`` corresponds to an edge from ``i`` to ``j``.
  781. Entries in the adjacency matrix are given by the `weight` edge attribute.
  782. When an edge does not have a weight attribute, the value of the entry is
  783. set to the number 1. For multiple (parallel) edges, the values of the
  784. entries are determined by the `multigraph_weight` parameter. The default is
  785. to sum the weight attributes for each of the parallel edges.
  786. When `nodelist` does not contain every node in `G`, the adjacency matrix is
  787. built from the subgraph of `G` that is induced by the nodes in `nodelist`.
  788. The convention used for self-loop edges in graphs is to assign the
  789. diagonal array entry value to the weight attribute of the edge
  790. (or the number 1 if the edge has no weight attribute). If the
  791. alternate convention of doubling the edge weight is desired the
  792. resulting NumPy array can be modified as follows:
  793. >>> import numpy as np
  794. >>> G = nx.Graph([(1, 1)])
  795. >>> A = nx.to_numpy_array(G)
  796. >>> A
  797. array([[1.]])
  798. >>> A[np.diag_indices_from(A)] *= 2
  799. >>> A
  800. array([[2.]])
  801. Examples
  802. --------
  803. >>> G = nx.MultiDiGraph()
  804. >>> G.add_edge(0, 1, weight=2)
  805. 0
  806. >>> G.add_edge(1, 0)
  807. 0
  808. >>> G.add_edge(2, 2, weight=3)
  809. 0
  810. >>> G.add_edge(2, 2)
  811. 1
  812. >>> nx.to_numpy_array(G, nodelist=[0, 1, 2])
  813. array([[0., 2., 0.],
  814. [1., 0., 0.],
  815. [0., 0., 4.]])
  816. When `nodelist` argument is used, nodes of `G` which do not appear in the `nodelist`
  817. and their edges are not included in the adjacency matrix. Here is an example:
  818. >>> G = nx.Graph()
  819. >>> G.add_edge(3, 1)
  820. >>> G.add_edge(2, 0)
  821. >>> G.add_edge(2, 1)
  822. >>> G.add_edge(3, 0)
  823. >>> nx.to_numpy_array(G, nodelist=[1, 2, 3])
  824. array([[0., 1., 1.],
  825. [1., 0., 0.],
  826. [1., 0., 0.]])
  827. This function can also be used to create adjacency matrices for multiple
  828. edge attributes with structured dtypes:
  829. >>> G = nx.Graph()
  830. >>> G.add_edge(0, 1, weight=10)
  831. >>> G.add_edge(1, 2, cost=5)
  832. >>> G.add_edge(2, 3, weight=3, cost=-4.0)
  833. >>> dtype = np.dtype([("weight", int), ("cost", float)])
  834. >>> A = nx.to_numpy_array(G, dtype=dtype, weight=None)
  835. >>> A["weight"]
  836. array([[ 0, 10, 0, 0],
  837. [10, 0, 1, 0],
  838. [ 0, 1, 0, 3],
  839. [ 0, 0, 3, 0]])
  840. >>> A["cost"]
  841. array([[ 0., 1., 0., 0.],
  842. [ 1., 0., 5., 0.],
  843. [ 0., 5., 0., -4.],
  844. [ 0., 0., -4., 0.]])
  845. As stated above, the argument "nonedge" is useful especially when there are
  846. actually edges with weight 0 in the graph. Setting a nonedge value different than 0,
  847. makes it much clearer to differentiate such 0-weighted edges and actual nonedge values.
  848. >>> G = nx.Graph()
  849. >>> G.add_edge(3, 1, weight=2)
  850. >>> G.add_edge(2, 0, weight=0)
  851. >>> G.add_edge(2, 1, weight=0)
  852. >>> G.add_edge(3, 0, weight=1)
  853. >>> nx.to_numpy_array(G, nonedge=-1.0)
  854. array([[-1., 2., -1., 1.],
  855. [ 2., -1., 0., -1.],
  856. [-1., 0., -1., 0.],
  857. [ 1., -1., 0., -1.]])
  858. """
  859. import numpy as np
  860. if nodelist is None:
  861. nodelist = list(G)
  862. nlen = len(nodelist)
  863. # Input validation
  864. nodeset = set(nodelist)
  865. if nodeset - set(G):
  866. raise nx.NetworkXError(f"Nodes {nodeset - set(G)} in nodelist is not in G")
  867. if len(nodeset) < nlen:
  868. raise nx.NetworkXError("nodelist contains duplicates.")
  869. A = np.full((nlen, nlen), fill_value=nonedge, dtype=dtype, order=order)
  870. # Corner cases: empty nodelist or graph without any edges
  871. if nlen == 0 or G.number_of_edges() == 0:
  872. return A
  873. # If dtype is structured and weight is None, use dtype field names as
  874. # edge attributes
  875. edge_attrs = None # Only single edge attribute by default
  876. if A.dtype.names:
  877. if weight is None:
  878. edge_attrs = dtype.names
  879. else:
  880. raise ValueError(
  881. "Specifying `weight` not supported for structured dtypes\n."
  882. "To create adjacency matrices from structured dtypes, use `weight=None`."
  883. )
  884. # Map nodes to row/col in matrix
  885. idx = dict(zip(nodelist, range(nlen)))
  886. if len(nodelist) < len(G):
  887. G = G.subgraph(nodelist).copy()
  888. # Collect all edge weights and reduce with `multigraph_weights`
  889. if G.is_multigraph():
  890. if edge_attrs:
  891. raise nx.NetworkXError(
  892. "Structured arrays are not supported for MultiGraphs"
  893. )
  894. d = defaultdict(list)
  895. for u, v, wt in G.edges(data=weight, default=1.0):
  896. d[(idx[u], idx[v])].append(wt)
  897. i, j = np.array(list(d.keys())).T # indices
  898. wts = [multigraph_weight(ws) for ws in d.values()] # reduced weights
  899. else:
  900. i, j, wts = [], [], []
  901. # Special branch: multi-attr adjacency from structured dtypes
  902. if edge_attrs:
  903. # Extract edges with all data
  904. for u, v, data in G.edges(data=True):
  905. i.append(idx[u])
  906. j.append(idx[v])
  907. wts.append(data)
  908. # Map each attribute to the appropriate named field in the
  909. # structured dtype
  910. for attr in edge_attrs:
  911. attr_data = [wt.get(attr, 1.0) for wt in wts]
  912. A[attr][i, j] = attr_data
  913. if not G.is_directed():
  914. A[attr][j, i] = attr_data
  915. return A
  916. for u, v, wt in G.edges(data=weight, default=1.0):
  917. i.append(idx[u])
  918. j.append(idx[v])
  919. wts.append(wt)
  920. # Set array values with advanced indexing
  921. A[i, j] = wts
  922. if not G.is_directed():
  923. A[j, i] = wts
  924. return A
  925. @nx._dispatchable(graphs=None, returns_graph=True)
  926. def from_numpy_array(
  927. A, parallel_edges=False, create_using=None, edge_attr="weight", *, nodelist=None
  928. ):
  929. """Returns a graph from a 2D NumPy array.
  930. The 2D NumPy array is interpreted as an adjacency matrix for the graph.
  931. Parameters
  932. ----------
  933. A : a 2D numpy.ndarray
  934. An adjacency matrix representation of a graph
  935. parallel_edges : Boolean
  936. If this is True, `create_using` is a multigraph, and `A` is an
  937. integer array, then entry *(i, j)* in the array is interpreted as the
  938. number of parallel edges joining vertices *i* and *j* in the graph.
  939. If it is False, then the entries in the array are interpreted as
  940. the weight of a single edge joining the vertices.
  941. create_using : NetworkX graph constructor, optional (default=nx.Graph)
  942. Graph type to create. If graph instance, then cleared before populated.
  943. edge_attr : String, optional (default="weight")
  944. The attribute to which the array values are assigned on each edge. If
  945. it is None, edge attributes will not be assigned.
  946. nodelist : sequence of nodes, optional
  947. A sequence of objects to use as the nodes in the graph. If provided, the
  948. list of nodes must be the same length as the dimensions of `A`. The
  949. default is `None`, in which case the nodes are drawn from ``range(n)``.
  950. Notes
  951. -----
  952. For directed graphs, explicitly mention create_using=nx.DiGraph,
  953. and entry i,j of A corresponds to an edge from i to j.
  954. If `create_using` is :class:`networkx.MultiGraph` or
  955. :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the
  956. entries of `A` are of type :class:`int`, then this function returns a
  957. multigraph (of the same type as `create_using`) with parallel edges.
  958. If `create_using` indicates an undirected multigraph, then only the edges
  959. indicated by the upper triangle of the array `A` will be added to the
  960. graph.
  961. If `edge_attr` is Falsy (False or None), edge attributes will not be
  962. assigned, and the array data will be treated like a binary mask of
  963. edge presence or absence. Otherwise, the attributes will be assigned
  964. as follows:
  965. If the NumPy array has a single data type for each array entry it
  966. will be converted to an appropriate Python data type.
  967. If the NumPy array has a user-specified compound data type the names
  968. of the data fields will be used as attribute keys in the resulting
  969. NetworkX graph.
  970. See Also
  971. --------
  972. to_numpy_array
  973. Examples
  974. --------
  975. Simple integer weights on edges:
  976. >>> import numpy as np
  977. >>> A = np.array([[1, 1], [2, 1]])
  978. >>> G = nx.from_numpy_array(A)
  979. >>> G.edges(data=True)
  980. EdgeDataView([(0, 0, {'weight': 1}), (0, 1, {'weight': 2}), (1, 1, {'weight': 1})])
  981. If `create_using` indicates a multigraph and the array has only integer
  982. entries and `parallel_edges` is False, then the entries will be treated
  983. as weights for edges joining the nodes (without creating parallel edges):
  984. >>> A = np.array([[1, 1], [1, 2]])
  985. >>> G = nx.from_numpy_array(A, create_using=nx.MultiGraph)
  986. >>> G[1][1]
  987. AtlasView({0: {'weight': 2}})
  988. If `create_using` indicates a multigraph and the array has only integer
  989. entries and `parallel_edges` is True, then the entries will be treated
  990. as the number of parallel edges joining those two vertices:
  991. >>> A = np.array([[1, 1], [1, 2]])
  992. >>> temp = nx.MultiGraph()
  993. >>> G = nx.from_numpy_array(A, parallel_edges=True, create_using=temp)
  994. >>> G[1][1]
  995. AtlasView({0: {'weight': 1}, 1: {'weight': 1}})
  996. User defined compound data type on edges:
  997. >>> dt = [("weight", float), ("cost", int)]
  998. >>> A = np.array([[(1.0, 2)]], dtype=dt)
  999. >>> G = nx.from_numpy_array(A)
  1000. >>> G.edges()
  1001. EdgeView([(0, 0)])
  1002. >>> G[0][0]["cost"]
  1003. 2
  1004. >>> G[0][0]["weight"]
  1005. 1.0
  1006. """
  1007. kind_to_python_type = {
  1008. "f": float,
  1009. "i": int,
  1010. "u": int,
  1011. "b": bool,
  1012. "c": complex,
  1013. "S": str,
  1014. "U": str,
  1015. "V": "void",
  1016. }
  1017. G = nx.empty_graph(0, create_using)
  1018. if A.ndim != 2:
  1019. raise nx.NetworkXError(f"Input array must be 2D, not {A.ndim}")
  1020. n, m = A.shape
  1021. if n != m:
  1022. raise nx.NetworkXError(f"Adjacency matrix not square: nx,ny={A.shape}")
  1023. dt = A.dtype
  1024. try:
  1025. python_type = kind_to_python_type[dt.kind]
  1026. except Exception as err:
  1027. raise TypeError(f"Unknown numpy data type: {dt}") from err
  1028. if _default_nodes := (nodelist is None):
  1029. nodelist = range(n)
  1030. else:
  1031. if len(nodelist) != n:
  1032. raise ValueError("nodelist must have the same length as A.shape[0]")
  1033. # Make sure we get even the isolated nodes of the graph.
  1034. G.add_nodes_from(nodelist)
  1035. # Get a list of all the entries in the array with nonzero entries. These
  1036. # coordinates become edges in the graph. (convert to int from np.int64)
  1037. edges = ((int(e[0]), int(e[1])) for e in zip(*A.nonzero()))
  1038. # handle numpy constructed data type
  1039. if python_type == "void":
  1040. # Sort the fields by their offset, then by dtype, then by name.
  1041. fields = sorted(
  1042. (offset, dtype, name) for name, (dtype, offset) in A.dtype.fields.items()
  1043. )
  1044. triples = (
  1045. (
  1046. u,
  1047. v,
  1048. {}
  1049. if edge_attr in [False, None]
  1050. else {
  1051. name: kind_to_python_type[dtype.kind](val)
  1052. for (_, dtype, name), val in zip(fields, A[u, v])
  1053. },
  1054. )
  1055. for u, v in edges
  1056. )
  1057. # If the entries in the adjacency matrix are integers, the graph is a
  1058. # multigraph, and parallel_edges is True, then create parallel edges, each
  1059. # with weight 1, for each entry in the adjacency matrix. Otherwise, create
  1060. # one edge for each positive entry in the adjacency matrix and set the
  1061. # weight of that edge to be the entry in the matrix.
  1062. elif python_type is int and G.is_multigraph() and parallel_edges:
  1063. chain = itertools.chain.from_iterable
  1064. # The following line is equivalent to:
  1065. #
  1066. # for (u, v) in edges:
  1067. # for d in range(A[u, v]):
  1068. # G.add_edge(u, v, weight=1)
  1069. #
  1070. if edge_attr in [False, None]:
  1071. triples = chain(((u, v, {}) for d in range(A[u, v])) for (u, v) in edges)
  1072. else:
  1073. triples = chain(
  1074. ((u, v, {edge_attr: 1}) for d in range(A[u, v])) for (u, v) in edges
  1075. )
  1076. else: # basic data type
  1077. if edge_attr in [False, None]:
  1078. triples = ((u, v, {}) for u, v in edges)
  1079. else:
  1080. triples = ((u, v, {edge_attr: python_type(A[u, v])}) for u, v in edges)
  1081. # If we are creating an undirected multigraph, only add the edges from the
  1082. # upper triangle of the matrix. Otherwise, add all the edges. This relies
  1083. # on the fact that the vertices created in the
  1084. # `_generated_weighted_edges()` function are actually the row/column
  1085. # indices for the matrix `A`.
  1086. #
  1087. # Without this check, we run into a problem where each edge is added twice
  1088. # when `G.add_edges_from()` is invoked below.
  1089. if G.is_multigraph() and not G.is_directed():
  1090. triples = ((u, v, d) for u, v, d in triples if u <= v)
  1091. # Remap nodes if user provided custom `nodelist`
  1092. if not _default_nodes:
  1093. idx_to_node = dict(enumerate(nodelist))
  1094. triples = ((idx_to_node[u], idx_to_node[v], d) for u, v, d in triples)
  1095. G.add_edges_from(triples)
  1096. return G