cellestial 0.49.0__tar.gz → 0.49.2__tar.gz

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 (66) hide show
  1. {cellestial-0.49.0 → cellestial-0.49.2}/PKG-INFO +17 -7
  2. {cellestial-0.49.0 → cellestial-0.49.2}/README.md +5 -5
  3. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/__init__.py +11 -1
  4. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/arrow.py +129 -75
  5. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/dimensional.py +2 -2
  6. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/distribution.py +3 -209
  7. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/subdimensional.py +8 -8
  8. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/subdimensionals.py +10 -10
  9. cellestial-0.49.2/cellestial/single/core/utilities.py +221 -0
  10. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/differential/markers.py +2 -145
  11. cellestial-0.49.2/cellestial/single/differential/utilities.py +309 -0
  12. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/differential/volcano.py +1 -152
  13. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/heatmap/dotplot.py +2 -2
  14. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/heatmap/heatmap.py +25 -148
  15. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/heatmap/stacked_violin.py +10 -133
  16. cellestial-0.49.2/cellestial/single/heatmap/utilities.py +872 -0
  17. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/spatial/spatial.py +1 -90
  18. cellestial-0.49.0/cellestial/spatial/_components.py → cellestial-0.49.2/cellestial/spatial/utilities.py +95 -10
  19. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/__init__.py +3 -0
  20. cellestial-0.49.2/cellestial/util/markers.py +229 -0
  21. {cellestial-0.49.0 → cellestial-0.49.2}/pyproject.toml +29 -2
  22. cellestial-0.49.0/cellestial/single/heatmap/_key_groups.py +0 -317
  23. cellestial-0.49.0/cellestial/single/heatmap/_rank_genes_groups.py +0 -187
  24. {cellestial-0.49.0 → cellestial-0.49.2}/LICENSE +0 -0
  25. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/_version.py +0 -0
  26. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/datasets/__init__.py +0 -0
  27. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/datasets/datasets.py +0 -0
  28. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/frames/__init__.py +0 -0
  29. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/frames/build.py +0 -0
  30. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/frames/operations.py +0 -0
  31. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/__init__.py +0 -0
  32. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/_deferred.py +0 -0
  33. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/bracket.py +0 -0
  34. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/ondata_legend.py +0 -0
  35. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/outline.py +0 -0
  36. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/stream.py +0 -0
  37. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/utilities.py +0 -0
  38. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/__init__.py +0 -0
  39. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/base/__init__.py +0 -0
  40. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/base/base.py +0 -0
  41. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/basic/__init__.py +0 -0
  42. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/basic/bar.py +0 -0
  43. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/basic/scatter.py +0 -0
  44. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/common/__init__.py +0 -0
  45. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/common/xyplot.py +0 -0
  46. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/common/xyplots.py +0 -0
  47. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/__init__.py +0 -0
  48. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/distributions.py +0 -0
  49. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/differential/__init__.py +0 -0
  50. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/heatmap/__init__.py +0 -0
  51. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/quick/__init__.py +0 -0
  52. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/quick/elbow.py +0 -0
  53. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/quick/highest.py +0 -0
  54. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/quick/ridge.py +0 -0
  55. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/spatial/__init__.py +0 -0
  56. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/spatial/spatials.py +0 -0
  57. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/themes/__init__.py +0 -0
  58. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/themes/_distributions.py +0 -0
  59. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/themes/_heatmaps.py +0 -0
  60. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/themes/_scatters.py +0 -0
  61. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/colors.py +0 -0
  62. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/dendrogram.py +0 -0
  63. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/errors.py +0 -0
  64. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/operations.py +0 -0
  65. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/save.py +0 -0
  66. {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/utilities.py +0 -0
@@ -1,17 +1,23 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cellestial
3
- Version: 0.49.0
4
- Summary:
3
+ Version: 0.49.2
4
+ Summary: Interactive single-cell and spatial omics visualization library
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
7
+ Keywords: anndata,bioinformatics,data-visualization,lets-plot,polars,single-cell,spatial-omics
7
8
  Author: Zaf4
8
9
  Author-email: zafermolbio@gmail.com
9
10
  Requires-Python: >=3.11,<4.0
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
10
14
  Classifier: Programming Language :: Python :: 3
11
15
  Classifier: Programming Language :: Python :: 3.11
12
16
  Classifier: Programming Language :: Python :: 3.12
13
17
  Classifier: Programming Language :: Python :: 3.13
14
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
20
+ Classifier: Topic :: Scientific/Engineering :: Visualization
15
21
  Provides-Extra: extras
16
22
  Requires-Dist: anndata (>=0.10.8,<1.0)
17
23
  Requires-Dist: lets-plot (>=4.9.0,<5.0.0)
@@ -23,6 +29,10 @@ Requires-Dist: scikit-image (>=0.23.1,<1.0)
23
29
  Requires-Dist: scvelo (>=0.3.3,<0.4.0) ; extra == "extras"
24
30
  Requires-Dist: spatialdata (>=0.7,<1.0)
25
31
  Requires-Dist: tqdm (>=4,<5)
32
+ Project-URL: Documentation, https://cellestial.datavil.org
33
+ Project-URL: Homepage, https://cellestial.datavil.org
34
+ Project-URL: Issues, https://github.com/DataVil/Cellestial/issues
35
+ Project-URL: Repository, https://github.com/DataVil/Cellestial
26
36
  Description-Content-Type: text/markdown
27
37
 
28
38
  # Cellestial
@@ -250,14 +260,14 @@ Cellestial mirrors most of `scanpy.pl` with a few naming shifts:
250
260
  - `groupby` becomes `group_by`, `var_names` becomes `keys`.
251
261
  - Saving is a separate `cl.save(plot, "umap.png")` call.
252
262
 
253
- See the [migration guide](sphinx/migrating_from_scanpy.rst) for a side-by-side mapping.
263
+ See the [migration guide](https://cellestial.datavil.org/migrating_from_scanpy.html) for a side-by-side mapping.
254
264
 
255
265
  ## Documentation
256
266
 
257
- - [Quickstart](sphinx/quickstart.rst) — guided tour of the API.
258
- - [Features](sphinx/features.rst) — tooltips, layers, grids, colors, faceting.
259
- - [API reference](sphinx/API.rst) — every function.
260
- - [Migrating from Scanpy](sphinx/migrating_from_scanpy.rst).
267
+ - [Quickstart](https://cellestial.datavil.org/quickstart.html) — guided tour of the API.
268
+ - [Features](https://cellestial.datavil.org/features.html) — tooltips, layers, grids, colors, faceting.
269
+ - [API reference](https://cellestial.datavil.org/API.html) — every function.
270
+ - [Migrating from Scanpy](https://cellestial.datavil.org/migrating_from_scanpy.html).
261
271
 
262
272
  ## License
263
273
 
@@ -223,14 +223,14 @@ Cellestial mirrors most of `scanpy.pl` with a few naming shifts:
223
223
  - `groupby` becomes `group_by`, `var_names` becomes `keys`.
224
224
  - Saving is a separate `cl.save(plot, "umap.png")` call.
225
225
 
226
- See the [migration guide](sphinx/migrating_from_scanpy.rst) for a side-by-side mapping.
226
+ See the [migration guide](https://cellestial.datavil.org/migrating_from_scanpy.html) for a side-by-side mapping.
227
227
 
228
228
  ## Documentation
229
229
 
230
- - [Quickstart](sphinx/quickstart.rst) — guided tour of the API.
231
- - [Features](sphinx/features.rst) — tooltips, layers, grids, colors, faceting.
232
- - [API reference](sphinx/API.rst) — every function.
233
- - [Migrating from Scanpy](sphinx/migrating_from_scanpy.rst).
230
+ - [Quickstart](https://cellestial.datavil.org/quickstart.html) — guided tour of the API.
231
+ - [Features](https://cellestial.datavil.org/features.html) — tooltips, layers, grids, colors, faceting.
232
+ - [API reference](https://cellestial.datavil.org/API.html) — every function.
233
+ - [Migrating from Scanpy](https://cellestial.datavil.org/migrating_from_scanpy.html).
234
234
 
235
235
  ## License
236
236
 
@@ -37,7 +37,15 @@ from cellestial.single import (
37
37
  xyplots,
38
38
  )
39
39
  from cellestial.spatial import spatial, spatials
40
- from cellestial.util import get_figure, get_figures, get_mapping, retrieve, save
40
+ from cellestial.util import (
41
+ get_figure,
42
+ get_figures,
43
+ get_mapping,
44
+ marker_genes,
45
+ marker_genes_dict,
46
+ retrieve,
47
+ save,
48
+ )
41
49
  from cellestial.util.colors import (
42
50
  BLUE,
43
51
  CHERRY,
@@ -84,6 +92,8 @@ __all__ = [
84
92
  "get_mapping",
85
93
  "heatmap",
86
94
  "highest_expressed_genes",
95
+ "marker_genes",
96
+ "marker_genes_dict",
87
97
  "markers",
88
98
  "matrixplot",
89
99
  "ondata_legend",
@@ -2,11 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Literal
4
4
 
5
+ import polars as pl
5
6
  from lets_plot import (
7
+ aes,
6
8
  arrow,
7
9
  element_blank,
8
10
  element_text,
9
11
  geom_blank,
12
+ geom_path,
10
13
  geom_segment,
11
14
  theme,
12
15
  )
@@ -19,6 +22,111 @@ if TYPE_CHECKING:
19
22
  from lets_plot.plot.core import FeatureSpecArray, PlotSpec
20
23
  from polars import DataFrame
21
24
 
25
+ # fraction of each axis arm drawn as the arrowhead-bearing tip segment;
26
+ # lets_plot caps the arrowhead to ~half the segment length, so the tip must be
27
+ # long enough to render a full-size head
28
+ _TIP_FRACTION = 0.3
29
+
30
+
31
+ def _axis_arrow_layers(
32
+ frame: DataFrame,
33
+ *,
34
+ x: str,
35
+ y: str,
36
+ length: float,
37
+ size: float,
38
+ color: str,
39
+ angle: float,
40
+ arrow_kwargs: dict | None = None,
41
+ ) -> FeatureSpecArray:
42
+ """
43
+ Build the arrow-axis as an L-shaped path plus two arrowhead tip segments.
44
+
45
+ The semi-square L is one continuous `geom_path` that stops short of each
46
+ open end so the corner joins cleanly with no overlap. Each tip is completed
47
+ by a `geom_segment` carrying the arrowhead, meeting the path end-to-end.
48
+
49
+ Parameters
50
+ ----------
51
+ frame : `polars.DataFrame`
52
+ DataFrame holding the `x` and `y` columns.
53
+ x : str
54
+ Name of the x axis column.
55
+ y : str
56
+ Name of the y axis column.
57
+ length : float
58
+ Length of each axis arm as a fraction of its data range.
59
+ size : float
60
+ Line width of the path and tip segments.
61
+ color : str
62
+ Color of the path and tip segments.
63
+ angle : float
64
+ Angle of the arrowhead in degrees.
65
+ arrow_kwargs : dict | None
66
+ Additional parameters for the `arrow` layer.
67
+
68
+ Returns
69
+ -------
70
+ `FeatureSpecArray`
71
+ The path and tip-segment layers.
72
+ """
73
+ arrow_kwargs = arrow_kwargs or {}
74
+
75
+ x_min = frame[x].min()
76
+ x_max = frame[x].max()
77
+ y_min = frame[y].min()
78
+ y_max = frame[y].max()
79
+
80
+ # find total difference between the max and min for both axis
81
+ x_diff = x_max - x_min
82
+ y_diff = y_max - y_min
83
+
84
+ # find the ends of the arrows
85
+ xend = x_min + length * x_diff
86
+ yend = y_min + length * y_diff
87
+
88
+ # adjust bottom ends of arrows
89
+ adjust_rate = 0.025
90
+ x0 = x_min - x_diff * adjust_rate
91
+ y0 = y_min - y_diff * adjust_rate
92
+
93
+ # tip segments carry the arrowheads; the path stops short of each tip
94
+ tip_x = (xend - x0) * _TIP_FRACTION
95
+ tip_y = (yend - y0) * _TIP_FRACTION
96
+
97
+ # semi-square L (stopping short of both tips), drawn as one connected path
98
+ path_frame = pl.DataFrame(
99
+ {"x": [xend - tip_x, x0, x0], "y": [y0, y0, yend - tip_y]}
100
+ )
101
+
102
+ layers = geom_path(
103
+ data=path_frame,
104
+ mapping=aes(x="x", y="y"),
105
+ color=color,
106
+ size=size,
107
+ )
108
+ # X axis tip
109
+ layers += geom_segment(
110
+ x=xend - tip_x,
111
+ y=y0,
112
+ xend=xend,
113
+ yend=y0,
114
+ color=color,
115
+ size=size,
116
+ arrow=arrow(angle, **arrow_kwargs),
117
+ )
118
+ # Y axis tip
119
+ layers += geom_segment(
120
+ x=x0,
121
+ y=yend - tip_y,
122
+ xend=x0,
123
+ yend=yend,
124
+ color=color,
125
+ size=size,
126
+ arrow=arrow(angle, **arrow_kwargs),
127
+ )
128
+ return layers
129
+
22
130
 
23
131
  def _modify_axis(
24
132
  frame: DataFrame,
@@ -45,13 +153,13 @@ def _modify_axis(
45
153
  axis_type : {'axis', 'arrow'} | None
46
154
  Whether to use regular axis or arrows as the axis.
47
155
  arrow_size : float
48
- Size of the arrow.
156
+ Width of the axis lines.
49
157
  arrow_color : str
50
158
  Color of the arrow.
51
159
  arrow_angle : float
52
160
  Angle of the arrow head in degrees.
53
161
  arrow_length : float
54
- Length of the arrow head (px).
162
+ Span of each axis line as a fraction of its data range (0.25 covers 25%).
55
163
 
56
164
  Returns
57
165
  -------
@@ -86,44 +194,14 @@ def _modify_axis(
86
194
  axis_title_x=element_text(hjust=arrow_length / 2.5), # better than 2
87
195
  axis_title_y=element_text(hjust=arrow_length / 2.5),
88
196
  )
89
- x_max = frame[x].max()
90
- x_min = frame[x].min()
91
- y_max = frame[y].max()
92
- y_min = frame[y].min()
93
-
94
- # find total difference between the max and min for both axis
95
- x_diff = x_max - x_min # ty:ignore[unsupported-operator]
96
- y_diff = y_max - y_min # ty:ignore[unsupported-operator]
97
-
98
- # find the ends of the arrows
99
- # ensure the arrow length is the same for both axis
100
- xend = x_min + arrow_length * x_diff
101
- yend = y_min + arrow_length * y_diff
102
-
103
- # adjust bottom ends of arrows
104
- adjust_rate = 0.025
105
- x0 = x_min - x_diff * adjust_rate
106
- y0 = y_min - y_diff * adjust_rate
107
-
108
- # X axis
109
- new_layer += geom_segment(
110
- x=x0,
111
- y=y0,
112
- xend=xend,
113
- yend=y0,
114
- color=arrow_color,
197
+ new_layer += _axis_arrow_layers(
198
+ frame,
199
+ x=x,
200
+ y=y,
201
+ length=arrow_length,
115
202
  size=arrow_size,
116
- arrow=arrow(arrow_angle),
117
- )
118
- # Y axis
119
- new_layer += geom_segment(
120
- x=x0,
121
- y=y0,
122
- xend=x0,
123
- yend=yend,
124
203
  color=arrow_color,
125
- size=arrow_size,
126
- arrow=arrow(arrow_angle),
204
+ angle=arrow_angle,
127
205
  )
128
206
  else:
129
207
  msg = f"expected 'axis' or 'arrow' for 'axis_type' argument, but received {axis_type}"
@@ -151,13 +229,13 @@ def arrow_axis(
151
229
  regardless of which plot the resulting layer is added to. When `None`,
152
230
  the layer is deferred and introspects the plot it is added to via `+`.
153
231
  size : float
154
- Size of the arrow.
232
+ Width of the axis lines.
155
233
  color : str
156
234
  Color of the arrow.
157
235
  angle : float
158
236
  Angle of the arrow head in degrees.
159
237
  length : float
160
- Length of the arrow head (px).
238
+ Span of each axis line as a fraction of its data range (0.25 covers 25%).
161
239
  **arrow_kwargs
162
240
  Additional parameters for the `arrow` layer.
163
241
  For more information on arrow parameters, see:
@@ -172,6 +250,10 @@ def arrow_axis(
172
250
  MissingAestheticError
173
251
  If `x` or `y` cannot be inferred from the receiving plot.
174
252
 
253
+ Notes
254
+ -----
255
+ Uses `geom_path` to draw the axis lines and `geom_segment` for the arrowheads.
256
+
175
257
  Examples
176
258
  --------
177
259
  Without arrow.
@@ -242,43 +324,15 @@ def arrow_axis(
242
324
  axis_title_x=element_text(hjust=length / 2.5), # better than 2
243
325
  axis_title_y=element_text(hjust=length / 2.5),
244
326
  )
245
- x_max = frame[x].max()
246
- x_min = frame[x].min()
247
- y_max = frame[y].max()
248
- y_min = frame[y].min()
249
-
250
- # find total difference between the max and min for both axis
251
- x_diff = x_max - x_min
252
- y_diff = y_max - y_min
253
-
254
- # find the ends of the arrows
255
- xend = x_min + length * x_diff
256
- yend = y_min + length * y_diff
257
-
258
- # adjust bottom ends of arrows
259
- adjust_rate = 0.025
260
- x0 = x_min - x_diff * adjust_rate
261
- y0 = y_min - y_diff * adjust_rate
262
-
263
- # X axis
264
- new_layer += geom_segment(
265
- x=x0,
266
- y=y0,
267
- xend=xend,
268
- yend=y0,
269
- color=color,
327
+ new_layer += _axis_arrow_layers(
328
+ frame,
329
+ x=x,
330
+ y=y,
331
+ length=length,
270
332
  size=size,
271
- arrow=arrow(angle, **arrow_kwargs),
272
- )
273
- # Y axis
274
- new_layer += geom_segment(
275
- x=x0,
276
- y=y0,
277
- xend=x0,
278
- yend=yend,
279
333
  color=color,
280
- size=size,
281
- arrow=arrow(angle, **arrow_kwargs),
334
+ angle=angle,
335
+ arrow_kwargs=arrow_kwargs,
282
336
  )
283
337
  return new_layer
284
338
 
@@ -116,9 +116,9 @@ def dimensional(
116
116
  axis_type : {'axis', 'arrow'} | None
117
117
  Whether to use regular axis or arrows as the axis.
118
118
  arrow_length : float, default=0.25
119
- Length of the arrow head (px).
119
+ Span of each axis line as a fraction of its data range (0.25 covers 25%).
120
120
  arrow_size : float, default=1
121
- Size of the arrow.
121
+ Width of the axis lines.
122
122
  arrow_color : str, default='#3f3f3f'
123
123
  Color of the arrows.
124
124
  arrow_angle : float, default=10
@@ -1,222 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Sequence
4
3
  from typing import TYPE_CHECKING, Any, Literal
5
4
 
6
- import polars as pl
7
5
  from anndata import AnnData
8
- from lets_plot import (
9
- aes,
10
- geom_boxplot,
11
- geom_jitter,
12
- geom_point,
13
- geom_sina,
14
- geom_violin,
15
- ggplot,
16
- ggtb,
17
- position_dodge,
18
- position_jitterdodge,
19
- )
20
6
  from lets_plot.plot.core import FeatureSpec, PlotSpec
21
7
 
22
- from cellestial.frames import build_frame
23
- from cellestial.themes import _THEME_DIST
24
- from cellestial.util import (
25
- _determine_axis,
26
- _resolve_tooltips,
27
- _select_variable_keys,
28
- _validate_tooltips,
29
- _warn,
30
- )
31
- from cellestial.util.errors import UnsupportedDataTypeError
8
+ from cellestial.single.core.utilities import _distribution
32
9
 
33
10
  if TYPE_CHECKING:
34
- from lets_plot.plot.core import PlotSpec
35
-
11
+ from collections.abc import Sequence
36
12
 
37
- def _distribution(
38
- data: AnnData,
39
- key: str | Sequence[str],
40
- *,
41
- group_by: str | None = None,
42
- mapping: FeatureSpec | None = None,
43
- geom: Literal["violin", "boxplot"] = "violin",
44
- axis: Literal[0, 1] | None = None,
45
- color: str | None = None,
46
- fill: str | None = None,
47
- threshold: float | None = None,
48
- add_keys: Sequence[str] | str | None = None,
49
- tooltips: Literal["none"] | Sequence[str] | FeatureSpec | None = None,
50
- geom_fill: str | None = None,
51
- geom_color: str | None = None,
52
- point_color: str = "#1f1f1f",
53
- point_alpha: float = 0.7,
54
- point_size: float = 0.5,
55
- point_geom: Literal["jitter", "point", "sina"] = "jitter",
56
- point_mapping: FeatureSpec | None = None,
57
- observations_name: str = "Barcode",
58
- variables_name: str = "Variable",
59
- show_points: bool = True,
60
- interactive: bool = False,
61
- value_column: str = "value",
62
- variable_column: str = "variable",
63
- point_kwargs: dict[str, Any] | None = None,
64
- **geom_kwargs,
65
- ) -> PlotSpec:
66
- # Handling Data types
67
- if not isinstance(data, AnnData):
68
- msg = f"Unsupported data type: `{type(data)}`"
69
- raise UnsupportedDataTypeError(msg)
70
-
71
- # HANDLE: mapping
72
- mapping = mapping or aes()
73
- point_mapping = point_mapping or aes()
74
-
75
- # merge standalone color/fill into mapping
76
- _mapping = mapping.as_dict()
77
- if color is not None and "color" not in _mapping:
78
- _mapping["color"] = color
79
- if fill is not None and "fill" not in _mapping:
80
- _mapping["fill"] = fill
81
- mapping = aes(**_mapping)
82
-
83
- mapping_color = _mapping.get("color")
84
- mapping_fill = _mapping.get("fill")
85
-
86
- # convert to list if single string
87
- if isinstance(key, str):
88
- keys = [key]
89
- elif isinstance(key, Sequence):
90
- keys = list(key)
91
-
92
- # handle value_column if single key is provided
93
- if len(keys) == 1:
94
- value_column = keys[0]
95
-
96
- # determine separator (group_by)
97
- group_by = group_by or mapping_fill or mapping_color
98
- # initialize point kwargs if necessary
99
- point_kwargs = {} if point_kwargs is None else dict(point_kwargs)
100
-
101
- # determine index to unpivot
102
- index = list(
103
- dict.fromkeys(c for c in (group_by, mapping_fill, mapping_color) if c is not None)
104
- )
105
- if add_keys is not None:
106
- if isinstance(add_keys, str):
107
- add_keys = [add_keys]
108
- index.extend(add_keys)
109
-
110
- # DETERMINE: axis if not provided
111
- axis = _determine_axis(data=data, keys=keys) if axis is None else axis
112
-
113
- # BUILD: the DataFrame
114
- variable_keys = _select_variable_keys(data=data, keys=keys)
115
- variable_keys.extend(_select_variable_keys(data=data, keys=add_keys))
116
- frame = build_frame(
117
- data=data,
118
- variable_keys=variable_keys,
119
- axis=axis,
120
- observations_name=observations_name,
121
- variables_name=variables_name,
122
- )
123
-
124
- if group_by is not None:
125
- frame = frame.filter(pl.col(group_by).is_not_null())
126
-
127
- frame = frame.unpivot(
128
- on=keys, index=index, value_name=value_column, variable_name=variable_column
129
- )
130
- frame = frame.drop_nulls(subset=[value_column])
131
- if threshold is not None:
132
- frame = frame.filter(pl.col(value_column) >= threshold)
133
- if group_by is None:
134
- group_by = variable_column
135
- elif len(keys) > 1 and group_by != variable_column:
136
- _warn(
137
- f"Multiple keys provided; `group_by={group_by!r}` cannot share the "
138
- f"x-axis with the variable column. Falling back to "
139
- f"`group_by={variable_column!r}`. For a per-key panel, use the plural variant."
140
- )
141
- group_by = variable_column
142
-
143
- # HANDLE: tooltips
144
- tooltips = _resolve_tooltips(
145
- tooltips,
146
- data=data,
147
- variable_keys=variable_keys,
148
- defaults=[variable_column, value_column],
149
- )
150
- _validate_tooltips(tooltips, frame)
151
-
152
- # BUILD: the plot
153
- dst = ggplot(data=frame) + _THEME_DIST
154
-
155
- # add the geom layer
156
- if geom == "violin":
157
- dst += geom_violin(
158
- mapping=aes(
159
- x=group_by,
160
- y=value_column,
161
- **mapping.as_dict(),
162
- ),
163
- fill=geom_fill,
164
- color=geom_color,
165
- tooltips=frame.columns,
166
- **geom_kwargs,
167
- )
168
- elif geom == "boxplot":
169
- dst += geom_boxplot(
170
- mapping=aes(
171
- x=group_by,
172
- y=value_column,
173
- **mapping.as_dict(),
174
- ),
175
- fill=geom_fill,
176
- color=geom_color,
177
- tooltips=frame.columns,
178
- **geom_kwargs,
179
- )
180
-
181
- # handle the points (jitter,point,sina)
182
- if show_points:
183
- if point_geom in ["jitter", "point", "sina"]:
184
- geom_functions = {
185
- "jitter": geom_jitter,
186
- "point": geom_point,
187
- "sina": geom_sina,
188
- }
189
- dodge_width = geom_kwargs.get("width", 0.95)
190
- positions = {
191
- "jitter": position_jitterdodge(dodge_width=dodge_width),
192
- "point": position_dodge(width=dodge_width),
193
- "sina": None,
194
- }
195
- geom_function = geom_functions.get(point_geom, geom_jitter)
196
- if "position" not in point_kwargs:
197
- position = positions.get(point_geom, position_jitterdodge())
198
- else:
199
- position = point_kwargs.pop("position")
200
-
201
- point_kwargs.update(color=point_color, alpha=point_alpha, size=point_size)
202
- for key in point_mapping.as_dict():
203
- point_kwargs.pop(key, None)
204
- dst += geom_function(
205
- data=frame,
206
- mapping=aes(x=group_by, y=value_column, **point_mapping.as_dict()),
207
- tooltips=tooltips,
208
- position=position,
209
- **point_kwargs,
210
- )
211
- else:
212
- msg = "point_geom must be one of ['jitter','point','sina']."
213
- raise ValueError(msg)
214
-
215
- # handle interactive
216
- if interactive:
217
- dst += ggtb(size_zoomin=-1)
218
-
219
- return dst
13
+ from lets_plot.plot.core import PlotSpec
220
14
 
221
15
 
222
16
  def violin(
@@ -90,9 +90,9 @@ def umap(
90
90
  axis_type : {'axis', 'arrow'} | None
91
91
  Whether to use regular axis or arrows as the axis.
92
92
  arrow_length : float, default=0.25
93
- Length of the arrow head (px).
93
+ Span of each axis line as a fraction of its data range (0.25 covers 25%).
94
94
  arrow_size : float, default=1
95
- Size of the arrow.
95
+ Width of the axis lines.
96
96
  arrow_color : str, default='#3f3f3f'
97
97
  Color of the arrows.
98
98
  arrow_angle : float, default=10
@@ -261,9 +261,9 @@ def tsne(
261
261
  axis_type : {'axis', 'arrow'} | None
262
262
  Whether to use regular axis or arrows as the axis.
263
263
  arrow_length : float, default=0.25
264
- Length of the arrow head (px).
264
+ Span of each axis line as a fraction of its data range (0.25 covers 25%).
265
265
  arrow_size : float, default=1
266
- Size of the arrow.
266
+ Width of the axis lines.
267
267
  arrow_color : str, default='#3f3f3f'
268
268
  Color of the arrows.
269
269
  arrow_angle : float, default=10
@@ -432,9 +432,9 @@ def pca(
432
432
  axis_type : {'axis', 'arrow'} | None
433
433
  Whether to use regular axis or arrows as the axis.
434
434
  arrow_length : float, default=0.25
435
- Length of the arrow head (px).
435
+ Span of each axis line as a fraction of its data range (0.25 covers 25%).
436
436
  arrow_size : float, default=1
437
- Size of the arrow.
437
+ Width of the axis lines.
438
438
  arrow_color : str, default='#3f3f3f'
439
439
  Color of the arrows.
440
440
  arrow_angle : float, default=10
@@ -606,9 +606,9 @@ def expression(
606
606
  axis_type : {'axis', 'arrow'} | None
607
607
  Whether to use regular axis or arrows as the axis.
608
608
  arrow_length : float, default=0.25
609
- Length of the arrow head (px).
609
+ Span of each axis line as a fraction of its data range (0.25 covers 25%).
610
610
  arrow_size : float, default=1
611
- Size of the arrow.
611
+ Width of the axis lines.
612
612
  arrow_color : str, default='#3f3f3f'
613
613
  Color of the arrows.
614
614
  arrow_angle : float, default=10