cellestial 0.44.0__tar.gz → 0.45.0__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.
- {cellestial-0.44.0 → cellestial-0.45.0}/PKG-INFO +1 -1
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/__init__.py +2 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/layers/utilities.py +3 -3
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/__init__.py +2 -1
- cellestial-0.45.0/cellestial/single/differential/__init__.py +4 -0
- cellestial-0.45.0/cellestial/single/differential/markers.py +446 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/util/utilities.py +21 -9
- {cellestial-0.44.0 → cellestial-0.45.0}/pyproject.toml +2 -1
- cellestial-0.44.0/cellestial/single/differential/__init__.py +0 -3
- {cellestial-0.44.0 → cellestial-0.45.0}/LICENSE +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/README.md +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/_version.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/datasets/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/datasets/datasets.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/frames/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/frames/build.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/frames/operations.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/layers/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/layers/_deferred.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/layers/arrow.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/layers/bracket.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/layers/ondata_legend.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/layers/outline.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/layers/stream.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/base/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/base/base.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/basic/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/basic/bar.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/basic/scatter.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/common/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/common/xyplot.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/common/xyplots.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/core/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/core/dimensional.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/core/distribution.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/core/distributions.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/core/subdimensional.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/core/subdimensionals.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/differential/volcano.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/heatmap/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/heatmap/_key_groups.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/heatmap/_rank_genes_groups.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/heatmap/dotplot.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/heatmap/heatmap.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/heatmap/stacked_violin.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/quick/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/quick/elbow.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/quick/highest.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/single/quick/ridge.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/spatial/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/spatial/_components.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/spatial/spatial.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/spatial/spatials.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/themes/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/themes/_distributions.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/themes/_heatmaps.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/themes/_scatters.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/util/__init__.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/util/colors.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/util/dendrogram.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/util/errors.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/util/operations.py +0 -0
- {cellestial-0.44.0 → cellestial-0.45.0}/cellestial/util/save.py +0 -0
|
@@ -16,6 +16,7 @@ from cellestial.single import (
|
|
|
16
16
|
expressions,
|
|
17
17
|
heatmap,
|
|
18
18
|
highest_expressed_genes,
|
|
19
|
+
markers,
|
|
19
20
|
matrixplot,
|
|
20
21
|
pca,
|
|
21
22
|
pcas,
|
|
@@ -82,6 +83,7 @@ __all__ = [
|
|
|
82
83
|
"get_mapping",
|
|
83
84
|
"heatmap",
|
|
84
85
|
"highest_expressed_genes",
|
|
86
|
+
"markers",
|
|
85
87
|
"matrixplot",
|
|
86
88
|
"ondata_legend",
|
|
87
89
|
"pca",
|
|
@@ -18,7 +18,7 @@ def borders(
|
|
|
18
18
|
)-> FeatureSpec:
|
|
19
19
|
"""Add colored rectangels around panel, legend, and plot areas."""
|
|
20
20
|
return theme(
|
|
21
|
-
panel_background=element_rect(color=
|
|
22
|
-
legend_background=element_rect(color=
|
|
23
|
-
plot_background=element_rect(color=
|
|
21
|
+
panel_background=element_rect(color=panel_color, size=panel_size),
|
|
22
|
+
legend_background=element_rect(color=legend_color, size=legend_size),
|
|
23
|
+
plot_background=element_rect(color=plot_color, size=plot_size),
|
|
24
24
|
)
|
|
@@ -17,7 +17,7 @@ from cellestial.single.core import (
|
|
|
17
17
|
violin,
|
|
18
18
|
violins,
|
|
19
19
|
)
|
|
20
|
-
from cellestial.single.differential import volcano, volcanos
|
|
20
|
+
from cellestial.single.differential import markers, volcano, volcanos
|
|
21
21
|
from cellestial.single.heatmap import dotplot, heatmap, matrixplot, stacked_violin
|
|
22
22
|
from cellestial.single.quick import elbow, highest_expressed_genes, ridge, ridges
|
|
23
23
|
|
|
@@ -33,6 +33,7 @@ __all__ = [
|
|
|
33
33
|
"expressions",
|
|
34
34
|
"heatmap",
|
|
35
35
|
"highest_expressed_genes",
|
|
36
|
+
"markers",
|
|
36
37
|
"matrixplot",
|
|
37
38
|
"pca",
|
|
38
39
|
"pcas",
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import polars as pl
|
|
8
|
+
from anndata import AnnData
|
|
9
|
+
from lets_plot import (
|
|
10
|
+
aes,
|
|
11
|
+
element_text,
|
|
12
|
+
geom_path,
|
|
13
|
+
geom_text,
|
|
14
|
+
gggrid,
|
|
15
|
+
ggplot,
|
|
16
|
+
ggtb,
|
|
17
|
+
ggtitle,
|
|
18
|
+
labs,
|
|
19
|
+
scale_color_gradient,
|
|
20
|
+
scale_y_continuous,
|
|
21
|
+
theme,
|
|
22
|
+
theme_bw,
|
|
23
|
+
)
|
|
24
|
+
from lets_plot.plot.core import FeatureSpec, LayerSpec
|
|
25
|
+
|
|
26
|
+
from cellestial.single.heatmap._rank_genes_groups import _resolve_rank_genes_groups_key
|
|
27
|
+
from cellestial.util import _share_axis, _share_labels
|
|
28
|
+
from cellestial.util.errors import KeyNotFoundError, UnsupportedDataTypeError
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from lets_plot.plot.subplots import SupPlotsSpec
|
|
32
|
+
from polars import DataFrame
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _build_markers_frame(
|
|
36
|
+
data: AnnData,
|
|
37
|
+
*,
|
|
38
|
+
key: bool | str,
|
|
39
|
+
n_genes: int,
|
|
40
|
+
groups: Sequence[str] | None,
|
|
41
|
+
variable_column: str,
|
|
42
|
+
score_column: str,
|
|
43
|
+
rank_column: str,
|
|
44
|
+
group_column: str,
|
|
45
|
+
) -> tuple[DataFrame, list[str], str]:
|
|
46
|
+
"""
|
|
47
|
+
Build a long-form frame of the top-N ranked genes and scores per group.
|
|
48
|
+
|
|
49
|
+
Reads ``data.uns[key]`` (scanpy's ``rank_genes_groups`` convention) and
|
|
50
|
+
pulls the top ``n_genes`` gene names and their scores for each selected
|
|
51
|
+
group. Unlike the heatmap extractor, genes are kept per group with no
|
|
52
|
+
cross-group de-duplication, since each group is drawn in its own panel.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
data : AnnData
|
|
57
|
+
Source object that already holds a ``rank_genes_groups`` result.
|
|
58
|
+
key : bool | str
|
|
59
|
+
``True`` reads the default ``rank_genes_groups`` key; a string reads
|
|
60
|
+
the matching custom key (e.g. ``"rank_genes_groups_wilcoxon"``).
|
|
61
|
+
n_genes : int
|
|
62
|
+
Number of top genes to pull per group.
|
|
63
|
+
groups : Sequence[str] | None
|
|
64
|
+
Subset of groups to include, in order. ``None`` keeps all groups in
|
|
65
|
+
their stored order.
|
|
66
|
+
variable_column : str
|
|
67
|
+
Output column name for the gene/feature names.
|
|
68
|
+
score_column : str
|
|
69
|
+
Output column name for the ranking scores.
|
|
70
|
+
rank_column : str
|
|
71
|
+
Output column name for the per-group rank index (0-based).
|
|
72
|
+
group_column : str
|
|
73
|
+
Output column name for the group label.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
frame : DataFrame
|
|
78
|
+
Long-form polars DataFrame with one row per (group, gene).
|
|
79
|
+
selected : list[str]
|
|
80
|
+
The selected group labels, in plotting order.
|
|
81
|
+
group_by : str
|
|
82
|
+
The categorical key used when ``rank_genes_groups`` was computed.
|
|
83
|
+
|
|
84
|
+
Raises
|
|
85
|
+
------
|
|
86
|
+
UnsupportedDataTypeError
|
|
87
|
+
If `data` is not a supported single-cell data object.
|
|
88
|
+
KeyNotFoundError
|
|
89
|
+
If the ranking result, a requested group, or the stored ``groupby``
|
|
90
|
+
is missing.
|
|
91
|
+
ValueError
|
|
92
|
+
If `n_genes` is out of range.
|
|
93
|
+
"""
|
|
94
|
+
if n_genes < 1:
|
|
95
|
+
msg = f"`n_genes` must be >= 1, got {n_genes}."
|
|
96
|
+
raise ValueError(msg)
|
|
97
|
+
|
|
98
|
+
uns_key = _resolve_rank_genes_groups_key(key)
|
|
99
|
+
|
|
100
|
+
if isinstance(data, AnnData):
|
|
101
|
+
if uns_key not in data.uns:
|
|
102
|
+
msg = (
|
|
103
|
+
f"`adata.uns[{uns_key!r}]` not found. "
|
|
104
|
+
"Run `scanpy.tl.rank_genes_groups` first "
|
|
105
|
+
"(or pass the correct `key_added` value as `key`)."
|
|
106
|
+
)
|
|
107
|
+
raise KeyNotFoundError(msg)
|
|
108
|
+
|
|
109
|
+
record = data.uns[uns_key]
|
|
110
|
+
if "names" not in record or "scores" not in record or "params" not in record:
|
|
111
|
+
msg = (
|
|
112
|
+
f"`adata.uns[{uns_key!r}]` does not look like a "
|
|
113
|
+
"`rank_genes_groups` result (missing 'names', 'scores', or 'params')."
|
|
114
|
+
)
|
|
115
|
+
raise KeyNotFoundError(msg)
|
|
116
|
+
|
|
117
|
+
names = record["names"]
|
|
118
|
+
scores = record["scores"]
|
|
119
|
+
available_groups = list(names.dtype.names)
|
|
120
|
+
|
|
121
|
+
if groups is None:
|
|
122
|
+
selected = available_groups
|
|
123
|
+
else:
|
|
124
|
+
selected = list(groups)
|
|
125
|
+
unknown = [group for group in selected if group not in available_groups]
|
|
126
|
+
if unknown:
|
|
127
|
+
msg = (
|
|
128
|
+
f"Groups {unknown!r} not found in "
|
|
129
|
+
f"`adata.uns[{uns_key!r}]`. Available: {available_groups!r}."
|
|
130
|
+
)
|
|
131
|
+
raise KeyNotFoundError(msg)
|
|
132
|
+
|
|
133
|
+
n_available = len(names)
|
|
134
|
+
if n_genes > n_available:
|
|
135
|
+
msg = (
|
|
136
|
+
f"`n_genes={n_genes}` exceeds the {n_available} ranked genes "
|
|
137
|
+
f"stored in `adata.uns[{uns_key!r}]`."
|
|
138
|
+
)
|
|
139
|
+
raise ValueError(msg)
|
|
140
|
+
|
|
141
|
+
ranks = np.arange(n_genes)
|
|
142
|
+
group_frames = [
|
|
143
|
+
pl.DataFrame(
|
|
144
|
+
{
|
|
145
|
+
group_column: group,
|
|
146
|
+
rank_column: ranks,
|
|
147
|
+
variable_column: [str(name) for name in names[group][:n_genes]],
|
|
148
|
+
score_column: np.asarray(scores[group][:n_genes], dtype=np.float64),
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
for group in selected
|
|
152
|
+
]
|
|
153
|
+
frame = pl.concat(group_frames)
|
|
154
|
+
|
|
155
|
+
params = record["params"]
|
|
156
|
+
stored_group_by = params["groupby"] if "groupby" in params else None
|
|
157
|
+
if stored_group_by is None:
|
|
158
|
+
msg = (
|
|
159
|
+
f"`adata.uns[{uns_key!r}]['params']` is missing 'groupby'; "
|
|
160
|
+
"cannot infer `group_by`."
|
|
161
|
+
)
|
|
162
|
+
raise KeyNotFoundError(msg)
|
|
163
|
+
|
|
164
|
+
# Round scores to keep the embedded JSON compact when the frame is
|
|
165
|
+
# serialized into the plot output.
|
|
166
|
+
frame = frame.with_columns(pl.col(score_column).round(4))
|
|
167
|
+
|
|
168
|
+
return frame, list(selected), str(stored_group_by)
|
|
169
|
+
|
|
170
|
+
msg = f"Unsupported data type: `{type(data)}`"
|
|
171
|
+
raise UnsupportedDataTypeError(msg)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def markers(
|
|
175
|
+
data: AnnData,
|
|
176
|
+
groups: Sequence[str] | None = None,
|
|
177
|
+
*,
|
|
178
|
+
key: str = "rank_genes_groups",
|
|
179
|
+
n_genes: int = 20,
|
|
180
|
+
mapping: FeatureSpec | None = None,
|
|
181
|
+
text_color: str = "#1f1f1f",
|
|
182
|
+
text_size: float = 4.0,
|
|
183
|
+
fontface: str = "bold",
|
|
184
|
+
angle: float = 90.0,
|
|
185
|
+
rank_color: bool = False,
|
|
186
|
+
line: bool = False,
|
|
187
|
+
line_color: str = "#3f3f3f",
|
|
188
|
+
line_size: float = 0.4,
|
|
189
|
+
line_alpha: float = 0.6,
|
|
190
|
+
line_kwargs: dict | None = None,
|
|
191
|
+
variable_column: str = "variable",
|
|
192
|
+
score_column: str = "score",
|
|
193
|
+
rank_column: str = "rank",
|
|
194
|
+
group_column: str = "group",
|
|
195
|
+
interactive: bool = False,
|
|
196
|
+
# multi plot args
|
|
197
|
+
share_labels: bool = True,
|
|
198
|
+
share_axis: bool = False,
|
|
199
|
+
layers: Sequence[FeatureSpec | LayerSpec] | FeatureSpec | LayerSpec | None = None,
|
|
200
|
+
# grid args
|
|
201
|
+
ncol: int | None = 3,
|
|
202
|
+
sharex: str | None = None,
|
|
203
|
+
sharey: str | None = None,
|
|
204
|
+
widths: list[float] | None = None,
|
|
205
|
+
heights: list[float] | None = None,
|
|
206
|
+
hspace: float | None = None,
|
|
207
|
+
vspace: float | None = None,
|
|
208
|
+
fit: bool | None = None,
|
|
209
|
+
align: bool | None = None,
|
|
210
|
+
guides: str = "auto",
|
|
211
|
+
**text_kwargs,
|
|
212
|
+
) -> SupPlotsSpec:
|
|
213
|
+
"""
|
|
214
|
+
Grid of ranked genes per group.
|
|
215
|
+
|
|
216
|
+
Builds one panel per group from a precomputed ranking, placing the top
|
|
217
|
+
``n_genes`` gene names as text at their ``(rank, score)`` position. Each
|
|
218
|
+
panel is titled ``"{group} vs. rest"``.
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
data : AnnData
|
|
223
|
+
The AnnData object holding the differential expression results
|
|
224
|
+
in ``data.uns[key]`` (scanpy's ``rank_genes_groups`` convention).
|
|
225
|
+
groups : Sequence[str] | None, default=None
|
|
226
|
+
Subset of groups to plot, one panel per group. ``None`` keeps all
|
|
227
|
+
groups in their stored order.
|
|
228
|
+
key : str, default='rank_genes_groups'
|
|
229
|
+
The key under ``data.uns`` storing the differential expression results.
|
|
230
|
+
n_genes : int, default=20
|
|
231
|
+
Number of top genes to show per panel.
|
|
232
|
+
mapping : FeatureSpec | None, default=None
|
|
233
|
+
Additional aesthetic mappings, the result of `aes()`. Merged on top of
|
|
234
|
+
the default ``aes(x=rank, y=score, label=variable)``.
|
|
235
|
+
text_color : str, default='#1f1f1f'
|
|
236
|
+
Color of the gene-name text.
|
|
237
|
+
text_size : float, default=4.0
|
|
238
|
+
Size of the gene-name text.
|
|
239
|
+
fontface : str, default='bold'
|
|
240
|
+
Font face of the gene-name text (e.g. 'plain', 'bold', 'italic').
|
|
241
|
+
angle : float, default=90.0
|
|
242
|
+
Rotation angle of the gene-name text, in degrees.
|
|
243
|
+
rank_color : bool, default=False
|
|
244
|
+
Whether to color the gene-name text by rank. When True, the best-ranked
|
|
245
|
+
gene is pure red, fading toward light gray for the lowest-ranked.
|
|
246
|
+
line : bool, default=False
|
|
247
|
+
Whether to draw a dashed path connecting the genes' ``(rank, score)``
|
|
248
|
+
points, showing the score decay across the ranking.
|
|
249
|
+
line_color : str, default='#3f3f3f'
|
|
250
|
+
Color of the dashed score-curve line.
|
|
251
|
+
line_size : float, default=0.4
|
|
252
|
+
Size (thickness) of the dashed score-curve line.
|
|
253
|
+
line_alpha : float, default=0.6
|
|
254
|
+
Alpha (opacity) of the dashed score-curve line.
|
|
255
|
+
line_kwargs : dict | None, default=None
|
|
256
|
+
Additional parameters passed to the score-curve ``geom_path`` layer.
|
|
257
|
+
variable_column : str, default='variable'
|
|
258
|
+
Output column name for the gene/feature names.
|
|
259
|
+
score_column : str, default='score'
|
|
260
|
+
Output column name for the ranking scores.
|
|
261
|
+
rank_column : str, default='rank'
|
|
262
|
+
Output column name for the per-group rank index (0-based).
|
|
263
|
+
group_column : str, default='group'
|
|
264
|
+
Output column name for the group label.
|
|
265
|
+
interactive : bool, default=False
|
|
266
|
+
Whether to make the plot interactive.
|
|
267
|
+
share_labels : bool, default=True
|
|
268
|
+
Whether to share axis labels across the grid.
|
|
269
|
+
If True, only X labels on bottom row and Y labels on left column are shown.
|
|
270
|
+
share_axis : bool, default=False
|
|
271
|
+
Whether to share axes across the grid.
|
|
272
|
+
If True, only X axis on bottom row and Y axis on left column is shown.
|
|
273
|
+
layers : Sequence[FeatureSpec|LayerSpec] | FeatureSpec | LayerSpec | None, default=None
|
|
274
|
+
Layers to add to all the plots in the grid.
|
|
275
|
+
ncol : int, default=3
|
|
276
|
+
Number of columns in grid. If None, shows plots horizontally, in one row.
|
|
277
|
+
sharex, sharey : str | None, default=None
|
|
278
|
+
Controls sharing of axis limits between subplots in the grid.
|
|
279
|
+
See :func:`lets_plot.gggrid`.
|
|
280
|
+
widths : list[float], default=None
|
|
281
|
+
Relative width of each column of the grid.
|
|
282
|
+
heights : list[float], default=None
|
|
283
|
+
Relative height of each row of the grid.
|
|
284
|
+
hspace, vspace : float | None, default=None
|
|
285
|
+
Cell horizontal/vertical spacing in px.
|
|
286
|
+
fit : bool, default=None
|
|
287
|
+
Whether to stretch each plot to match the aspect ratio of its cell.
|
|
288
|
+
align : bool, default=None
|
|
289
|
+
Whether to align inner areas of plots.
|
|
290
|
+
guides : str, default='auto'
|
|
291
|
+
How guides (legends/colorbars) should be treated in the layout.
|
|
292
|
+
See :func:`lets_plot.gggrid`.
|
|
293
|
+
**text_kwargs
|
|
294
|
+
Additional parameters for the ``geom_text`` layer of each panel.
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
SupPlotsSpec
|
|
299
|
+
Grid of ranked-genes panels, one per group.
|
|
300
|
+
|
|
301
|
+
Raises
|
|
302
|
+
------
|
|
303
|
+
UnsupportedDataTypeError
|
|
304
|
+
If `data` is not a supported single-cell data object.
|
|
305
|
+
KeyNotFoundError
|
|
306
|
+
If the ranking result or a requested group is missing.
|
|
307
|
+
ValueError
|
|
308
|
+
If `n_genes` is out of range.
|
|
309
|
+
TypeError
|
|
310
|
+
If `groups` is neither a Sequence of strings nor None.
|
|
311
|
+
|
|
312
|
+
Examples
|
|
313
|
+
--------
|
|
314
|
+
A simple grid of ranked genes per group, with optional score-curve lines.
|
|
315
|
+
|
|
316
|
+
.. jupyter-execute::
|
|
317
|
+
|
|
318
|
+
import scanpy as sc
|
|
319
|
+
|
|
320
|
+
import cellestial as cl
|
|
321
|
+
|
|
322
|
+
data = sc.datasets.pbmc68k_reduced()
|
|
323
|
+
|
|
324
|
+
cl.markers(data, line=True)
|
|
325
|
+
|
|
326
|
+
Color the gene names by rank.
|
|
327
|
+
|
|
328
|
+
.. jupyter-execute::
|
|
329
|
+
|
|
330
|
+
cl.markers(data, rank_color=True)
|
|
331
|
+
|
|
332
|
+
Select a subset of groups.
|
|
333
|
+
|
|
334
|
+
.. jupyter-execute::
|
|
335
|
+
|
|
336
|
+
cl.markers(
|
|
337
|
+
data,
|
|
338
|
+
groups=["CD14+ Monocyte", "CD4+/CD25 T Reg", "CD4+/CD45RO+ Memory", "CD34+"],
|
|
339
|
+
ncol=2,
|
|
340
|
+
rank_color=True,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
"""
|
|
344
|
+
# HANDLE: Data types
|
|
345
|
+
if not isinstance(data, AnnData):
|
|
346
|
+
msg = f"Unsupported data type: `{type(data)}`"
|
|
347
|
+
raise UnsupportedDataTypeError(msg)
|
|
348
|
+
|
|
349
|
+
if groups is not None and (not isinstance(groups, Sequence) or isinstance(groups, str)):
|
|
350
|
+
msg = "`groups` must be a Sequence of strings or None"
|
|
351
|
+
raise TypeError(msg)
|
|
352
|
+
|
|
353
|
+
# BUILD: long-form frame via the helper
|
|
354
|
+
frame, selected_groups, _ = _build_markers_frame(
|
|
355
|
+
data=data,
|
|
356
|
+
key=key,
|
|
357
|
+
n_genes=n_genes,
|
|
358
|
+
groups=groups,
|
|
359
|
+
variable_column=variable_column,
|
|
360
|
+
score_column=score_column,
|
|
361
|
+
rank_column=rank_column,
|
|
362
|
+
group_column=group_column,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# DEFINE: mapping with defaults
|
|
366
|
+
_mapping = {
|
|
367
|
+
"x": rank_column,
|
|
368
|
+
"y": score_column,
|
|
369
|
+
"label": variable_column,
|
|
370
|
+
}
|
|
371
|
+
if rank_color:
|
|
372
|
+
_mapping["color"] = rank_column
|
|
373
|
+
if mapping is not None:
|
|
374
|
+
_mapping.update(mapping.as_dict())
|
|
375
|
+
|
|
376
|
+
# BUILD: one panel per group
|
|
377
|
+
plots = []
|
|
378
|
+
for i, group in enumerate(selected_groups):
|
|
379
|
+
group_frame = frame.filter(pl.col(group_column) == group)
|
|
380
|
+
plot = ggplot(group_frame)
|
|
381
|
+
|
|
382
|
+
# ADD: optional dashed score-curve path (drawn under the text)
|
|
383
|
+
if line:
|
|
384
|
+
_line_kwargs = {
|
|
385
|
+
"color": line_color,
|
|
386
|
+
"size": line_size,
|
|
387
|
+
"alpha": line_alpha,
|
|
388
|
+
"linetype": "dashed",
|
|
389
|
+
} | (line_kwargs or {})
|
|
390
|
+
plot += geom_path(aes(x=rank_column, y=score_column), **_line_kwargs)
|
|
391
|
+
|
|
392
|
+
# A constant `color=` would override the rank color aesthetic, so only
|
|
393
|
+
# set it when not coloring by rank.
|
|
394
|
+
_text_color = {} if rank_color else {"color": text_color}
|
|
395
|
+
plot += geom_text(
|
|
396
|
+
aes(**_mapping),
|
|
397
|
+
size=text_size,
|
|
398
|
+
fontface=fontface,
|
|
399
|
+
angle=angle,
|
|
400
|
+
**_text_color,
|
|
401
|
+
**text_kwargs,
|
|
402
|
+
)
|
|
403
|
+
if rank_color:
|
|
404
|
+
# rank 0 (best) is pure red, fading toward light gray for the rest.
|
|
405
|
+
plot += scale_color_gradient(low="#ff0000", high="#e6e6e6")
|
|
406
|
+
# Gene names run vertically (8-10 chars), so expand the y range to keep
|
|
407
|
+
# the top- and bottom-most labels from being clipped.
|
|
408
|
+
plot += scale_y_continuous(expand=[0.25, 0.0])
|
|
409
|
+
plot += labs(x="ranking", y="score")
|
|
410
|
+
plot += ggtitle(f"{group} vs. rest")
|
|
411
|
+
plot += theme(
|
|
412
|
+
plot_title=element_text(size=11, hjust=0.5, family="Georgia"),
|
|
413
|
+
legend_position="none",
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# handle the layers
|
|
417
|
+
if layers is not None:
|
|
418
|
+
if isinstance(layers, (FeatureSpec, LayerSpec)):
|
|
419
|
+
layers = [layers]
|
|
420
|
+
for layer in layers:
|
|
421
|
+
plot += layer
|
|
422
|
+
if share_labels:
|
|
423
|
+
plot = _share_labels(plot, i, selected_groups, ncol)
|
|
424
|
+
if share_axis:
|
|
425
|
+
plot = _share_axis(plot, i, selected_groups, ncol, "axis")
|
|
426
|
+
|
|
427
|
+
plots.append(plot)
|
|
428
|
+
|
|
429
|
+
grid = gggrid(
|
|
430
|
+
plots,
|
|
431
|
+
ncol=ncol, # ty:ignore[invalid-argument-type]
|
|
432
|
+
sharex=sharex, # ty:ignore[invalid-argument-type]
|
|
433
|
+
sharey=sharey, # ty:ignore[invalid-argument-type]
|
|
434
|
+
widths=widths, # ty:ignore[invalid-argument-type]
|
|
435
|
+
heights=heights, # ty:ignore[invalid-argument-type]
|
|
436
|
+
hspace=hspace, # ty:ignore[invalid-argument-type]
|
|
437
|
+
vspace=vspace, # ty:ignore[invalid-argument-type]
|
|
438
|
+
fit=fit, # ty:ignore[invalid-argument-type]
|
|
439
|
+
align=align, # ty:ignore[invalid-argument-type]
|
|
440
|
+
guides=guides,
|
|
441
|
+
) + theme_bw()
|
|
442
|
+
|
|
443
|
+
if interactive:
|
|
444
|
+
grid += ggtb(size_zoomin=-1)
|
|
445
|
+
|
|
446
|
+
return grid
|
|
@@ -253,9 +253,13 @@ def _share_labels(plot, i: int, keys: Sequence[str], ncol: int | None) -> SupPlo
|
|
|
253
253
|
nrow = ceil(total / ncol)
|
|
254
254
|
left_places = [i for i in range(total) if i % ncol == 0]
|
|
255
255
|
bottom_places = [i for i in range(total) if i >= ncol * (nrow - 1)]
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
# the last grid row may be incomplete; for the columns it does not cover,
|
|
257
|
+
# the bottom-most plot lives in the penultimate row.
|
|
258
|
+
last_row_count = total - ncol * (nrow - 1)
|
|
259
|
+
if nrow >= 2 and last_row_count < ncol:
|
|
260
|
+
bottom_places.extend(
|
|
261
|
+
ncol * (nrow - 2) + col for col in range(last_row_count, ncol)
|
|
262
|
+
)
|
|
259
263
|
if i not in bottom_places: # remove x axis title except for bottom row
|
|
260
264
|
plot = plot + theme(axis_title_x=element_blank())
|
|
261
265
|
if i not in left_places: # remove y axis title except for left column
|
|
@@ -273,9 +277,13 @@ def _share_axis(
|
|
|
273
277
|
nrow = ceil(total / ncol)
|
|
274
278
|
left_places = [i for i in range(total) if i % ncol == 0]
|
|
275
279
|
bottom_places = [i for i in range(total) if i >= ncol * (nrow - 1)]
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
# the last grid row may be incomplete; for the columns it does not cover,
|
|
281
|
+
# the bottom-most plot lives in the penultimate row.
|
|
282
|
+
last_row_count = total - ncol * (nrow - 1)
|
|
283
|
+
if nrow >= 2 and last_row_count < ncol:
|
|
284
|
+
bottom_places.extend(
|
|
285
|
+
ncol * (nrow - 2) + col for col in range(last_row_count, ncol)
|
|
286
|
+
)
|
|
279
287
|
|
|
280
288
|
if axis_type == "axis":
|
|
281
289
|
if i not in bottom_places: # remove x axis title except for bottom row
|
|
@@ -308,9 +316,13 @@ def _share_ticks(plot, i: int, keys: Sequence[str], ncol: int | None) -> SupPlot
|
|
|
308
316
|
nrow = ceil(total / ncol)
|
|
309
317
|
left_places = [i for i in range(total) if i % ncol == 0]
|
|
310
318
|
bottom_places = [i for i in range(total) if i >= ncol * (nrow - 1)]
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
319
|
+
# the last grid row may be incomplete; for the columns it does not cover,
|
|
320
|
+
# the bottom-most plot lives in the penultimate row.
|
|
321
|
+
last_row_count = total - ncol * (nrow - 1)
|
|
322
|
+
if nrow >= 2 and last_row_count < ncol:
|
|
323
|
+
bottom_places.extend(
|
|
324
|
+
ncol * (nrow - 2) + col for col in range(last_row_count, ncol)
|
|
325
|
+
)
|
|
314
326
|
if i not in bottom_places: # remove x axis title except for bottom row
|
|
315
327
|
plot = plot + theme(axis_text_x=element_blank())
|
|
316
328
|
if i not in left_places: # remove y axis title except for left column
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cellestial"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.45.0"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Zaf4", email = "zafermolbio@gmail.com" },
|
|
@@ -40,6 +40,7 @@ pandas = "2.2.2" # pinned to avoid 3.0
|
|
|
40
40
|
tqdm = "^4.67.3"
|
|
41
41
|
pytest = "^9.0.3"
|
|
42
42
|
pytest-cov = "^7.1.0"
|
|
43
|
+
nbstripout = "^0.9.1"
|
|
43
44
|
|
|
44
45
|
[tool.poetry.group.docs.dependencies]
|
|
45
46
|
sphinx = ">=8.0.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|