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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
-
"""tests for visualization
|
|
3
|
+
"""tests for visualization of graphs"""
|
|
4
4
|
|
|
5
5
|
import tempfile
|
|
6
6
|
import unittest
|
|
@@ -10,7 +10,7 @@ from scipy import sparse
|
|
|
10
10
|
|
|
11
11
|
from sknetwork.data.test_graphs import test_disconnected_graph, test_bigraph_disconnect
|
|
12
12
|
from sknetwork.data.toy_graphs import karate_club, painters, movie_actor, bow_tie, star_wars
|
|
13
|
-
from sknetwork.visualization.graphs import svg_graph, svg_bigraph, svg_text, rescale
|
|
13
|
+
from sknetwork.visualization.graphs import visualize_graph, visualize_bigraph, svg_graph, svg_bigraph, svg_text, rescale
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
# noinspection DuplicatedCode
|
|
@@ -21,74 +21,77 @@ class TestVisualization(unittest.TestCase):
|
|
|
21
21
|
adjacency = graph.adjacency
|
|
22
22
|
position = graph.position
|
|
23
23
|
labels = graph.labels
|
|
24
|
+
image = visualize_graph(adjacency, position, labels=labels)
|
|
25
|
+
self.assertEqual(image[1:4], 'svg')
|
|
26
|
+
# alias
|
|
24
27
|
image = svg_graph(adjacency, position, labels=labels)
|
|
25
28
|
self.assertEqual(image[1:4], 'svg')
|
|
26
|
-
image =
|
|
29
|
+
image = visualize_graph(adjacency, position, labels=list(labels))
|
|
27
30
|
self.assertEqual(image[1:4], 'svg')
|
|
28
|
-
image =
|
|
31
|
+
image = visualize_graph(adjacency, position, display_edges=False)
|
|
29
32
|
self.assertEqual(image[1:4], 'svg')
|
|
30
|
-
image =
|
|
33
|
+
image = visualize_graph(adjacency, position, height=None)
|
|
31
34
|
self.assertEqual(image[1:4], 'svg')
|
|
32
|
-
image =
|
|
35
|
+
image = visualize_graph(adjacency, position, height=300, width=None)
|
|
33
36
|
self.assertEqual(image[1:4], 'svg')
|
|
34
|
-
image =
|
|
37
|
+
image = visualize_graph(adjacency, position, height=None, width=200)
|
|
35
38
|
self.assertEqual(image[1:4], 'svg')
|
|
36
39
|
n = adjacency.shape[0]
|
|
37
40
|
edge_labels = [(0, 1, 0), (1, 1, 1), (3, 10, 2)]
|
|
38
|
-
image =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
image = visualize_graph(adjacency, position=None, names=np.arange(n), labels=np.arange(n), scores=np.arange(n),
|
|
42
|
+
seeds=[0, 1], width=200, height=200, margin=10, margin_text=5, scale=3,
|
|
43
|
+
node_order=np.flip(np.arange(n)),
|
|
44
|
+
node_size=5, node_size_min=2, node_size_max=6, display_node_weight=True,
|
|
45
|
+
node_weights=np.arange(n),
|
|
46
|
+
node_width=2, node_width_max=5, node_color='red', edge_width=2, edge_width_min=2,
|
|
47
|
+
edge_width_max=4, edge_color='blue', edge_labels=edge_labels, display_edge_weight=True,
|
|
48
|
+
font_size=14)
|
|
46
49
|
self.assertEqual(image[1:4], 'svg')
|
|
47
|
-
image =
|
|
50
|
+
image = visualize_graph(adjacency, position=None, labels={0: 0})
|
|
48
51
|
self.assertEqual(image[1:4], 'svg')
|
|
49
|
-
image =
|
|
52
|
+
image = visualize_graph(adjacency, position=None, scores={0: 0})
|
|
50
53
|
self.assertEqual(image[1:4], 'svg')
|
|
51
|
-
image =
|
|
54
|
+
image = visualize_graph(adjacency=None, position=position)
|
|
52
55
|
self.assertEqual(image[1:4], 'svg')
|
|
53
|
-
image =
|
|
56
|
+
image = visualize_graph(adjacency=None, position=position, edge_labels=edge_labels)
|
|
54
57
|
self.assertEqual(image[1:4], 'svg')
|
|
55
|
-
image =
|
|
58
|
+
image = visualize_graph(adjacency, position, labels, label_colors={0: "red", 1: "blue"})
|
|
56
59
|
self.assertEqual(image[1:4], 'svg')
|
|
57
|
-
image =
|
|
60
|
+
image = visualize_graph(adjacency, position, labels, label_colors=["red", "blue"])
|
|
58
61
|
self.assertEqual(image[1:4], 'svg')
|
|
59
|
-
image =
|
|
62
|
+
image = visualize_graph(adjacency, position, labels, node_weights=np.arange(adjacency.shape[0]))
|
|
60
63
|
self.assertEqual(image[1:4], 'svg')
|
|
61
|
-
image =
|
|
64
|
+
image = visualize_graph(adjacency, position, scores=list(np.arange(n)))
|
|
62
65
|
self.assertEqual(image[1:4], 'svg')
|
|
63
|
-
image =
|
|
66
|
+
image = visualize_graph(adjacency, position, seeds={0: 1, 2: 1})
|
|
64
67
|
self.assertEqual(image[1:4], 'svg')
|
|
65
|
-
image =
|
|
68
|
+
image = visualize_graph(adjacency, position, labels=np.arange(n), name_position='left')
|
|
66
69
|
self.assertEqual(image[1:4], 'svg')
|
|
67
|
-
image =
|
|
70
|
+
image = visualize_graph(adjacency, position, scale=2, labels=np.arange(n), name_position='left')
|
|
68
71
|
self.assertEqual(image[1:4], 'svg')
|
|
69
72
|
with self.assertRaises(ValueError):
|
|
70
|
-
|
|
73
|
+
visualize_graph(adjacency, position, labels=[0, 1])
|
|
71
74
|
with self.assertRaises(ValueError):
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
visualize_graph(adjacency, position, scores=[0, 1])
|
|
76
|
+
visualize_graph(adjacency, position, scale=2, labels=np.arange(n), name_position='left')
|
|
74
77
|
|
|
75
78
|
def test_directed(self):
|
|
76
79
|
graph = painters(True)
|
|
77
80
|
adjacency = graph.adjacency
|
|
78
81
|
position = graph.position
|
|
79
82
|
names = graph.names
|
|
80
|
-
image =
|
|
83
|
+
image = visualize_graph(adjacency, position, names=names)
|
|
81
84
|
self.assertEqual(image[1:4], 'svg')
|
|
82
|
-
image =
|
|
85
|
+
image = visualize_graph(adjacency, position, display_edges=False)
|
|
83
86
|
self.assertEqual(image[1:4], 'svg')
|
|
84
87
|
n = adjacency.shape[0]
|
|
85
|
-
image =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
image = visualize_graph(adjacency, position=None, names=np.arange(n), labels=np.arange(n), scores=np.arange(n),
|
|
89
|
+
name_position='below', seeds=[0, 1], width=200, height=200, margin=10, margin_text=5,
|
|
90
|
+
scale=3, node_order=np.flip(np.arange(n)),
|
|
91
|
+
node_size=5, node_size_min=2, node_size_max=6, display_node_weight=True,
|
|
92
|
+
node_weights=np.arange(n),
|
|
93
|
+
node_width=2, node_width_max=5, node_color='red', edge_width=2, edge_width_min=2,
|
|
94
|
+
edge_width_max=4, edge_color='blue', display_edge_weight=True, font_size=14)
|
|
92
95
|
self.assertEqual(image[1:4], 'svg')
|
|
93
96
|
|
|
94
97
|
def test_bipartite(self):
|
|
@@ -96,54 +99,57 @@ class TestVisualization(unittest.TestCase):
|
|
|
96
99
|
biadjacency = graph.biadjacency
|
|
97
100
|
names_row = graph.names_row
|
|
98
101
|
names_col = graph.names_col
|
|
102
|
+
image = visualize_bigraph(biadjacency, names_row, names_col)
|
|
103
|
+
self.assertEqual(image[1:4], 'svg')
|
|
104
|
+
# alias
|
|
99
105
|
image = svg_bigraph(biadjacency, names_row, names_col)
|
|
100
106
|
self.assertEqual(image[1:4], 'svg')
|
|
101
|
-
image =
|
|
107
|
+
image = visualize_bigraph(biadjacency, display_edges=False)
|
|
102
108
|
self.assertEqual(image[1:4], 'svg')
|
|
103
|
-
image =
|
|
109
|
+
image = visualize_bigraph(biadjacency, reorder=False)
|
|
104
110
|
self.assertEqual(image[1:4], 'svg')
|
|
105
111
|
n_row, n_col = biadjacency.shape
|
|
106
112
|
position_row = np.random.random((n_row, 2))
|
|
107
113
|
position_col = np.random.random((n_col, 2))
|
|
108
114
|
edge_labels = [(0, 1, 0), (1, 1, 1), (3, 10, 2)]
|
|
109
|
-
image =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
image = visualize_bigraph(biadjacency=biadjacency, names_row=np.arange(n_row), names_col=np.arange(n_col),
|
|
116
|
+
labels_row=np.arange(n_row), labels_col=np.arange(n_col), scores_row=np.arange(n_row),
|
|
117
|
+
scores_col=np.arange(n_col), seeds_row=[0, 1], seeds_col=[1, 2],
|
|
118
|
+
position_row=position_row, position_col=position_col, color_row='red', color_col='white',
|
|
119
|
+
width=200, height=200, margin=10, margin_text=5, scale=3, node_size=5,
|
|
120
|
+
node_size_min=1, node_size_max=30, node_weights_row=np.arange(n_row),
|
|
121
|
+
node_weights_col=np.arange(n_col), display_node_weight=True, node_width=2, node_width_max=5,
|
|
122
|
+
edge_labels=edge_labels, edge_width=2, edge_width_min=0.3, edge_width_max=4,
|
|
123
|
+
edge_color='red', display_edge_weight=True, font_size=14)
|
|
118
124
|
self.assertEqual(image[1:4], 'svg')
|
|
119
125
|
|
|
120
126
|
def test_disconnect(self):
|
|
121
127
|
adjacency = test_disconnected_graph()
|
|
122
128
|
position = np.random.random((adjacency.shape[0], 2))
|
|
123
|
-
image =
|
|
129
|
+
image = visualize_graph(adjacency, position)
|
|
124
130
|
self.assertEqual(image[1:4], 'svg')
|
|
125
131
|
biadjacency = test_bigraph_disconnect()
|
|
126
|
-
image =
|
|
132
|
+
image = visualize_bigraph(biadjacency)
|
|
127
133
|
self.assertEqual(image[1:4], 'svg')
|
|
128
134
|
|
|
129
135
|
def test_probs(self):
|
|
130
136
|
adjacency = bow_tie()
|
|
131
137
|
probs = np.array([[.5, .5], [0, 0], [1, 0], [0, 1], [0, 1]])
|
|
132
|
-
image =
|
|
138
|
+
image = visualize_graph(adjacency, probs=probs)
|
|
133
139
|
self.assertEqual(image[1:4], 'svg')
|
|
134
140
|
probs = sparse.csr_matrix(probs)
|
|
135
|
-
image =
|
|
141
|
+
image = visualize_graph(adjacency, probs=probs)
|
|
136
142
|
self.assertEqual(image[1:4], 'svg')
|
|
137
143
|
biadjacency = star_wars()
|
|
138
144
|
probs_row = sparse.csr_matrix([[.5, .5], [0, 0], [1, 0], [0, 1]])
|
|
139
145
|
probs_col = sparse.csr_matrix([[.5, .5], [0, 0], [1, 0]])
|
|
140
|
-
image =
|
|
146
|
+
image = visualize_bigraph(biadjacency, probs_row=probs_row, probs_col=probs_col)
|
|
141
147
|
self.assertEqual(image[1:4], 'svg')
|
|
142
148
|
|
|
143
149
|
def test_labels(self):
|
|
144
150
|
adjacency = bow_tie()
|
|
145
151
|
names = ["aa", "bb", "<>", "a&b", ""]
|
|
146
|
-
image =
|
|
152
|
+
image = visualize_graph(adjacency, names=names)
|
|
147
153
|
self.assertEqual(image[1:4], 'svg')
|
|
148
154
|
|
|
149
155
|
def test_text(self):
|
|
@@ -160,11 +166,11 @@ class TestVisualization(unittest.TestCase):
|
|
|
160
166
|
graph = karate_club(True)
|
|
161
167
|
adjacency = graph.adjacency
|
|
162
168
|
position = graph.position
|
|
163
|
-
_ =
|
|
169
|
+
_ = visualize_graph(adjacency, position, filename=filename)
|
|
164
170
|
with open(filename + '.svg', 'r') as f:
|
|
165
171
|
row = f.readline()
|
|
166
172
|
self.assertEqual(row[1:4], 'svg')
|
|
167
|
-
_ =
|
|
173
|
+
_ = visualize_bigraph(adjacency, position, filename=filename)
|
|
168
174
|
with open(filename + '.svg', 'r') as f:
|
|
169
175
|
row = f.readline()
|
|
170
176
|
self.assertEqual(row[1:4], 'svg')
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# coding: utf-8
|
|
3
|
-
"""
|
|
4
|
-
Created on Dec 2020
|
|
5
|
-
@author: Quentin Lutz <qlutz@enst.fr>
|
|
6
|
-
"""
|
|
7
|
-
from typing import Optional, Union
|
|
8
|
-
|
|
9
|
-
import numpy as np
|
|
10
|
-
from scipy import sparse
|
|
11
|
-
|
|
12
|
-
from sknetwork.utils.check import check_format, check_random_state
|
|
13
|
-
from sknetwork.utils.format import get_adjacency
|
|
14
|
-
from sknetwork.clustering.louvain import Louvain
|
|
15
|
-
from sknetwork.embedding.base import BaseEmbedding
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class LouvainNE(BaseEmbedding):
|
|
19
|
-
"""Embedding of graphs based on the hierarchical Louvain algorithm with random scattering per level.
|
|
20
|
-
|
|
21
|
-
Parameters
|
|
22
|
-
----------
|
|
23
|
-
n_components : int
|
|
24
|
-
Dimension of the embedding.
|
|
25
|
-
scale : float
|
|
26
|
-
Dilution factor to be applied on the random vector to be added at each iteration of the clustering method.
|
|
27
|
-
resolution :
|
|
28
|
-
Resolution parameter.
|
|
29
|
-
tol_optimization :
|
|
30
|
-
Minimum increase in the objective function to enter a new optimization pass.
|
|
31
|
-
tol_aggregation :
|
|
32
|
-
Minimum increase in the objective function to enter a new aggregation pass.
|
|
33
|
-
n_aggregations :
|
|
34
|
-
Maximum number of aggregations.
|
|
35
|
-
A negative value is interpreted as no limit.
|
|
36
|
-
shuffle_nodes :
|
|
37
|
-
Enables node shuffling before optimization.
|
|
38
|
-
random_state :
|
|
39
|
-
Random number generator or random seed. If None, numpy.random is used.
|
|
40
|
-
|
|
41
|
-
Attributes
|
|
42
|
-
----------
|
|
43
|
-
embedding_ : array, shape = (n, n_components)
|
|
44
|
-
Embedding of the nodes.
|
|
45
|
-
embedding_row_ : array, shape = (n_row, n_components)
|
|
46
|
-
Embedding of the rows, for bipartite graphs.
|
|
47
|
-
embedding_col_ : array, shape = (n_col, n_components)
|
|
48
|
-
Embedding of the columns, for bipartite graphs.
|
|
49
|
-
Example
|
|
50
|
-
-------
|
|
51
|
-
>>> from sknetwork.embedding import LouvainNE
|
|
52
|
-
>>> from sknetwork.data import karate_club
|
|
53
|
-
>>> louvain = LouvainNE(n_components=3)
|
|
54
|
-
>>> adjacency = karate_club()
|
|
55
|
-
>>> embedding = louvain.fit_transform(adjacency)
|
|
56
|
-
>>> embedding.shape
|
|
57
|
-
(34, 3)
|
|
58
|
-
|
|
59
|
-
References
|
|
60
|
-
----------
|
|
61
|
-
Bhowmick, A. K., Meneni, K., Danisch, M., Guillaume, J. L., & Mitra, B. (2020, January).
|
|
62
|
-
`LouvainNE: Hierarchical Louvain Method for High Quality and Scalable Network Embedding.
|
|
63
|
-
<https://hal.archives-ouvertes.fr/hal-02999888/document>`_
|
|
64
|
-
In Proceedings of the 13th International Conference on Web Search and Data Mining (pp. 43-51).
|
|
65
|
-
"""
|
|
66
|
-
def __init__(self, n_components: int = 2, scale: float = .1, resolution: float = 1, tol_optimization: float = 1e-3,
|
|
67
|
-
tol_aggregation: float = 1e-3, n_aggregations: int = -1, shuffle_nodes: bool = False,
|
|
68
|
-
random_state: Optional[Union[np.random.RandomState, int]] = None, verbose: bool = False):
|
|
69
|
-
super(LouvainNE, self).__init__()
|
|
70
|
-
|
|
71
|
-
self.n_components = n_components
|
|
72
|
-
self.scale = scale
|
|
73
|
-
self._clustering_method = Louvain(resolution=resolution, tol_optimization=tol_optimization,
|
|
74
|
-
tol_aggregation=tol_aggregation, n_aggregations=n_aggregations,
|
|
75
|
-
shuffle_nodes=shuffle_nodes, random_state=random_state, verbose=verbose)
|
|
76
|
-
self.random_state = check_random_state(random_state)
|
|
77
|
-
self.bipartite = None
|
|
78
|
-
|
|
79
|
-
def _recursive_louvain(self, adjacency: Union[sparse.csr_matrix, np.ndarray], depth: int,
|
|
80
|
-
nodes: Optional[np.ndarray] = None):
|
|
81
|
-
"""Recursive function for fit, modifies the embedding in place.
|
|
82
|
-
|
|
83
|
-
Parameters
|
|
84
|
-
----------
|
|
85
|
-
adjacency :
|
|
86
|
-
Adjacency matrix of the graph.
|
|
87
|
-
depth :
|
|
88
|
-
Depth of the recursion.
|
|
89
|
-
nodes :
|
|
90
|
-
The indices of the current nodes in the original graph.
|
|
91
|
-
"""
|
|
92
|
-
n = adjacency.shape[0]
|
|
93
|
-
if nodes is None:
|
|
94
|
-
nodes = np.arange(n)
|
|
95
|
-
|
|
96
|
-
if adjacency.nnz:
|
|
97
|
-
labels = self._clustering_method.fit_transform(adjacency)
|
|
98
|
-
else:
|
|
99
|
-
labels = np.zeros(n)
|
|
100
|
-
|
|
101
|
-
clusters = np.unique(labels)
|
|
102
|
-
|
|
103
|
-
if len(clusters) != 1:
|
|
104
|
-
random_vectors = (self.scale ** depth) * self.random_state.rand(self.n_components, len(clusters))
|
|
105
|
-
for index, cluster in enumerate(clusters):
|
|
106
|
-
mask = (labels == cluster)
|
|
107
|
-
nodes_cluster = nodes[mask]
|
|
108
|
-
self.embedding_[nodes_cluster, :] += random_vectors[:, index]
|
|
109
|
-
n_row = len(mask)
|
|
110
|
-
indptr = np.zeros(n_row + 1, dtype=int)
|
|
111
|
-
indptr[1:] = np.cumsum(mask)
|
|
112
|
-
n_col = indptr[-1]
|
|
113
|
-
combiner = sparse.csr_matrix((np.ones(n_col), np.arange(n_col, dtype=int), indptr),
|
|
114
|
-
shape=(n_row, n_col))
|
|
115
|
-
adjacency_cluster = adjacency[mask, :].dot(combiner)
|
|
116
|
-
self._recursive_louvain(adjacency_cluster, depth + 1, nodes_cluster)
|
|
117
|
-
|
|
118
|
-
def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], force_bipartite: bool = False):
|
|
119
|
-
"""Embedding of graphs from a clustering obtained with Louvain.
|
|
120
|
-
|
|
121
|
-
Parameters
|
|
122
|
-
----------
|
|
123
|
-
input_matrix :
|
|
124
|
-
Adjacency matrix or biadjacency matrix of the graph.
|
|
125
|
-
force_bipartite :
|
|
126
|
-
If ``True``, force the input matrix to be considered as a biadjacency matrix even if square.
|
|
127
|
-
Returns
|
|
128
|
-
-------
|
|
129
|
-
self: :class:`LouvainNE`
|
|
130
|
-
"""
|
|
131
|
-
# input
|
|
132
|
-
input_matrix = check_format(input_matrix)
|
|
133
|
-
adjacency, self.bipartite = get_adjacency(input_matrix, force_bipartite=force_bipartite)
|
|
134
|
-
n = adjacency.shape[0]
|
|
135
|
-
|
|
136
|
-
# embedding
|
|
137
|
-
self.embedding_ = np.zeros((n, self.n_components))
|
|
138
|
-
self._recursive_louvain(adjacency, 0)
|
|
139
|
-
|
|
140
|
-
if self.bipartite:
|
|
141
|
-
self._split_vars(input_matrix.shape)
|
|
142
|
-
return self
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""tests for LouvainNE"""
|
|
4
|
-
import unittest
|
|
5
|
-
|
|
6
|
-
from sknetwork.data.test_graphs import test_graph, test_disconnected_graph, test_digraph, test_bigraph
|
|
7
|
-
from sknetwork.embedding import LouvainNE
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TestLouvainNE(unittest.TestCase):
|
|
11
|
-
|
|
12
|
-
def test_louvain_hierarchy(self):
|
|
13
|
-
louvain = LouvainNE()
|
|
14
|
-
for adjacency in [test_graph(), test_disconnected_graph(), test_digraph()]:
|
|
15
|
-
self.assertTupleEqual(louvain.fit_transform(adjacency).shape, (10, 2))
|
|
16
|
-
louvain.fit(test_bigraph())
|
|
17
|
-
self.assertTupleEqual(louvain.embedding_.shape, (6, 2))
|
|
18
|
-
louvain.fit(test_graph(), force_bipartite=True)
|
|
19
|
-
self.assertTupleEqual(louvain.embedding_.shape, (10, 2))
|
|
File without changes
|
|
File without changes
|