sclab 0.1.7__py3-none-any.whl → 0.3.4__py3-none-any.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.
- sclab/__init__.py +3 -1
- sclab/_io.py +83 -12
- sclab/_methods_registry.py +65 -0
- sclab/_sclab.py +241 -21
- sclab/dataset/_dataset.py +4 -6
- sclab/dataset/processor/_processor.py +41 -19
- sclab/dataset/processor/_results_panel.py +94 -0
- sclab/dataset/processor/step/_processor_step_base.py +12 -6
- sclab/examples/processor_steps/__init__.py +8 -0
- sclab/examples/processor_steps/_cluster.py +2 -2
- sclab/examples/processor_steps/_differential_expression.py +329 -0
- sclab/examples/processor_steps/_doublet_detection.py +68 -0
- sclab/examples/processor_steps/_gene_expression.py +125 -0
- sclab/examples/processor_steps/_integration.py +116 -0
- sclab/examples/processor_steps/_neighbors.py +26 -6
- sclab/examples/processor_steps/_pca.py +13 -8
- sclab/examples/processor_steps/_preprocess.py +52 -25
- sclab/examples/processor_steps/_qc.py +24 -8
- sclab/examples/processor_steps/_umap.py +2 -2
- sclab/gui/__init__.py +0 -0
- sclab/gui/components/__init__.py +7 -0
- sclab/gui/components/_guided_pseudotime.py +482 -0
- sclab/gui/components/_transfer_metadata.py +186 -0
- sclab/methods/__init__.py +50 -0
- sclab/preprocess/__init__.py +26 -0
- sclab/preprocess/_cca.py +176 -0
- sclab/preprocess/_cca_integrate.py +109 -0
- sclab/preprocess/_filter_obs.py +42 -0
- sclab/preprocess/_harmony.py +421 -0
- sclab/preprocess/_harmony_integrate.py +53 -0
- sclab/preprocess/_normalize_weighted.py +65 -0
- sclab/preprocess/_pca.py +51 -0
- sclab/preprocess/_preprocess.py +155 -0
- sclab/preprocess/_qc.py +38 -0
- sclab/preprocess/_rpca.py +116 -0
- sclab/preprocess/_subset.py +208 -0
- sclab/preprocess/_transfer_metadata.py +196 -0
- sclab/preprocess/_transform.py +82 -0
- sclab/preprocess/_utils.py +96 -0
- sclab/scanpy/__init__.py +0 -0
- sclab/scanpy/_compat.py +92 -0
- sclab/scanpy/_settings.py +526 -0
- sclab/scanpy/logging.py +290 -0
- sclab/scanpy/plotting/__init__.py +0 -0
- sclab/scanpy/plotting/_rcmod.py +73 -0
- sclab/scanpy/plotting/palettes.py +221 -0
- sclab/scanpy/readwrite.py +1108 -0
- sclab/tools/__init__.py +0 -0
- sclab/tools/cellflow/__init__.py +0 -0
- sclab/tools/cellflow/density_dynamics/__init__.py +0 -0
- sclab/tools/cellflow/density_dynamics/_density_dynamics.py +349 -0
- sclab/tools/cellflow/pseudotime/__init__.py +0 -0
- sclab/tools/cellflow/pseudotime/_pseudotime.py +336 -0
- sclab/tools/cellflow/pseudotime/timeseries.py +226 -0
- sclab/tools/cellflow/utils/__init__.py +0 -0
- sclab/tools/cellflow/utils/density_nd.py +215 -0
- sclab/tools/cellflow/utils/interpolate.py +334 -0
- sclab/tools/cellflow/utils/periodic_genes.py +106 -0
- sclab/tools/cellflow/utils/smoothen.py +124 -0
- sclab/tools/cellflow/utils/times.py +55 -0
- sclab/tools/differential_expression/__init__.py +7 -0
- sclab/tools/differential_expression/_pseudobulk_edger.py +309 -0
- sclab/tools/differential_expression/_pseudobulk_helpers.py +290 -0
- sclab/tools/differential_expression/_pseudobulk_limma.py +257 -0
- sclab/tools/doublet_detection/__init__.py +5 -0
- sclab/tools/doublet_detection/_scrublet.py +64 -0
- sclab/tools/embedding/__init__.py +0 -0
- sclab/tools/imputation/__init__.py +0 -0
- sclab/tools/imputation/_alra.py +135 -0
- sclab/tools/labeling/__init__.py +6 -0
- sclab/tools/labeling/sctype.py +233 -0
- sclab/tools/utils/__init__.py +5 -0
- sclab/tools/utils/_aggregate_and_filter.py +290 -0
- sclab/utils/__init__.py +5 -0
- sclab/utils/_write_excel.py +510 -0
- {sclab-0.1.7.dist-info → sclab-0.3.4.dist-info}/METADATA +29 -12
- sclab-0.3.4.dist-info/RECORD +93 -0
- {sclab-0.1.7.dist-info → sclab-0.3.4.dist-info}/WHEEL +1 -1
- sclab-0.3.4.dist-info/licenses/LICENSE +29 -0
- sclab-0.1.7.dist-info/RECORD +0 -30
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from ipywidgets import Box, Dropdown, Layout, Stack, VBox, link
|
|
2
|
+
|
|
3
|
+
from sclab.event import EventBroker, EventClient
|
|
4
|
+
|
|
5
|
+
# Create a layout with a bottom border to act as the horizontal line
|
|
6
|
+
hr_layout = Layout(
|
|
7
|
+
border="1px solid black", # 1px width, solid style, black color
|
|
8
|
+
margin="10px 0", # Add margin for spacing above and below
|
|
9
|
+
width="100%", # Extend the line across the full width
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Create a Box widget with the styled layout
|
|
13
|
+
hr = Box(layout=hr_layout)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _Results:
|
|
17
|
+
namespace: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ResultsPanel(VBox, EventClient):
|
|
21
|
+
available_results: Dropdown
|
|
22
|
+
results_stack: Stack
|
|
23
|
+
|
|
24
|
+
events: list[str] = [
|
|
25
|
+
# "rslt_add_result",
|
|
26
|
+
# "rslt_remove_result",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
broker: EventBroker,
|
|
32
|
+
):
|
|
33
|
+
EventClient.__init__(self, broker)
|
|
34
|
+
|
|
35
|
+
self.available_results = Dropdown(options={}, description="Category")
|
|
36
|
+
self.results_stack = Stack([])
|
|
37
|
+
|
|
38
|
+
link(
|
|
39
|
+
(self.available_results, "value"),
|
|
40
|
+
(self.results_stack, "selected_index"),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
VBox.__init__(
|
|
44
|
+
self,
|
|
45
|
+
[
|
|
46
|
+
self.available_results,
|
|
47
|
+
hr,
|
|
48
|
+
self.results_stack,
|
|
49
|
+
],
|
|
50
|
+
# layout=Layout(
|
|
51
|
+
# width="100%",
|
|
52
|
+
# grid_template_columns="150px auto",
|
|
53
|
+
# grid_template_areas=""" "available-results selected-results_stack" """,
|
|
54
|
+
# border="0px solid black",
|
|
55
|
+
# ),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def add_result(self, results: _Results):
|
|
59
|
+
current_stack = list(self.results_stack.children)
|
|
60
|
+
namespace = results.namespace
|
|
61
|
+
|
|
62
|
+
options: dict[str, int] = self.available_results.options
|
|
63
|
+
options = options.copy()
|
|
64
|
+
idx = options.get(namespace, len(options))
|
|
65
|
+
options[namespace] = idx
|
|
66
|
+
|
|
67
|
+
if len(current_stack) < idx + 1:
|
|
68
|
+
current_stack.append(results)
|
|
69
|
+
else:
|
|
70
|
+
current_stack[idx] = results
|
|
71
|
+
|
|
72
|
+
self.results_stack.children = tuple(current_stack)
|
|
73
|
+
self.available_results.options = options
|
|
74
|
+
|
|
75
|
+
def remove_result(self, name: str):
|
|
76
|
+
options: dict[str, int] = self.available_results.options
|
|
77
|
+
options = options.copy()
|
|
78
|
+
idx = options.pop(name)
|
|
79
|
+
|
|
80
|
+
current_stack = list(self.results_stack.children)
|
|
81
|
+
current_stack.pop(idx)
|
|
82
|
+
|
|
83
|
+
current_selection = self.results_stack.selected_index
|
|
84
|
+
if (
|
|
85
|
+
current_selection is not None
|
|
86
|
+
and current_selection > 0
|
|
87
|
+
and current_selection == idx
|
|
88
|
+
):
|
|
89
|
+
idx = current_selection - 1
|
|
90
|
+
self.results_stack.selected_index = idx
|
|
91
|
+
|
|
92
|
+
self.results_stack.children = tuple(current_stack)
|
|
93
|
+
self.available_results.options = options
|
|
94
|
+
self.available_results.value = idx
|
|
@@ -8,33 +8,36 @@ from ipywidgets.widgets.widget_description import DescriptionWidget
|
|
|
8
8
|
|
|
9
9
|
from ....event import EventClient
|
|
10
10
|
from .._processor import Processor
|
|
11
|
+
from .._results_panel import _Results
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class ProcessorStepBase(EventClient):
|
|
14
15
|
events: list[str] = None
|
|
15
16
|
parent: Processor
|
|
16
|
-
name: str
|
|
17
|
-
description: str
|
|
17
|
+
name: str = None
|
|
18
|
+
description: str = None
|
|
18
19
|
fixed_params: dict[str, Any]
|
|
19
20
|
variable_controls: dict[str, DescriptionWidget | ValueWidget]
|
|
20
21
|
output: Output
|
|
21
22
|
run_button: Button
|
|
22
23
|
controls_list: list[DescriptionWidget | ValueWidget | Button]
|
|
23
24
|
controls: VBox
|
|
25
|
+
results: _Results | None
|
|
26
|
+
order: int = 1000
|
|
24
27
|
|
|
25
28
|
run_button_description = "Run"
|
|
26
29
|
|
|
27
30
|
def __init__(
|
|
28
31
|
self,
|
|
29
32
|
parent: Processor,
|
|
30
|
-
name: str,
|
|
31
|
-
description: str,
|
|
32
33
|
fixed_params: dict[str, Any],
|
|
33
34
|
variable_controls: dict[str, DescriptionWidget | ValueWidget],
|
|
35
|
+
results: _Results | None = None,
|
|
34
36
|
):
|
|
37
|
+
assert self.name
|
|
38
|
+
assert self.description
|
|
39
|
+
|
|
35
40
|
self.parent = parent
|
|
36
|
-
self.name = name
|
|
37
|
-
self.description = description
|
|
38
41
|
self.fixed_params = fixed_params
|
|
39
42
|
self.variable_controls = variable_controls
|
|
40
43
|
|
|
@@ -56,6 +59,9 @@ class ProcessorStepBase(EventClient):
|
|
|
56
59
|
]
|
|
57
60
|
self.make_controls()
|
|
58
61
|
|
|
62
|
+
if results is not None:
|
|
63
|
+
self.results = results
|
|
64
|
+
parent.results_panel.add_result(self.results)
|
|
59
65
|
super().__init__(parent.broker)
|
|
60
66
|
|
|
61
67
|
def make_controls(self):
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
from ._cluster import Cluster
|
|
2
|
+
from ._differential_expression import DifferentialExpression
|
|
3
|
+
from ._doublet_detection import DoubletDetection
|
|
4
|
+
from ._gene_expression import GeneExpression
|
|
5
|
+
from ._integration import Integration
|
|
2
6
|
from ._neighbors import Neighbors
|
|
3
7
|
from ._pca import PCA
|
|
4
8
|
from ._preprocess import Preprocess
|
|
@@ -9,7 +13,11 @@ __all__ = [
|
|
|
9
13
|
"QC",
|
|
10
14
|
"Preprocess",
|
|
11
15
|
"PCA",
|
|
16
|
+
"Integration",
|
|
12
17
|
"Neighbors",
|
|
13
18
|
"UMAP",
|
|
14
19
|
"Cluster",
|
|
20
|
+
"DoubletDetection",
|
|
21
|
+
"GeneExpression",
|
|
22
|
+
"DifferentialExpression",
|
|
15
23
|
]
|
|
@@ -6,6 +6,8 @@ from sclab.dataset.processor.step import ProcessorStepBase
|
|
|
6
6
|
|
|
7
7
|
class Cluster(ProcessorStepBase):
|
|
8
8
|
parent: Processor
|
|
9
|
+
name: str = "cluster"
|
|
10
|
+
description: str = "Cluster"
|
|
9
11
|
|
|
10
12
|
def __init__(self, parent: Processor) -> None:
|
|
11
13
|
try:
|
|
@@ -21,8 +23,6 @@ class Cluster(ProcessorStepBase):
|
|
|
21
23
|
|
|
22
24
|
super().__init__(
|
|
23
25
|
parent=parent,
|
|
24
|
-
name="cluster",
|
|
25
|
-
description="Cluster",
|
|
26
26
|
fixed_params={},
|
|
27
27
|
variable_controls=variable_controls,
|
|
28
28
|
)
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
from typing import Any, Iterable, Literal
|
|
2
|
+
|
|
3
|
+
import itables
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from anndata import AnnData
|
|
7
|
+
from IPython.display import Markdown, display
|
|
8
|
+
from ipywidgets import Dropdown, Output, SelectMultiple, Text, ToggleButtons
|
|
9
|
+
from ipywidgets.widgets.valuewidget import ValueWidget
|
|
10
|
+
from ipywidgets.widgets.widget_box import VBox
|
|
11
|
+
from ipywidgets.widgets.widget_description import DescriptionWidget
|
|
12
|
+
from packaging.version import Version
|
|
13
|
+
|
|
14
|
+
from sclab.dataset import SCLabDataset
|
|
15
|
+
from sclab.dataset.processor import Processor
|
|
16
|
+
from sclab.dataset.processor.step import ProcessorStepBase
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DifferentialExpressionResults(VBox):
|
|
20
|
+
dataset: SCLabDataset
|
|
21
|
+
result_selector: Dropdown
|
|
22
|
+
group_selector: ToggleButtons
|
|
23
|
+
table_output: Output
|
|
24
|
+
namespace: str = "differential_expression"
|
|
25
|
+
|
|
26
|
+
def __init__(self, dataset: SCLabDataset):
|
|
27
|
+
self.dataset = dataset
|
|
28
|
+
self.result_selector = Dropdown(description="Analysis Name")
|
|
29
|
+
self.group_selector = ToggleButtons()
|
|
30
|
+
self.table_output = Output()
|
|
31
|
+
|
|
32
|
+
self.result_selector.observe(self._update_group_selector, "value")
|
|
33
|
+
self.result_selector.observe(self._update_table, "value")
|
|
34
|
+
self.group_selector.observe(self._update_table, "value")
|
|
35
|
+
|
|
36
|
+
super().__init__(
|
|
37
|
+
[
|
|
38
|
+
self.result_selector,
|
|
39
|
+
self.group_selector,
|
|
40
|
+
self.table_output,
|
|
41
|
+
]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
self.sync_results_list()
|
|
45
|
+
|
|
46
|
+
def sync_results_list(self, focus_result: str | None = None):
|
|
47
|
+
adata = self.dataset.adata
|
|
48
|
+
uns: dict[str, Any] = adata.uns
|
|
49
|
+
current_selection = self.result_selector.value
|
|
50
|
+
new_options = tuple(filter(lambda x: x.startswith(self.namespace), uns.keys()))
|
|
51
|
+
|
|
52
|
+
if focus_result is not None and focus_result in new_options:
|
|
53
|
+
current_selection = focus_result
|
|
54
|
+
elif current_selection not in new_options:
|
|
55
|
+
current_selection = None
|
|
56
|
+
|
|
57
|
+
self.result_selector.options = new_options
|
|
58
|
+
self.result_selector.value = current_selection
|
|
59
|
+
|
|
60
|
+
def _update_group_selector(self, *args, **kwargs):
|
|
61
|
+
selected_result = self.result_selector.value
|
|
62
|
+
uns: dict[str, Any] = self.dataset.adata.uns
|
|
63
|
+
gene_names: np.rec.recarray = uns[selected_result]["names"]
|
|
64
|
+
self.group_selector.options = ("all",) + gene_names.dtype.names
|
|
65
|
+
self.group_selector.value = "all"
|
|
66
|
+
|
|
67
|
+
def _update_table(self, *args, **kwargs):
|
|
68
|
+
selected_result = self.result_selector.value
|
|
69
|
+
selected_group = self.group_selector.value
|
|
70
|
+
|
|
71
|
+
adata = self.dataset.adata
|
|
72
|
+
params = adata.uns[selected_result]["params"]
|
|
73
|
+
|
|
74
|
+
groupby = params["groupby"]
|
|
75
|
+
reference = params["reference"]
|
|
76
|
+
table_name = f"{selected_result}_by_{groupby}_{selected_group}_vs_{reference}"
|
|
77
|
+
|
|
78
|
+
params_text = "Parameters:\n "
|
|
79
|
+
params_text += "\n ".join(f"{k}: {v}" for k, v in params.items())
|
|
80
|
+
params_text = f"```\n{params_text}\n```"
|
|
81
|
+
|
|
82
|
+
if "gene_name" in adata.var:
|
|
83
|
+
gene_symbols = "gene_name"
|
|
84
|
+
elif "name" in adata.var:
|
|
85
|
+
gene_symbols = "name"
|
|
86
|
+
elif "gene_symbol" in adata.var:
|
|
87
|
+
gene_symbols = "gene_symbol"
|
|
88
|
+
elif "symbol" in adata.var:
|
|
89
|
+
gene_symbols = "symbol"
|
|
90
|
+
else:
|
|
91
|
+
gene_symbols = None
|
|
92
|
+
|
|
93
|
+
group = selected_group if selected_group != "all" else None
|
|
94
|
+
df = _rank_genes_groups_df(
|
|
95
|
+
adata, group=group, key=selected_result, gene_symbols=gene_symbols
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
self.table_output.clear_output()
|
|
99
|
+
with self.table_output:
|
|
100
|
+
display(Markdown(f"## {table_name}"))
|
|
101
|
+
itables.show(
|
|
102
|
+
df,
|
|
103
|
+
buttons=[
|
|
104
|
+
"pageLength",
|
|
105
|
+
{
|
|
106
|
+
"extend": "colvis",
|
|
107
|
+
"collectionLayout": "fixed columns",
|
|
108
|
+
"popoverTitle": "Column visibility control",
|
|
109
|
+
},
|
|
110
|
+
"copyHtml5",
|
|
111
|
+
{"extend": "csvHtml5", "title": table_name},
|
|
112
|
+
],
|
|
113
|
+
columnDefs=[
|
|
114
|
+
{"visible": True, "targets": [0]},
|
|
115
|
+
{"visible": True, "targets": "_all"},
|
|
116
|
+
],
|
|
117
|
+
style="width:100%",
|
|
118
|
+
classes="display cell-border",
|
|
119
|
+
stateSave=False,
|
|
120
|
+
)
|
|
121
|
+
display(Markdown(params_text))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class DifferentialExpression(ProcessorStepBase):
|
|
125
|
+
parent: Processor
|
|
126
|
+
results: DifferentialExpressionResults
|
|
127
|
+
name: str = "differential_expression"
|
|
128
|
+
description: str = "Differential Expression"
|
|
129
|
+
|
|
130
|
+
def __init__(self, parent: Processor) -> None:
|
|
131
|
+
try:
|
|
132
|
+
import scanpy as sc # noqa: F401
|
|
133
|
+
except ImportError:
|
|
134
|
+
raise ImportError("Please install scanpy: `pip install scanpy`")
|
|
135
|
+
|
|
136
|
+
metadata = parent.dataset._metadata.select_dtypes(
|
|
137
|
+
include=["object", "category"]
|
|
138
|
+
)
|
|
139
|
+
groupby_options = (None,) + tuple(metadata.columns)
|
|
140
|
+
|
|
141
|
+
variable_controls: dict[str, DescriptionWidget | ValueWidget]
|
|
142
|
+
variable_controls = dict(
|
|
143
|
+
groupby=Dropdown(options=groupby_options, description="Group by"),
|
|
144
|
+
groups=SelectMultiple(description="Groups"),
|
|
145
|
+
reference=Dropdown(description="Reference"),
|
|
146
|
+
layer=Dropdown(
|
|
147
|
+
options=(None,) + tuple(parent.dataset.adata.layers.keys()),
|
|
148
|
+
value=None,
|
|
149
|
+
description="Layer",
|
|
150
|
+
),
|
|
151
|
+
name=Text(description="Name", value="", continuous_update=False),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
variable_controls["groupby"].observe(
|
|
155
|
+
self._update_groups_options, "value", "change"
|
|
156
|
+
)
|
|
157
|
+
variable_controls["groupby"].observe(
|
|
158
|
+
self._update_reference_options, "value", "change"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
results = DifferentialExpressionResults(parent.dataset)
|
|
162
|
+
super().__init__(
|
|
163
|
+
parent=parent,
|
|
164
|
+
fixed_params={},
|
|
165
|
+
variable_controls=variable_controls,
|
|
166
|
+
results=results,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def function(
|
|
170
|
+
self,
|
|
171
|
+
groupby: str,
|
|
172
|
+
groups: Iterable[str] | Literal["all"],
|
|
173
|
+
reference: str,
|
|
174
|
+
layer: str | None,
|
|
175
|
+
name: str | None,
|
|
176
|
+
):
|
|
177
|
+
import scanpy as sc
|
|
178
|
+
|
|
179
|
+
assert groupby
|
|
180
|
+
|
|
181
|
+
if not groups:
|
|
182
|
+
groups = "all"
|
|
183
|
+
|
|
184
|
+
key_added = "differential_expression"
|
|
185
|
+
if name:
|
|
186
|
+
key_added = f"{key_added}_{name}"
|
|
187
|
+
|
|
188
|
+
adata = self.parent.dataset.adata
|
|
189
|
+
uns: dict[str, Any] = adata.uns
|
|
190
|
+
if key_added in adata.uns:
|
|
191
|
+
related_names = list(filter(lambda x: x.startswith(key_added), uns.keys()))
|
|
192
|
+
key_added = f"{key_added}_{len(related_names) + 1}"
|
|
193
|
+
|
|
194
|
+
sc.tl.rank_genes_groups(
|
|
195
|
+
adata,
|
|
196
|
+
groupby,
|
|
197
|
+
groups=groups,
|
|
198
|
+
reference=reference,
|
|
199
|
+
layer=layer,
|
|
200
|
+
key_added=key_added,
|
|
201
|
+
pts=True,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
self.results.sync_results_list(focus_result=key_added)
|
|
205
|
+
|
|
206
|
+
def _update_groups_options(self, *args, **kwargs):
|
|
207
|
+
groupby = self.variable_controls["groupby"].value
|
|
208
|
+
metadata = self.parent.dataset._metadata
|
|
209
|
+
control: Dropdown = self.variable_controls["groups"]
|
|
210
|
+
|
|
211
|
+
if groupby is None:
|
|
212
|
+
control.options = ("",)
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
options = tuple(metadata[groupby].sort_values().unique())
|
|
216
|
+
control.options = options
|
|
217
|
+
|
|
218
|
+
def _update_reference_options(self, *args, **kwargs):
|
|
219
|
+
groupby = self.variable_controls["groupby"].value
|
|
220
|
+
metadata = self.parent.dataset._metadata
|
|
221
|
+
control: Dropdown = self.variable_controls["reference"]
|
|
222
|
+
|
|
223
|
+
if groupby is None:
|
|
224
|
+
control.options = ("",)
|
|
225
|
+
control.value = ""
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
options = ("rest",)
|
|
229
|
+
options += tuple(metadata[groupby].sort_values().unique())
|
|
230
|
+
|
|
231
|
+
current_value = control.value
|
|
232
|
+
control.options = options
|
|
233
|
+
if current_value not in control.options:
|
|
234
|
+
control.value = "rest"
|
|
235
|
+
else:
|
|
236
|
+
control.value = current_value
|
|
237
|
+
|
|
238
|
+
def dset_var_dataframe_change_callback(self, *args, **kwargs):
|
|
239
|
+
var_df = self.parent.dataset.adata.var
|
|
240
|
+
df = var_df.select_dtypes(include=["bool"])
|
|
241
|
+
options = {"": None, **{c: c for c in df.columns}}
|
|
242
|
+
|
|
243
|
+
control: Dropdown = self.variable_controls["mask_var"]
|
|
244
|
+
current_value = control.value
|
|
245
|
+
control.options = options
|
|
246
|
+
if current_value not in control.options:
|
|
247
|
+
control.value = None
|
|
248
|
+
else:
|
|
249
|
+
control.value = current_value
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# from scanpy 1.10.4
|
|
253
|
+
# scanpy/src/scanpy/get/get.py
|
|
254
|
+
def _rank_genes_groups_df(
|
|
255
|
+
adata: AnnData,
|
|
256
|
+
group: str | Iterable[str] | None,
|
|
257
|
+
*,
|
|
258
|
+
key: str = "rank_genes_groups",
|
|
259
|
+
pval_cutoff: float | None = None,
|
|
260
|
+
log2fc_min: float | None = None,
|
|
261
|
+
log2fc_max: float | None = None,
|
|
262
|
+
gene_symbols: str | None = None,
|
|
263
|
+
) -> pd.DataFrame:
|
|
264
|
+
"""\
|
|
265
|
+
Params
|
|
266
|
+
------
|
|
267
|
+
adata
|
|
268
|
+
Object to get results from.
|
|
269
|
+
group
|
|
270
|
+
Which group (as in :func:`scanpy.tl.rank_genes_groups`'s `groupby`
|
|
271
|
+
argument) to return results from. Can be a list. All groups are
|
|
272
|
+
returned if groups is `None`.
|
|
273
|
+
key
|
|
274
|
+
Key differential expression groups were stored under.
|
|
275
|
+
pval_cutoff
|
|
276
|
+
Return only adjusted p-values below the cutoff.
|
|
277
|
+
log2fc_min
|
|
278
|
+
Minimum logfc to return.
|
|
279
|
+
log2fc_max
|
|
280
|
+
Maximum logfc to return.
|
|
281
|
+
gene_symbols
|
|
282
|
+
Column name in `.var` DataFrame that stores gene symbols. Specifying
|
|
283
|
+
this will add that column to the returned dataframe.
|
|
284
|
+
|
|
285
|
+
"""
|
|
286
|
+
if isinstance(group, str):
|
|
287
|
+
group = [group]
|
|
288
|
+
if group is None:
|
|
289
|
+
group = list(adata.uns[key]["names"].dtype.names)
|
|
290
|
+
method = adata.uns[key]["params"]["method"]
|
|
291
|
+
if method == "logreg":
|
|
292
|
+
colnames = ["names", "scores"]
|
|
293
|
+
else:
|
|
294
|
+
colnames = ["names", "scores", "logfoldchanges", "pvals", "pvals_adj"]
|
|
295
|
+
|
|
296
|
+
d = [pd.DataFrame(adata.uns[key][c])[group] for c in colnames]
|
|
297
|
+
d = pd.concat(d, axis=1, names=[None, "group"], keys=colnames)
|
|
298
|
+
if Version(pd.__version__) >= Version("2.1"):
|
|
299
|
+
d = d.stack(level=1, future_stack=True).reset_index()
|
|
300
|
+
else:
|
|
301
|
+
d = d.stack(level=1).reset_index()
|
|
302
|
+
d["group"] = pd.Categorical(d["group"], categories=group)
|
|
303
|
+
d = d.sort_values(["group", "level_0"]).drop(columns="level_0")
|
|
304
|
+
|
|
305
|
+
if method != "logreg":
|
|
306
|
+
if pval_cutoff is not None:
|
|
307
|
+
d = d[d["pvals_adj"] < pval_cutoff]
|
|
308
|
+
if log2fc_min is not None:
|
|
309
|
+
d = d[d["logfoldchanges"] > log2fc_min]
|
|
310
|
+
if log2fc_max is not None:
|
|
311
|
+
d = d[d["logfoldchanges"] < log2fc_max]
|
|
312
|
+
if gene_symbols is not None:
|
|
313
|
+
d = d.join(adata.var[gene_symbols], on="names")
|
|
314
|
+
|
|
315
|
+
for pts, name in {"pts": "pct_nz_group", "pts_rest": "pct_nz_reference"}.items():
|
|
316
|
+
if pts in adata.uns[key]:
|
|
317
|
+
pts_df = (
|
|
318
|
+
adata.uns[key][pts][group]
|
|
319
|
+
.rename_axis(index="names")
|
|
320
|
+
.reset_index()
|
|
321
|
+
.melt(id_vars="names", var_name="group", value_name=name)
|
|
322
|
+
)
|
|
323
|
+
d = d.merge(pts_df)
|
|
324
|
+
|
|
325
|
+
# remove group column for backward compat if len(group) == 1
|
|
326
|
+
if len(group) == 1:
|
|
327
|
+
d.drop(columns="group", inplace=True)
|
|
328
|
+
|
|
329
|
+
return d.reset_index(drop=True)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from ipywidgets import Dropdown
|
|
2
|
+
|
|
3
|
+
from sclab.dataset.processor import Processor
|
|
4
|
+
from sclab.dataset.processor.step import ProcessorStepBase
|
|
5
|
+
from sclab.tools.doublet_detection import scrublet
|
|
6
|
+
|
|
7
|
+
# from sclab.tools.doublet_detection import doubletdetection
|
|
8
|
+
# from sclab.tools.doublet_detection import scdblfinder
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DoubletDetection(ProcessorStepBase):
|
|
12
|
+
parent: Processor
|
|
13
|
+
name: str = "doublet_detection"
|
|
14
|
+
description: str = "Doublet Detection"
|
|
15
|
+
|
|
16
|
+
def __init__(self, parent: Processor) -> None:
|
|
17
|
+
variable_controls = dict(
|
|
18
|
+
layer=Dropdown(
|
|
19
|
+
options=tuple(parent.dataset.adata.layers.keys()),
|
|
20
|
+
value=None,
|
|
21
|
+
description="Layer",
|
|
22
|
+
),
|
|
23
|
+
flavor=Dropdown(
|
|
24
|
+
options=[
|
|
25
|
+
"scrublet",
|
|
26
|
+
# "doubletdetection",
|
|
27
|
+
# "scDblFinder",
|
|
28
|
+
],
|
|
29
|
+
description="Flavor",
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
super().__init__(
|
|
34
|
+
parent=parent,
|
|
35
|
+
fixed_params={},
|
|
36
|
+
variable_controls=variable_controls,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def function(self, layer: str, flavor: str):
|
|
40
|
+
adata = self.parent.dataset.adata
|
|
41
|
+
|
|
42
|
+
kvargs = {"adata": adata, "layer": layer, "key_added": flavor}
|
|
43
|
+
|
|
44
|
+
self.broker.std_output.clear_output(wait=False)
|
|
45
|
+
with self.broker.std_output:
|
|
46
|
+
match flavor:
|
|
47
|
+
# case "scDblFinder":
|
|
48
|
+
# scdblfinder(**kvargs, clusters_col="leiden")
|
|
49
|
+
|
|
50
|
+
# case "doubletdetection":
|
|
51
|
+
# doubletdetection(
|
|
52
|
+
# **kvargs,
|
|
53
|
+
# pseudocount=1,
|
|
54
|
+
# clustering_algorithm="leiden",
|
|
55
|
+
# clustering_kwargs=dict(resolution=5.0),
|
|
56
|
+
# )
|
|
57
|
+
|
|
58
|
+
case "scrublet":
|
|
59
|
+
scrublet(**kvargs)
|
|
60
|
+
|
|
61
|
+
case _:
|
|
62
|
+
raise ValueError(f"Unknown flavor: {flavor}")
|
|
63
|
+
|
|
64
|
+
self.broker.publish(
|
|
65
|
+
"dset_metadata_change",
|
|
66
|
+
self.parent.dataset.metadata,
|
|
67
|
+
f"{flavor}_label",
|
|
68
|
+
)
|