plot-io 0.12.0__py3-none-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.
Files changed (130) hide show
  1. plot_io/__init__.py +73 -0
  2. plot_io/_methods.py +1691 -0
  3. plot_io/_native/__init__.py +199 -0
  4. plot_io/_native/__main__.py +197 -0
  5. plot_io/_native/_api.py +457 -0
  6. plot_io/_native/bin/windows-x86_64.dll +0 -0
  7. plot_io/_native/kernels.py +335 -0
  8. plot_io/_native/manifest.json +49 -0
  9. plot_io/axis.py +1347 -0
  10. plot_io/boxes.py +1156 -0
  11. plot_io/canvas.py +389 -0
  12. plot_io/color/__init__.py +3 -0
  13. plot_io/color/data.py +1646 -0
  14. plot_io/color/maps.py +922 -0
  15. plot_io/color/values.py +295 -0
  16. plot_io/color/variants.py +1246 -0
  17. plot_io/colorbar.py +1426 -0
  18. plot_io/columns.py +614 -0
  19. plot_io/decorations.py +920 -0
  20. plot_io/defaults.py +809 -0
  21. plot_io/density.py +113 -0
  22. plot_io/ext.py +68 -0
  23. plot_io/facets.py +240 -0
  24. plot_io/figure.py +788 -0
  25. plot_io/formatters.py +97 -0
  26. plot_io/genome/__init__.py +17 -0
  27. plot_io/genome/adapters.py +356 -0
  28. plot_io/genome/combine.py +123 -0
  29. plot_io/genome/contacts.py +508 -0
  30. plot_io/genome/intervals.py +750 -0
  31. plot_io/genome/lanes.py +191 -0
  32. plot_io/genome/layout.py +184 -0
  33. plot_io/genome/links.py +364 -0
  34. plot_io/genome/pack.py +27 -0
  35. plot_io/genome/readers.py +145 -0
  36. plot_io/genome/regions.py +228 -0
  37. plot_io/genome/scale.py +349 -0
  38. plot_io/genome/series.py +344 -0
  39. plot_io/genome/signal.py +621 -0
  40. plot_io/genome/sites.py +782 -0
  41. plot_io/genome/sources.py +459 -0
  42. plot_io/genome/testing.py +161 -0
  43. plot_io/geometry.py +289 -0
  44. plot_io/groups.py +308 -0
  45. plot_io/guides.py +503 -0
  46. plot_io/helpers/__init__.py +17 -0
  47. plot_io/helpers/clustermap.py +202 -0
  48. plot_io/helpers/dendrogram.py +172 -0
  49. plot_io/helpers/grids.py +304 -0
  50. plot_io/helpers/qq.py +334 -0
  51. plot_io/helpers/raincloud.py +249 -0
  52. plot_io/helpers/tree.py +214 -0
  53. plot_io/helpers/volcano.py +505 -0
  54. plot_io/hints.py +125 -0
  55. plot_io/kernels.py +740 -0
  56. plot_io/labels.py +566 -0
  57. plot_io/layers/__init__.py +100 -0
  58. plot_io/layers/base.py +630 -0
  59. plot_io/layers/context.py +87 -0
  60. plot_io/layers/core.py +446 -0
  61. plot_io/layers/geoms.py +1362 -0
  62. plot_io/layers/grammar.py +586 -0
  63. plot_io/layers/paint.py +614 -0
  64. plot_io/layers/pipeline.py +170 -0
  65. plot_io/layers/positions.py +431 -0
  66. plot_io/layers/stats.py +1088 -0
  67. plot_io/layout.py +1381 -0
  68. plot_io/legend.py +605 -0
  69. plot_io/levels.py +509 -0
  70. plot_io/limits.py +124 -0
  71. plot_io/lines.py +42 -0
  72. plot_io/mapped.py +522 -0
  73. plot_io/markers.py +110 -0
  74. plot_io/markup.py +128 -0
  75. plot_io/numbers.py +354 -0
  76. plot_io/occupancy.py +295 -0
  77. plot_io/plot.py +597 -0
  78. plot_io/presets/__init__.py +51 -0
  79. plot_io/presets/_util.py +153 -0
  80. plot_io/presets/area.py +145 -0
  81. plot_io/presets/bar.py +261 -0
  82. plot_io/presets/box.py +400 -0
  83. plot_io/presets/catpoints.py +177 -0
  84. plot_io/presets/contour.py +353 -0
  85. plot_io/presets/curves.py +30 -0
  86. plot_io/presets/dots.py +355 -0
  87. plot_io/presets/ecdf.py +97 -0
  88. plot_io/presets/errorbar.py +106 -0
  89. plot_io/presets/errors.py +296 -0
  90. plot_io/presets/fill.py +74 -0
  91. plot_io/presets/heatmap.py +322 -0
  92. plot_io/presets/hexbin.py +198 -0
  93. plot_io/presets/hist.py +391 -0
  94. plot_io/presets/hist2d.py +87 -0
  95. plot_io/presets/kde.py +162 -0
  96. plot_io/presets/kde2d.py +147 -0
  97. plot_io/presets/line.py +143 -0
  98. plot_io/presets/pie.py +502 -0
  99. plot_io/presets/regression.py +115 -0
  100. plot_io/presets/scatter.py +194 -0
  101. plot_io/presets/stem.py +100 -0
  102. plot_io/presets/violin.py +323 -0
  103. plot_io/py.typed +0 -0
  104. plot_io/registry.py +82 -0
  105. plot_io/render.py +480 -0
  106. plot_io/rows.py +491 -0
  107. plot_io/scales.py +103 -0
  108. plot_io/seams.py +138 -0
  109. plot_io/settings.py +807 -0
  110. plot_io/shapes.py +66 -0
  111. plot_io/sharing.py +232 -0
  112. plot_io/sizemaps.py +97 -0
  113. plot_io/spec.py +240 -0
  114. plot_io/states.py +216 -0
  115. plot_io/styles/__init__.py +307 -0
  116. plot_io/styles/library.py +397 -0
  117. plot_io/tables.py +138 -0
  118. plot_io/text.py +412 -0
  119. plot_io/ticks.py +400 -0
  120. plot_io/times.py +287 -0
  121. plot_io/titles.py +225 -0
  122. plot_io/transform.py +114 -0
  123. plot_io/types.py +13604 -0
  124. plot_io/units.py +768 -0
  125. plot_io/validators.py +1029 -0
  126. plot_io/values.py +190 -0
  127. plot_io-0.12.0.dist-info/METADATA +215 -0
  128. plot_io-0.12.0.dist-info/RECORD +130 -0
  129. plot_io-0.12.0.dist-info/WHEEL +4 -0
  130. plot_io-0.12.0.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,214 @@
1
+ """Hierarchical clustering: :func:`linkage` (scipy's algorithms, in plot_io's C library) and
2
+ :class:`Tree`, its merges with the leaf order and flat clusters."""
3
+
4
+ import array
5
+ from typing import Any
6
+
7
+ from .. import kernels
8
+
9
+ __all__ = ["Tree", "linkage"]
10
+
11
+
12
+ class Tree:
13
+ """A binary tree of ``n`` observations as ``n - 1`` merges in scipy's layout: row ``i``
14
+ joins clusters ``left`` and ``right`` (observations ``0 … n - 1``, merges ``n + j``) at
15
+ ``height`` into a cluster of ``size`` observations. ``merges`` is a scipy ``Z`` or any
16
+ table of such rows; ``labels`` names the observations.
17
+
18
+ ``merges`` is ``n − 1`` rows ``(left, right, height, size)`` as ``array('d')``; ``n``;
19
+ ``labels`` (``None`` when not given); ``leaves``, the observations in drawing order
20
+ (scipy's ``leaves_list``); ``cut(k=None, height=None)``, the flat cluster of each
21
+ observation, numbered from 1, as scipy's ``fcluster`` with ``maxclust`` (at most ``k``
22
+ clusters) or ``distance`` (merges up to ``height``).
23
+ """
24
+
25
+ __slots__ = ("merges", "n", "labels", "_leaves")
26
+
27
+ def __init__(self, merges: Any, labels: Any = None) -> None:
28
+ g = kernels.grid(merges)
29
+ if g is not None:
30
+ if g[1] != 4 or g[2] != 1:
31
+ raise ValueError("Tree: merges must have 4 columns")
32
+ rows = [g[3][4 * i:4 * i + 4] for i in range(g[0])]
33
+ else:
34
+ rows = [kernels.doubles(r) for r in merges]
35
+ n = len(rows) + 1
36
+ for i, r in enumerate(rows):
37
+ if len(r) != 4:
38
+ raise ValueError("Tree: merges must have 4 columns")
39
+ a, b = r[0], r[1]
40
+ if not (a == int(a) and b == int(b) and 0 <= a < n + i and 0 <= b < n + i
41
+ and a != b):
42
+ raise ValueError(f"Tree: merge {i} joins {a!r} and {b!r}, not two earlier "
43
+ "clusters")
44
+ if labels is not None:
45
+ labels = list(labels)
46
+ if len(labels) != n:
47
+ raise ValueError(f"Tree: {len(labels)} labels for {n} observations")
48
+ #: the merges: rows of ``(left, right, height, size)`` (``array('d')``)
49
+ self.merges = rows
50
+ #: the number of observations
51
+ self.n = n
52
+ #: the names of the observations (``None``: none given)
53
+ self.labels = labels
54
+ self._leaves: list[int] | None = None
55
+
56
+ def __repr__(self) -> str:
57
+ return f"<Tree of {self.n} observations>"
58
+
59
+ @property
60
+ def leaves(self) -> list[int]:
61
+ """The observations in the order a dendrogram draws them (scipy's
62
+ ``leaves_list``: each merge's left cluster first)."""
63
+ if self._leaves is None:
64
+ n, out = self.n, []
65
+ stack = [2 * n - 2] if n > 1 else [0]
66
+ while stack:
67
+ k = stack.pop()
68
+ if k < n:
69
+ out.append(k)
70
+ else:
71
+ r = self.merges[k - n]
72
+ stack += [int(r[1]), int(r[0])]
73
+ self._leaves = out
74
+ return list(self._leaves)
75
+
76
+ def _max_heights(self) -> list[float]:
77
+ """The largest height in each merge's subtree."""
78
+ n = self.n
79
+ out: list[float] = []
80
+ for r in self.merges:
81
+ h = r[2]
82
+ for c in (int(r[0]), int(r[1])):
83
+ if c >= n and out[c - n] > h:
84
+ h = out[c - n]
85
+ out.append(h)
86
+ return out
87
+
88
+ def cut(self, k: int | None = None, height: float | None = None) -> list[int]:
89
+ """Flat clusters, numbered from 1, of each observation (scipy's ``fcluster``): at
90
+ most ``k`` clusters (``criterion="maxclust"``), or the subtrees whose merges are all
91
+ at most ``height`` (``criterion="distance"``)."""
92
+ if (k is None) == (height is None):
93
+ raise ValueError("cut: give k or height")
94
+ n = self.n
95
+ if n < 2:
96
+ return [1] * n
97
+ md = self._max_heights()
98
+ if k is not None:
99
+ if int(k) != k or k < 1:
100
+ raise ValueError(f"cut: k must be a positive integer, not {k!r}")
101
+ ts = [float("-inf")] + sorted(md) # the count falls as the threshold rises
102
+ lo, hi = 0, len(ts) - 1
103
+ while lo < hi:
104
+ mid = (lo + hi) // 2
105
+ if self._count(md, ts[mid]) <= k:
106
+ hi = mid
107
+ else:
108
+ lo = mid + 1
109
+ return self._flat(md, ts[lo])
110
+ return self._flat(md, float(height)) # type: ignore[arg-type]
111
+
112
+ def _count(self, md: list[float], t: float) -> int:
113
+ """The number of flat clusters at threshold ``t``."""
114
+ n = self.n
115
+ count, stack = 0, [2 * n - 2]
116
+ while stack:
117
+ c = stack.pop()
118
+ if c < n:
119
+ count += 1
120
+ elif md[c - n] <= t:
121
+ count += 1
122
+ else:
123
+ r = self.merges[c - n]
124
+ stack += [int(r[0]), int(r[1])]
125
+ return count
126
+
127
+ def _flat(self, md: list[float], t: float) -> list[int]:
128
+ """scipy's ``cluster_monocrit``: the labels in its traversal order."""
129
+ n, merges = self.n, self.merges
130
+ out = [0] * n
131
+ visited: set[int] = set()
132
+ nodes, leader, count = [2 * n - 2], -1, 0
133
+ while nodes:
134
+ root = nodes[-1] - n
135
+ lc, rc = int(merges[root][0]), int(merges[root][1])
136
+ if leader == -1 and md[root] <= t:
137
+ leader = root
138
+ count += 1
139
+ if lc >= n and lc not in visited:
140
+ visited.add(lc)
141
+ nodes.append(lc)
142
+ continue
143
+ if rc >= n and rc not in visited:
144
+ visited.add(rc)
145
+ nodes.append(rc)
146
+ continue
147
+ for c in (lc, rc):
148
+ if c < n:
149
+ if leader == -1:
150
+ count += 1
151
+ out[c] = count
152
+ if leader == root:
153
+ leader = -1
154
+ nodes.pop()
155
+ return out
156
+
157
+
158
+ def _frame(data: Any) -> tuple[Any, Any, Any]:
159
+ """A data frame's values with its row and column names (pandas, polars), else
160
+ ``data``."""
161
+ if hasattr(data, "to_numpy") and hasattr(data, "columns"):
162
+ index = getattr(data, "index", None)
163
+ rows = list(index) if index is not None and hasattr(index, "__iter__") else None
164
+ return data.to_numpy(dtype=float), rows, list(data.columns)
165
+ return data, None, None
166
+
167
+
168
+ def linkage(data: Any, method: str = "average", metric: str = "euclidean",
169
+ axis: str = "rows", labels: Any = None) -> Tree:
170
+ """The :class:`Tree` of a hierarchical clustering of the rows (``axis="cols"``: the
171
+ columns) of ``data``, a 2-D buffer, a sequence of rows or a data frame (its row or
172
+ column names the labels); ``metric="precomputed"``: ``data`` is the condensed distance
173
+ vector. Methods: ``single``, ``complete``, ``average``, ``weighted``, ``ward``,
174
+ ``centroid``, ``median``; metrics: ``euclidean``, ``sqeuclidean``, ``cityblock``,
175
+ ``chebyshev``, ``cosine``, ``correlation``, ``precomputed`` (ward, centroid and median:
176
+ euclidean or precomputed). The merges equal scipy's ``linkage`` on data without equal
177
+ distances; equal distances merge in a fixed order. At most 16,384 observations.
178
+
179
+ The algorithms are scipy's, in plot_io's C library; ``data`` with ``precomputed`` is
180
+ the condensed distance vector as scipy's ``pdist`` gives. Equal distances merge in a
181
+ fixed order, the same on every run and platform but not always scipy's. NaN or
182
+ infinite values, a constant row with ``correlation``, a zero row with ``cosine`` and
183
+ more than 16,384 observations raise. The :class:`Tree` gives ``merges``, ``n``,
184
+ ``labels``, ``leaves`` and ``cut``; :class:`Dendrogram` draws it.
185
+
186
+ Example::
187
+
188
+ rng = random.Random(1)
189
+ rows = [[rng.gauss(c, 1) for _ in range(4)] for c in (0, 0, 0, 4, 4, 8)]
190
+ tree = plt.linkage(rows, method="average")
191
+ clusters = tree.cut(k=3)
192
+ """
193
+ if axis not in ("rows", "cols"):
194
+ raise ValueError(f"linkage: axis is 'rows' or 'cols', not {axis!r}")
195
+ values, row_names, col_names = _frame(data)
196
+ if labels is None:
197
+ labels = col_names if axis == "cols" else row_names
198
+ if axis == "cols" and metric != "precomputed":
199
+ g = kernels.grid(values)
200
+ if g is not None:
201
+ h, w, flat = g[0], g[1], g[3]
202
+ else:
203
+ rs = [kernels.doubles(r) for r in values]
204
+ h, w = len(rs), len(rs[0]) if rs else 0
205
+ flat = array.array("d")
206
+ for r in rs:
207
+ if len(r) != w:
208
+ raise ValueError("linkage: rows of different lengths")
209
+ flat.extend(r)
210
+ values = [flat[j::w] for j in range(w)] if h else []
211
+ n, z = kernels.linkage(values, method, metric)
212
+ if n < 1:
213
+ raise ValueError("linkage: no observations")
214
+ return Tree([z[4 * i:4 * i + 4] for i in range(n - 1)], labels)