scikit-network 0.31.0__cp310-cp310-win_amd64.whl → 0.32.1__cp310-cp310-win_amd64.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.
Potentially problematic release.
This version of scikit-network might be problematic. Click here for more details.
- {scikit_network-0.31.0.dist-info → scikit_network-0.32.1.dist-info}/AUTHORS.rst +3 -0
- {scikit_network-0.31.0.dist-info → scikit_network-0.32.1.dist-info}/METADATA +19 -3
- {scikit_network-0.31.0.dist-info → scikit_network-0.32.1.dist-info}/RECORD +112 -105
- {scikit_network-0.31.0.dist-info → scikit_network-0.32.1.dist-info}/WHEEL +1 -1
- sknetwork/__init__.py +1 -1
- sknetwork/classification/base.py +1 -1
- sknetwork/classification/base_rank.py +3 -3
- sknetwork/classification/diffusion.py +21 -13
- sknetwork/classification/knn.py +19 -13
- sknetwork/classification/metrics.py +1 -1
- sknetwork/classification/pagerank.py +12 -8
- sknetwork/classification/propagation.py +22 -15
- sknetwork/classification/tests/test_diffusion.py +10 -0
- sknetwork/classification/vote.cp310-win_amd64.pyd +0 -0
- sknetwork/classification/vote.cpp +14549 -8668
- sknetwork/clustering/__init__.py +3 -1
- sknetwork/clustering/base.py +1 -1
- sknetwork/clustering/kcenters.py +253 -0
- sknetwork/clustering/leiden.py +241 -0
- sknetwork/clustering/leiden_core.cp310-win_amd64.pyd +0 -0
- sknetwork/clustering/leiden_core.cpp +31564 -0
- sknetwork/clustering/leiden_core.pyx +124 -0
- sknetwork/clustering/louvain.py +118 -83
- sknetwork/clustering/louvain_core.cp310-win_amd64.pyd +0 -0
- sknetwork/clustering/louvain_core.cpp +21876 -16332
- sknetwork/clustering/louvain_core.pyx +86 -94
- sknetwork/clustering/postprocess.py +2 -2
- sknetwork/clustering/propagation_clustering.py +4 -4
- sknetwork/clustering/tests/test_API.py +7 -3
- sknetwork/clustering/tests/test_kcenters.py +92 -0
- sknetwork/clustering/tests/test_leiden.py +34 -0
- sknetwork/clustering/tests/test_louvain.py +2 -3
- sknetwork/data/load.py +2 -4
- sknetwork/data/parse.py +41 -20
- sknetwork/data/tests/test_parse.py +9 -12
- sknetwork/embedding/__init__.py +0 -1
- sknetwork/embedding/base.py +20 -19
- sknetwork/embedding/force_atlas.py +3 -2
- sknetwork/embedding/louvain_embedding.py +1 -1
- sknetwork/embedding/random_projection.py +5 -3
- sknetwork/embedding/spectral.py +0 -73
- sknetwork/embedding/tests/test_API.py +4 -28
- sknetwork/embedding/tests/test_louvain_embedding.py +4 -9
- sknetwork/embedding/tests/test_spectral.py +2 -5
- sknetwork/embedding/tests/test_svd.py +1 -1
- sknetwork/gnn/base_layer.py +3 -3
- sknetwork/gnn/gnn_classifier.py +40 -86
- sknetwork/gnn/layer.py +1 -1
- sknetwork/gnn/loss.py +1 -1
- sknetwork/gnn/optimizer.py +4 -3
- sknetwork/gnn/tests/test_base_layer.py +4 -4
- sknetwork/gnn/tests/test_gnn_classifier.py +12 -39
- sknetwork/gnn/utils.py +8 -8
- sknetwork/hierarchy/base.py +27 -0
- sknetwork/hierarchy/louvain_hierarchy.py +45 -41
- sknetwork/hierarchy/paris.cp310-win_amd64.pyd +0 -0
- sknetwork/hierarchy/paris.cpp +27521 -20771
- sknetwork/hierarchy/paris.pyx +7 -7
- sknetwork/hierarchy/postprocess.py +16 -16
- sknetwork/hierarchy/tests/test_algos.py +5 -0
- sknetwork/linalg/__init__.py +1 -1
- sknetwork/linalg/diteration.cp310-win_amd64.pyd +0 -0
- sknetwork/linalg/diteration.cpp +13916 -8050
- sknetwork/linalg/{normalization.py → normalizer.py} +17 -14
- sknetwork/linalg/operators.py +1 -1
- sknetwork/linalg/ppr_solver.py +1 -1
- sknetwork/linalg/push.cp310-win_amd64.pyd +0 -0
- sknetwork/linalg/push.cpp +23187 -16973
- sknetwork/linalg/tests/test_normalization.py +3 -7
- sknetwork/linalg/tests/test_operators.py +2 -6
- sknetwork/linalg/tests/test_ppr.py +1 -1
- sknetwork/linkpred/base.py +12 -1
- sknetwork/linkpred/nn.py +6 -6
- sknetwork/path/distances.py +11 -4
- sknetwork/path/shortest_path.py +1 -1
- sknetwork/path/tests/test_distances.py +7 -0
- sknetwork/path/tests/test_search.py +2 -2
- sknetwork/ranking/base.py +11 -6
- sknetwork/ranking/betweenness.cp310-win_amd64.pyd +0 -0
- sknetwork/ranking/betweenness.cpp +5256 -2190
- sknetwork/ranking/pagerank.py +13 -12
- sknetwork/ranking/tests/test_API.py +0 -2
- sknetwork/ranking/tests/test_betweenness.py +1 -1
- sknetwork/ranking/tests/test_pagerank.py +11 -5
- sknetwork/regression/base.py +18 -1
- sknetwork/regression/diffusion.py +24 -10
- sknetwork/regression/tests/test_diffusion.py +8 -0
- sknetwork/topology/__init__.py +3 -1
- sknetwork/topology/cliques.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/cliques.cpp +23528 -16848
- sknetwork/topology/core.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/core.cpp +22849 -16581
- sknetwork/topology/cycles.py +243 -0
- sknetwork/topology/minheap.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/minheap.cpp +19495 -13469
- sknetwork/topology/structure.py +2 -42
- sknetwork/topology/tests/test_cycles.py +65 -0
- sknetwork/topology/tests/test_structure.py +2 -16
- sknetwork/topology/triangles.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/triangles.cpp +5283 -1397
- sknetwork/topology/triangles.pyx +7 -4
- sknetwork/topology/weisfeiler_lehman_core.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/weisfeiler_lehman_core.cpp +14781 -8915
- sknetwork/utils/format.py +1 -1
- sknetwork/utils/membership.py +2 -2
- sknetwork/visualization/__init__.py +2 -2
- sknetwork/visualization/dendrograms.py +55 -7
- sknetwork/visualization/graphs.py +261 -44
- sknetwork/visualization/tests/test_dendrograms.py +9 -9
- sknetwork/visualization/tests/test_graphs.py +63 -57
- sknetwork/embedding/louvain_hierarchy.py +0 -142
- sknetwork/embedding/tests/test_louvain_hierarchy.py +0 -19
- {scikit_network-0.31.0.dist-info → scikit_network-0.32.1.dist-info}/LICENSE +0 -0
- {scikit_network-0.31.0.dist-info → scikit_network-0.32.1.dist-info}/top_level.txt +0 -0
sknetwork/clustering/__init__.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""clustering module"""
|
|
2
2
|
from sknetwork.clustering.base import BaseClustering
|
|
3
3
|
from sknetwork.clustering.louvain import Louvain
|
|
4
|
+
from sknetwork.clustering.leiden import Leiden
|
|
5
|
+
from sknetwork.clustering.propagation_clustering import PropagationClustering
|
|
4
6
|
from sknetwork.clustering.metrics import get_modularity
|
|
5
7
|
from sknetwork.clustering.postprocess import reindex_labels, aggregate_graph
|
|
6
|
-
from sknetwork.clustering.
|
|
8
|
+
from sknetwork.clustering.kcenters import KCenters
|
sknetwork/clustering/base.py
CHANGED
|
@@ -9,7 +9,7 @@ from abc import ABC
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
from scipy import sparse
|
|
11
11
|
|
|
12
|
-
from sknetwork.linalg.
|
|
12
|
+
from sknetwork.linalg.normalizer import normalize
|
|
13
13
|
from sknetwork.base import Algorithm
|
|
14
14
|
from sknetwork.utils.membership import get_membership
|
|
15
15
|
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created in March 2024
|
|
3
|
+
@author: Laurène David <laurene.david@ip-paris.fr>
|
|
4
|
+
@author: Thomas Bonald <bonald@enst.fr>
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy import sparse
|
|
11
|
+
|
|
12
|
+
from sknetwork.clustering import BaseClustering
|
|
13
|
+
from sknetwork.ranking import PageRank
|
|
14
|
+
from sknetwork.clustering import get_modularity
|
|
15
|
+
from sknetwork.classification.pagerank import PageRankClassifier
|
|
16
|
+
from sknetwork.utils.format import get_adjacency, directed2undirected
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class KCenters(BaseClustering):
|
|
20
|
+
"""K-center clustering algorithm. The center of each cluster is obtained by the PageRank algorithm.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
n_clusters : int
|
|
25
|
+
Number of clusters.
|
|
26
|
+
directed : bool, default False
|
|
27
|
+
If ``True``, the graph is considered directed.
|
|
28
|
+
center_position : str, default "row"
|
|
29
|
+
Force centers to correspond to the nodes on the rows or columns of the biadjacency matrix.
|
|
30
|
+
Can be ``row``, ``col`` or ``both``. Only considered for bipartite graphs.
|
|
31
|
+
n_init : int, default 5
|
|
32
|
+
Number of reruns of the k-centers algorithm with different centers.
|
|
33
|
+
The run that produce the best modularity is chosen as the final result.
|
|
34
|
+
max_iter : int, default 20
|
|
35
|
+
Maximum number of iterations of the k-centers algorithm for a single run.
|
|
36
|
+
|
|
37
|
+
Attributes
|
|
38
|
+
----------
|
|
39
|
+
labels_ : np.ndarray, shape (n_nodes,)
|
|
40
|
+
Label of each node.
|
|
41
|
+
labels_row_, labels_col_ : np.ndarray
|
|
42
|
+
Labels of rows and columns, for bipartite graphs.
|
|
43
|
+
centers_ : np.ndarray, shape (n_nodes,)
|
|
44
|
+
Cluster centers.
|
|
45
|
+
centers_row_, centers_col_ : np.ndarray
|
|
46
|
+
Cluster centers of rows and columns, for bipartite graphs.
|
|
47
|
+
|
|
48
|
+
Example
|
|
49
|
+
-------
|
|
50
|
+
>>> from sknetwork.clustering import KCenters
|
|
51
|
+
>>> from sknetwork.data import karate_club
|
|
52
|
+
>>> kcenters = KCenters(n_clusters=2)
|
|
53
|
+
>>> adjacency = karate_club()
|
|
54
|
+
>>> labels = kcenters.fit_predict(adjacency)
|
|
55
|
+
>>> len(set(labels))
|
|
56
|
+
2
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
def __init__(self, n_clusters: int, directed: bool = False, center_position: str = "row", n_init: int = 5,
|
|
60
|
+
max_iter: int = 20):
|
|
61
|
+
super(BaseClustering, self).__init__()
|
|
62
|
+
self.n_clusters = n_clusters
|
|
63
|
+
self.directed = directed
|
|
64
|
+
self.bipartite = None
|
|
65
|
+
self.center_position = center_position
|
|
66
|
+
self.n_init = n_init
|
|
67
|
+
self.max_iter = max_iter
|
|
68
|
+
self.labels_ = None
|
|
69
|
+
self.centers_ = None
|
|
70
|
+
self.centers_row_ = None
|
|
71
|
+
self.centers_col_ = None
|
|
72
|
+
|
|
73
|
+
def _compute_mask_centers(self, input_matrix: Union[sparse.csr_matrix, np.ndarray]):
|
|
74
|
+
"""Generate mask to filter nodes that can be cluster centers.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
input_matrix :
|
|
79
|
+
Adjacency matrix or biadjacency matrix of the graph.
|
|
80
|
+
|
|
81
|
+
Return
|
|
82
|
+
------
|
|
83
|
+
mask : np.array, shape (n_nodes,)
|
|
84
|
+
Mask for possible cluster centers.
|
|
85
|
+
|
|
86
|
+
"""
|
|
87
|
+
n_row, n_col = input_matrix.shape
|
|
88
|
+
if self.bipartite:
|
|
89
|
+
n_nodes = n_row + n_col
|
|
90
|
+
mask = np.zeros(n_nodes, dtype=bool)
|
|
91
|
+
if self.center_position == "row":
|
|
92
|
+
mask[:n_row] = True
|
|
93
|
+
elif self.center_position == "col":
|
|
94
|
+
mask[n_row:] = True
|
|
95
|
+
elif self.center_position == "both":
|
|
96
|
+
mask[:] = True
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError('Unknown center position')
|
|
99
|
+
else:
|
|
100
|
+
mask = np.ones(n_row, dtype=bool)
|
|
101
|
+
|
|
102
|
+
return mask
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def _init_centers(adjacency: Union[sparse.csr_matrix, np.ndarray], mask: np.ndarray, n_clusters: int):
|
|
106
|
+
"""
|
|
107
|
+
Kcenters++ initialization to select cluster centers.
|
|
108
|
+
This algorithm is an adaptation of the Kmeans++ algorithm to graphs.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
adjacency :
|
|
113
|
+
Adjacency matrix of the graph.
|
|
114
|
+
mask :
|
|
115
|
+
Initial mask for allowed positions of centers.
|
|
116
|
+
n_clusters : int
|
|
117
|
+
Number of centers to initialize.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
---------
|
|
121
|
+
centers : np.array, shape (n_clusters,)
|
|
122
|
+
Initial cluster centers.
|
|
123
|
+
"""
|
|
124
|
+
mask = mask.copy()
|
|
125
|
+
n_nodes = adjacency.shape[0]
|
|
126
|
+
nodes = np.arange(n_nodes)
|
|
127
|
+
centers = []
|
|
128
|
+
|
|
129
|
+
# Choose the first center uniformly at random
|
|
130
|
+
center = np.random.choice(nodes[mask])
|
|
131
|
+
mask[center] = 0
|
|
132
|
+
centers.append(center)
|
|
133
|
+
|
|
134
|
+
pagerank = PageRank()
|
|
135
|
+
weights = {center: 1}
|
|
136
|
+
|
|
137
|
+
for k in range(n_clusters - 1):
|
|
138
|
+
# select nodes that are far from existing centers
|
|
139
|
+
ppr_scores = pagerank.fit_predict(adjacency, weights)
|
|
140
|
+
ppr_scores = ppr_scores[mask]
|
|
141
|
+
|
|
142
|
+
if min(ppr_scores) == 0:
|
|
143
|
+
center = np.random.choice(nodes[mask][ppr_scores == 0])
|
|
144
|
+
else:
|
|
145
|
+
probs = 1 / ppr_scores
|
|
146
|
+
probs = probs / np.sum(probs)
|
|
147
|
+
center = np.random.choice(nodes[mask], p=probs)
|
|
148
|
+
|
|
149
|
+
mask[center] = 0
|
|
150
|
+
centers.append(center)
|
|
151
|
+
weights.update({center: 1})
|
|
152
|
+
|
|
153
|
+
centers = np.array(centers)
|
|
154
|
+
return centers
|
|
155
|
+
|
|
156
|
+
def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], force_bipartite: bool = False) -> "KCenters":
|
|
157
|
+
"""Compute the clustering of the graph by k-centers.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
input_matrix :
|
|
162
|
+
Adjacency matrix or biadjacency matrix of the graph.
|
|
163
|
+
force_bipartite :
|
|
164
|
+
If ``True``, force the input matrix to be considered as a biadjacency matrix even if square.
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
self : :class:`KCenters`
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
if self.n_clusters < 2:
|
|
172
|
+
raise ValueError("The number of clusters must be at least 2.")
|
|
173
|
+
|
|
174
|
+
if self.n_init < 1:
|
|
175
|
+
raise ValueError("The n_init parameter must be at least 1.")
|
|
176
|
+
|
|
177
|
+
if self.directed:
|
|
178
|
+
input_matrix = directed2undirected(input_matrix)
|
|
179
|
+
|
|
180
|
+
adjacency, self.bipartite = get_adjacency(input_matrix, force_bipartite=force_bipartite)
|
|
181
|
+
n_row = input_matrix.shape[0]
|
|
182
|
+
n_nodes = adjacency.shape[0]
|
|
183
|
+
nodes = np.arange(n_nodes)
|
|
184
|
+
|
|
185
|
+
mask = self._compute_mask_centers(input_matrix)
|
|
186
|
+
if self.n_clusters > np.sum(mask):
|
|
187
|
+
raise ValueError("The number of clusters is to high. This might be due to the center_position parameter.")
|
|
188
|
+
|
|
189
|
+
pagerank_clf = PageRankClassifier()
|
|
190
|
+
pagerank = PageRank()
|
|
191
|
+
|
|
192
|
+
labels_ = []
|
|
193
|
+
centers_ = []
|
|
194
|
+
modularity_ = []
|
|
195
|
+
|
|
196
|
+
# Restarts
|
|
197
|
+
for i in range(self.n_init):
|
|
198
|
+
|
|
199
|
+
# Initialization
|
|
200
|
+
centers = self._init_centers(adjacency, mask, self.n_clusters)
|
|
201
|
+
prev_centers = None
|
|
202
|
+
labels = None
|
|
203
|
+
n_iter = 0
|
|
204
|
+
|
|
205
|
+
while not np.equal(prev_centers, centers).all() and (n_iter < self.max_iter):
|
|
206
|
+
|
|
207
|
+
# Assign nodes to centers
|
|
208
|
+
labels_center = {center: label for label, center in enumerate(centers)}
|
|
209
|
+
labels = pagerank_clf.fit_predict(adjacency, labels_center)
|
|
210
|
+
|
|
211
|
+
# Find new centers
|
|
212
|
+
prev_centers = centers.copy()
|
|
213
|
+
new_centers = []
|
|
214
|
+
|
|
215
|
+
for label in np.unique(labels):
|
|
216
|
+
mask_cluster = labels == label
|
|
217
|
+
mask_cluster &= mask
|
|
218
|
+
scores = pagerank.fit_predict(adjacency, weights=mask_cluster)
|
|
219
|
+
scores[~mask_cluster] = 0
|
|
220
|
+
new_centers.append(nodes[np.argmax(scores)])
|
|
221
|
+
|
|
222
|
+
n_iter += 1
|
|
223
|
+
|
|
224
|
+
# Store results
|
|
225
|
+
if self.bipartite:
|
|
226
|
+
labels_row = labels[:n_row]
|
|
227
|
+
labels_col = labels[n_row:]
|
|
228
|
+
modularity = get_modularity(input_matrix, labels_row, labels_col)
|
|
229
|
+
else:
|
|
230
|
+
modularity = get_modularity(adjacency, labels)
|
|
231
|
+
|
|
232
|
+
labels_.append(labels)
|
|
233
|
+
centers_.append(centers)
|
|
234
|
+
modularity_.append(modularity)
|
|
235
|
+
|
|
236
|
+
# Select restart with the highest modularity
|
|
237
|
+
idx_max = np.argmax(modularity_)
|
|
238
|
+
self.labels_ = np.array(labels_[idx_max])
|
|
239
|
+
self.centers_ = np.array(centers_[idx_max])
|
|
240
|
+
|
|
241
|
+
if self.bipartite:
|
|
242
|
+
self._split_vars(input_matrix.shape)
|
|
243
|
+
|
|
244
|
+
# Define centers based on center position
|
|
245
|
+
if self.center_position == "row":
|
|
246
|
+
self.centers_row_ = self.centers_
|
|
247
|
+
elif self.center_position == "col":
|
|
248
|
+
self.centers_col_ = self.centers_ - n_row
|
|
249
|
+
else:
|
|
250
|
+
self.centers_row_ = self.centers_[self.centers_ < n_row]
|
|
251
|
+
self.centers_col_ = self.centers_[~np.isin(self.centers_, self.centers_row_)] - n_row
|
|
252
|
+
|
|
253
|
+
return self
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created in March 2024
|
|
5
|
+
@author: Thomas Bonald <bonald@enst.fr>
|
|
6
|
+
@author: Ahmed Zaiou <ahmed.zaiou@capgemini.com>
|
|
7
|
+
"""
|
|
8
|
+
from typing import Union, Optional
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from scipy import sparse
|
|
12
|
+
|
|
13
|
+
from sknetwork.clustering import Louvain
|
|
14
|
+
from sknetwork.clustering.louvain_core import optimize_core
|
|
15
|
+
from sknetwork.clustering.leiden_core import optimize_refine_core
|
|
16
|
+
from sknetwork.utils.membership import get_membership
|
|
17
|
+
from sknetwork.utils.check import check_random_state
|
|
18
|
+
from sknetwork.log import Log
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Leiden(Louvain):
|
|
22
|
+
"""Leiden algorithm for clustering graphs by maximization of modularity.
|
|
23
|
+
Compared to the Louvain algorithm, the partition is refined before each aggregation.
|
|
24
|
+
|
|
25
|
+
For bipartite graphs, the algorithm maximizes Barber's modularity by default.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
resolution :
|
|
30
|
+
Resolution parameter.
|
|
31
|
+
modularity : str
|
|
32
|
+
Type of modularity to maximize. Can be ``'Dugue'``, ``'Newman'`` or ``'Potts'`` (default = ``'dugue'``).
|
|
33
|
+
tol_optimization :
|
|
34
|
+
Minimum increase in modularity to enter a new optimization pass in the local search.
|
|
35
|
+
tol_aggregation :
|
|
36
|
+
Minimum increase in modularity to enter a new aggregation pass.
|
|
37
|
+
n_aggregations :
|
|
38
|
+
Maximum number of aggregations.
|
|
39
|
+
A negative value is interpreted as no limit.
|
|
40
|
+
shuffle_nodes :
|
|
41
|
+
Enables node shuffling before optimization.
|
|
42
|
+
sort_clusters :
|
|
43
|
+
If ``True``, sort labels in decreasing order of cluster size.
|
|
44
|
+
return_probs :
|
|
45
|
+
If ``True``, return the probability distribution over clusters (soft clustering).
|
|
46
|
+
return_aggregate :
|
|
47
|
+
If ``True``, return the adjacency matrix of the graph between clusters.
|
|
48
|
+
random_state :
|
|
49
|
+
Random number generator or random seed. If None, numpy.random is used.
|
|
50
|
+
verbose :
|
|
51
|
+
Verbose mode.
|
|
52
|
+
|
|
53
|
+
Attributes
|
|
54
|
+
----------
|
|
55
|
+
labels_ : np.ndarray, shape (n_labels,)
|
|
56
|
+
Label of each node.
|
|
57
|
+
probs_ : sparse.csr_matrix, shape (n_row, n_labels)
|
|
58
|
+
Probability distribution over labels.
|
|
59
|
+
labels_row_, labels_col_ : np.ndarray
|
|
60
|
+
Labels of rows and columns, for bipartite graphs.
|
|
61
|
+
probs_row_, probs_col_ : sparse.csr_matrix, shape (n_row, n_labels)
|
|
62
|
+
Probability distributions over labels for rows and columns (for bipartite graphs).
|
|
63
|
+
aggregate_ : sparse.csr_matrix
|
|
64
|
+
Aggregate adjacency matrix or biadjacency matrix between clusters.
|
|
65
|
+
|
|
66
|
+
Example
|
|
67
|
+
-------
|
|
68
|
+
>>> from sknetwork.clustering import Leiden
|
|
69
|
+
>>> from sknetwork.data import karate_club
|
|
70
|
+
>>> leiden = Leiden()
|
|
71
|
+
>>> adjacency = karate_club()
|
|
72
|
+
>>> labels = leiden.fit_predict(adjacency)
|
|
73
|
+
>>> len(set(labels))
|
|
74
|
+
4
|
|
75
|
+
|
|
76
|
+
References
|
|
77
|
+
----------
|
|
78
|
+
* Traag, V. A., Waltman, L., & Van Eck, N. J. (2019).
|
|
79
|
+
`From Louvain to Leiden: guaranteeing well-connected communities`, Scientific reports.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, resolution: float = 1, modularity: str = 'dugue', tol_optimization: float = 1e-3,
|
|
83
|
+
tol_aggregation: float = 1e-3, n_aggregations: int = -1, shuffle_nodes: bool = False,
|
|
84
|
+
sort_clusters: bool = True, return_probs: bool = True, return_aggregate: bool = True,
|
|
85
|
+
random_state: Optional[Union[np.random.RandomState, int]] = None, verbose: bool = False):
|
|
86
|
+
super(Leiden, self).__init__(sort_clusters=sort_clusters, return_probs=return_probs,
|
|
87
|
+
return_aggregate=return_aggregate)
|
|
88
|
+
Log.__init__(self, verbose)
|
|
89
|
+
|
|
90
|
+
self.labels_ = None
|
|
91
|
+
self.resolution = resolution
|
|
92
|
+
self.modularity = modularity.lower()
|
|
93
|
+
self.tol_optimization = tol_optimization
|
|
94
|
+
self.tol_aggregation = tol_aggregation
|
|
95
|
+
self.n_aggregations = n_aggregations
|
|
96
|
+
self.shuffle_nodes = shuffle_nodes
|
|
97
|
+
self.random_state = check_random_state(random_state)
|
|
98
|
+
self.bipartite = None
|
|
99
|
+
|
|
100
|
+
def _optimize(self, labels, adjacency, out_weights, in_weights):
|
|
101
|
+
"""One optimization pass of the Leiden algorithm.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
labels :
|
|
106
|
+
Labels of nodes.
|
|
107
|
+
adjacency :
|
|
108
|
+
Adjacency matrix.
|
|
109
|
+
out_weights :
|
|
110
|
+
Out-weights of nodes.
|
|
111
|
+
in_weights :
|
|
112
|
+
In-weights of nodes
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
labels :
|
|
117
|
+
Labels of nodes after optimization.
|
|
118
|
+
increase :
|
|
119
|
+
Gain in modularity after optimization.
|
|
120
|
+
"""
|
|
121
|
+
indices = adjacency.indices
|
|
122
|
+
indptr = adjacency.indptr
|
|
123
|
+
data = adjacency.data.astype(np.float32)
|
|
124
|
+
out_weights = out_weights.astype(np.float32)
|
|
125
|
+
in_weights = in_weights.astype(np.float32)
|
|
126
|
+
membership = get_membership(labels)
|
|
127
|
+
out_cluster_weights = membership.T.dot(out_weights)
|
|
128
|
+
in_cluster_weights = membership.T.dot(in_weights)
|
|
129
|
+
cluster_weights = np.zeros_like(out_cluster_weights).astype(np.float32)
|
|
130
|
+
labels = labels.astype(np.int32)
|
|
131
|
+
self_loops = adjacency.diagonal().astype(np.float32)
|
|
132
|
+
return optimize_core(labels, indices, indptr, data, out_weights, in_weights, out_cluster_weights,
|
|
133
|
+
in_cluster_weights, cluster_weights, self_loops, self.resolution, self.tol_optimization)
|
|
134
|
+
|
|
135
|
+
def _optimize_refine(self, labels, labels_refined, adjacency, out_weights, in_weights):
|
|
136
|
+
"""Get the refined partition optimizing modularity.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
labels :
|
|
141
|
+
Labels of nodes.
|
|
142
|
+
labels_refined :
|
|
143
|
+
Refined labels of nodes.
|
|
144
|
+
adjacency :
|
|
145
|
+
Adjacency matrix.
|
|
146
|
+
out_weights :
|
|
147
|
+
Out-weights of nodes.
|
|
148
|
+
in_weights :
|
|
149
|
+
In-weights of nodes
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
labels_refined :
|
|
154
|
+
Refined labels of nodes.
|
|
155
|
+
"""
|
|
156
|
+
indices = adjacency.indices
|
|
157
|
+
indptr = adjacency.indptr
|
|
158
|
+
data = adjacency.data.astype(np.float32)
|
|
159
|
+
out_weights = out_weights.astype(np.float32)
|
|
160
|
+
in_weights = in_weights.astype(np.float32)
|
|
161
|
+
membership = get_membership(labels_refined)
|
|
162
|
+
out_cluster_weights = membership.T.dot(out_weights)
|
|
163
|
+
in_cluster_weights = membership.T.dot(in_weights)
|
|
164
|
+
cluster_weights = np.zeros_like(out_cluster_weights).astype(np.float32)
|
|
165
|
+
self_loops = adjacency.diagonal().astype(np.float32)
|
|
166
|
+
labels = labels.astype(np.int32)
|
|
167
|
+
labels_refined = labels_refined.astype(np.int32)
|
|
168
|
+
return optimize_refine_core(labels, labels_refined, indices, indptr, data, out_weights, in_weights,
|
|
169
|
+
out_cluster_weights, in_cluster_weights, cluster_weights, self_loops,
|
|
170
|
+
self.resolution)
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def _aggregate_refine(labels, labels_refined, adjacency, out_weights, in_weights):
|
|
174
|
+
"""Aggregate nodes according to refined labels.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
labels :
|
|
179
|
+
Labels of nodes.
|
|
180
|
+
labels_refined :
|
|
181
|
+
Refined labels of nodes.
|
|
182
|
+
adjacency :
|
|
183
|
+
Adjacency matrix.
|
|
184
|
+
out_weights :
|
|
185
|
+
Out-weights of nodes.
|
|
186
|
+
in_weights :
|
|
187
|
+
In-weights of nodes.
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
Aggregate graph (labels, adjacency matrix, out-weights, in-weights).
|
|
192
|
+
"""
|
|
193
|
+
membership = get_membership(labels)
|
|
194
|
+
membership_refined = get_membership(labels_refined)
|
|
195
|
+
adjacency_ = membership_refined.T.tocsr().dot(adjacency.dot(membership_refined))
|
|
196
|
+
out_weights_ = membership_refined.T.dot(out_weights)
|
|
197
|
+
in_weights_ = membership_refined.T.dot(in_weights)
|
|
198
|
+
labels_ = membership_refined.T.tocsr().dot(membership).indices
|
|
199
|
+
return labels_, adjacency_, out_weights_, in_weights_
|
|
200
|
+
|
|
201
|
+
def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], force_bipartite: bool = False) -> 'Leiden':
|
|
202
|
+
"""Fit algorithm to data.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
input_matrix :
|
|
207
|
+
Adjacency matrix or biadjacency matrix of the graph.
|
|
208
|
+
force_bipartite :
|
|
209
|
+
If ``True``, force the input matrix to be considered as a biadjacency matrix even if square.
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
self : :class:`Leiden`
|
|
214
|
+
"""
|
|
215
|
+
adjacency, out_weights, in_weights, membership, index = self._pre_processing(input_matrix, force_bipartite)
|
|
216
|
+
n = adjacency.shape[0]
|
|
217
|
+
labels = np.arange(n)
|
|
218
|
+
count = 0
|
|
219
|
+
stop = False
|
|
220
|
+
while not stop:
|
|
221
|
+
count += 1
|
|
222
|
+
labels, increase = self._optimize(labels, adjacency, out_weights, in_weights)
|
|
223
|
+
_, labels = np.unique(labels, return_inverse=True)
|
|
224
|
+
labels_original = labels.copy()
|
|
225
|
+
labels_refined = np.arange(len(labels))
|
|
226
|
+
labels_refined = self._optimize_refine(labels, labels_refined, adjacency, out_weights, in_weights)
|
|
227
|
+
_, labels_refined = np.unique(labels_refined, return_inverse=True)
|
|
228
|
+
labels, adjacency, out_weights, in_weights = self._aggregate_refine(labels, labels_refined, adjacency,
|
|
229
|
+
out_weights, in_weights)
|
|
230
|
+
n = adjacency.shape[0]
|
|
231
|
+
stop = n == 1
|
|
232
|
+
stop |= increase <= self.tol_aggregation
|
|
233
|
+
stop |= count == self.n_aggregations
|
|
234
|
+
if stop:
|
|
235
|
+
membership = membership.dot(get_membership(labels_original))
|
|
236
|
+
else:
|
|
237
|
+
membership = membership.dot(get_membership(labels_refined))
|
|
238
|
+
self.print_log("Aggregation:", count, " Clusters:", n, " Increase:", increase)
|
|
239
|
+
|
|
240
|
+
self._post_processing(input_matrix, membership, index)
|
|
241
|
+
return self
|
|
Binary file
|