Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/networkx/convert_matrix.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
| author | shellac |
|---|---|
| date | Mon, 01 Jun 2020 08:59:25 -0400 |
| parents | 79f47841a781 |
| children |
comparison
equal
deleted
inserted
replaced
| 4:79f47841a781 | 5:9b1c78e6ba9c |
|---|---|
| 1 # Copyright (C) 2006-2019 by | |
| 2 # Aric Hagberg <hagberg@lanl.gov> | |
| 3 # Dan Schult <dschult@colgate.edu> | |
| 4 # Pieter Swart <swart@lanl.gov> | |
| 5 # All rights reserved. | |
| 6 # BSD license. | |
| 7 """Functions to convert NetworkX graphs to and from numpy/scipy matrices. | |
| 8 | |
| 9 The preferred way of converting data to a NetworkX graph is through the | |
| 10 graph constructor. The constructor calls the to_networkx_graph() function | |
| 11 which attempts to guess the input type and convert it automatically. | |
| 12 | |
| 13 Examples | |
| 14 -------- | |
| 15 Create a 10 node random graph from a numpy matrix | |
| 16 | |
| 17 >>> import numpy as np | |
| 18 >>> a = np.random.randint(0, 2, size=(10, 10)) | |
| 19 >>> D = nx.DiGraph(a) | |
| 20 | |
| 21 or equivalently | |
| 22 | |
| 23 >>> D = nx.to_networkx_graph(a, create_using=nx.DiGraph) | |
| 24 | |
| 25 See Also | |
| 26 -------- | |
| 27 nx_agraph, nx_pydot | |
| 28 """ | |
| 29 | |
| 30 import itertools | |
| 31 import networkx as nx | |
| 32 from networkx.utils import not_implemented_for | |
| 33 | |
| 34 __all__ = ['from_numpy_matrix', 'to_numpy_matrix', | |
| 35 'from_pandas_adjacency', 'to_pandas_adjacency', | |
| 36 'from_pandas_edgelist', 'to_pandas_edgelist', | |
| 37 'to_numpy_recarray', | |
| 38 'from_scipy_sparse_matrix', 'to_scipy_sparse_matrix', | |
| 39 'from_numpy_array', 'to_numpy_array'] | |
| 40 | |
| 41 | |
| 42 def to_pandas_adjacency(G, nodelist=None, dtype=None, order=None, | |
| 43 multigraph_weight=sum, weight='weight', nonedge=0.0): | |
| 44 """Returns the graph adjacency matrix as a Pandas DataFrame. | |
| 45 | |
| 46 Parameters | |
| 47 ---------- | |
| 48 G : graph | |
| 49 The NetworkX graph used to construct the Pandas DataFrame. | |
| 50 | |
| 51 nodelist : list, optional | |
| 52 The rows and columns are ordered according to the nodes in `nodelist`. | |
| 53 If `nodelist` is None, then the ordering is produced by G.nodes(). | |
| 54 | |
| 55 multigraph_weight : {sum, min, max}, optional | |
| 56 An operator that determines how weights in multigraphs are handled. | |
| 57 The default is to sum the weights of the multiple edges. | |
| 58 | |
| 59 weight : string or None, optional | |
| 60 The edge attribute that holds the numerical value used for | |
| 61 the edge weight. If an edge does not have that attribute, then the | |
| 62 value 1 is used instead. | |
| 63 | |
| 64 nonedge : float, optional | |
| 65 The matrix values corresponding to nonedges are typically set to zero. | |
| 66 However, this could be undesirable if there are matrix values | |
| 67 corresponding to actual edges that also have the value zero. If so, | |
| 68 one might prefer nonedges to have some other value, such as nan. | |
| 69 | |
| 70 Returns | |
| 71 ------- | |
| 72 df : Pandas DataFrame | |
| 73 Graph adjacency matrix | |
| 74 | |
| 75 Notes | |
| 76 ----- | |
| 77 For directed graphs, entry i,j corresponds to an edge from i to j. | |
| 78 | |
| 79 The DataFrame entries are assigned to the weight edge attribute. When | |
| 80 an edge does not have a weight attribute, the value of the entry is set to | |
| 81 the number 1. For multiple (parallel) edges, the values of the entries | |
| 82 are determined by the 'multigraph_weight' parameter. The default is to | |
| 83 sum the weight attributes for each of the parallel edges. | |
| 84 | |
| 85 When `nodelist` does not contain every node in `G`, the matrix is built | |
| 86 from the subgraph of `G` that is induced by the nodes in `nodelist`. | |
| 87 | |
| 88 The convention used for self-loop edges in graphs is to assign the | |
| 89 diagonal matrix entry value to the weight attribute of the edge | |
| 90 (or the number 1 if the edge has no weight attribute). If the | |
| 91 alternate convention of doubling the edge weight is desired the | |
| 92 resulting Pandas DataFrame can be modified as follows: | |
| 93 | |
| 94 >>> import pandas as pd | |
| 95 >>> pd.options.display.max_columns = 20 | |
| 96 >>> import numpy as np | |
| 97 >>> G = nx.Graph([(1, 1)]) | |
| 98 >>> df = nx.to_pandas_adjacency(G, dtype=int) | |
| 99 >>> df | |
| 100 1 | |
| 101 1 1 | |
| 102 >>> df.values[np.diag_indices_from(df)] *= 2 | |
| 103 >>> df | |
| 104 1 | |
| 105 1 2 | |
| 106 | |
| 107 Examples | |
| 108 -------- | |
| 109 >>> G = nx.MultiDiGraph() | |
| 110 >>> G.add_edge(0, 1, weight=2) | |
| 111 0 | |
| 112 >>> G.add_edge(1, 0) | |
| 113 0 | |
| 114 >>> G.add_edge(2, 2, weight=3) | |
| 115 0 | |
| 116 >>> G.add_edge(2, 2) | |
| 117 1 | |
| 118 >>> nx.to_pandas_adjacency(G, nodelist=[0, 1, 2], dtype=int) | |
| 119 0 1 2 | |
| 120 0 0 2 0 | |
| 121 1 1 0 0 | |
| 122 2 0 0 4 | |
| 123 | |
| 124 """ | |
| 125 import pandas as pd | |
| 126 M = to_numpy_array(G, nodelist=nodelist, dtype=dtype, order=order, | |
| 127 multigraph_weight=multigraph_weight, weight=weight, | |
| 128 nonedge=nonedge) | |
| 129 if nodelist is None: | |
| 130 nodelist = list(G) | |
| 131 return pd.DataFrame(data=M, index=nodelist, columns=nodelist) | |
| 132 | |
| 133 | |
| 134 def from_pandas_adjacency(df, create_using=None): | |
| 135 r"""Returns a graph from Pandas DataFrame. | |
| 136 | |
| 137 The Pandas DataFrame is interpreted as an adjacency matrix for the graph. | |
| 138 | |
| 139 Parameters | |
| 140 ---------- | |
| 141 df : Pandas DataFrame | |
| 142 An adjacency matrix representation of a graph | |
| 143 | |
| 144 create_using : NetworkX graph constructor, optional (default=nx.Graph) | |
| 145 Graph type to create. If graph instance, then cleared before populated. | |
| 146 | |
| 147 Notes | |
| 148 ----- | |
| 149 For directed graphs, explicitly mention create_using=nx.Digraph, | |
| 150 and entry i,j of df corresponds to an edge from i to j. | |
| 151 | |
| 152 If the numpy matrix has a single data type for each matrix entry it | |
| 153 will be converted to an appropriate Python data type. | |
| 154 | |
| 155 If the numpy matrix has a user-specified compound data type the names | |
| 156 of the data fields will be used as attribute keys in the resulting | |
| 157 NetworkX graph. | |
| 158 | |
| 159 See Also | |
| 160 -------- | |
| 161 to_pandas_adjacency | |
| 162 | |
| 163 Examples | |
| 164 -------- | |
| 165 Simple integer weights on edges: | |
| 166 | |
| 167 >>> import pandas as pd | |
| 168 >>> pd.options.display.max_columns = 20 | |
| 169 >>> df = pd.DataFrame([[1, 1], [2, 1]]) | |
| 170 >>> df | |
| 171 0 1 | |
| 172 0 1 1 | |
| 173 1 2 1 | |
| 174 >>> G = nx.from_pandas_adjacency(df) | |
| 175 >>> G.name = 'Graph from pandas adjacency matrix' | |
| 176 >>> print(nx.info(G)) | |
| 177 Name: Graph from pandas adjacency matrix | |
| 178 Type: Graph | |
| 179 Number of nodes: 2 | |
| 180 Number of edges: 3 | |
| 181 Average degree: 3.0000 | |
| 182 | |
| 183 """ | |
| 184 | |
| 185 try: | |
| 186 df = df[df.index] | |
| 187 except Exception: | |
| 188 msg = "%s not in columns" | |
| 189 missing = list(set(df.index).difference(set(df.columns))) | |
| 190 raise nx.NetworkXError("Columns must match Indices.", msg % missing) | |
| 191 | |
| 192 A = df.values | |
| 193 G = from_numpy_matrix(A, create_using=create_using) | |
| 194 | |
| 195 nx.relabel.relabel_nodes(G, dict(enumerate(df.columns)), copy=False) | |
| 196 return G | |
| 197 | |
| 198 | |
| 199 def to_pandas_edgelist(G, source='source', target='target', nodelist=None, | |
| 200 dtype=None, order=None): | |
| 201 """Returns the graph edge list as a Pandas DataFrame. | |
| 202 | |
| 203 Parameters | |
| 204 ---------- | |
| 205 G : graph | |
| 206 The NetworkX graph used to construct the Pandas DataFrame. | |
| 207 | |
| 208 source : str or int, optional | |
| 209 A valid column name (string or integer) for the source nodes (for the | |
| 210 directed case). | |
| 211 | |
| 212 target : str or int, optional | |
| 213 A valid column name (string or integer) for the target nodes (for the | |
| 214 directed case). | |
| 215 | |
| 216 nodelist : list, optional | |
| 217 Use only nodes specified in nodelist | |
| 218 | |
| 219 Returns | |
| 220 ------- | |
| 221 df : Pandas DataFrame | |
| 222 Graph edge list | |
| 223 | |
| 224 Examples | |
| 225 -------- | |
| 226 >>> G = nx.Graph([('A', 'B', {'cost': 1, 'weight': 7}), | |
| 227 ... ('C', 'E', {'cost': 9, 'weight': 10})]) | |
| 228 >>> df = nx.to_pandas_edgelist(G, nodelist=['A', 'C']) | |
| 229 >>> df[['source', 'target', 'cost', 'weight']] | |
| 230 source target cost weight | |
| 231 0 A B 1 7 | |
| 232 1 C E 9 10 | |
| 233 | |
| 234 """ | |
| 235 import pandas as pd | |
| 236 if nodelist is None: | |
| 237 edgelist = G.edges(data=True) | |
| 238 else: | |
| 239 edgelist = G.edges(nodelist, data=True) | |
| 240 source_nodes = [s for s, t, d in edgelist] | |
| 241 target_nodes = [t for s, t, d in edgelist] | |
| 242 all_keys = set().union(*(d.keys() for s, t, d in edgelist)) | |
| 243 edge_attr = {k: [d.get(k, float("nan")) for s, t, d in edgelist] | |
| 244 for k in all_keys} | |
| 245 edgelistdict = {source: source_nodes, target: target_nodes} | |
| 246 edgelistdict.update(edge_attr) | |
| 247 return pd.DataFrame(edgelistdict) | |
| 248 | |
| 249 | |
| 250 def from_pandas_edgelist(df, source='source', target='target', edge_attr=None, | |
| 251 create_using=None): | |
| 252 """Returns a graph from Pandas DataFrame containing an edge list. | |
| 253 | |
| 254 The Pandas DataFrame should contain at least two columns of node names and | |
| 255 zero or more columns of edge attributes. Each row will be processed as one | |
| 256 edge instance. | |
| 257 | |
| 258 Note: This function iterates over DataFrame.values, which is not | |
| 259 guaranteed to retain the data type across columns in the row. This is only | |
| 260 a problem if your row is entirely numeric and a mix of ints and floats. In | |
| 261 that case, all values will be returned as floats. See the | |
| 262 DataFrame.iterrows documentation for an example. | |
| 263 | |
| 264 Parameters | |
| 265 ---------- | |
| 266 df : Pandas DataFrame | |
| 267 An edge list representation of a graph | |
| 268 | |
| 269 source : str or int | |
| 270 A valid column name (string or integer) for the source nodes (for the | |
| 271 directed case). | |
| 272 | |
| 273 target : str or int | |
| 274 A valid column name (string or integer) for the target nodes (for the | |
| 275 directed case). | |
| 276 | |
| 277 edge_attr : str or int, iterable, True, or None | |
| 278 A valid column name (str or int) or iterable of column names that are | |
| 279 used to retrieve items and add them to the graph as edge attributes. | |
| 280 If `True`, all of the remaining columns will be added. | |
| 281 If `None`, no edge attributes are added to the graph. | |
| 282 | |
| 283 create_using : NetworkX graph constructor, optional (default=nx.Graph) | |
| 284 Graph type to create. If graph instance, then cleared before populated. | |
| 285 | |
| 286 See Also | |
| 287 -------- | |
| 288 to_pandas_edgelist | |
| 289 | |
| 290 Examples | |
| 291 -------- | |
| 292 Simple integer weights on edges: | |
| 293 | |
| 294 >>> import pandas as pd | |
| 295 >>> pd.options.display.max_columns = 20 | |
| 296 >>> import numpy as np | |
| 297 >>> rng = np.random.RandomState(seed=5) | |
| 298 >>> ints = rng.randint(1, 11, size=(3,2)) | |
| 299 >>> a = ['A', 'B', 'C'] | |
| 300 >>> b = ['D', 'A', 'E'] | |
| 301 >>> df = pd.DataFrame(ints, columns=['weight', 'cost']) | |
| 302 >>> df[0] = a | |
| 303 >>> df['b'] = b | |
| 304 >>> df[['weight', 'cost', 0, 'b']] | |
| 305 weight cost 0 b | |
| 306 0 4 7 A D | |
| 307 1 7 1 B A | |
| 308 2 10 9 C E | |
| 309 >>> G = nx.from_pandas_edgelist(df, 0, 'b', ['weight', 'cost']) | |
| 310 >>> G['E']['C']['weight'] | |
| 311 10 | |
| 312 >>> G['E']['C']['cost'] | |
| 313 9 | |
| 314 >>> edges = pd.DataFrame({'source': [0, 1, 2], | |
| 315 ... 'target': [2, 2, 3], | |
| 316 ... 'weight': [3, 4, 5], | |
| 317 ... 'color': ['red', 'blue', 'blue']}) | |
| 318 >>> G = nx.from_pandas_edgelist(edges, edge_attr=True) | |
| 319 >>> G[0][2]['color'] | |
| 320 'red' | |
| 321 | |
| 322 """ | |
| 323 g = nx.empty_graph(0, create_using) | |
| 324 | |
| 325 if edge_attr is None: | |
| 326 g.add_edges_from(zip(df[source], df[target])) | |
| 327 return g | |
| 328 | |
| 329 # Additional columns requested | |
| 330 if edge_attr is True: | |
| 331 cols = [c for c in df.columns if c is not source and c is not target] | |
| 332 elif isinstance(edge_attr, (list, tuple)): | |
| 333 cols = edge_attr | |
| 334 else: | |
| 335 cols = [edge_attr] | |
| 336 if len(cols) == 0: | |
| 337 msg = "Invalid edge_attr argument. No columns found with name: %s" | |
| 338 raise nx.NetworkXError(msg % cols) | |
| 339 | |
| 340 try: | |
| 341 eattrs = zip(*[df[col] for col in cols]) | |
| 342 except (KeyError, TypeError) as e: | |
| 343 msg = "Invalid edge_attr argument: %s" % edge_attr | |
| 344 raise nx.NetworkXError(msg) | |
| 345 for s, t, attrs in zip(df[source], df[target], eattrs): | |
| 346 if g.is_multigraph(): | |
| 347 key = g.add_edge(s, t) | |
| 348 g[s][t][key].update(zip(cols, attrs)) | |
| 349 else: | |
| 350 g.add_edge(s, t) | |
| 351 g[s][t].update(zip(cols, attrs)) | |
| 352 | |
| 353 return g | |
| 354 | |
| 355 | |
| 356 def to_numpy_matrix(G, nodelist=None, dtype=None, order=None, | |
| 357 multigraph_weight=sum, weight='weight', nonedge=0.0): | |
| 358 """Returns the graph adjacency matrix as a NumPy matrix. | |
| 359 | |
| 360 Parameters | |
| 361 ---------- | |
| 362 G : graph | |
| 363 The NetworkX graph used to construct the NumPy matrix. | |
| 364 | |
| 365 nodelist : list, optional | |
| 366 The rows and columns are ordered according to the nodes in `nodelist`. | |
| 367 If `nodelist` is None, then the ordering is produced by G.nodes(). | |
| 368 | |
| 369 dtype : NumPy data type, optional | |
| 370 A valid single NumPy data type used to initialize the array. | |
| 371 This must be a simple type such as int or numpy.float64 and | |
| 372 not a compound data type (see to_numpy_recarray) | |
| 373 If None, then the NumPy default is used. | |
| 374 | |
| 375 order : {'C', 'F'}, optional | |
| 376 Whether to store multidimensional data in C- or Fortran-contiguous | |
| 377 (row- or column-wise) order in memory. If None, then the NumPy default | |
| 378 is used. | |
| 379 | |
| 380 multigraph_weight : {sum, min, max}, optional | |
| 381 An operator that determines how weights in multigraphs are handled. | |
| 382 The default is to sum the weights of the multiple edges. | |
| 383 | |
| 384 weight : string or None optional (default = 'weight') | |
| 385 The edge attribute that holds the numerical value used for | |
| 386 the edge weight. If an edge does not have that attribute, then the | |
| 387 value 1 is used instead. | |
| 388 | |
| 389 nonedge : float (default = 0.0) | |
| 390 The matrix values corresponding to nonedges are typically set to zero. | |
| 391 However, this could be undesirable if there are matrix values | |
| 392 corresponding to actual edges that also have the value zero. If so, | |
| 393 one might prefer nonedges to have some other value, such as nan. | |
| 394 | |
| 395 Returns | |
| 396 ------- | |
| 397 M : NumPy matrix | |
| 398 Graph adjacency matrix | |
| 399 | |
| 400 See Also | |
| 401 -------- | |
| 402 to_numpy_recarray, from_numpy_matrix | |
| 403 | |
| 404 Notes | |
| 405 ----- | |
| 406 For directed graphs, entry i,j corresponds to an edge from i to j. | |
| 407 | |
| 408 The matrix entries are assigned to the weight edge attribute. When | |
| 409 an edge does not have a weight attribute, the value of the entry is set to | |
| 410 the number 1. For multiple (parallel) edges, the values of the entries | |
| 411 are determined by the `multigraph_weight` parameter. The default is to | |
| 412 sum the weight attributes for each of the parallel edges. | |
| 413 | |
| 414 When `nodelist` does not contain every node in `G`, the matrix is built | |
| 415 from the subgraph of `G` that is induced by the nodes in `nodelist`. | |
| 416 | |
| 417 The convention used for self-loop edges in graphs is to assign the | |
| 418 diagonal matrix entry value to the weight attribute of the edge | |
| 419 (or the number 1 if the edge has no weight attribute). If the | |
| 420 alternate convention of doubling the edge weight is desired the | |
| 421 resulting Numpy matrix can be modified as follows: | |
| 422 | |
| 423 >>> import numpy as np | |
| 424 >>> G = nx.Graph([(1, 1)]) | |
| 425 >>> A = nx.to_numpy_matrix(G) | |
| 426 >>> A | |
| 427 matrix([[1.]]) | |
| 428 >>> A[np.diag_indices_from(A)] *= 2 | |
| 429 >>> A | |
| 430 matrix([[2.]]) | |
| 431 | |
| 432 Examples | |
| 433 -------- | |
| 434 >>> G = nx.MultiDiGraph() | |
| 435 >>> G.add_edge(0, 1, weight=2) | |
| 436 0 | |
| 437 >>> G.add_edge(1, 0) | |
| 438 0 | |
| 439 >>> G.add_edge(2, 2, weight=3) | |
| 440 0 | |
| 441 >>> G.add_edge(2, 2) | |
| 442 1 | |
| 443 >>> nx.to_numpy_matrix(G, nodelist=[0, 1, 2]) | |
| 444 matrix([[0., 2., 0.], | |
| 445 [1., 0., 0.], | |
| 446 [0., 0., 4.]]) | |
| 447 | |
| 448 """ | |
| 449 import numpy as np | |
| 450 | |
| 451 A = to_numpy_array(G, nodelist=nodelist, dtype=dtype, order=order, | |
| 452 multigraph_weight=multigraph_weight, weight=weight, | |
| 453 nonedge=nonedge) | |
| 454 M = np.asmatrix(A, dtype=dtype) | |
| 455 return M | |
| 456 | |
| 457 | |
| 458 def from_numpy_matrix(A, parallel_edges=False, create_using=None): | |
| 459 """Returns a graph from numpy matrix. | |
| 460 | |
| 461 The numpy matrix is interpreted as an adjacency matrix for the graph. | |
| 462 | |
| 463 Parameters | |
| 464 ---------- | |
| 465 A : numpy matrix | |
| 466 An adjacency matrix representation of a graph | |
| 467 | |
| 468 parallel_edges : Boolean | |
| 469 If True, `create_using` is a multigraph, and `A` is an | |
| 470 integer matrix, then entry *(i, j)* in the matrix is interpreted as the | |
| 471 number of parallel edges joining vertices *i* and *j* in the graph. | |
| 472 If False, then the entries in the adjacency matrix are interpreted as | |
| 473 the weight of a single edge joining the vertices. | |
| 474 | |
| 475 create_using : NetworkX graph constructor, optional (default=nx.Graph) | |
| 476 Graph type to create. If graph instance, then cleared before populated. | |
| 477 | |
| 478 Notes | |
| 479 ----- | |
| 480 For directed graphs, explicitly mention create_using=nx.Digraph, | |
| 481 and entry i,j of A corresponds to an edge from i to j. | |
| 482 | |
| 483 If `create_using` is :class:`networkx.MultiGraph` or | |
| 484 :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the | |
| 485 entries of `A` are of type :class:`int`, then this function returns a | |
| 486 multigraph (constructed from `create_using`) with parallel edges. | |
| 487 | |
| 488 If `create_using` indicates an undirected multigraph, then only the edges | |
| 489 indicated by the upper triangle of the matrix `A` will be added to the | |
| 490 graph. | |
| 491 | |
| 492 If the numpy matrix has a single data type for each matrix entry it | |
| 493 will be converted to an appropriate Python data type. | |
| 494 | |
| 495 If the numpy matrix has a user-specified compound data type the names | |
| 496 of the data fields will be used as attribute keys in the resulting | |
| 497 NetworkX graph. | |
| 498 | |
| 499 See Also | |
| 500 -------- | |
| 501 to_numpy_matrix, to_numpy_recarray | |
| 502 | |
| 503 Examples | |
| 504 -------- | |
| 505 Simple integer weights on edges: | |
| 506 | |
| 507 >>> import numpy as np | |
| 508 >>> A = np.array([[1, 1], [2, 1]]) | |
| 509 >>> G = nx.from_numpy_matrix(A) | |
| 510 | |
| 511 If `create_using` indicates a multigraph and the matrix has only integer | |
| 512 entries and `parallel_edges` is False, then the entries will be treated | |
| 513 as weights for edges joining the nodes (without creating parallel edges): | |
| 514 | |
| 515 >>> A = np.array([[1, 1], [1, 2]]) | |
| 516 >>> G = nx.from_numpy_matrix(A, create_using=nx.MultiGraph) | |
| 517 >>> G[1][1] | |
| 518 AtlasView({0: {'weight': 2}}) | |
| 519 | |
| 520 If `create_using` indicates a multigraph and the matrix has only integer | |
| 521 entries and `parallel_edges` is True, then the entries will be treated | |
| 522 as the number of parallel edges joining those two vertices: | |
| 523 | |
| 524 >>> A = np.array([[1, 1], [1, 2]]) | |
| 525 >>> temp = nx.MultiGraph() | |
| 526 >>> G = nx.from_numpy_matrix(A, parallel_edges=True, create_using=temp) | |
| 527 >>> G[1][1] | |
| 528 AtlasView({0: {'weight': 1}, 1: {'weight': 1}}) | |
| 529 | |
| 530 User defined compound data type on edges: | |
| 531 | |
| 532 >>> dt = [('weight', float), ('cost', int)] | |
| 533 >>> A = np.array([[(1.0, 2)]], dtype=dt) | |
| 534 >>> G = nx.from_numpy_matrix(A) | |
| 535 >>> list(G.edges()) | |
| 536 [(0, 0)] | |
| 537 >>> G[0][0]['cost'] | |
| 538 2 | |
| 539 >>> G[0][0]['weight'] | |
| 540 1.0 | |
| 541 | |
| 542 """ | |
| 543 # This should never fail if you have created a numpy matrix with numpy... | |
| 544 import numpy as np | |
| 545 kind_to_python_type = {'f': float, | |
| 546 'i': int, | |
| 547 'u': int, | |
| 548 'b': bool, | |
| 549 'c': complex, | |
| 550 'S': str, | |
| 551 'V': 'void'} | |
| 552 try: # Python 3.x | |
| 553 blurb = chr(1245) # just to trigger the exception | |
| 554 kind_to_python_type['U'] = str | |
| 555 except ValueError: # Python 2.7 | |
| 556 kind_to_python_type['U'] = unicode | |
| 557 G = nx.empty_graph(0, create_using) | |
| 558 n, m = A.shape | |
| 559 if n != m: | |
| 560 raise nx.NetworkXError("Adjacency matrix is not square.", | |
| 561 "nx,ny=%s" % (A.shape,)) | |
| 562 dt = A.dtype | |
| 563 try: | |
| 564 python_type = kind_to_python_type[dt.kind] | |
| 565 except Exception: | |
| 566 raise TypeError("Unknown numpy data type: %s" % dt) | |
| 567 | |
| 568 # Make sure we get even the isolated nodes of the graph. | |
| 569 G.add_nodes_from(range(n)) | |
| 570 # Get a list of all the entries in the matrix with nonzero entries. These | |
| 571 # coordinates will become the edges in the graph. | |
| 572 edges = map(lambda e: (int(e[0]), int(e[1])), | |
| 573 zip(*(np.asarray(A).nonzero()))) | |
| 574 # handle numpy constructed data type | |
| 575 if python_type == 'void': | |
| 576 # Sort the fields by their offset, then by dtype, then by name. | |
| 577 fields = sorted((offset, dtype, name) for name, (dtype, offset) in | |
| 578 A.dtype.fields.items()) | |
| 579 triples = ((u, v, {name: kind_to_python_type[dtype.kind](val) | |
| 580 for (_, dtype, name), val in zip(fields, A[u, v])}) | |
| 581 for u, v in edges) | |
| 582 # If the entries in the adjacency matrix are integers, the graph is a | |
| 583 # multigraph, and parallel_edges is True, then create parallel edges, each | |
| 584 # with weight 1, for each entry in the adjacency matrix. Otherwise, create | |
| 585 # one edge for each positive entry in the adjacency matrix and set the | |
| 586 # weight of that edge to be the entry in the matrix. | |
| 587 elif python_type is int and G.is_multigraph() and parallel_edges: | |
| 588 chain = itertools.chain.from_iterable | |
| 589 # The following line is equivalent to: | |
| 590 # | |
| 591 # for (u, v) in edges: | |
| 592 # for d in range(A[u, v]): | |
| 593 # G.add_edge(u, v, weight=1) | |
| 594 # | |
| 595 triples = chain(((u, v, dict(weight=1)) for d in range(A[u, v])) | |
| 596 for (u, v) in edges) | |
| 597 else: # basic data type | |
| 598 triples = ((u, v, dict(weight=python_type(A[u, v]))) | |
| 599 for u, v in edges) | |
| 600 # If we are creating an undirected multigraph, only add the edges from the | |
| 601 # upper triangle of the matrix. Otherwise, add all the edges. This relies | |
| 602 # on the fact that the vertices created in the | |
| 603 # `_generated_weighted_edges()` function are actually the row/column | |
| 604 # indices for the matrix `A`. | |
| 605 # | |
| 606 # Without this check, we run into a problem where each edge is added twice | |
| 607 # when `G.add_edges_from()` is invoked below. | |
| 608 if G.is_multigraph() and not G.is_directed(): | |
| 609 triples = ((u, v, d) for u, v, d in triples if u <= v) | |
| 610 G.add_edges_from(triples) | |
| 611 return G | |
| 612 | |
| 613 | |
| 614 @not_implemented_for('multigraph') | |
| 615 def to_numpy_recarray(G, nodelist=None, dtype=None, order=None): | |
| 616 """Returns the graph adjacency matrix as a NumPy recarray. | |
| 617 | |
| 618 Parameters | |
| 619 ---------- | |
| 620 G : graph | |
| 621 The NetworkX graph used to construct the NumPy matrix. | |
| 622 | |
| 623 nodelist : list, optional | |
| 624 The rows and columns are ordered according to the nodes in `nodelist`. | |
| 625 If `nodelist` is None, then the ordering is produced by G.nodes(). | |
| 626 | |
| 627 dtype : NumPy data-type, optional | |
| 628 A valid NumPy named dtype used to initialize the NumPy recarray. | |
| 629 The data type names are assumed to be keys in the graph edge attribute | |
| 630 dictionary. | |
| 631 | |
| 632 order : {'C', 'F'}, optional | |
| 633 Whether to store multidimensional data in C- or Fortran-contiguous | |
| 634 (row- or column-wise) order in memory. If None, then the NumPy default | |
| 635 is used. | |
| 636 | |
| 637 Returns | |
| 638 ------- | |
| 639 M : NumPy recarray | |
| 640 The graph with specified edge data as a Numpy recarray | |
| 641 | |
| 642 Notes | |
| 643 ----- | |
| 644 When `nodelist` does not contain every node in `G`, the matrix is built | |
| 645 from the subgraph of `G` that is induced by the nodes in `nodelist`. | |
| 646 | |
| 647 Examples | |
| 648 -------- | |
| 649 >>> G = nx.Graph() | |
| 650 >>> G.add_edge(1, 2, weight=7.0, cost=5) | |
| 651 >>> A = nx.to_numpy_recarray(G, dtype=[('weight', float), ('cost', int)]) | |
| 652 >>> print(A.weight) | |
| 653 [[0. 7.] | |
| 654 [7. 0.]] | |
| 655 >>> print(A.cost) | |
| 656 [[0 5] | |
| 657 [5 0]] | |
| 658 | |
| 659 """ | |
| 660 if dtype is None: | |
| 661 dtype = [('weight', float)] | |
| 662 import numpy as np | |
| 663 if nodelist is None: | |
| 664 nodelist = list(G) | |
| 665 nodeset = set(nodelist) | |
| 666 if len(nodelist) != len(nodeset): | |
| 667 msg = "Ambiguous ordering: `nodelist` contained duplicates." | |
| 668 raise nx.NetworkXError(msg) | |
| 669 nlen = len(nodelist) | |
| 670 undirected = not G.is_directed() | |
| 671 index = dict(zip(nodelist, range(nlen))) | |
| 672 M = np.zeros((nlen, nlen), dtype=dtype, order=order) | |
| 673 | |
| 674 names = M.dtype.names | |
| 675 for u, v, attrs in G.edges(data=True): | |
| 676 if (u in nodeset) and (v in nodeset): | |
| 677 i, j = index[u], index[v] | |
| 678 values = tuple([attrs[n] for n in names]) | |
| 679 M[i, j] = values | |
| 680 if undirected: | |
| 681 M[j, i] = M[i, j] | |
| 682 | |
| 683 return M.view(np.recarray) | |
| 684 | |
| 685 | |
| 686 def to_scipy_sparse_matrix(G, nodelist=None, dtype=None, | |
| 687 weight='weight', format='csr'): | |
| 688 """Returns the graph adjacency matrix as a SciPy sparse matrix. | |
| 689 | |
| 690 Parameters | |
| 691 ---------- | |
| 692 G : graph | |
| 693 The NetworkX graph used to construct the NumPy matrix. | |
| 694 | |
| 695 nodelist : list, optional | |
| 696 The rows and columns are ordered according to the nodes in `nodelist`. | |
| 697 If `nodelist` is None, then the ordering is produced by G.nodes(). | |
| 698 | |
| 699 dtype : NumPy data-type, optional | |
| 700 A valid NumPy dtype used to initialize the array. If None, then the | |
| 701 NumPy default is used. | |
| 702 | |
| 703 weight : string or None optional (default='weight') | |
| 704 The edge attribute that holds the numerical value used for | |
| 705 the edge weight. If None then all edge weights are 1. | |
| 706 | |
| 707 format : str in {'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'} | |
| 708 The type of the matrix to be returned (default 'csr'). For | |
| 709 some algorithms different implementations of sparse matrices | |
| 710 can perform better. See [1]_ for details. | |
| 711 | |
| 712 Returns | |
| 713 ------- | |
| 714 M : SciPy sparse matrix | |
| 715 Graph adjacency matrix. | |
| 716 | |
| 717 Notes | |
| 718 ----- | |
| 719 For directed graphs, matrix entry i,j corresponds to an edge from i to j. | |
| 720 | |
| 721 The matrix entries are populated using the edge attribute held in | |
| 722 parameter weight. When an edge does not have that attribute, the | |
| 723 value of the entry is 1. | |
| 724 | |
| 725 For multiple edges the matrix values are the sums of the edge weights. | |
| 726 | |
| 727 When `nodelist` does not contain every node in `G`, the matrix is built | |
| 728 from the subgraph of `G` that is induced by the nodes in `nodelist`. | |
| 729 | |
| 730 Uses coo_matrix format. To convert to other formats specify the | |
| 731 format= keyword. | |
| 732 | |
| 733 The convention used for self-loop edges in graphs is to assign the | |
| 734 diagonal matrix entry value to the weight attribute of the edge | |
| 735 (or the number 1 if the edge has no weight attribute). If the | |
| 736 alternate convention of doubling the edge weight is desired the | |
| 737 resulting Scipy sparse matrix can be modified as follows: | |
| 738 | |
| 739 >>> import scipy as sp | |
| 740 >>> G = nx.Graph([(1, 1)]) | |
| 741 >>> A = nx.to_scipy_sparse_matrix(G) | |
| 742 >>> print(A.todense()) | |
| 743 [[1]] | |
| 744 >>> A.setdiag(A.diagonal() * 2) | |
| 745 >>> print(A.todense()) | |
| 746 [[2]] | |
| 747 | |
| 748 Examples | |
| 749 -------- | |
| 750 >>> G = nx.MultiDiGraph() | |
| 751 >>> G.add_edge(0, 1, weight=2) | |
| 752 0 | |
| 753 >>> G.add_edge(1, 0) | |
| 754 0 | |
| 755 >>> G.add_edge(2, 2, weight=3) | |
| 756 0 | |
| 757 >>> G.add_edge(2, 2) | |
| 758 1 | |
| 759 >>> S = nx.to_scipy_sparse_matrix(G, nodelist=[0, 1, 2]) | |
| 760 >>> print(S.todense()) | |
| 761 [[0 2 0] | |
| 762 [1 0 0] | |
| 763 [0 0 4]] | |
| 764 | |
| 765 References | |
| 766 ---------- | |
| 767 .. [1] Scipy Dev. References, "Sparse Matrices", | |
| 768 https://docs.scipy.org/doc/scipy/reference/sparse.html | |
| 769 """ | |
| 770 from scipy import sparse | |
| 771 if nodelist is None: | |
| 772 nodelist = list(G) | |
| 773 nlen = len(nodelist) | |
| 774 if nlen == 0: | |
| 775 raise nx.NetworkXError("Graph has no nodes or edges") | |
| 776 | |
| 777 if len(nodelist) != len(set(nodelist)): | |
| 778 msg = "Ambiguous ordering: `nodelist` contained duplicates." | |
| 779 raise nx.NetworkXError(msg) | |
| 780 | |
| 781 index = dict(zip(nodelist, range(nlen))) | |
| 782 coefficients = zip(*((index[u], index[v], d.get(weight, 1)) | |
| 783 for u, v, d in G.edges(nodelist, data=True) | |
| 784 if u in index and v in index)) | |
| 785 try: | |
| 786 row, col, data = coefficients | |
| 787 except ValueError: | |
| 788 # there is no edge in the subgraph | |
| 789 row, col, data = [], [], [] | |
| 790 | |
| 791 if G.is_directed(): | |
| 792 M = sparse.coo_matrix((data, (row, col)), | |
| 793 shape=(nlen, nlen), dtype=dtype) | |
| 794 else: | |
| 795 # symmetrize matrix | |
| 796 d = data + data | |
| 797 r = row + col | |
| 798 c = col + row | |
| 799 # selfloop entries get double counted when symmetrizing | |
| 800 # so we subtract the data on the diagonal | |
| 801 selfloops = list(nx.selfloop_edges(G, data=True)) | |
| 802 if selfloops: | |
| 803 diag_index, diag_data = zip(*((index[u], -d.get(weight, 1)) | |
| 804 for u, v, d in selfloops | |
| 805 if u in index and v in index)) | |
| 806 d += diag_data | |
| 807 r += diag_index | |
| 808 c += diag_index | |
| 809 M = sparse.coo_matrix((d, (r, c)), shape=(nlen, nlen), dtype=dtype) | |
| 810 try: | |
| 811 return M.asformat(format) | |
| 812 # From Scipy 1.1.0, asformat will throw a ValueError instead of an | |
| 813 # AttributeError if the format if not recognized. | |
| 814 except (AttributeError, ValueError): | |
| 815 raise nx.NetworkXError("Unknown sparse matrix format: %s" % format) | |
| 816 | |
| 817 | |
| 818 def _csr_gen_triples(A): | |
| 819 """Converts a SciPy sparse matrix in **Compressed Sparse Row** format to | |
| 820 an iterable of weighted edge triples. | |
| 821 | |
| 822 """ | |
| 823 nrows = A.shape[0] | |
| 824 data, indices, indptr = A.data, A.indices, A.indptr | |
| 825 for i in range(nrows): | |
| 826 for j in range(indptr[i], indptr[i + 1]): | |
| 827 yield i, indices[j], data[j] | |
| 828 | |
| 829 | |
| 830 def _csc_gen_triples(A): | |
| 831 """Converts a SciPy sparse matrix in **Compressed Sparse Column** format to | |
| 832 an iterable of weighted edge triples. | |
| 833 | |
| 834 """ | |
| 835 ncols = A.shape[1] | |
| 836 data, indices, indptr = A.data, A.indices, A.indptr | |
| 837 for i in range(ncols): | |
| 838 for j in range(indptr[i], indptr[i + 1]): | |
| 839 yield indices[j], i, data[j] | |
| 840 | |
| 841 | |
| 842 def _coo_gen_triples(A): | |
| 843 """Converts a SciPy sparse matrix in **Coordinate** format to an iterable | |
| 844 of weighted edge triples. | |
| 845 | |
| 846 """ | |
| 847 row, col, data = A.row, A.col, A.data | |
| 848 return zip(row, col, data) | |
| 849 | |
| 850 | |
| 851 def _dok_gen_triples(A): | |
| 852 """Converts a SciPy sparse matrix in **Dictionary of Keys** format to an | |
| 853 iterable of weighted edge triples. | |
| 854 | |
| 855 """ | |
| 856 for (r, c), v in A.items(): | |
| 857 yield r, c, v | |
| 858 | |
| 859 | |
| 860 def _generate_weighted_edges(A): | |
| 861 """Returns an iterable over (u, v, w) triples, where u and v are adjacent | |
| 862 vertices and w is the weight of the edge joining u and v. | |
| 863 | |
| 864 `A` is a SciPy sparse matrix (in any format). | |
| 865 | |
| 866 """ | |
| 867 if A.format == 'csr': | |
| 868 return _csr_gen_triples(A) | |
| 869 if A.format == 'csc': | |
| 870 return _csc_gen_triples(A) | |
| 871 if A.format == 'dok': | |
| 872 return _dok_gen_triples(A) | |
| 873 # If A is in any other format (including COO), convert it to COO format. | |
| 874 return _coo_gen_triples(A.tocoo()) | |
| 875 | |
| 876 | |
| 877 def from_scipy_sparse_matrix(A, parallel_edges=False, create_using=None, | |
| 878 edge_attribute='weight'): | |
| 879 """Creates a new graph from an adjacency matrix given as a SciPy sparse | |
| 880 matrix. | |
| 881 | |
| 882 Parameters | |
| 883 ---------- | |
| 884 A: scipy sparse matrix | |
| 885 An adjacency matrix representation of a graph | |
| 886 | |
| 887 parallel_edges : Boolean | |
| 888 If this is True, `create_using` is a multigraph, and `A` is an | |
| 889 integer matrix, then entry *(i, j)* in the matrix is interpreted as the | |
| 890 number of parallel edges joining vertices *i* and *j* in the graph. | |
| 891 If it is False, then the entries in the matrix are interpreted as | |
| 892 the weight of a single edge joining the vertices. | |
| 893 | |
| 894 create_using : NetworkX graph constructor, optional (default=nx.Graph) | |
| 895 Graph type to create. If graph instance, then cleared before populated. | |
| 896 | |
| 897 edge_attribute: string | |
| 898 Name of edge attribute to store matrix numeric value. The data will | |
| 899 have the same type as the matrix entry (int, float, (real,imag)). | |
| 900 | |
| 901 Notes | |
| 902 ----- | |
| 903 For directed graphs, explicitly mention create_using=nx.Digraph, | |
| 904 and entry i,j of A corresponds to an edge from i to j. | |
| 905 | |
| 906 If `create_using` is :class:`networkx.MultiGraph` or | |
| 907 :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the | |
| 908 entries of `A` are of type :class:`int`, then this function returns a | |
| 909 multigraph (constructed from `create_using`) with parallel edges. | |
| 910 In this case, `edge_attribute` will be ignored. | |
| 911 | |
| 912 If `create_using` indicates an undirected multigraph, then only the edges | |
| 913 indicated by the upper triangle of the matrix `A` will be added to the | |
| 914 graph. | |
| 915 | |
| 916 Examples | |
| 917 -------- | |
| 918 >>> import scipy as sp | |
| 919 >>> A = sp.sparse.eye(2, 2, 1) | |
| 920 >>> G = nx.from_scipy_sparse_matrix(A) | |
| 921 | |
| 922 If `create_using` indicates a multigraph and the matrix has only integer | |
| 923 entries and `parallel_edges` is False, then the entries will be treated | |
| 924 as weights for edges joining the nodes (without creating parallel edges): | |
| 925 | |
| 926 >>> A = sp.sparse.csr_matrix([[1, 1], [1, 2]]) | |
| 927 >>> G = nx.from_scipy_sparse_matrix(A, create_using=nx.MultiGraph) | |
| 928 >>> G[1][1] | |
| 929 AtlasView({0: {'weight': 2}}) | |
| 930 | |
| 931 If `create_using` indicates a multigraph and the matrix has only integer | |
| 932 entries and `parallel_edges` is True, then the entries will be treated | |
| 933 as the number of parallel edges joining those two vertices: | |
| 934 | |
| 935 >>> A = sp.sparse.csr_matrix([[1, 1], [1, 2]]) | |
| 936 >>> G = nx.from_scipy_sparse_matrix(A, parallel_edges=True, | |
| 937 ... create_using=nx.MultiGraph) | |
| 938 >>> G[1][1] | |
| 939 AtlasView({0: {'weight': 1}, 1: {'weight': 1}}) | |
| 940 | |
| 941 """ | |
| 942 G = nx.empty_graph(0, create_using) | |
| 943 n, m = A.shape | |
| 944 if n != m: | |
| 945 raise nx.NetworkXError( | |
| 946 "Adjacency matrix is not square. nx,ny=%s" % (A.shape,)) | |
| 947 # Make sure we get even the isolated nodes of the graph. | |
| 948 G.add_nodes_from(range(n)) | |
| 949 # Create an iterable over (u, v, w) triples and for each triple, add an | |
| 950 # edge from u to v with weight w. | |
| 951 triples = _generate_weighted_edges(A) | |
| 952 # If the entries in the adjacency matrix are integers, the graph is a | |
| 953 # multigraph, and parallel_edges is True, then create parallel edges, each | |
| 954 # with weight 1, for each entry in the adjacency matrix. Otherwise, create | |
| 955 # one edge for each positive entry in the adjacency matrix and set the | |
| 956 # weight of that edge to be the entry in the matrix. | |
| 957 if A.dtype.kind in ('i', 'u') and G.is_multigraph() and parallel_edges: | |
| 958 chain = itertools.chain.from_iterable | |
| 959 # The following line is equivalent to: | |
| 960 # | |
| 961 # for (u, v) in edges: | |
| 962 # for d in range(A[u, v]): | |
| 963 # G.add_edge(u, v, weight=1) | |
| 964 # | |
| 965 triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples) | |
| 966 # If we are creating an undirected multigraph, only add the edges from the | |
| 967 # upper triangle of the matrix. Otherwise, add all the edges. This relies | |
| 968 # on the fact that the vertices created in the | |
| 969 # `_generated_weighted_edges()` function are actually the row/column | |
| 970 # indices for the matrix `A`. | |
| 971 # | |
| 972 # Without this check, we run into a problem where each edge is added twice | |
| 973 # when `G.add_weighted_edges_from()` is invoked below. | |
| 974 if G.is_multigraph() and not G.is_directed(): | |
| 975 triples = ((u, v, d) for u, v, d in triples if u <= v) | |
| 976 G.add_weighted_edges_from(triples, weight=edge_attribute) | |
| 977 return G | |
| 978 | |
| 979 | |
| 980 def to_numpy_array(G, nodelist=None, dtype=None, order=None, | |
| 981 multigraph_weight=sum, weight='weight', nonedge=0.0): | |
| 982 """Returns the graph adjacency matrix as a NumPy array. | |
| 983 | |
| 984 Parameters | |
| 985 ---------- | |
| 986 G : graph | |
| 987 The NetworkX graph used to construct the NumPy array. | |
| 988 | |
| 989 nodelist : list, optional | |
| 990 The rows and columns are ordered according to the nodes in `nodelist`. | |
| 991 If `nodelist` is None, then the ordering is produced by G.nodes(). | |
| 992 | |
| 993 dtype : NumPy data type, optional | |
| 994 A valid single NumPy data type used to initialize the array. | |
| 995 This must be a simple type such as int or numpy.float64 and | |
| 996 not a compound data type (see to_numpy_recarray) | |
| 997 If None, then the NumPy default is used. | |
| 998 | |
| 999 order : {'C', 'F'}, optional | |
| 1000 Whether to store multidimensional data in C- or Fortran-contiguous | |
| 1001 (row- or column-wise) order in memory. If None, then the NumPy default | |
| 1002 is used. | |
| 1003 | |
| 1004 multigraph_weight : {sum, min, max}, optional | |
| 1005 An operator that determines how weights in multigraphs are handled. | |
| 1006 The default is to sum the weights of the multiple edges. | |
| 1007 | |
| 1008 weight : string or None optional (default = 'weight') | |
| 1009 The edge attribute that holds the numerical value used for | |
| 1010 the edge weight. If an edge does not have that attribute, then the | |
| 1011 value 1 is used instead. | |
| 1012 | |
| 1013 nonedge : float (default = 0.0) | |
| 1014 The array values corresponding to nonedges are typically set to zero. | |
| 1015 However, this could be undesirable if there are array values | |
| 1016 corresponding to actual edges that also have the value zero. If so, | |
| 1017 one might prefer nonedges to have some other value, such as nan. | |
| 1018 | |
| 1019 Returns | |
| 1020 ------- | |
| 1021 A : NumPy ndarray | |
| 1022 Graph adjacency matrix | |
| 1023 | |
| 1024 See Also | |
| 1025 -------- | |
| 1026 from_numpy_array | |
| 1027 | |
| 1028 Notes | |
| 1029 ----- | |
| 1030 For directed graphs, entry i,j corresponds to an edge from i to j. | |
| 1031 | |
| 1032 Entries in the adjacency matrix are assigned to the weight edge attribute. | |
| 1033 When an edge does not have a weight attribute, the value of the entry is | |
| 1034 set to the number 1. For multiple (parallel) edges, the values of the | |
| 1035 entries are determined by the `multigraph_weight` parameter. The default is | |
| 1036 to sum the weight attributes for each of the parallel edges. | |
| 1037 | |
| 1038 When `nodelist` does not contain every node in `G`, the adjacency matrix is | |
| 1039 built from the subgraph of `G` that is induced by the nodes in `nodelist`. | |
| 1040 | |
| 1041 The convention used for self-loop edges in graphs is to assign the | |
| 1042 diagonal array entry value to the weight attribute of the edge | |
| 1043 (or the number 1 if the edge has no weight attribute). If the | |
| 1044 alternate convention of doubling the edge weight is desired the | |
| 1045 resulting NumPy array can be modified as follows: | |
| 1046 | |
| 1047 >>> import numpy as np | |
| 1048 >>> G = nx.Graph([(1, 1)]) | |
| 1049 >>> A = nx.to_numpy_array(G) | |
| 1050 >>> A | |
| 1051 array([[1.]]) | |
| 1052 >>> A[np.diag_indices_from(A)] *= 2 | |
| 1053 >>> A | |
| 1054 array([[2.]]) | |
| 1055 | |
| 1056 Examples | |
| 1057 -------- | |
| 1058 >>> G = nx.MultiDiGraph() | |
| 1059 >>> G.add_edge(0, 1, weight=2) | |
| 1060 0 | |
| 1061 >>> G.add_edge(1, 0) | |
| 1062 0 | |
| 1063 >>> G.add_edge(2, 2, weight=3) | |
| 1064 0 | |
| 1065 >>> G.add_edge(2, 2) | |
| 1066 1 | |
| 1067 >>> nx.to_numpy_array(G, nodelist=[0, 1, 2]) | |
| 1068 array([[0., 2., 0.], | |
| 1069 [1., 0., 0.], | |
| 1070 [0., 0., 4.]]) | |
| 1071 | |
| 1072 """ | |
| 1073 import numpy as np | |
| 1074 | |
| 1075 if nodelist is None: | |
| 1076 nodelist = list(G) | |
| 1077 nodeset = set(nodelist) | |
| 1078 if len(nodelist) != len(nodeset): | |
| 1079 msg = "Ambiguous ordering: `nodelist` contained duplicates." | |
| 1080 raise nx.NetworkXError(msg) | |
| 1081 | |
| 1082 nlen = len(nodelist) | |
| 1083 undirected = not G.is_directed() | |
| 1084 index = dict(zip(nodelist, range(nlen))) | |
| 1085 | |
| 1086 # Initially, we start with an array of nans. Then we populate the array | |
| 1087 # using data from the graph. Afterwards, any leftover nans will be | |
| 1088 # converted to the value of `nonedge`. Note, we use nans initially, | |
| 1089 # instead of zero, for two reasons: | |
| 1090 # | |
| 1091 # 1) It can be important to distinguish a real edge with the value 0 | |
| 1092 # from a nonedge with the value 0. | |
| 1093 # | |
| 1094 # 2) When working with multi(di)graphs, we must combine the values of all | |
| 1095 # edges between any two nodes in some manner. This often takes the | |
| 1096 # form of a sum, min, or max. Using the value 0 for a nonedge would | |
| 1097 # have undesirable effects with min and max, but using nanmin and | |
| 1098 # nanmax with initially nan values is not problematic at all. | |
| 1099 # | |
| 1100 # That said, there are still some drawbacks to this approach. Namely, if | |
| 1101 # a real edge is nan, then that value is a) not distinguishable from | |
| 1102 # nonedges and b) is ignored by the default combinator (nansum, nanmin, | |
| 1103 # nanmax) functions used for multi(di)graphs. If this becomes an issue, | |
| 1104 # an alternative approach is to use masked arrays. Initially, every | |
| 1105 # element is masked and set to some `initial` value. As we populate the | |
| 1106 # graph, elements are unmasked (automatically) when we combine the initial | |
| 1107 # value with the values given by real edges. At the end, we convert all | |
| 1108 # masked values to `nonedge`. Using masked arrays fully addresses reason 1, | |
| 1109 # but for reason 2, we would still have the issue with min and max if the | |
| 1110 # initial values were 0.0. Note: an initial value of +inf is appropriate | |
| 1111 # for min, while an initial value of -inf is appropriate for max. When | |
| 1112 # working with sum, an initial value of zero is appropriate. Ideally then, | |
| 1113 # we'd want to allow users to specify both a value for nonedges and also | |
| 1114 # an initial value. For multi(di)graphs, the choice of the initial value | |
| 1115 # will, in general, depend on the combinator function---sensible defaults | |
| 1116 # can be provided. | |
| 1117 | |
| 1118 if G.is_multigraph(): | |
| 1119 # Handle MultiGraphs and MultiDiGraphs | |
| 1120 A = np.full((nlen, nlen), np.nan, order=order) | |
| 1121 # use numpy nan-aware operations | |
| 1122 operator = {sum: np.nansum, min: np.nanmin, max: np.nanmax} | |
| 1123 try: | |
| 1124 op = operator[multigraph_weight] | |
| 1125 except Exception: | |
| 1126 raise ValueError('multigraph_weight must be sum, min, or max') | |
| 1127 | |
| 1128 for u, v, attrs in G.edges(data=True): | |
| 1129 if (u in nodeset) and (v in nodeset): | |
| 1130 i, j = index[u], index[v] | |
| 1131 e_weight = attrs.get(weight, 1) | |
| 1132 A[i, j] = op([e_weight, A[i, j]]) | |
| 1133 if undirected: | |
| 1134 A[j, i] = A[i, j] | |
| 1135 else: | |
| 1136 # Graph or DiGraph, this is much faster than above | |
| 1137 A = np.full((nlen, nlen), np.nan, order=order) | |
| 1138 for u, nbrdict in G.adjacency(): | |
| 1139 for v, d in nbrdict.items(): | |
| 1140 try: | |
| 1141 A[index[u], index[v]] = d.get(weight, 1) | |
| 1142 except KeyError: | |
| 1143 # This occurs when there are fewer desired nodes than | |
| 1144 # there are nodes in the graph: len(nodelist) < len(G) | |
| 1145 pass | |
| 1146 | |
| 1147 A[np.isnan(A)] = nonedge | |
| 1148 A = np.asarray(A, dtype=dtype) | |
| 1149 return A | |
| 1150 | |
| 1151 | |
| 1152 def from_numpy_array(A, parallel_edges=False, create_using=None): | |
| 1153 """Returns a graph from NumPy array. | |
| 1154 | |
| 1155 The NumPy array is interpreted as an adjacency matrix for the graph. | |
| 1156 | |
| 1157 Parameters | |
| 1158 ---------- | |
| 1159 A : NumPy ndarray | |
| 1160 An adjacency matrix representation of a graph | |
| 1161 | |
| 1162 parallel_edges : Boolean | |
| 1163 If this is True, `create_using` is a multigraph, and `A` is an | |
| 1164 integer array, then entry *(i, j)* in the array is interpreted as the | |
| 1165 number of parallel edges joining vertices *i* and *j* in the graph. | |
| 1166 If it is False, then the entries in the array are interpreted as | |
| 1167 the weight of a single edge joining the vertices. | |
| 1168 | |
| 1169 create_using : NetworkX graph constructor, optional (default=nx.Graph) | |
| 1170 Graph type to create. If graph instance, then cleared before populated. | |
| 1171 | |
| 1172 Notes | |
| 1173 ----- | |
| 1174 For directed graphs, explicitly mention create_using=nx.Digraph, | |
| 1175 and entry i,j of A corresponds to an edge from i to j. | |
| 1176 | |
| 1177 If `create_using` is :class:`networkx.MultiGraph` or | |
| 1178 :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the | |
| 1179 entries of `A` are of type :class:`int`, then this function returns a | |
| 1180 multigraph (of the same type as `create_using`) with parallel edges. | |
| 1181 | |
| 1182 If `create_using` indicates an undirected multigraph, then only the edges | |
| 1183 indicated by the upper triangle of the array `A` will be added to the | |
| 1184 graph. | |
| 1185 | |
| 1186 If the NumPy array has a single data type for each array entry it | |
| 1187 will be converted to an appropriate Python data type. | |
| 1188 | |
| 1189 If the NumPy array has a user-specified compound data type the names | |
| 1190 of the data fields will be used as attribute keys in the resulting | |
| 1191 NetworkX graph. | |
| 1192 | |
| 1193 See Also | |
| 1194 -------- | |
| 1195 to_numpy_array | |
| 1196 | |
| 1197 Examples | |
| 1198 -------- | |
| 1199 Simple integer weights on edges: | |
| 1200 | |
| 1201 >>> import numpy as np | |
| 1202 >>> A = np.array([[1, 1], [2, 1]]) | |
| 1203 >>> G = nx.from_numpy_array(A) | |
| 1204 >>> G.edges(data=True) | |
| 1205 EdgeDataView([(0, 0, {'weight': 1}), (0, 1, {'weight': 2}), \ | |
| 1206 (1, 1, {'weight': 1})]) | |
| 1207 | |
| 1208 If `create_using` indicates a multigraph and the array has only integer | |
| 1209 entries and `parallel_edges` is False, then the entries will be treated | |
| 1210 as weights for edges joining the nodes (without creating parallel edges): | |
| 1211 | |
| 1212 >>> A = np.array([[1, 1], [1, 2]]) | |
| 1213 >>> G = nx.from_numpy_array(A, create_using=nx.MultiGraph) | |
| 1214 >>> G[1][1] | |
| 1215 AtlasView({0: {'weight': 2}}) | |
| 1216 | |
| 1217 If `create_using` indicates a multigraph and the array has only integer | |
| 1218 entries and `parallel_edges` is True, then the entries will be treated | |
| 1219 as the number of parallel edges joining those two vertices: | |
| 1220 | |
| 1221 >>> A = np.array([[1, 1], [1, 2]]) | |
| 1222 >>> temp = nx.MultiGraph() | |
| 1223 >>> G = nx.from_numpy_array(A, parallel_edges=True, create_using=temp) | |
| 1224 >>> G[1][1] | |
| 1225 AtlasView({0: {'weight': 1}, 1: {'weight': 1}}) | |
| 1226 | |
| 1227 User defined compound data type on edges: | |
| 1228 | |
| 1229 >>> dt = [('weight', float), ('cost', int)] | |
| 1230 >>> A = np.array([[(1.0, 2)]], dtype=dt) | |
| 1231 >>> G = nx.from_numpy_array(A) | |
| 1232 >>> G.edges() | |
| 1233 EdgeView([(0, 0)]) | |
| 1234 >>> G[0][0]['cost'] | |
| 1235 2 | |
| 1236 >>> G[0][0]['weight'] | |
| 1237 1.0 | |
| 1238 | |
| 1239 """ | |
| 1240 return from_numpy_matrix(A, parallel_edges=parallel_edges, | |
| 1241 create_using=create_using) | |
| 1242 | |
| 1243 | |
| 1244 # fixture for pytest | |
| 1245 def setup_module(module): | |
| 1246 import pytest | |
| 1247 numpy = pytest.importorskip('numpy') | |
| 1248 scipy = pytest.importorskip('scipy') | |
| 1249 pandas = pytest.importorskip('pandas') |
