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.
- scikit_network-0.33.4.dist-info/METADATA +122 -0
- scikit_network-0.33.4.dist-info/RECORD +229 -0
- scikit_network-0.33.4.dist-info/WHEEL +6 -0
- scikit_network-0.33.4.dist-info/licenses/AUTHORS.rst +43 -0
- scikit_network-0.33.4.dist-info/licenses/LICENSE +34 -0
- scikit_network-0.33.4.dist-info/top_level.txt +1 -0
- scikit_network.libs/libgomp-a34b3233.so.1.0.0 +0 -0
- sknetwork/__init__.py +21 -0
- sknetwork/base.py +67 -0
- sknetwork/classification/__init__.py +8 -0
- sknetwork/classification/base.py +138 -0
- sknetwork/classification/base_rank.py +129 -0
- sknetwork/classification/diffusion.py +127 -0
- sknetwork/classification/knn.py +131 -0
- sknetwork/classification/metrics.py +205 -0
- sknetwork/classification/pagerank.py +58 -0
- sknetwork/classification/propagation.py +144 -0
- sknetwork/classification/tests/__init__.py +1 -0
- sknetwork/classification/tests/test_API.py +30 -0
- sknetwork/classification/tests/test_diffusion.py +77 -0
- sknetwork/classification/tests/test_knn.py +23 -0
- sknetwork/classification/tests/test_metrics.py +53 -0
- sknetwork/classification/tests/test_pagerank.py +20 -0
- sknetwork/classification/tests/test_propagation.py +24 -0
- sknetwork/classification/vote.cpp +27593 -0
- sknetwork/classification/vote.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/classification/vote.pyx +56 -0
- sknetwork/clustering/__init__.py +8 -0
- sknetwork/clustering/base.py +168 -0
- sknetwork/clustering/kcenters.py +251 -0
- sknetwork/clustering/leiden.py +238 -0
- sknetwork/clustering/leiden_core.cpp +31928 -0
- sknetwork/clustering/leiden_core.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/clustering/leiden_core.pyx +124 -0
- sknetwork/clustering/louvain.py +282 -0
- sknetwork/clustering/louvain_core.cpp +31573 -0
- sknetwork/clustering/louvain_core.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/clustering/louvain_core.pyx +124 -0
- sknetwork/clustering/metrics.py +91 -0
- sknetwork/clustering/postprocess.py +66 -0
- sknetwork/clustering/propagation_clustering.py +100 -0
- sknetwork/clustering/tests/__init__.py +1 -0
- sknetwork/clustering/tests/test_API.py +38 -0
- sknetwork/clustering/tests/test_kcenters.py +60 -0
- sknetwork/clustering/tests/test_leiden.py +34 -0
- sknetwork/clustering/tests/test_louvain.py +135 -0
- sknetwork/clustering/tests/test_metrics.py +50 -0
- sknetwork/clustering/tests/test_postprocess.py +39 -0
- sknetwork/data/__init__.py +6 -0
- sknetwork/data/base.py +33 -0
- sknetwork/data/load.py +292 -0
- sknetwork/data/models.py +459 -0
- sknetwork/data/parse.py +644 -0
- sknetwork/data/test_graphs.py +93 -0
- sknetwork/data/tests/__init__.py +1 -0
- sknetwork/data/tests/test_API.py +30 -0
- sknetwork/data/tests/test_base.py +14 -0
- sknetwork/data/tests/test_load.py +61 -0
- sknetwork/data/tests/test_models.py +52 -0
- sknetwork/data/tests/test_parse.py +250 -0
- sknetwork/data/tests/test_test_graphs.py +29 -0
- sknetwork/data/tests/test_toy_graphs.py +68 -0
- sknetwork/data/timeout.py +38 -0
- sknetwork/data/toy_graphs.py +611 -0
- sknetwork/embedding/__init__.py +8 -0
- sknetwork/embedding/base.py +90 -0
- sknetwork/embedding/force_atlas.py +198 -0
- sknetwork/embedding/louvain_embedding.py +142 -0
- sknetwork/embedding/random_projection.py +131 -0
- sknetwork/embedding/spectral.py +137 -0
- sknetwork/embedding/spring.py +198 -0
- sknetwork/embedding/svd.py +351 -0
- sknetwork/embedding/tests/__init__.py +1 -0
- sknetwork/embedding/tests/test_API.py +49 -0
- sknetwork/embedding/tests/test_force_atlas.py +35 -0
- sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
- sknetwork/embedding/tests/test_random_projection.py +28 -0
- sknetwork/embedding/tests/test_spectral.py +81 -0
- sknetwork/embedding/tests/test_spring.py +50 -0
- sknetwork/embedding/tests/test_svd.py +43 -0
- sknetwork/gnn/__init__.py +10 -0
- sknetwork/gnn/activation.py +117 -0
- sknetwork/gnn/base.py +181 -0
- sknetwork/gnn/base_activation.py +90 -0
- sknetwork/gnn/base_layer.py +109 -0
- sknetwork/gnn/gnn_classifier.py +305 -0
- sknetwork/gnn/layer.py +153 -0
- sknetwork/gnn/loss.py +180 -0
- sknetwork/gnn/neighbor_sampler.py +65 -0
- sknetwork/gnn/optimizer.py +164 -0
- sknetwork/gnn/tests/__init__.py +1 -0
- sknetwork/gnn/tests/test_activation.py +56 -0
- sknetwork/gnn/tests/test_base.py +75 -0
- sknetwork/gnn/tests/test_base_layer.py +37 -0
- sknetwork/gnn/tests/test_gnn_classifier.py +130 -0
- sknetwork/gnn/tests/test_layers.py +80 -0
- sknetwork/gnn/tests/test_loss.py +33 -0
- sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
- sknetwork/gnn/tests/test_optimizer.py +43 -0
- sknetwork/gnn/tests/test_utils.py +41 -0
- sknetwork/gnn/utils.py +127 -0
- sknetwork/hierarchy/__init__.py +6 -0
- sknetwork/hierarchy/base.py +90 -0
- sknetwork/hierarchy/louvain_hierarchy.py +260 -0
- sknetwork/hierarchy/metrics.py +234 -0
- sknetwork/hierarchy/paris.cpp +37877 -0
- sknetwork/hierarchy/paris.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/hierarchy/paris.pyx +310 -0
- sknetwork/hierarchy/postprocess.py +350 -0
- sknetwork/hierarchy/tests/__init__.py +1 -0
- sknetwork/hierarchy/tests/test_API.py +24 -0
- sknetwork/hierarchy/tests/test_algos.py +34 -0
- sknetwork/hierarchy/tests/test_metrics.py +62 -0
- sknetwork/hierarchy/tests/test_postprocess.py +57 -0
- sknetwork/linalg/__init__.py +9 -0
- sknetwork/linalg/basics.py +37 -0
- sknetwork/linalg/diteration.cpp +27409 -0
- sknetwork/linalg/diteration.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/linalg/diteration.pyx +47 -0
- sknetwork/linalg/eig_solver.py +93 -0
- sknetwork/linalg/laplacian.py +15 -0
- sknetwork/linalg/normalizer.py +86 -0
- sknetwork/linalg/operators.py +225 -0
- sknetwork/linalg/polynome.py +76 -0
- sknetwork/linalg/ppr_solver.py +170 -0
- sknetwork/linalg/push.cpp +31081 -0
- sknetwork/linalg/push.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/linalg/push.pyx +71 -0
- sknetwork/linalg/sparse_lowrank.py +142 -0
- sknetwork/linalg/svd_solver.py +91 -0
- sknetwork/linalg/tests/__init__.py +1 -0
- sknetwork/linalg/tests/test_eig.py +44 -0
- sknetwork/linalg/tests/test_laplacian.py +18 -0
- sknetwork/linalg/tests/test_normalization.py +34 -0
- sknetwork/linalg/tests/test_operators.py +66 -0
- sknetwork/linalg/tests/test_polynome.py +38 -0
- sknetwork/linalg/tests/test_ppr.py +50 -0
- sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
- sknetwork/linalg/tests/test_svd.py +38 -0
- sknetwork/linkpred/__init__.py +2 -0
- sknetwork/linkpred/base.py +46 -0
- sknetwork/linkpred/nn.py +126 -0
- sknetwork/linkpred/tests/__init__.py +1 -0
- sknetwork/linkpred/tests/test_nn.py +26 -0
- sknetwork/log.py +19 -0
- sknetwork/path/__init__.py +5 -0
- sknetwork/path/dag.py +54 -0
- sknetwork/path/distances.py +98 -0
- sknetwork/path/search.py +31 -0
- sknetwork/path/shortest_path.py +61 -0
- sknetwork/path/tests/__init__.py +1 -0
- sknetwork/path/tests/test_dag.py +37 -0
- sknetwork/path/tests/test_distances.py +62 -0
- sknetwork/path/tests/test_search.py +40 -0
- sknetwork/path/tests/test_shortest_path.py +40 -0
- sknetwork/ranking/__init__.py +8 -0
- sknetwork/ranking/base.py +57 -0
- sknetwork/ranking/betweenness.cpp +9716 -0
- sknetwork/ranking/betweenness.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/ranking/betweenness.pyx +97 -0
- sknetwork/ranking/closeness.py +92 -0
- sknetwork/ranking/hits.py +90 -0
- sknetwork/ranking/katz.py +79 -0
- sknetwork/ranking/pagerank.py +106 -0
- sknetwork/ranking/postprocess.py +37 -0
- sknetwork/ranking/tests/__init__.py +1 -0
- sknetwork/ranking/tests/test_API.py +32 -0
- sknetwork/ranking/tests/test_betweenness.py +38 -0
- sknetwork/ranking/tests/test_closeness.py +30 -0
- sknetwork/ranking/tests/test_hits.py +20 -0
- sknetwork/ranking/tests/test_pagerank.py +62 -0
- sknetwork/ranking/tests/test_postprocess.py +26 -0
- sknetwork/regression/__init__.py +4 -0
- sknetwork/regression/base.py +57 -0
- sknetwork/regression/diffusion.py +204 -0
- sknetwork/regression/tests/__init__.py +1 -0
- sknetwork/regression/tests/test_API.py +32 -0
- sknetwork/regression/tests/test_diffusion.py +56 -0
- sknetwork/sknetwork.py +3 -0
- sknetwork/test_base.py +35 -0
- sknetwork/test_log.py +15 -0
- sknetwork/topology/__init__.py +8 -0
- sknetwork/topology/cliques.cpp +32574 -0
- sknetwork/topology/cliques.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/topology/cliques.pyx +149 -0
- sknetwork/topology/core.cpp +30660 -0
- sknetwork/topology/core.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/topology/core.pyx +90 -0
- sknetwork/topology/cycles.py +243 -0
- sknetwork/topology/minheap.cpp +27341 -0
- sknetwork/topology/minheap.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/topology/minheap.pxd +20 -0
- sknetwork/topology/minheap.pyx +109 -0
- sknetwork/topology/structure.py +194 -0
- sknetwork/topology/tests/__init__.py +1 -0
- sknetwork/topology/tests/test_cliques.py +28 -0
- sknetwork/topology/tests/test_core.py +19 -0
- sknetwork/topology/tests/test_cycles.py +65 -0
- sknetwork/topology/tests/test_structure.py +85 -0
- sknetwork/topology/tests/test_triangles.py +38 -0
- sknetwork/topology/tests/test_wl.py +72 -0
- sknetwork/topology/triangles.cpp +8903 -0
- sknetwork/topology/triangles.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/topology/triangles.pyx +151 -0
- sknetwork/topology/weisfeiler_lehman.py +133 -0
- sknetwork/topology/weisfeiler_lehman_core.cpp +27644 -0
- sknetwork/topology/weisfeiler_lehman_core.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
- sknetwork/utils/__init__.py +7 -0
- sknetwork/utils/check.py +355 -0
- sknetwork/utils/format.py +221 -0
- sknetwork/utils/membership.py +82 -0
- sknetwork/utils/neighbors.py +115 -0
- sknetwork/utils/tests/__init__.py +1 -0
- sknetwork/utils/tests/test_check.py +190 -0
- sknetwork/utils/tests/test_format.py +63 -0
- sknetwork/utils/tests/test_membership.py +24 -0
- sknetwork/utils/tests/test_neighbors.py +41 -0
- sknetwork/utils/tests/test_tfidf.py +18 -0
- sknetwork/utils/tests/test_values.py +66 -0
- sknetwork/utils/tfidf.py +37 -0
- sknetwork/utils/values.py +76 -0
- sknetwork/visualization/__init__.py +4 -0
- sknetwork/visualization/colors.py +34 -0
- sknetwork/visualization/dendrograms.py +277 -0
- sknetwork/visualization/graphs.py +1039 -0
- sknetwork/visualization/tests/__init__.py +1 -0
- sknetwork/visualization/tests/test_dendrograms.py +53 -0
- 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])
|