matrix.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. """
  2. ====================
  3. Biadjacency matrices
  4. ====================
  5. """
  6. import itertools
  7. import networkx as nx
  8. from networkx.convert_matrix import _generate_weighted_edges
  9. __all__ = ["biadjacency_matrix", "from_biadjacency_matrix"]
  10. def biadjacency_matrix(
  11. G, row_order, column_order=None, dtype=None, weight="weight", format="csr"
  12. ):
  13. r"""Returns the biadjacency matrix of the bipartite graph G.
  14. Let `G = (U, V, E)` be a bipartite graph with node sets
  15. `U = u_{1},...,u_{r}` and `V = v_{1},...,v_{s}`. The biadjacency
  16. matrix [1]_ is the `r` x `s` matrix `B` in which `b_{i,j} = 1`
  17. if, and only if, `(u_i, v_j) \in E`. If the parameter `weight` is
  18. not `None` and matches the name of an edge attribute, its value is
  19. used instead of 1.
  20. Parameters
  21. ----------
  22. G : graph
  23. A NetworkX graph
  24. row_order : list of nodes
  25. The rows of the matrix are ordered according to the list of nodes.
  26. column_order : list, optional
  27. The columns of the matrix are ordered according to the list of nodes.
  28. If column_order is None, then the ordering of columns is arbitrary.
  29. dtype : NumPy data-type, optional
  30. A valid NumPy dtype used to initialize the array. If None, then the
  31. NumPy default is used.
  32. weight : string or None, optional (default='weight')
  33. The edge data key used to provide each value in the matrix.
  34. If None, then each edge has weight 1.
  35. format : str in {'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'}
  36. The type of the matrix to be returned (default 'csr'). For
  37. some algorithms different implementations of sparse matrices
  38. can perform better. See [2]_ for details.
  39. Returns
  40. -------
  41. M : SciPy sparse array
  42. Biadjacency matrix representation of the bipartite graph G.
  43. Notes
  44. -----
  45. No attempt is made to check that the input graph is bipartite.
  46. For directed bipartite graphs only successors are considered as neighbors.
  47. To obtain an adjacency matrix with ones (or weight values) for both
  48. predecessors and successors you have to generate two biadjacency matrices
  49. where the rows of one of them are the columns of the other, and then add
  50. one to the transpose of the other.
  51. See Also
  52. --------
  53. adjacency_matrix
  54. from_biadjacency_matrix
  55. References
  56. ----------
  57. .. [1] https://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph
  58. .. [2] Scipy Dev. References, "Sparse Matrices",
  59. https://docs.scipy.org/doc/scipy/reference/sparse.html
  60. """
  61. import scipy as sp
  62. import scipy.sparse # call as sp.sparse
  63. nlen = len(row_order)
  64. if nlen == 0:
  65. raise nx.NetworkXError("row_order is empty list")
  66. if len(row_order) != len(set(row_order)):
  67. msg = "Ambiguous ordering: `row_order` contained duplicates."
  68. raise nx.NetworkXError(msg)
  69. if column_order is None:
  70. column_order = list(set(G) - set(row_order))
  71. mlen = len(column_order)
  72. if len(column_order) != len(set(column_order)):
  73. msg = "Ambiguous ordering: `column_order` contained duplicates."
  74. raise nx.NetworkXError(msg)
  75. row_index = dict(zip(row_order, itertools.count()))
  76. col_index = dict(zip(column_order, itertools.count()))
  77. if G.number_of_edges() == 0:
  78. row, col, data = [], [], []
  79. else:
  80. row, col, data = zip(
  81. *(
  82. (row_index[u], col_index[v], d.get(weight, 1))
  83. for u, v, d in G.edges(row_order, data=True)
  84. if u in row_index and v in col_index
  85. )
  86. )
  87. A = sp.sparse.coo_array((data, (row, col)), shape=(nlen, mlen), dtype=dtype)
  88. try:
  89. return A.asformat(format)
  90. except ValueError as err:
  91. raise nx.NetworkXError(f"Unknown sparse array format: {format}") from err
  92. def from_biadjacency_matrix(A, create_using=None, edge_attribute="weight"):
  93. r"""Creates a new bipartite graph from a biadjacency matrix given as a
  94. SciPy sparse array.
  95. Parameters
  96. ----------
  97. A: scipy sparse array
  98. A biadjacency matrix representation of a graph
  99. create_using: NetworkX graph
  100. Use specified graph for result. The default is Graph()
  101. edge_attribute: string
  102. Name of edge attribute to store matrix numeric value. The data will
  103. have the same type as the matrix entry (int, float, (real,imag)).
  104. Notes
  105. -----
  106. The nodes are labeled with the attribute `bipartite` set to an integer
  107. 0 or 1 representing membership in part 0 or part 1 of the bipartite graph.
  108. If `create_using` is an instance of :class:`networkx.MultiGraph` or
  109. :class:`networkx.MultiDiGraph` and the entries of `A` are of
  110. type :class:`int`, then this function returns a multigraph (of the same
  111. type as `create_using`) with parallel edges. In this case, `edge_attribute`
  112. will be ignored.
  113. See Also
  114. --------
  115. biadjacency_matrix
  116. from_numpy_array
  117. References
  118. ----------
  119. [1] https://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph
  120. """
  121. G = nx.empty_graph(0, create_using)
  122. n, m = A.shape
  123. # Make sure we get even the isolated nodes of the graph.
  124. G.add_nodes_from(range(n), bipartite=0)
  125. G.add_nodes_from(range(n, n + m), bipartite=1)
  126. # Create an iterable over (u, v, w) triples and for each triple, add an
  127. # edge from u to v with weight w.
  128. triples = ((u, n + v, d) for (u, v, d) in _generate_weighted_edges(A))
  129. # If the entries in the adjacency matrix are integers and the graph is a
  130. # multigraph, then create parallel edges, each with weight 1, for each
  131. # entry in the adjacency matrix. Otherwise, create one edge for each
  132. # positive entry in the adjacency matrix and set the weight of that edge to
  133. # be the entry in the matrix.
  134. if A.dtype.kind in ("i", "u") and G.is_multigraph():
  135. chain = itertools.chain.from_iterable
  136. triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples)
  137. G.add_weighted_edges_from(triples, weight=edge_attribute)
  138. return G