scikit-network 0.33.4__cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. scikit_network-0.33.4.dist-info/METADATA +122 -0
  2. scikit_network-0.33.4.dist-info/RECORD +229 -0
  3. scikit_network-0.33.4.dist-info/WHEEL +6 -0
  4. scikit_network-0.33.4.dist-info/licenses/AUTHORS.rst +43 -0
  5. scikit_network-0.33.4.dist-info/licenses/LICENSE +34 -0
  6. scikit_network-0.33.4.dist-info/top_level.txt +1 -0
  7. scikit_network.libs/libgomp-a34b3233.so.1.0.0 +0 -0
  8. sknetwork/__init__.py +21 -0
  9. sknetwork/base.py +67 -0
  10. sknetwork/classification/__init__.py +8 -0
  11. sknetwork/classification/base.py +138 -0
  12. sknetwork/classification/base_rank.py +129 -0
  13. sknetwork/classification/diffusion.py +127 -0
  14. sknetwork/classification/knn.py +131 -0
  15. sknetwork/classification/metrics.py +205 -0
  16. sknetwork/classification/pagerank.py +58 -0
  17. sknetwork/classification/propagation.py +144 -0
  18. sknetwork/classification/tests/__init__.py +1 -0
  19. sknetwork/classification/tests/test_API.py +30 -0
  20. sknetwork/classification/tests/test_diffusion.py +77 -0
  21. sknetwork/classification/tests/test_knn.py +23 -0
  22. sknetwork/classification/tests/test_metrics.py +53 -0
  23. sknetwork/classification/tests/test_pagerank.py +20 -0
  24. sknetwork/classification/tests/test_propagation.py +24 -0
  25. sknetwork/classification/vote.cpp +27593 -0
  26. sknetwork/classification/vote.cpython-312-x86_64-linux-gnu.so +0 -0
  27. sknetwork/classification/vote.pyx +56 -0
  28. sknetwork/clustering/__init__.py +8 -0
  29. sknetwork/clustering/base.py +168 -0
  30. sknetwork/clustering/kcenters.py +251 -0
  31. sknetwork/clustering/leiden.py +238 -0
  32. sknetwork/clustering/leiden_core.cpp +31928 -0
  33. sknetwork/clustering/leiden_core.cpython-312-x86_64-linux-gnu.so +0 -0
  34. sknetwork/clustering/leiden_core.pyx +124 -0
  35. sknetwork/clustering/louvain.py +282 -0
  36. sknetwork/clustering/louvain_core.cpp +31573 -0
  37. sknetwork/clustering/louvain_core.cpython-312-x86_64-linux-gnu.so +0 -0
  38. sknetwork/clustering/louvain_core.pyx +124 -0
  39. sknetwork/clustering/metrics.py +91 -0
  40. sknetwork/clustering/postprocess.py +66 -0
  41. sknetwork/clustering/propagation_clustering.py +100 -0
  42. sknetwork/clustering/tests/__init__.py +1 -0
  43. sknetwork/clustering/tests/test_API.py +38 -0
  44. sknetwork/clustering/tests/test_kcenters.py +60 -0
  45. sknetwork/clustering/tests/test_leiden.py +34 -0
  46. sknetwork/clustering/tests/test_louvain.py +135 -0
  47. sknetwork/clustering/tests/test_metrics.py +50 -0
  48. sknetwork/clustering/tests/test_postprocess.py +39 -0
  49. sknetwork/data/__init__.py +6 -0
  50. sknetwork/data/base.py +33 -0
  51. sknetwork/data/load.py +292 -0
  52. sknetwork/data/models.py +459 -0
  53. sknetwork/data/parse.py +644 -0
  54. sknetwork/data/test_graphs.py +93 -0
  55. sknetwork/data/tests/__init__.py +1 -0
  56. sknetwork/data/tests/test_API.py +30 -0
  57. sknetwork/data/tests/test_base.py +14 -0
  58. sknetwork/data/tests/test_load.py +61 -0
  59. sknetwork/data/tests/test_models.py +52 -0
  60. sknetwork/data/tests/test_parse.py +250 -0
  61. sknetwork/data/tests/test_test_graphs.py +29 -0
  62. sknetwork/data/tests/test_toy_graphs.py +68 -0
  63. sknetwork/data/timeout.py +38 -0
  64. sknetwork/data/toy_graphs.py +611 -0
  65. sknetwork/embedding/__init__.py +8 -0
  66. sknetwork/embedding/base.py +90 -0
  67. sknetwork/embedding/force_atlas.py +198 -0
  68. sknetwork/embedding/louvain_embedding.py +142 -0
  69. sknetwork/embedding/random_projection.py +131 -0
  70. sknetwork/embedding/spectral.py +137 -0
  71. sknetwork/embedding/spring.py +198 -0
  72. sknetwork/embedding/svd.py +351 -0
  73. sknetwork/embedding/tests/__init__.py +1 -0
  74. sknetwork/embedding/tests/test_API.py +49 -0
  75. sknetwork/embedding/tests/test_force_atlas.py +35 -0
  76. sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
  77. sknetwork/embedding/tests/test_random_projection.py +28 -0
  78. sknetwork/embedding/tests/test_spectral.py +81 -0
  79. sknetwork/embedding/tests/test_spring.py +50 -0
  80. sknetwork/embedding/tests/test_svd.py +43 -0
  81. sknetwork/gnn/__init__.py +10 -0
  82. sknetwork/gnn/activation.py +117 -0
  83. sknetwork/gnn/base.py +181 -0
  84. sknetwork/gnn/base_activation.py +90 -0
  85. sknetwork/gnn/base_layer.py +109 -0
  86. sknetwork/gnn/gnn_classifier.py +305 -0
  87. sknetwork/gnn/layer.py +153 -0
  88. sknetwork/gnn/loss.py +180 -0
  89. sknetwork/gnn/neighbor_sampler.py +65 -0
  90. sknetwork/gnn/optimizer.py +164 -0
  91. sknetwork/gnn/tests/__init__.py +1 -0
  92. sknetwork/gnn/tests/test_activation.py +56 -0
  93. sknetwork/gnn/tests/test_base.py +75 -0
  94. sknetwork/gnn/tests/test_base_layer.py +37 -0
  95. sknetwork/gnn/tests/test_gnn_classifier.py +130 -0
  96. sknetwork/gnn/tests/test_layers.py +80 -0
  97. sknetwork/gnn/tests/test_loss.py +33 -0
  98. sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
  99. sknetwork/gnn/tests/test_optimizer.py +43 -0
  100. sknetwork/gnn/tests/test_utils.py +41 -0
  101. sknetwork/gnn/utils.py +127 -0
  102. sknetwork/hierarchy/__init__.py +6 -0
  103. sknetwork/hierarchy/base.py +90 -0
  104. sknetwork/hierarchy/louvain_hierarchy.py +260 -0
  105. sknetwork/hierarchy/metrics.py +234 -0
  106. sknetwork/hierarchy/paris.cpp +37877 -0
  107. sknetwork/hierarchy/paris.cpython-312-x86_64-linux-gnu.so +0 -0
  108. sknetwork/hierarchy/paris.pyx +310 -0
  109. sknetwork/hierarchy/postprocess.py +350 -0
  110. sknetwork/hierarchy/tests/__init__.py +1 -0
  111. sknetwork/hierarchy/tests/test_API.py +24 -0
  112. sknetwork/hierarchy/tests/test_algos.py +34 -0
  113. sknetwork/hierarchy/tests/test_metrics.py +62 -0
  114. sknetwork/hierarchy/tests/test_postprocess.py +57 -0
  115. sknetwork/linalg/__init__.py +9 -0
  116. sknetwork/linalg/basics.py +37 -0
  117. sknetwork/linalg/diteration.cpp +27409 -0
  118. sknetwork/linalg/diteration.cpython-312-x86_64-linux-gnu.so +0 -0
  119. sknetwork/linalg/diteration.pyx +47 -0
  120. sknetwork/linalg/eig_solver.py +93 -0
  121. sknetwork/linalg/laplacian.py +15 -0
  122. sknetwork/linalg/normalizer.py +86 -0
  123. sknetwork/linalg/operators.py +225 -0
  124. sknetwork/linalg/polynome.py +76 -0
  125. sknetwork/linalg/ppr_solver.py +170 -0
  126. sknetwork/linalg/push.cpp +31081 -0
  127. sknetwork/linalg/push.cpython-312-x86_64-linux-gnu.so +0 -0
  128. sknetwork/linalg/push.pyx +71 -0
  129. sknetwork/linalg/sparse_lowrank.py +142 -0
  130. sknetwork/linalg/svd_solver.py +91 -0
  131. sknetwork/linalg/tests/__init__.py +1 -0
  132. sknetwork/linalg/tests/test_eig.py +44 -0
  133. sknetwork/linalg/tests/test_laplacian.py +18 -0
  134. sknetwork/linalg/tests/test_normalization.py +34 -0
  135. sknetwork/linalg/tests/test_operators.py +66 -0
  136. sknetwork/linalg/tests/test_polynome.py +38 -0
  137. sknetwork/linalg/tests/test_ppr.py +50 -0
  138. sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
  139. sknetwork/linalg/tests/test_svd.py +38 -0
  140. sknetwork/linkpred/__init__.py +2 -0
  141. sknetwork/linkpred/base.py +46 -0
  142. sknetwork/linkpred/nn.py +126 -0
  143. sknetwork/linkpred/tests/__init__.py +1 -0
  144. sknetwork/linkpred/tests/test_nn.py +26 -0
  145. sknetwork/log.py +19 -0
  146. sknetwork/path/__init__.py +5 -0
  147. sknetwork/path/dag.py +54 -0
  148. sknetwork/path/distances.py +98 -0
  149. sknetwork/path/search.py +31 -0
  150. sknetwork/path/shortest_path.py +61 -0
  151. sknetwork/path/tests/__init__.py +1 -0
  152. sknetwork/path/tests/test_dag.py +37 -0
  153. sknetwork/path/tests/test_distances.py +62 -0
  154. sknetwork/path/tests/test_search.py +40 -0
  155. sknetwork/path/tests/test_shortest_path.py +40 -0
  156. sknetwork/ranking/__init__.py +8 -0
  157. sknetwork/ranking/base.py +57 -0
  158. sknetwork/ranking/betweenness.cpp +9716 -0
  159. sknetwork/ranking/betweenness.cpython-312-x86_64-linux-gnu.so +0 -0
  160. sknetwork/ranking/betweenness.pyx +97 -0
  161. sknetwork/ranking/closeness.py +92 -0
  162. sknetwork/ranking/hits.py +90 -0
  163. sknetwork/ranking/katz.py +79 -0
  164. sknetwork/ranking/pagerank.py +106 -0
  165. sknetwork/ranking/postprocess.py +37 -0
  166. sknetwork/ranking/tests/__init__.py +1 -0
  167. sknetwork/ranking/tests/test_API.py +32 -0
  168. sknetwork/ranking/tests/test_betweenness.py +38 -0
  169. sknetwork/ranking/tests/test_closeness.py +30 -0
  170. sknetwork/ranking/tests/test_hits.py +20 -0
  171. sknetwork/ranking/tests/test_pagerank.py +62 -0
  172. sknetwork/ranking/tests/test_postprocess.py +26 -0
  173. sknetwork/regression/__init__.py +4 -0
  174. sknetwork/regression/base.py +57 -0
  175. sknetwork/regression/diffusion.py +204 -0
  176. sknetwork/regression/tests/__init__.py +1 -0
  177. sknetwork/regression/tests/test_API.py +32 -0
  178. sknetwork/regression/tests/test_diffusion.py +56 -0
  179. sknetwork/sknetwork.py +3 -0
  180. sknetwork/test_base.py +35 -0
  181. sknetwork/test_log.py +15 -0
  182. sknetwork/topology/__init__.py +8 -0
  183. sknetwork/topology/cliques.cpp +32574 -0
  184. sknetwork/topology/cliques.cpython-312-x86_64-linux-gnu.so +0 -0
  185. sknetwork/topology/cliques.pyx +149 -0
  186. sknetwork/topology/core.cpp +30660 -0
  187. sknetwork/topology/core.cpython-312-x86_64-linux-gnu.so +0 -0
  188. sknetwork/topology/core.pyx +90 -0
  189. sknetwork/topology/cycles.py +243 -0
  190. sknetwork/topology/minheap.cpp +27341 -0
  191. sknetwork/topology/minheap.cpython-312-x86_64-linux-gnu.so +0 -0
  192. sknetwork/topology/minheap.pxd +20 -0
  193. sknetwork/topology/minheap.pyx +109 -0
  194. sknetwork/topology/structure.py +194 -0
  195. sknetwork/topology/tests/__init__.py +1 -0
  196. sknetwork/topology/tests/test_cliques.py +28 -0
  197. sknetwork/topology/tests/test_core.py +19 -0
  198. sknetwork/topology/tests/test_cycles.py +65 -0
  199. sknetwork/topology/tests/test_structure.py +85 -0
  200. sknetwork/topology/tests/test_triangles.py +38 -0
  201. sknetwork/topology/tests/test_wl.py +72 -0
  202. sknetwork/topology/triangles.cpp +8903 -0
  203. sknetwork/topology/triangles.cpython-312-x86_64-linux-gnu.so +0 -0
  204. sknetwork/topology/triangles.pyx +151 -0
  205. sknetwork/topology/weisfeiler_lehman.py +133 -0
  206. sknetwork/topology/weisfeiler_lehman_core.cpp +27644 -0
  207. sknetwork/topology/weisfeiler_lehman_core.cpython-312-x86_64-linux-gnu.so +0 -0
  208. sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
  209. sknetwork/utils/__init__.py +7 -0
  210. sknetwork/utils/check.py +355 -0
  211. sknetwork/utils/format.py +221 -0
  212. sknetwork/utils/membership.py +82 -0
  213. sknetwork/utils/neighbors.py +115 -0
  214. sknetwork/utils/tests/__init__.py +1 -0
  215. sknetwork/utils/tests/test_check.py +190 -0
  216. sknetwork/utils/tests/test_format.py +63 -0
  217. sknetwork/utils/tests/test_membership.py +24 -0
  218. sknetwork/utils/tests/test_neighbors.py +41 -0
  219. sknetwork/utils/tests/test_tfidf.py +18 -0
  220. sknetwork/utils/tests/test_values.py +66 -0
  221. sknetwork/utils/tfidf.py +37 -0
  222. sknetwork/utils/values.py +76 -0
  223. sknetwork/visualization/__init__.py +4 -0
  224. sknetwork/visualization/colors.py +34 -0
  225. sknetwork/visualization/dendrograms.py +277 -0
  226. sknetwork/visualization/graphs.py +1039 -0
  227. sknetwork/visualization/tests/__init__.py +1 -0
  228. sknetwork/visualization/tests/test_dendrograms.py +53 -0
  229. sknetwork/visualization/tests/test_graphs.py +176 -0
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created on Apr 2020
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ """
7
+ from typing import Optional, Union
8
+
9
+ import numpy as np
10
+ from scipy import sparse
11
+ from scipy.spatial import cKDTree
12
+
13
+ from sknetwork.embedding.base import BaseEmbedding
14
+ from sknetwork.embedding.spectral import Spectral
15
+ from sknetwork.linalg import normalize
16
+ from sknetwork.utils.check import check_adjacency_vector, check_format, check_square, is_symmetric
17
+ from sknetwork.utils.format import directed2undirected
18
+
19
+
20
+ class Spring(BaseEmbedding):
21
+ """Spring layout for displaying small graphs.
22
+
23
+ Parameters
24
+ ----------
25
+ n_components : int
26
+ Dimension of the graph layout.
27
+ strength : float
28
+ Intensity of the force that moves the nodes.
29
+ n_iter : int
30
+ Number of iterations to update positions.
31
+ tol : float
32
+ Minimum relative change in positions to continue updating.
33
+ approx_radius : float
34
+ If a positive value is provided, only the nodes within this distance a given node are used to compute
35
+ the repulsive force.
36
+ position_init : str
37
+ How to initialize the layout. If 'spectral', use Spectral embedding in dimension 2,
38
+ otherwise, use random initialization.
39
+
40
+ Attributes
41
+ ----------
42
+ embedding\_ : np.ndarray, shape = (n_nodes, n_components)
43
+ Embedding of the nodes.
44
+
45
+ Example
46
+ -------
47
+ >>> from sknetwork.embedding import Spring
48
+ >>> from sknetwork.data import karate_club
49
+ >>> spring = Spring()
50
+ >>> adjacency = karate_club()
51
+ >>> embedding = spring.fit_transform(adjacency)
52
+ >>> embedding.shape
53
+ (34, 2)
54
+
55
+ Notes
56
+ -----
57
+ Simple implementation designed to display small graphs.
58
+
59
+ References
60
+ ----------
61
+ Fruchterman, T. M. J., Reingold, E. M. (1991).
62
+ `Graph Drawing by Force-Directed Placement.
63
+ <https://onlinelibrary.wiley.com/doi/pdf/10.1002/spe.4380211102>`_
64
+ Software – Practice & Experience.
65
+ """
66
+ def __init__(self, n_components: int = 2, strength: float = None, n_iter: int = 50, tol: float = 1e-4,
67
+ approx_radius: float = -1, position_init: str = 'random'):
68
+ super(Spring, self).__init__()
69
+ self.n_components = n_components
70
+ self.strength = strength
71
+ self.n_iter = n_iter
72
+ self.tol = tol
73
+ self.approx_radius = approx_radius
74
+ if position_init not in ['random', 'spectral']:
75
+ raise ValueError('Unknown initial position, try "spectral" or "random".')
76
+ else:
77
+ self.position_init = position_init
78
+
79
+ def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray], position_init: Optional[np.ndarray] = None,
80
+ n_iter: Optional[int] = None) -> 'Spring':
81
+ """Compute layout.
82
+
83
+ Parameters
84
+ ----------
85
+ adjacency :
86
+ Adjacency matrix of the graph, treated as undirected.
87
+ position_init : np.ndarray
88
+ Custom initial positions of the nodes. Shape must be (n, 2).
89
+ If ``None``, use the value of self.pos_init.
90
+ n_iter : int
91
+ Number of iterations to update positions.
92
+ If ``None``, use the value of self.n_iter.
93
+
94
+ Returns
95
+ -------
96
+ self: :class:`Spring`
97
+ """
98
+ adjacency = check_format(adjacency)
99
+ check_square(adjacency)
100
+ if not is_symmetric(adjacency):
101
+ adjacency = directed2undirected(adjacency)
102
+ n = adjacency.shape[0]
103
+
104
+ position = np.zeros((n, self.n_components))
105
+ if position_init is None:
106
+ if self.position_init == 'random':
107
+ position = np.random.randn(n, self.n_components)
108
+ elif self.position_init == 'spectral':
109
+ position = Spectral(n_components=self.n_components).fit_transform(adjacency)
110
+ elif isinstance(position_init, np.ndarray):
111
+ if position_init.shape == (n, self.n_components):
112
+ position = position_init.copy()
113
+ else:
114
+ raise ValueError('Initial position has invalid shape.')
115
+ else:
116
+ raise TypeError('Initial position must be a numpy array.')
117
+
118
+ if n_iter is None:
119
+ n_iter = self.n_iter
120
+
121
+ if self.strength is None:
122
+ strength = np.sqrt((1 / n))
123
+ else:
124
+ strength = self.strength
125
+
126
+ pos_max = position.max(axis=0)
127
+ pos_min = position.min(axis=0)
128
+ step_max: float = 0.1 * (pos_max - pos_min).max()
129
+ step: float = step_max / (n_iter + 1)
130
+ tree = None
131
+
132
+ delta = np.zeros((n, self.n_components))
133
+ for iteration in range(n_iter):
134
+ delta *= 0
135
+ if self.approx_radius > 0:
136
+ tree = cKDTree(position)
137
+
138
+ for i in range(n):
139
+ # attraction
140
+ indices = adjacency.indices[adjacency.indptr[i]:adjacency.indptr[i+1]]
141
+ attraction = adjacency.data[adjacency.indptr[i]:adjacency.indptr[i+1]] / strength
142
+
143
+ grad = position[i] - position[indices]
144
+ attraction *= np.linalg.norm(grad, axis=1)
145
+ attraction = (grad * attraction[:, np.newaxis]).sum(axis=0)
146
+
147
+ # repulsion
148
+ if tree is None:
149
+ grad: np.ndarray = (position[i] - position) # shape (n, n_components)
150
+ distance: np.ndarray = np.linalg.norm(grad, axis=1) # shape (n,)
151
+ else:
152
+ neighbors = tree.query_ball_point(position[i], self.approx_radius)
153
+ grad: np.ndarray = (position[i] - position[neighbors]) # shape (n_neigh, n_components)
154
+ distance: np.ndarray = np.linalg.norm(grad, axis=1) # shape (n_neigh,)
155
+
156
+ distance = np.where(distance < 0.01, 0.01, distance)
157
+ repulsion = (grad * (strength / distance)[:, np.newaxis] ** 2).sum(axis=0)
158
+
159
+ # total force
160
+ delta[i]: np.ndarray = repulsion - attraction
161
+
162
+ length = np.linalg.norm(delta, axis=0)
163
+ length = np.where(length < 0.01, 0.1, length)
164
+ delta = delta * step_max / length
165
+ position += delta
166
+ step_max -= step
167
+ err: float = np.linalg.norm(delta) / n
168
+ if err < self.tol:
169
+ break
170
+
171
+ self.embedding_ = position
172
+ return self
173
+
174
+ def predict(self, adjacency_vectors: Union[sparse.csr_matrix, np.ndarray]) -> np.ndarray:
175
+ """Predict the embedding of new rows, defined by their adjacency vectors.
176
+
177
+ Parameters
178
+ ----------
179
+ adjacency_vectors :
180
+ Adjacency vectors of nodes.
181
+ Array of shape (n_col,) (single vector) or (n_vectors, n_col)
182
+
183
+ Returns
184
+ -------
185
+ embedding_vectors : np.ndarray
186
+ Embedding of the nodes.
187
+ """
188
+ self._check_fitted()
189
+ embedding = self.embedding_
190
+ n = embedding.shape[0]
191
+
192
+ adjacency_vectors = check_adjacency_vector(adjacency_vectors, n)
193
+ embedding_vectors = normalize(adjacency_vectors).dot(embedding)
194
+
195
+ if embedding_vectors.shape[0] == 1:
196
+ embedding_vectors = embedding_vectors.ravel()
197
+
198
+ return embedding_vectors
@@ -0,0 +1,351 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created in May 2018
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ @author: Thomas Bonald <bonald@enst.fr>
7
+ """
8
+ from typing import Union
9
+
10
+ import numpy as np
11
+ from scipy import sparse
12
+
13
+ from sknetwork.embedding.base import BaseEmbedding
14
+ from sknetwork.linalg import SVDSolver, LanczosSVD, safe_sparse_dot, diagonal_pseudo_inverse, normalize, Regularizer, SparseLR
15
+ from sknetwork.utils.check import check_format, check_adjacency_vector, check_nonnegative, check_n_components
16
+
17
+
18
+ class GSVD(BaseEmbedding):
19
+ """Graph embedding by Generalized Singular Value Decomposition of the adjacency or biadjacency matrix :math:`A`.
20
+ This is equivalent to the Singular Value Decomposition of the matrix :math:`D_1^{- \\alpha_1}AD_2^{- \\alpha_2}`
21
+ where :math:`D_1, D_2` are the diagonal matrices of row weights and columns weights, respectively, and
22
+ :math:`\\alpha_1, \\alpha_2` are parameters.
23
+
24
+ Parameters
25
+ -----------
26
+ n_components : int
27
+ Dimension of the embedding.
28
+ regularization : ``None`` or float (default = ``None``)
29
+ Regularization factor :math:`\\alpha` so that the matrix is :math:`A + \\alpha \\frac{11^T}{n}`.
30
+ factor_row : float (default = 0.5)
31
+ Power factor :math:`\\alpha_1` applied to the diagonal matrix of row weights.
32
+ factor_col : float (default = 0.5)
33
+ Power factor :math:`\\alpha_2` applied to the diagonal matrix of column weights.
34
+ factor_singular : float (default = 0.)
35
+ Parameter :math:`\\alpha` applied to the singular values on right singular vectors.
36
+ The embedding of rows and columns are respectively :math:`D_1^{- \\alpha_1}U \\Sigma^{1-\\alpha}` and
37
+ :math:`D_2^{- \\alpha_2}V \\Sigma^\\alpha` where:
38
+
39
+ * :math:`U` is the matrix of left singular vectors, shape (n_row, n_components)
40
+ * :math:`V` is the matrix of right singular vectors, shape (n_col, n_components)
41
+ * :math:`\\Sigma` is the diagonal matrix of singular values, shape (n_components, n_components)
42
+
43
+ normalized : bool (default = ``True``)
44
+ If ``True``, normalized the embedding so that each vector has norm 1 in the embedding space, i.e.,
45
+ each vector lies on the unit sphere.
46
+ solver : ``'lanczos'`` (Lanczos algorithm, default) or :class:`SVDSolver` (custom solver)
47
+ Which solver to use.
48
+
49
+ Attributes
50
+ ----------
51
+ embedding\_ : array, shape = (n, n_components)
52
+ Embedding of the nodes.
53
+ embedding_row\_ : array, shape = (n_row, n_components)
54
+ Embedding of the rows, for bipartite graphs.
55
+ embedding_col\_ : array, shape = (n_col, n_components)
56
+ Embedding of the columns, for bipartite graphs.
57
+ singular_values\_ : np.ndarray, shape = (n_components)
58
+ Singular values.
59
+ singular_vectors_left\_ : np.ndarray, shape = (n_row, n_components)
60
+ Left singular vectors.
61
+ singular_vectors_right\_ : np.ndarray, shape = (n_col, n_components)
62
+ Right singular vectors.
63
+ weights_col\_ : np.ndarray, shape = (n2)
64
+ Weights applied to columns.
65
+
66
+ Example
67
+ -------
68
+ >>> from sknetwork.embedding import GSVD
69
+ >>> from sknetwork.data import karate_club
70
+ >>> gsvd = GSVD()
71
+ >>> adjacency = karate_club()
72
+ >>> embedding = gsvd.fit_transform(adjacency)
73
+ >>> embedding.shape
74
+ (34, 2)
75
+
76
+ References
77
+ ----------
78
+ Abdi, H. (2007).
79
+ `Singular value decomposition (SVD) and generalized singular value decomposition.
80
+ <https://www.cs.cornell.edu/cv/ResearchPDF/Generalizing%20The%20Singular%20Value%20Decomposition.pdf>`_
81
+ Encyclopedia of measurement and statistics, 907-912.
82
+ """
83
+ def __init__(self, n_components=2, regularization: Union[None, float] = None,
84
+ factor_row: float = 0.5, factor_col: float = 0.5, factor_singular: float = 0., normalized: bool = True,
85
+ solver: Union[str, SVDSolver] = 'lanczos'):
86
+ super(GSVD, self).__init__()
87
+
88
+ self.n_components = n_components
89
+ if regularization == 0:
90
+ self.regularization = None
91
+ else:
92
+ self.regularization = regularization
93
+ self.factor_row = factor_row
94
+ self.factor_col = factor_col
95
+ self.factor_singular = factor_singular
96
+ self.normalized = normalized
97
+ self.solver = solver
98
+
99
+ self.singular_values_ = None
100
+ self.singular_vectors_left_ = None
101
+ self.singular_vectors_right_ = None
102
+ self.regularization_ = None
103
+ self.weights_col_ = None
104
+
105
+ def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray]) -> 'GSVD':
106
+ """Compute the embedding of the graph.
107
+
108
+ Parameters
109
+ ----------
110
+ input_matrix :
111
+ Adjacency matrix or biadjacency matrix of the graph.
112
+
113
+ Returns
114
+ -------
115
+ self: :class:`GSVD`
116
+ """
117
+ self._init_vars()
118
+
119
+ adjacency = check_format(input_matrix).asfptype()
120
+ n_row, n_col = adjacency.shape
121
+ n_components = check_n_components(self.n_components, min(n_row, n_col) - 1)
122
+
123
+ if isinstance(self.solver, str):
124
+ self.solver = LanczosSVD()
125
+ regularization = self.regularization
126
+ if regularization:
127
+ adjacency_reg = Regularizer(adjacency, regularization)
128
+ else:
129
+ adjacency_reg = adjacency
130
+
131
+ weights_row = adjacency_reg.dot(np.ones(n_col))
132
+ weights_col = adjacency_reg.T.dot(np.ones(n_row))
133
+ diag_row = diagonal_pseudo_inverse(np.power(weights_row, self.factor_row))
134
+ diag_col = diagonal_pseudo_inverse(np.power(weights_col, self.factor_col))
135
+ self.solver.fit(safe_sparse_dot(diag_row, safe_sparse_dot(adjacency_reg, diag_col)), n_components)
136
+
137
+ singular_values = self.solver.singular_values_
138
+ index = np.argsort(-singular_values)
139
+ singular_values = singular_values[index]
140
+ singular_vectors_left = self.solver.singular_vectors_left_[:, index]
141
+ singular_vectors_right = self.solver.singular_vectors_right_[:, index]
142
+ singular_left_diag = sparse.diags(np.power(singular_values, 1 - self.factor_singular))
143
+ singular_right_diag = sparse.diags(np.power(singular_values, self.factor_singular))
144
+
145
+ embedding_row = diag_row.dot(singular_vectors_left)
146
+ embedding_col = diag_col.dot(singular_vectors_right)
147
+ embedding_row = singular_left_diag.dot(embedding_row.T).T
148
+ embedding_col = singular_right_diag.dot(embedding_col.T).T
149
+
150
+ if self.normalized:
151
+ embedding_row = normalize(embedding_row, p=2)
152
+ embedding_col = normalize(embedding_col, p=2)
153
+
154
+ self.embedding_row_ = embedding_row
155
+ self.embedding_col_ = embedding_col
156
+ self.embedding_ = embedding_row
157
+ self.singular_values_ = singular_values
158
+ self.singular_vectors_left_ = singular_vectors_left
159
+ self.singular_vectors_right_ = singular_vectors_right
160
+ self.weights_col_ = weights_col
161
+
162
+ return self
163
+
164
+ @staticmethod
165
+ def _check_adj_vector(adjacency_vectors):
166
+ check_nonnegative(adjacency_vectors)
167
+
168
+ def predict(self, adjacency_vectors: Union[sparse.csr_matrix, np.ndarray]) -> np.ndarray:
169
+ """Predict the embedding of new rows, defined by their adjacency vectors.
170
+
171
+ Parameters
172
+ ----------
173
+ adjacency_vectors :
174
+ Adjacency vectors of nodes.
175
+ Array of shape (n_col,) (single vector) or (n_vectors, n_col)
176
+
177
+ Returns
178
+ -------
179
+ embedding_vectors : np.ndarray
180
+ Embedding of the nodes.
181
+ """
182
+ self._check_fitted()
183
+ singular_vectors_right = self.singular_vectors_right_
184
+ singular_values = self.singular_values_
185
+
186
+ n_row, _ = self.embedding_row_.shape
187
+ n_col, _ = self.embedding_col_.shape
188
+
189
+ adjacency_vectors = check_adjacency_vector(adjacency_vectors, n_col)
190
+ self._check_adj_vector(adjacency_vectors)
191
+
192
+ # regularization
193
+ if self.regularization:
194
+ adjacency_vectors = Regularizer(adjacency_vectors, self.regularization)
195
+
196
+ # weighting
197
+ weights_row = adjacency_vectors.dot(np.ones(n_col))
198
+ diag_row = diagonal_pseudo_inverse(np.power(weights_row, self.factor_row))
199
+ diag_col = diagonal_pseudo_inverse(np.power(self.weights_col_, self.factor_col))
200
+ adjacency_vectors = safe_sparse_dot(diag_row, safe_sparse_dot(adjacency_vectors, diag_col))
201
+
202
+ # projection in the embedding space
203
+ averaging = adjacency_vectors
204
+ embedding_vectors = diag_row.dot(averaging.dot(singular_vectors_right))
205
+
206
+ # scaling
207
+ embedding_vectors /= np.power(singular_values, self.factor_singular)
208
+
209
+ if self.normalized:
210
+ embedding_vectors = normalize(embedding_vectors, p=2)
211
+
212
+ if len(embedding_vectors) == 1:
213
+ embedding_vectors = embedding_vectors.ravel()
214
+
215
+ return embedding_vectors
216
+
217
+
218
+ class SVD(GSVD):
219
+ """Graph embedding by Singular Value Decomposition of the adjacency or biadjacency matrix of the graph.
220
+
221
+ Parameters
222
+ ----------
223
+ n_components : int
224
+ Dimension of the embedding.
225
+ regularization : ``None`` or float (default = ``None``)
226
+ Regularization factor :math:`\\alpha` so that the matrix is :math:`A + \\alpha \\frac{11^T}{n}`.
227
+ factor_singular : float (default = 0.)
228
+ Power factor :math:`\\alpha` applied to the singular values on right singular vectors.
229
+ The embedding of rows and columns are respectively :math:`U \\Sigma^{1-\\alpha}` and
230
+ :math:`V \\Sigma^\\alpha` where:
231
+
232
+ * :math:`U` is the matrix of left singular vectors, shape (n_row, n_components)
233
+ * :math:`V` is the matrix of right singular vectors, shape (n_col, n_components)
234
+ * :math:`\\Sigma` is the diagonal matrix of singular values, shape (n_components, n_components)
235
+
236
+ normalized : bool (default = ``False``)
237
+ If ``True``, normalized the embedding so that each vector has norm 1 in the embedding space, i.e.,
238
+ each vector lies on the unit sphere.
239
+ solver : ``'lanczos'`` (Lanczos algorithm, default) or :class:`SVDSolver` (custom solver)
240
+ Which solver to use.
241
+
242
+ Attributes
243
+ ----------
244
+ embedding\_ : np.ndarray, shape = (n_nodes, n_components)
245
+ Embedding of the nodes.
246
+ singular_values\_ : np.ndarray, shape = (n_components)
247
+ Singular values.
248
+ singular_vectors_left\_ : np.ndarray, shape = (n_row, n_components)
249
+ Left singular vectors.
250
+ singular_vectors_right\_ : np.ndarray, shape = (n_col, n_components)
251
+ Right singular vectors.
252
+
253
+ Example
254
+ -------
255
+ >>> from sknetwork.embedding import SVD
256
+ >>> from sknetwork.data import karate_club
257
+ >>> svd = SVD()
258
+ >>> adjacency = karate_club()
259
+ >>> embedding = svd.fit_transform(adjacency)
260
+ >>> embedding.shape
261
+ (34, 2)
262
+
263
+ References
264
+ ----------
265
+ Abdi, H. (2007).
266
+ `Singular value decomposition (SVD) and generalized singular value decomposition.
267
+ <https://www.cs.cornell.edu/cv/ResearchPDF/Generalizing%20The%20Singular%20Value%20Decomposition.pdf>`_
268
+ Encyclopedia of measurement and statistics.
269
+ """
270
+ def __init__(self, n_components=2, regularization: Union[None, float] = None, factor_singular: float = 0.,
271
+ normalized: bool = False, solver: Union[str, SVDSolver] = 'lanczos'):
272
+ super(SVD, self).__init__(n_components=n_components, regularization=regularization,
273
+ factor_singular=factor_singular, factor_row=0., factor_col=0., normalized=normalized,
274
+ solver=solver)
275
+
276
+
277
+ class PCA(SVD):
278
+ """Graph embedding by Principal Component Analysis of the adjacency or biadjacency matrix.
279
+
280
+ Parameters
281
+ ----------
282
+ n_components : int
283
+ Dimension of the embedding.
284
+ normalized : bool (default = ``False``)
285
+ If ``True``, normalized the embedding so that each vector has norm 1 in the embedding space, i.e.,
286
+ each vector lies on the unit sphere.
287
+ solver : ``'lanczos'`` (Lanczos algorithm, default) or :class:`SVDSolver` (custom solver)
288
+ Which solver to use.
289
+
290
+ Attributes
291
+ ----------
292
+ embedding\_ : array, shape = (n_nodes, n_components)
293
+ Embedding of the nodes.
294
+ singular_values\_ : np.ndarray, shape = (n_components)
295
+ Singular values.
296
+ singular_vectors_left\_ : np.ndarray, shape = (n_row, n_components)
297
+ Left singular vectors.
298
+ singular_vectors_right\_ : np.ndarray, shape = (n_col, n_components)
299
+ Right singular vectors.
300
+
301
+ Example
302
+ -------
303
+ >>> from sknetwork.embedding import PCA
304
+ >>> from sknetwork.data import karate_club
305
+ >>> pca = PCA()
306
+ >>> adjacency = karate_club()
307
+ >>> embedding = pca.fit_transform(adjacency)
308
+ >>> embedding.shape
309
+ (34, 2)
310
+
311
+ References
312
+ ----------
313
+ Jolliffe, I.T. (2002).
314
+ `Principal Component Analysis`
315
+ Series: Springer Series in Statistics.
316
+ """
317
+ def __init__(self, n_components=2, normalized: bool = False, solver: Union[str, SVDSolver] = 'lanczos'):
318
+ super(PCA, self).__init__()
319
+ self.n_components = n_components
320
+ self.normalized = normalized
321
+ if isinstance(solver, str):
322
+ self.solver = LanczosSVD()
323
+ else:
324
+ self.solver = solver
325
+
326
+ def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray]) -> 'PCA':
327
+ """Compute the embedding of the graph.
328
+
329
+ Parameters
330
+ ----------
331
+ adjacency :
332
+ Adjacency or biadjacency matrix of the graph.
333
+
334
+ Returns
335
+ -------
336
+ self: :class:`PCA`
337
+ """
338
+ adjacency = check_format(adjacency).asfptype()
339
+ n_row, n_col = adjacency.shape
340
+ adjacency_centered = SparseLR(adjacency, (-np.ones(n_row), adjacency.T.dot(np.ones(n_row)) / n_row))
341
+
342
+ svd = self.solver
343
+ svd.fit(adjacency_centered, self.n_components)
344
+ self.embedding_row_ = svd.singular_vectors_left_
345
+ self.embedding_col_ = svd.singular_vectors_right_
346
+ self.embedding_ = svd.singular_vectors_left_
347
+ self.singular_values_ = svd.singular_values_
348
+ self.singular_vectors_left_ = svd.singular_vectors_left_
349
+ self.singular_vectors_right_ = svd.singular_vectors_right_
350
+
351
+ return self
@@ -0,0 +1 @@
1
+ """tests for embedding"""
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for embeddings"""
4
+
5
+ import unittest
6
+
7
+ from sknetwork.data.test_graphs import *
8
+ from sknetwork.embedding import Spectral, SVD, GSVD, Spring
9
+
10
+
11
+ class TestEmbeddings(unittest.TestCase):
12
+
13
+ def setUp(self):
14
+ """Algorithms by input types."""
15
+ self.methods = [Spectral(), GSVD(), SVD()]
16
+
17
+ def test_undirected(self):
18
+ adjacency = test_graph()
19
+ n = adjacency.shape[0]
20
+
21
+ method = Spring()
22
+ embedding = method.fit_transform(adjacency)
23
+ self.assertEqual(embedding.shape, (n, 2))
24
+
25
+ embedding = method.transform()
26
+ self.assertEqual(embedding.shape, (n, 2))
27
+
28
+ def test_bipartite(self):
29
+ for adjacency in [test_digraph(), test_bigraph()]:
30
+ n_row, n_col = adjacency.shape
31
+
32
+ for method in self.methods:
33
+ method.fit(adjacency)
34
+
35
+ self.assertEqual(method.embedding_.shape, (n_row, 2))
36
+ self.assertEqual(method.embedding_row_.shape, (n_row, 2))
37
+ self.assertEqual(method.embedding_col_.shape, (n_col, 2))
38
+
39
+ def test_disconnected(self):
40
+ n = 10
41
+ adjacency = np.eye(n)
42
+ for method in self.methods:
43
+ embedding = method.fit_transform(adjacency)
44
+ self.assertEqual(embedding.shape, (n, 2))
45
+
46
+ def test_regularization(self):
47
+ adjacency = test_graph()
48
+ method = Spectral()
49
+ self.assertEqual(method._get_regularization(-1, adjacency), 0)
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for force atlas2 embeddings"""
4
+ import unittest
5
+
6
+ import numpy as np
7
+
8
+ from sknetwork.data.test_graphs import test_graph, test_digraph
9
+ from sknetwork.embedding.force_atlas import ForceAtlas
10
+
11
+
12
+ class TestEmbeddings(unittest.TestCase):
13
+
14
+ def test_options(self):
15
+ for adjacency in [test_graph(), test_digraph()]:
16
+ n = adjacency.shape[0]
17
+
18
+ force_atlas = ForceAtlas()
19
+ layout = force_atlas.fit_transform(adjacency)
20
+ self.assertEqual((n, 2), layout.shape)
21
+
22
+ force_atlas = ForceAtlas(lin_log=True)
23
+ layout = force_atlas.fit_transform(adjacency)
24
+ self.assertEqual((n, 2), layout.shape)
25
+
26
+ force_atlas = ForceAtlas(approx_radius=1.)
27
+ layout = force_atlas.fit_transform(adjacency)
28
+ self.assertEqual((n, 2), layout.shape)
29
+
30
+ force_atlas.fit(adjacency, pos_init=layout, n_iter=1)
31
+
32
+ def test_errors(self):
33
+ adjacency = test_graph()
34
+ with self.assertRaises(ValueError):
35
+ ForceAtlas().fit(adjacency, pos_init=np.ones((5, 7)))
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for Louvain embedding"""
4
+ import unittest
5
+
6
+ import numpy as np
7
+
8
+ from sknetwork.data.test_graphs import test_graph, test_bigraph
9
+ from sknetwork.embedding import LouvainEmbedding
10
+
11
+
12
+ class TestLouvainEmbedding(unittest.TestCase):
13
+
14
+ def test_predict(self):
15
+ adjacency = test_graph()
16
+ adjacency_vector = np.zeros(10, dtype=int)
17
+ adjacency_vector[:5] = 1
18
+ louvain = LouvainEmbedding()
19
+ louvain.fit(adjacency)
20
+ self.assertEqual(louvain.embedding_.shape[0], 10)
21
+ louvain.fit(adjacency, force_bipartite=True)
22
+ self.assertEqual(louvain.embedding_.shape[0], 10)
23
+
24
+ # bipartite
25
+ biadjacency = test_bigraph()
26
+ louvain.fit(biadjacency)
27
+ self.assertEqual(louvain.embedding_row_.shape[0], 6)
28
+ self.assertEqual(louvain.embedding_col_.shape[0], 8)
29
+
30
+ for method in ['remove', 'merge', 'keep']:
31
+ louvain = LouvainEmbedding(isolated_nodes=method)
32
+ embedding = louvain.fit_transform(adjacency)
33
+ self.assertEqual(embedding.shape[0], adjacency.shape[0])