scikit-network 0.33.3__cp313-cp313-macosx_10_13_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.

Potentially problematic release.


This version of scikit-network might be problematic. Click here for more details.

Files changed (228) hide show
  1. scikit_network-0.33.3.dist-info/METADATA +122 -0
  2. scikit_network-0.33.3.dist-info/RECORD +228 -0
  3. scikit_network-0.33.3.dist-info/WHEEL +6 -0
  4. scikit_network-0.33.3.dist-info/licenses/AUTHORS.rst +43 -0
  5. scikit_network-0.33.3.dist-info/licenses/LICENSE +34 -0
  6. scikit_network-0.33.3.dist-info/top_level.txt +1 -0
  7. sknetwork/__init__.py +21 -0
  8. sknetwork/base.py +67 -0
  9. sknetwork/classification/__init__.py +8 -0
  10. sknetwork/classification/base.py +142 -0
  11. sknetwork/classification/base_rank.py +133 -0
  12. sknetwork/classification/diffusion.py +134 -0
  13. sknetwork/classification/knn.py +139 -0
  14. sknetwork/classification/metrics.py +205 -0
  15. sknetwork/classification/pagerank.py +66 -0
  16. sknetwork/classification/propagation.py +152 -0
  17. sknetwork/classification/tests/__init__.py +1 -0
  18. sknetwork/classification/tests/test_API.py +30 -0
  19. sknetwork/classification/tests/test_diffusion.py +77 -0
  20. sknetwork/classification/tests/test_knn.py +23 -0
  21. sknetwork/classification/tests/test_metrics.py +53 -0
  22. sknetwork/classification/tests/test_pagerank.py +20 -0
  23. sknetwork/classification/tests/test_propagation.py +24 -0
  24. sknetwork/classification/vote.cpp +27581 -0
  25. sknetwork/classification/vote.cpython-313-darwin.so +0 -0
  26. sknetwork/classification/vote.pyx +56 -0
  27. sknetwork/clustering/__init__.py +8 -0
  28. sknetwork/clustering/base.py +172 -0
  29. sknetwork/clustering/kcenters.py +253 -0
  30. sknetwork/clustering/leiden.py +242 -0
  31. sknetwork/clustering/leiden_core.cpp +31572 -0
  32. sknetwork/clustering/leiden_core.cpython-313-darwin.so +0 -0
  33. sknetwork/clustering/leiden_core.pyx +124 -0
  34. sknetwork/clustering/louvain.py +286 -0
  35. sknetwork/clustering/louvain_core.cpp +31217 -0
  36. sknetwork/clustering/louvain_core.cpython-313-darwin.so +0 -0
  37. sknetwork/clustering/louvain_core.pyx +124 -0
  38. sknetwork/clustering/metrics.py +91 -0
  39. sknetwork/clustering/postprocess.py +66 -0
  40. sknetwork/clustering/propagation_clustering.py +104 -0
  41. sknetwork/clustering/tests/__init__.py +1 -0
  42. sknetwork/clustering/tests/test_API.py +38 -0
  43. sknetwork/clustering/tests/test_kcenters.py +60 -0
  44. sknetwork/clustering/tests/test_leiden.py +34 -0
  45. sknetwork/clustering/tests/test_louvain.py +135 -0
  46. sknetwork/clustering/tests/test_metrics.py +50 -0
  47. sknetwork/clustering/tests/test_postprocess.py +39 -0
  48. sknetwork/data/__init__.py +6 -0
  49. sknetwork/data/base.py +33 -0
  50. sknetwork/data/load.py +406 -0
  51. sknetwork/data/models.py +459 -0
  52. sknetwork/data/parse.py +644 -0
  53. sknetwork/data/test_graphs.py +84 -0
  54. sknetwork/data/tests/__init__.py +1 -0
  55. sknetwork/data/tests/test_API.py +30 -0
  56. sknetwork/data/tests/test_base.py +14 -0
  57. sknetwork/data/tests/test_load.py +95 -0
  58. sknetwork/data/tests/test_models.py +52 -0
  59. sknetwork/data/tests/test_parse.py +250 -0
  60. sknetwork/data/tests/test_test_graphs.py +29 -0
  61. sknetwork/data/tests/test_toy_graphs.py +68 -0
  62. sknetwork/data/timeout.py +38 -0
  63. sknetwork/data/toy_graphs.py +611 -0
  64. sknetwork/embedding/__init__.py +8 -0
  65. sknetwork/embedding/base.py +94 -0
  66. sknetwork/embedding/force_atlas.py +198 -0
  67. sknetwork/embedding/louvain_embedding.py +148 -0
  68. sknetwork/embedding/random_projection.py +135 -0
  69. sknetwork/embedding/spectral.py +141 -0
  70. sknetwork/embedding/spring.py +198 -0
  71. sknetwork/embedding/svd.py +359 -0
  72. sknetwork/embedding/tests/__init__.py +1 -0
  73. sknetwork/embedding/tests/test_API.py +49 -0
  74. sknetwork/embedding/tests/test_force_atlas.py +35 -0
  75. sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
  76. sknetwork/embedding/tests/test_random_projection.py +28 -0
  77. sknetwork/embedding/tests/test_spectral.py +81 -0
  78. sknetwork/embedding/tests/test_spring.py +50 -0
  79. sknetwork/embedding/tests/test_svd.py +43 -0
  80. sknetwork/gnn/__init__.py +10 -0
  81. sknetwork/gnn/activation.py +117 -0
  82. sknetwork/gnn/base.py +181 -0
  83. sknetwork/gnn/base_activation.py +90 -0
  84. sknetwork/gnn/base_layer.py +109 -0
  85. sknetwork/gnn/gnn_classifier.py +305 -0
  86. sknetwork/gnn/layer.py +153 -0
  87. sknetwork/gnn/loss.py +180 -0
  88. sknetwork/gnn/neighbor_sampler.py +65 -0
  89. sknetwork/gnn/optimizer.py +164 -0
  90. sknetwork/gnn/tests/__init__.py +1 -0
  91. sknetwork/gnn/tests/test_activation.py +56 -0
  92. sknetwork/gnn/tests/test_base.py +75 -0
  93. sknetwork/gnn/tests/test_base_layer.py +37 -0
  94. sknetwork/gnn/tests/test_gnn_classifier.py +130 -0
  95. sknetwork/gnn/tests/test_layers.py +80 -0
  96. sknetwork/gnn/tests/test_loss.py +33 -0
  97. sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
  98. sknetwork/gnn/tests/test_optimizer.py +43 -0
  99. sknetwork/gnn/tests/test_utils.py +41 -0
  100. sknetwork/gnn/utils.py +127 -0
  101. sknetwork/hierarchy/__init__.py +6 -0
  102. sknetwork/hierarchy/base.py +96 -0
  103. sknetwork/hierarchy/louvain_hierarchy.py +272 -0
  104. sknetwork/hierarchy/metrics.py +234 -0
  105. sknetwork/hierarchy/paris.cpp +37865 -0
  106. sknetwork/hierarchy/paris.cpython-313-darwin.so +0 -0
  107. sknetwork/hierarchy/paris.pyx +316 -0
  108. sknetwork/hierarchy/postprocess.py +350 -0
  109. sknetwork/hierarchy/tests/__init__.py +1 -0
  110. sknetwork/hierarchy/tests/test_API.py +24 -0
  111. sknetwork/hierarchy/tests/test_algos.py +34 -0
  112. sknetwork/hierarchy/tests/test_metrics.py +62 -0
  113. sknetwork/hierarchy/tests/test_postprocess.py +57 -0
  114. sknetwork/linalg/__init__.py +9 -0
  115. sknetwork/linalg/basics.py +37 -0
  116. sknetwork/linalg/diteration.cpp +27397 -0
  117. sknetwork/linalg/diteration.cpython-313-darwin.so +0 -0
  118. sknetwork/linalg/diteration.pyx +47 -0
  119. sknetwork/linalg/eig_solver.py +93 -0
  120. sknetwork/linalg/laplacian.py +15 -0
  121. sknetwork/linalg/normalizer.py +86 -0
  122. sknetwork/linalg/operators.py +225 -0
  123. sknetwork/linalg/polynome.py +76 -0
  124. sknetwork/linalg/ppr_solver.py +170 -0
  125. sknetwork/linalg/push.cpp +31069 -0
  126. sknetwork/linalg/push.cpython-313-darwin.so +0 -0
  127. sknetwork/linalg/push.pyx +71 -0
  128. sknetwork/linalg/sparse_lowrank.py +142 -0
  129. sknetwork/linalg/svd_solver.py +91 -0
  130. sknetwork/linalg/tests/__init__.py +1 -0
  131. sknetwork/linalg/tests/test_eig.py +44 -0
  132. sknetwork/linalg/tests/test_laplacian.py +18 -0
  133. sknetwork/linalg/tests/test_normalization.py +34 -0
  134. sknetwork/linalg/tests/test_operators.py +66 -0
  135. sknetwork/linalg/tests/test_polynome.py +38 -0
  136. sknetwork/linalg/tests/test_ppr.py +50 -0
  137. sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
  138. sknetwork/linalg/tests/test_svd.py +38 -0
  139. sknetwork/linkpred/__init__.py +2 -0
  140. sknetwork/linkpred/base.py +46 -0
  141. sknetwork/linkpred/nn.py +126 -0
  142. sknetwork/linkpred/tests/__init__.py +1 -0
  143. sknetwork/linkpred/tests/test_nn.py +27 -0
  144. sknetwork/log.py +19 -0
  145. sknetwork/path/__init__.py +5 -0
  146. sknetwork/path/dag.py +54 -0
  147. sknetwork/path/distances.py +98 -0
  148. sknetwork/path/search.py +31 -0
  149. sknetwork/path/shortest_path.py +61 -0
  150. sknetwork/path/tests/__init__.py +1 -0
  151. sknetwork/path/tests/test_dag.py +37 -0
  152. sknetwork/path/tests/test_distances.py +62 -0
  153. sknetwork/path/tests/test_search.py +40 -0
  154. sknetwork/path/tests/test_shortest_path.py +40 -0
  155. sknetwork/ranking/__init__.py +8 -0
  156. sknetwork/ranking/base.py +61 -0
  157. sknetwork/ranking/betweenness.cpp +9704 -0
  158. sknetwork/ranking/betweenness.cpython-313-darwin.so +0 -0
  159. sknetwork/ranking/betweenness.pyx +97 -0
  160. sknetwork/ranking/closeness.py +92 -0
  161. sknetwork/ranking/hits.py +94 -0
  162. sknetwork/ranking/katz.py +83 -0
  163. sknetwork/ranking/pagerank.py +110 -0
  164. sknetwork/ranking/postprocess.py +37 -0
  165. sknetwork/ranking/tests/__init__.py +1 -0
  166. sknetwork/ranking/tests/test_API.py +32 -0
  167. sknetwork/ranking/tests/test_betweenness.py +38 -0
  168. sknetwork/ranking/tests/test_closeness.py +30 -0
  169. sknetwork/ranking/tests/test_hits.py +20 -0
  170. sknetwork/ranking/tests/test_pagerank.py +62 -0
  171. sknetwork/ranking/tests/test_postprocess.py +26 -0
  172. sknetwork/regression/__init__.py +4 -0
  173. sknetwork/regression/base.py +61 -0
  174. sknetwork/regression/diffusion.py +210 -0
  175. sknetwork/regression/tests/__init__.py +1 -0
  176. sknetwork/regression/tests/test_API.py +32 -0
  177. sknetwork/regression/tests/test_diffusion.py +56 -0
  178. sknetwork/sknetwork.py +3 -0
  179. sknetwork/test_base.py +35 -0
  180. sknetwork/test_log.py +15 -0
  181. sknetwork/topology/__init__.py +8 -0
  182. sknetwork/topology/cliques.cpp +32562 -0
  183. sknetwork/topology/cliques.cpython-313-darwin.so +0 -0
  184. sknetwork/topology/cliques.pyx +149 -0
  185. sknetwork/topology/core.cpp +30648 -0
  186. sknetwork/topology/core.cpython-313-darwin.so +0 -0
  187. sknetwork/topology/core.pyx +90 -0
  188. sknetwork/topology/cycles.py +243 -0
  189. sknetwork/topology/minheap.cpp +27329 -0
  190. sknetwork/topology/minheap.cpython-313-darwin.so +0 -0
  191. sknetwork/topology/minheap.pxd +20 -0
  192. sknetwork/topology/minheap.pyx +109 -0
  193. sknetwork/topology/structure.py +194 -0
  194. sknetwork/topology/tests/__init__.py +1 -0
  195. sknetwork/topology/tests/test_cliques.py +28 -0
  196. sknetwork/topology/tests/test_core.py +19 -0
  197. sknetwork/topology/tests/test_cycles.py +65 -0
  198. sknetwork/topology/tests/test_structure.py +85 -0
  199. sknetwork/topology/tests/test_triangles.py +38 -0
  200. sknetwork/topology/tests/test_wl.py +72 -0
  201. sknetwork/topology/triangles.cpp +8891 -0
  202. sknetwork/topology/triangles.cpython-313-darwin.so +0 -0
  203. sknetwork/topology/triangles.pyx +151 -0
  204. sknetwork/topology/weisfeiler_lehman.py +133 -0
  205. sknetwork/topology/weisfeiler_lehman_core.cpp +27632 -0
  206. sknetwork/topology/weisfeiler_lehman_core.cpython-313-darwin.so +0 -0
  207. sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
  208. sknetwork/utils/__init__.py +7 -0
  209. sknetwork/utils/check.py +355 -0
  210. sknetwork/utils/format.py +221 -0
  211. sknetwork/utils/membership.py +82 -0
  212. sknetwork/utils/neighbors.py +115 -0
  213. sknetwork/utils/tests/__init__.py +1 -0
  214. sknetwork/utils/tests/test_check.py +190 -0
  215. sknetwork/utils/tests/test_format.py +63 -0
  216. sknetwork/utils/tests/test_membership.py +24 -0
  217. sknetwork/utils/tests/test_neighbors.py +41 -0
  218. sknetwork/utils/tests/test_tfidf.py +18 -0
  219. sknetwork/utils/tests/test_values.py +66 -0
  220. sknetwork/utils/tfidf.py +37 -0
  221. sknetwork/utils/values.py +76 -0
  222. sknetwork/visualization/__init__.py +4 -0
  223. sknetwork/visualization/colors.py +34 -0
  224. sknetwork/visualization/dendrograms.py +277 -0
  225. sknetwork/visualization/graphs.py +1039 -0
  226. sknetwork/visualization/tests/__init__.py +1 -0
  227. sknetwork/visualization/tests/test_dendrograms.py +53 -0
  228. sknetwork/visualization/tests/test_graphs.py +176 -0
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created in April 2019
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ """
7
+ import warnings
8
+ from typing import Optional, Union
9
+
10
+ import numpy as np
11
+
12
+
13
+ def get_values(shape: tuple, values: Union[np.ndarray, list, dict], default_value: float = -1) -> np.ndarray:
14
+ """Get values as array."""
15
+ n = shape[0]
16
+ if isinstance(values, list):
17
+ values = np.array(values)
18
+ if isinstance(values, np.ndarray):
19
+ if len(values) != n:
20
+ raise ValueError('Dimensions mismatch between adjacency and values.')
21
+ else:
22
+ values = values.astype(float)
23
+ elif isinstance(values, dict):
24
+ keys, values_ = np.array(list(values.keys())), np.array(list(values.values()))
25
+ if np.min(values_) < 0:
26
+ warnings.warn(Warning("Negative values will not be taken into account."))
27
+ values = default_value * np.ones(n)
28
+ values[keys] = values_
29
+ else:
30
+ values = np.ones(n)
31
+ return values
32
+
33
+
34
+ def stack_values(shape: tuple, values_row: Optional[Union[np.ndarray, list, dict]],
35
+ values_col: Optional[Union[np.ndarray, list, dict]] = None, default_value: float = -1) -> np.ndarray:
36
+ """Process values for rows and columns and stack the results into a single vector."""
37
+ n_row, n_col = shape
38
+ if values_row is None and values_col is None:
39
+ values_row = np.ones(n_row)
40
+ values_col = default_value * np.ones(n_col)
41
+ elif values_row is None:
42
+ values_row = default_value * np.ones(n_row)
43
+ elif values_col is None:
44
+ values_col = default_value * np.ones(n_col)
45
+ values_row = get_values(shape, values_row, default_value)
46
+ values_col = get_values((n_col,), values_col, default_value)
47
+ return np.hstack((values_row, values_col))
48
+
49
+
50
+ def values2prob(n: int, values: np.ndarray = None) -> np.ndarray:
51
+ """Transform seed values into probability vector.
52
+
53
+ Parameters
54
+ ----------
55
+ n : int
56
+ Number of nodes.
57
+ values :
58
+ If ``None``, the uniform distribution is used.
59
+ Otherwise, a non-negative, non-zero vector or a dictionary must be provided.
60
+
61
+ Returns
62
+ -------
63
+ probs: np.ndarray
64
+ A probability vector.
65
+ """
66
+ if values is None:
67
+ return np.ones(n) / n
68
+ else:
69
+ values = get_values((n,), values)
70
+ probs = np.zeros_like(values, dtype=float)
71
+ ix = (values > 0)
72
+ probs[ix] = values[ix]
73
+ if probs.sum() > 0:
74
+ return probs / probs.sum()
75
+ else:
76
+ raise ValueError('At least one value must be positive.')
@@ -0,0 +1,4 @@
1
+ """Visualization module."""
2
+
3
+ from sknetwork.visualization.dendrograms import visualize_dendrogram, svg_dendrogram
4
+ from sknetwork.visualization.graphs import visualize_graph, visualize_bigraph, svg_graph, svg_bigraph
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on April 2020
5
+ @authors:
6
+ Thomas Bonald <bonald@enst.fr>
7
+ """
8
+
9
+ import numpy as np
10
+
11
+ # standard SVG colors
12
+ STANDARD_COLORS = np.array(['blue', 'red', 'green', 'orange', 'purple', 'yellow', 'fuchsia', 'olive', 'aqua', 'brown'])
13
+
14
+ # 100 RGB colors of coolwarm color map.
15
+ COOLWARM_RGB = np.array([[58, 76, 192], [60, 79, 195], [64, 84, 199], [66, 88, 202], [70, 93, 207], [72, 96, 209],
16
+ [76, 102, 214], [80, 107, 218], [82, 110, 220], [86, 115, 224], [88, 118, 226], [92, 123, 229],
17
+ [96, 128, 232], [99, 131, 234], [103, 136, 237], [105, 139, 239], [109, 144, 241],
18
+ [112, 147, 243], [116, 151, 245], [120, 155, 247], [123, 158, 248], [127, 162, 250],
19
+ [130, 165, 251], [134, 169, 252], [138, 173, 253], [141, 175, 253], [145, 179, 254],
20
+ [148, 181, 254], [152, 185, 254], [155, 187, 254], [159, 190, 254], [163, 193, 254],
21
+ [166, 195, 253], [170, 198, 253], [172, 200, 252], [176, 203, 251], [180, 205, 250],
22
+ [183, 207, 249], [187, 209, 247], [189, 210, 246], [193, 212, 244], [197, 213, 242],
23
+ [199, 214, 240], [202, 216, 238], [205, 217, 236], [208, 218, 233], [210, 218, 231],
24
+ [214, 219, 228], [217, 220, 224], [219, 220, 222], [222, 219, 218], [224, 218, 215],
25
+ [227, 217, 211], [230, 215, 207], [231, 214, 204], [234, 211, 199], [236, 210, 196],
26
+ [237, 207, 192], [239, 206, 188], [241, 203, 184], [242, 200, 179], [243, 198, 176],
27
+ [244, 195, 171], [245, 193, 168], [246, 189, 164], [246, 186, 159], [246, 183, 156],
28
+ [247, 179, 151], [247, 177, 148], [247, 173, 143], [246, 169, 138], [246, 166, 135],
29
+ [245, 161, 130], [245, 158, 127], [244, 154, 123], [243, 150, 120], [242, 145, 115],
30
+ [240, 141, 111], [239, 137, 108], [237, 132, 103], [236, 128, 100], [234, 123, 96],
31
+ [231, 117, 92], [230, 114, 89], [227, 108, 84], [225, 104, 82], [222, 98, 78],
32
+ [220, 94, 75], [217, 88, 71], [214, 82, 67], [211, 77, 64], [207, 70, 61],
33
+ [205, 66, 58], [201, 59, 55], [197, 50, 51], [194, 45, 49], [190, 35, 45],
34
+ [187, 26, 43], [182, 13, 40], [179, 3, 38]])
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created in April 2020
5
+ @author: Thomas Bonald <bonald@enst.fr>
6
+ """
7
+ from typing import Iterable, Optional
8
+
9
+ import numpy as np
10
+
11
+ from sknetwork.hierarchy.postprocess import cut_straight
12
+ from sknetwork.visualization.colors import STANDARD_COLORS
13
+
14
+
15
+ def get_index(dendrogram, reorder=True):
16
+ """Index nodes for pretty dendrogram."""
17
+ n = dendrogram.shape[0] + 1
18
+ tree = {i: [i] for i in range(n)}
19
+ for t in range(n - 1):
20
+ i = int(dendrogram[t, 0])
21
+ j = int(dendrogram[t, 1])
22
+ left: list = tree.pop(i)
23
+ right: list = tree.pop(j)
24
+ if reorder and len(left) < len(right):
25
+ tree[n + t] = right + left
26
+ else:
27
+ tree[n + t] = left + right
28
+ return list(tree.values())[0]
29
+
30
+
31
+ def svg_dendrogram_top(dendrogram, names, width, height, margin, margin_text, scale, line_width, n_clusters,
32
+ color, colors, font_size, reorder, rotate_names):
33
+ """Dendrogram as SVG image with root on top."""
34
+
35
+ # scaling
36
+ height *= scale
37
+ width *= scale
38
+
39
+ # positioning
40
+ labels = cut_straight(dendrogram, n_clusters, return_dendrogram=False)
41
+ index = get_index(dendrogram, reorder)
42
+ n = len(index)
43
+ unit_height = height / dendrogram[-1, 2]
44
+ unit_width = width / n
45
+ height_basis = margin + height
46
+ position = {index[i]: (margin + i * unit_width, height_basis) for i in range(n)}
47
+ label = {i: l for i, l in enumerate(labels)}
48
+ width += 2 * margin
49
+ height += 2 * margin
50
+ if names is not None:
51
+ text_length = np.max(np.array([len(str(name)) for name in names]))
52
+ height += text_length * font_size * .5 + margin_text
53
+
54
+ svg = """<svg width="{}" height="{}" xmlns="http://www.w3.org/2000/svg">""".format(width, height)
55
+
56
+ # text
57
+ if names is not None:
58
+ for i in range(n):
59
+ x, y = position[i]
60
+ x -= margin_text
61
+ y += margin_text
62
+ text = str(names[i]).replace('&', ' ')
63
+ if rotate_names:
64
+ svg += """<text x="{}" y="{}" transform="rotate(60, {}, {})" font-size="{}">{}</text>""" \
65
+ .format(x, y, x, y, font_size, text)
66
+ else:
67
+ y += margin_text
68
+ svg += """<text x="{}" y="{}" font-size="{}">{}</text>""" \
69
+ .format(x, y, font_size, text)
70
+
71
+ # tree
72
+ for t in range(n - 1):
73
+ i = int(dendrogram[t, 0])
74
+ j = int(dendrogram[t, 1])
75
+ x1, y1 = position.pop(i)
76
+ x2, y2 = position.pop(j)
77
+ l1 = label.pop(i)
78
+ l2 = label.pop(j)
79
+ if l1 == l2:
80
+ line_color = colors[l1 % len(colors)]
81
+ else:
82
+ line_color = color
83
+ x = .5 * (x1 + x2)
84
+ y = height_basis - dendrogram[t, 2] * unit_height
85
+ svg += """<path stroke-width="{}" stroke="{}" d="M {} {} {} {}" />"""\
86
+ .format(line_width, line_color, x1, y1, x1, y)
87
+ svg += """<path stroke-width="{}" stroke="{}" d="M {} {} {} {}" />"""\
88
+ .format(line_width, line_color, x2, y2, x2, y)
89
+ svg += """<path stroke-width="{}" stroke="{}" d="M {} {} {} {}" />"""\
90
+ .format(line_width, line_color, x1, y, x2, y)
91
+ position[n + t] = (x, y)
92
+ label[n + t] = l1
93
+
94
+ svg += '</svg>'
95
+ return svg
96
+
97
+
98
+ def svg_dendrogram_left(dendrogram, names, width, height, margin, margin_text, scale, line_width, n_clusters,
99
+ color, colors, font_size, reorder):
100
+ """Dendrogram as SVG image with root on left side."""
101
+
102
+ # scaling
103
+ height *= scale
104
+ width *= scale
105
+
106
+ # positioning
107
+ labels = cut_straight(dendrogram, n_clusters, return_dendrogram=False)
108
+ index = get_index(dendrogram, reorder)
109
+ n = len(index)
110
+ unit_height = height / n
111
+ unit_width = width / dendrogram[-1, 2]
112
+ width_basis = width + margin
113
+ position = {index[i]: (width_basis, margin + i * unit_height) for i in range(n)}
114
+ label = {i: l for i, l in enumerate(labels)}
115
+ width += 2 * margin
116
+ height += 2 * margin
117
+ if names is not None:
118
+ text_length = np.max(np.array([len(str(name)) for name in names]))
119
+ width += text_length * font_size * .5 + margin_text
120
+
121
+ svg = """<svg width="{}" height="{}" xmlns="http://www.w3.org/2000/svg">""".format(width, height)
122
+
123
+ # text
124
+ if names is not None:
125
+ for i in range(n):
126
+ x, y = position[i]
127
+ x += margin_text
128
+ y += unit_height / 3
129
+ text = str(names[i]).replace('&', ' ')
130
+ svg += """<text x="{}" y="{}" font-size="{}">{}</text>""" \
131
+ .format(x, y, font_size, text)
132
+
133
+ # tree
134
+ for t in range(n - 1):
135
+ i = int(dendrogram[t, 0])
136
+ j = int(dendrogram[t, 1])
137
+ x1, y1 = position.pop(i)
138
+ x2, y2 = position.pop(j)
139
+ l1 = label.pop(i)
140
+ l2 = label.pop(j)
141
+ if l1 == l2:
142
+ line_color = colors[l1 % len(colors)]
143
+ else:
144
+ line_color = color
145
+ y = .5 * (y1 + y2)
146
+ x = width_basis - dendrogram[t, 2] * unit_width
147
+ svg += """<path stroke-width="{}" stroke="{}" d="M {} {} {} {}" />"""\
148
+ .format(line_width, line_color, x1, y1, x, y1)
149
+ svg += """<path stroke-width="{}" stroke="{}" d="M {} {} {} {}" />"""\
150
+ .format(line_width, line_color, x2, y2, x, y2)
151
+ svg += """<path stroke-width="{}" stroke="{}" d="M {} {} {} {}" />"""\
152
+ .format(line_width, line_color, x, y1, x, y2)
153
+ position[n + t] = (x, y)
154
+ label[n + t] = l1
155
+
156
+ svg += '</svg>'
157
+
158
+ return svg
159
+
160
+
161
+ def visualize_dendrogram(dendrogram: np.ndarray, names: Optional[np.ndarray] = None, rotate: bool = False,
162
+ width: float = 400, height: float = 300, margin: float = 10, margin_text: float = 5,
163
+ scale: float = 1, line_width: float = 2, n_clusters: int = 2, color: str = 'black',
164
+ colors: Optional[Iterable] = None, font_size: int = 12, reorder: bool = False,
165
+ rotate_names: bool = True, filename: Optional[str] = None):
166
+ """Return the image of a dendrogram in SVG format.
167
+
168
+ Parameters
169
+ ----------
170
+ dendrogram :
171
+ Dendrogram to display.
172
+ names :
173
+ Names of leaves.
174
+ rotate :
175
+ If ``True``, rotate the tree so that the root is on the left.
176
+ width :
177
+ Width of the image (margins excluded).
178
+ height :
179
+ Height of the image (margins excluded).
180
+ margin :
181
+ Margin.
182
+ margin_text :
183
+ Margin between leaves and their names, if any.
184
+ scale :
185
+ Scaling factor.
186
+ line_width :
187
+ Line width.
188
+ n_clusters :
189
+ Number of coloured clusters to display.
190
+ color :
191
+ Default SVG color for the dendrogram.
192
+ colors :
193
+ SVG colors of the clusters of the dendrogram (optional).
194
+ font_size :
195
+ Font size.
196
+ reorder :
197
+ If ``True``, reorder leaves so that left subtree has more leaves than right subtree.
198
+ rotate_names :
199
+ If ``True``, rotate names of leaves (only valid if **rotate** is ``False``).
200
+ filename :
201
+ Filename for saving image (optional).
202
+
203
+ Example
204
+ -------
205
+ >>> dendrogram = np.array([[0, 1, 1, 2], [2, 3, 2, 3]])
206
+ >>> from sknetwork.visualization import svg_dendrogram
207
+ >>> image = svg_dendrogram(dendrogram)
208
+ >>> image[1:4]
209
+ 'svg'
210
+ """
211
+ if colors is None:
212
+ colors = STANDARD_COLORS
213
+ elif isinstance(colors, dict):
214
+ colors = np.array(list(colors.values()))
215
+ elif isinstance(colors, list):
216
+ colors = np.array(colors)
217
+
218
+ if rotate:
219
+ svg = svg_dendrogram_left(dendrogram, names, width, height, margin, margin_text, scale, line_width, n_clusters,
220
+ color, colors, font_size, reorder)
221
+ else:
222
+ svg = svg_dendrogram_top(dendrogram, names, width, height, margin, margin_text, scale, line_width, n_clusters,
223
+ color, colors, font_size, reorder, rotate_names)
224
+
225
+ if filename is not None:
226
+ with open(filename + '.svg', 'w') as f:
227
+ f.write(svg)
228
+
229
+ return svg
230
+
231
+
232
+ def svg_dendrogram(dendrogram: np.ndarray, names: Optional[np.ndarray] = None, rotate: bool = False, width: float = 400,
233
+ height: float = 300, margin: float = 10, margin_text: float = 5, scale: float = 1,
234
+ line_width: float = 2, n_clusters: int = 2, color: str = 'black', colors: Optional[Iterable] = None,
235
+ font_size: int = 12, reorder: bool = False, rotate_names: bool = True,
236
+ filename: Optional[str] = None):
237
+ """Return the image of a dendrogram in SVG format.
238
+
239
+ Alias for visualize_dendrogram.
240
+
241
+ Parameters
242
+ ----------
243
+ dendrogram :
244
+ Dendrogram to display.
245
+ names :
246
+ Names of leaves.
247
+ rotate :
248
+ If ``True``, rotate the tree so that the root is on the left.
249
+ width :
250
+ Width of the image (margins excluded).
251
+ height :
252
+ Height of the image (margins excluded).
253
+ margin :
254
+ Margin.
255
+ margin_text :
256
+ Margin between leaves and their names, if any.
257
+ scale :
258
+ Scaling factor.
259
+ line_width :
260
+ Line width.
261
+ n_clusters :
262
+ Number of coloured clusters to display.
263
+ color :
264
+ Default SVG color for the dendrogram.
265
+ colors :
266
+ SVG colors of the clusters of the dendrogram (optional).
267
+ font_size :
268
+ Font size.
269
+ reorder :
270
+ If ``True``, reorder leaves so that left subtree has more leaves than right subtree.
271
+ rotate_names :
272
+ If ``True``, rotate names of leaves (only valid if **rotate** is ``False``).
273
+ filename :
274
+ Filename for saving image (optional).
275
+ """
276
+ return visualize_dendrogram(dendrogram, names, rotate, width, height, margin, margin_text, scale, line_width,
277
+ n_clusters, color, colors, font_size, reorder, rotate_names, filename)