opencode-skills-collection 4.1.7 → 4.1.8
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.
- package/bundled-skills/.antigravity-install-manifest.json +7 -1
- package/bundled-skills/court/SKILL.md +141 -0
- package/bundled-skills/court/references/jurors.json +14 -0
- package/bundled-skills/court/scripts/court.py +348 -0
- package/bundled-skills/darkmoon-pentest/SKILL.md +123 -0
- package/bundled-skills/lognorm/SKILL.md +86 -0
- package/bundled-skills/lognorm/references/playbooks.md +76 -0
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package-lock.json +6 -6
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package.json +1 -1
- package/bundled-skills/parallel-search-mcp/SKILL.md +12 -0
- package/bundled-skills/scarf-single-cell/SKILL.md +326 -0
- package/bundled-skills/scarf-single-cell/references/LICENSE.md +29 -0
- package/bundled-skills/scarf-single-cell/references/clustering-and-embedding.md +270 -0
- package/bundled-skills/scarf-single-cell/references/data-access.md +269 -0
- package/bundled-skills/scarf-single-cell/references/features-and-graphs.md +279 -0
- package/bundled-skills/scarf-single-cell/references/gene-blacklists.md +182 -0
- package/bundled-skills/scarf-single-cell/references/integration-and-comparisons.md +295 -0
- package/bundled-skills/scarf-single-cell/references/markers-and-annotation.md +280 -0
- package/bundled-skills/scarf-single-cell/references/performance-and-export.md +291 -0
- package/bundled-skills/scarf-single-cell/references/pipeline-runs-and-artifacts.md +273 -0
- package/bundled-skills/scarf-single-cell/references/plotting.md +260 -0
- package/bundled-skills/scarf-single-cell/references/quality-control.md +290 -0
- package/bundled-skills/session-handoff/SKILL.md +112 -0
- package/bundled-skills/shark/SKILL.md +134 -0
- package/bundled-skills/shark/references/investors.json +12 -0
- package/bundled-skills/shark/scripts/shark.py +302 -0
- package/bundled-skills/telegram/assets/boilerplate/nodejs/package-lock.json +6 -6
- package/bundled-skills/telegram/assets/boilerplate/nodejs/package.json +1 -1
- package/bundled-skills/whatsapp-cloud-api/assets/boilerplate/nodejs/package-lock.json +6 -6
- package/bundled-skills/whatsapp-cloud-api/assets/boilerplate/nodejs/package.json +1 -1
- package/package.json +1 -1
- package/skills_index.json +201 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# Features and graphs
|
|
2
|
+
|
|
3
|
+
Select features, normalize, reduce with PCA and build the neighbourhood graph as an explicit
|
|
4
|
+
chain of artifacts, then branch or subcluster it safely. Docs: <https://scarf.readthedocs.io/en/latest/tutorials/feature_selection.html>,
|
|
5
|
+
<https://scarf.readthedocs.io/en/latest/tutorials/graph_construction.html>, <https://scarf.readthedocs.io/en/latest/tutorials/dimensionality_reduction.html> and
|
|
6
|
+
<https://scarf.readthedocs.io/en/latest/reference/api/graph_construction.html>.
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
- A pipeline default (`hvg_count=1000`, `pca_dims=21`, `neighbors_k=11`) needs to change, or two
|
|
11
|
+
settings need comparing side by side.
|
|
12
|
+
- Building a graph on a custom cell selection (QC variant, subset) or feature list.
|
|
13
|
+
- Subclustering one lineage of a coarse partition with its own HVGs, PCA and graph.
|
|
14
|
+
- Diagnosing a graph before clustering or embedding.
|
|
15
|
+
|
|
16
|
+
## Key concepts
|
|
17
|
+
|
|
18
|
+
- RNA chain, each step returning an `ArtifactRef` that the next step consumes:
|
|
19
|
+
cell selection + `select_hvgs` (`feature_selection`) -> `run_normalization` (`normalized`) ->
|
|
20
|
+
`run_pca` (`reduction`) -> optional `run_harmony` -> `build_ann_index` (`ann_index`) ->
|
|
21
|
+
`query_neighbors` (`neighbors`) -> `build_connectivity_map` (`connectivity_map`).
|
|
22
|
+
`build_embedding_initialization(pca)` is a side branch that seeds UMAP and t-SNE only.
|
|
23
|
+
- Identity is operation + scientific parameters + exact inputs. An identical call returns the
|
|
24
|
+
existing ref without recomputing; a changed `k` reuses normalization, PCA and the ANN index.
|
|
25
|
+
Changing cells or features invalidates every downstream stage.
|
|
26
|
+
- `ds.pipeline.run(hvg_count=, pca_dims=, neighbors_k=)` forwards to `select_hvgs(top_n=)`,
|
|
27
|
+
`run_pca(dims=)` and `query_neighbors(k=)`. Explicit calls with the same arguments return the
|
|
28
|
+
very refs in `run[...]`. `pca_dims=0` skips PCA and builds the graph on normalized HVG values.
|
|
29
|
+
- RNA normalization: counts scaled to 1000 over the selected features, then `log1p`. PCA
|
|
30
|
+
standardizes features. HVGs are an artifact, not a feature column; the chain writes no metadata.
|
|
31
|
+
- Count passes: a new cell selection costs one pass for the feature summary (`select_hvgs`,
|
|
32
|
+
`select_detected_features`) and one per `run_normalization`; later stages read local artifacts.
|
|
33
|
+
|
|
34
|
+
## Recipes
|
|
35
|
+
|
|
36
|
+
### Rebuild the chain explicitly from a run
|
|
37
|
+
|
|
38
|
+
Start from any frozen cell selection: `run["analysis_cell_selection"]`, a QC ref, or
|
|
39
|
+
`ds.snapshot_cell_selection("I")`.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import numpy as np
|
|
43
|
+
import pandas as pd
|
|
44
|
+
import scarf
|
|
45
|
+
import scarf.plotting as splt
|
|
46
|
+
|
|
47
|
+
ds = scarf.DataStore("analysis.zarr", min_features_per_cell=-1) # reopen; see data-access.md
|
|
48
|
+
run = ds.pipeline.open(label="baseline")
|
|
49
|
+
cells = run["analysis_cell_selection"]
|
|
50
|
+
|
|
51
|
+
hvg = ds.select_hvgs(cells, top_n=1000, show_plot=False)
|
|
52
|
+
normalized = ds.run_normalization(cells, hvg)
|
|
53
|
+
pca = ds.run_pca(normalized, dims=21)
|
|
54
|
+
ann = ds.build_ann_index(pca)
|
|
55
|
+
neighbors = ds.query_neighbors(ann, k=11)
|
|
56
|
+
graph = ds.build_connectivity_map(neighbors)
|
|
57
|
+
init = ds.build_embedding_initialization(pca)
|
|
58
|
+
print({key: ref == run[key] for key, ref in {
|
|
59
|
+
"highly_variable_features": hvg, "normalized": normalized, "pca": pca, "ann_index": ann,
|
|
60
|
+
"neighbors": neighbors, "connectivity_map": graph, "embedding_initialization": init}.items()})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Feature-selection diagnostics
|
|
64
|
+
|
|
65
|
+
The HVG artifact stores `values` (selected mask) and `corrected_variance` for every feature.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from scarf.features.gene_families import GENE_FAMILY_PATTERNS
|
|
69
|
+
|
|
70
|
+
hvg_group = ds.load_artifact(hvg)
|
|
71
|
+
selected = np.asarray(hvg_group["values"][:], dtype=bool)
|
|
72
|
+
corrected = np.asarray(hvg_group["corrected_variance"][:])
|
|
73
|
+
names = ds.RNA.feats.fetch_all("names").astype(str)
|
|
74
|
+
print(int(selected.sum()), pd.Series(corrected[selected], index=names[selected]).nlargest(15).round(1).to_dict())
|
|
75
|
+
panel = ["CD3D", "MS4A1", "LYZ", "NKG7", "PPBP", "JCHAIN"]
|
|
76
|
+
print({gene: bool(selected[names == gene].any()) for gene in panel})
|
|
77
|
+
print({family: len(ds.RNA.feats.grep(p)) for family, p in GENE_FAMILY_PATTERNS.items()})
|
|
78
|
+
|
|
79
|
+
# Headless mean-variance plot. show_plot=True only displays, then closes, its figure.
|
|
80
|
+
summary = ds.load_artifact(ds.inspect_artifact(hvg).input_ref("feature_summary"))
|
|
81
|
+
n_cells = np.asarray(summary["normed_n"][:], dtype=float)
|
|
82
|
+
total = np.asarray(summary["normed_tot"][:], dtype=float)
|
|
83
|
+
splt.highly_variable_features(
|
|
84
|
+
mean_nonzero=np.divide(total, n_cells, out=np.zeros_like(total), where=n_cells > 0),
|
|
85
|
+
corrected_variance=corrected, n_cells=n_cells, selected=selected, show=False,
|
|
86
|
+
).save("hvg_mean_variance.png")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
(10x 5K PBMC docs dataset) The top HVGs were platelet (`PPBP`, `NRGN`), light-chain (`IGLC2`,
|
|
90
|
+
`IGKC`) and `IGHM` genes, which the default blacklist keeps. What to exclude: `gene-blacklists.md`.
|
|
91
|
+
|
|
92
|
+
### Alternative feature sets and a custom blacklist
|
|
93
|
+
|
|
94
|
+
`blacklist` is one regex, matched (`re.match`, ignoring case) against every feature name. It
|
|
95
|
+
replaces the default entirely, so pass a complete string: the default plus add-ons, or a full
|
|
96
|
+
string from `gene-blacklists.md`.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from scarf.features.variability import DEFAULT_HVG_BLACKLIST
|
|
100
|
+
|
|
101
|
+
base = DEFAULT_HVG_BLACKLIST # or a complete string chosen with gene-blacklists.md
|
|
102
|
+
extra_patterns = [] # add-on regexes chosen with gene-blacklists.md; empty adds nothing
|
|
103
|
+
blacklist = "|".join([base, *extra_patterns])
|
|
104
|
+
new, old = set(ds.RNA.feats.grep(blacklist)), set(ds.RNA.feats.grep(DEFAULT_HVG_BLACKLIST))
|
|
105
|
+
print("added:", sorted(new - old)[:20], "released:", sorted(old - new)[:20]) # check both
|
|
106
|
+
hvg_2k = ds.select_hvgs(cells, top_n=2000, show_plot=False, blacklist=blacklist)
|
|
107
|
+
detected = ds.select_detected_features(cells, min_cells=20) # inclusive detection threshold
|
|
108
|
+
everything = ds.select_all_features(from_assay="RNA") # canonical all-true universe
|
|
109
|
+
custom = ds.set_feature_selection(from_assay="RNA", mask=np.isin(names, panel)) # or feature_indexes=
|
|
110
|
+
shared = np.asarray(ds.load_artifact(hvg_2k)["values"][:], dtype=bool) & selected
|
|
111
|
+
print(int(shared.sum()), "of the 1000 baseline HVGs kept")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`blacklist=""` disables the name filter; `max_cells=np.inf` disables the ubiquitous-gene filter.
|
|
115
|
+
The blacklist affects HVG selection only; marker search still tests every gene.
|
|
116
|
+
|
|
117
|
+
### Choose PCA dimensions
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
pca_30 = ds.run_pca(normalized, dims=30) # more PCs than needed; local, no count pass
|
|
121
|
+
scores = np.asarray(ds.load_artifact(pca_30)["data"]) # cells x dims; also "loadings", "center"
|
|
122
|
+
share = scores.var(axis=0, ddof=1)
|
|
123
|
+
share = share / share.sum()
|
|
124
|
+
print(np.round(np.cumsum(share)[[9, 14, 20, 29]], 3)) # share of the 30-PC variance at 10/15/21/30
|
|
125
|
+
splt.elbow(share, show=False).save("pca_elbow.png")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
An elbow is a hint, not a rule. Too few PCs merge populations; too many restore noise.
|
|
129
|
+
|
|
130
|
+
### Graph diagnostics and a k branch
|
|
131
|
+
|
|
132
|
+
`load_graph` returns a SciPy CSR matrix over the graph's cells, in cell-selection order. The default
|
|
133
|
+
is directed: each row holds exactly `k` weighted edges. Keep every returned ref; downstream calls
|
|
134
|
+
take one graph explicitly, so branches never overwrite each other.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
in_graph = np.asarray(ds.load_artifact(cells)["values"][:], dtype=bool)
|
|
138
|
+
G = ds.load_graph(graph)
|
|
139
|
+
S = ds.load_graph(graph, symmetric=True)
|
|
140
|
+
degree = np.asarray((S != 0).sum(axis=1)).ravel()
|
|
141
|
+
print(G.shape, G.nnz, "degree min/median/max", degree.min(), np.median(degree), degree.max())
|
|
142
|
+
qc = ds.cells.to_pandas_dataframe(["RNA_nCounts", "RNA_nFeatures"])[in_graph]
|
|
143
|
+
print(qc.assign(degree=degree).corr(method="spearman").round(2)["degree"])
|
|
144
|
+
splt.graph_qc(G, show=False).save("graph_qc.png")
|
|
145
|
+
|
|
146
|
+
graph_21 = ds.build_connectivity_map(ds.query_neighbors(ann, k=21)) # reuses the ANN index
|
|
147
|
+
leiden = {name: ds.run_leiden_clustering(g, resolution=1.0)
|
|
148
|
+
for name, g in {"k11": graph, "k21": graph_21}.items()}
|
|
149
|
+
labels = {name: np.asarray(ds.load_artifact(ref)["values"][:]) for name, ref in leiden.items()}
|
|
150
|
+
print(pd.crosstab(labels["k11"], labels["k21"]))
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
(10x 5K PBMC docs dataset) Degree vs `RNA_nCounts` Spearman 0.10; 16 clusters at k=11 versus 15
|
|
154
|
+
at k=21 (ARI 0.81). A larger `k` usually absorbs small clusters; the crosstab shows which.
|
|
155
|
+
|
|
156
|
+
### Same knobs through the pipeline
|
|
157
|
+
|
|
158
|
+
`params` forwards other stage settings, including the blacklist. Giving a setting both ways (for
|
|
159
|
+
example `hvg_count` and `params["hvg"]["top_n"]`) raises before anything is written. A completed
|
|
160
|
+
label cannot be run again, even unchanged: reopen it with `ds.pipeline.open` or use a new label.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
run_alt = ds.pipeline.run(
|
|
164
|
+
label="hvg2k_pc30_k21_blacklist", hvg_count=2000, pca_dims=30, neighbors_k=21,
|
|
165
|
+
params={"hvg": {"blacklist": blacklist}, "connectivity": {"bandwidth": 1.5}},
|
|
166
|
+
)
|
|
167
|
+
print(run_alt.status, ds.inspect_artifact(run_alt["neighbors"]).parameters["k"])
|
|
168
|
+
print(run_alt["highly_variable_features"] == hvg_2k) # same cells and blacklist: same artifact
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Subcluster one lineage
|
|
172
|
+
|
|
173
|
+
Fix a coarse lineage partition first (`clustering-and-embedding.md`, "Choose the partition"), then
|
|
174
|
+
rebuild HVGs, normalization, PCA and the graph inside the lineage. `ds.cells.insert` writes to the
|
|
175
|
+
store itself; prefix and version column names on shared stores.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from itertools import combinations
|
|
179
|
+
from sklearn.metrics import adjusted_rand_score
|
|
180
|
+
|
|
181
|
+
def on_cell_axis(ref):
|
|
182
|
+
"""Spread Leiden labels over every store cell (-1 outside the ref's selection)."""
|
|
183
|
+
mask = ds.load_artifact(ds.inspect_artifact(ref).input_ref("cell_selection"))["values"][:]
|
|
184
|
+
full = np.full(ds.cells.N, -1)
|
|
185
|
+
full[np.asarray(mask, dtype=bool)] = ds.load_artifact(ref)["values"][:]
|
|
186
|
+
return full
|
|
187
|
+
|
|
188
|
+
def build_graph(cell_selection, top_n=1000, dims=21, k=11):
|
|
189
|
+
hvg = ds.select_hvgs(cell_selection, top_n=top_n, show_plot=False) # summary: one count pass
|
|
190
|
+
pca = ds.run_pca(ds.run_normalization(cell_selection, hvg), dims=dims) # one count pass
|
|
191
|
+
return ds.build_connectivity_map(ds.query_neighbors(ds.build_ann_index(pca), k=k))
|
|
192
|
+
|
|
193
|
+
parent, parent_markers = run["clusters"], run["markers"] # or your chosen Leiden ref and its table
|
|
194
|
+
table = ds.get_markers(marker=parent_markers, min_score=-1, min_frac_exp=-1)
|
|
195
|
+
cd3e = table[table["feature_name"] == "CD3E"].set_index("group_id")["frac_exp"]
|
|
196
|
+
lineage_ids = sorted(int(g) for g in cd3e.index[cd3e > 0.5]) # a T gate; confirm with markers
|
|
197
|
+
sub_cells = ds.select_cells(parent, include=lineage_ids) # frozen, over every store cell
|
|
198
|
+
ds.cells.insert("t_lineage_v1", np.isin(on_cell_axis(parent), lineage_ids), overwrite=True)
|
|
199
|
+
alt_cells = ds.snapshot_cell_selection("t_lineage_v1") # same cells from any boolean column
|
|
200
|
+
assert np.array_equal(*(ds.load_artifact(r)["values"][:] for r in (sub_cells, alt_cells)))
|
|
201
|
+
|
|
202
|
+
sub_graph, sub_leiden, rows = build_graph(sub_cells), {}, []
|
|
203
|
+
for resolution in (0.3, 0.5, 0.8):
|
|
204
|
+
refs = [ds.run_leiden_clustering(sub_graph, resolution=resolution, random_seed=seed)
|
|
205
|
+
for seed in (4444, 1, 2)]
|
|
206
|
+
seeds = [np.asarray(ds.load_artifact(ref)["values"][:]) for ref in refs]
|
|
207
|
+
sub_leiden[resolution] = refs[0]
|
|
208
|
+
rows.append({"resolution": resolution, "n_clusters": len(np.unique(seeds[0])),
|
|
209
|
+
"min_seed_ari": min(adjusted_rand_score(a, b) for a, b in combinations(seeds, 2))})
|
|
210
|
+
print(pd.DataFrame(rows).round(3))
|
|
211
|
+
|
|
212
|
+
# Choose as for a global partition; then check markers, covariates, HVG count, parent overlap.
|
|
213
|
+
sub_resolution = 0.3 # the finest resolution that passed the seed check above
|
|
214
|
+
sub = sub_leiden[sub_resolution]
|
|
215
|
+
sub_full, parent_full = on_cell_axis(sub), on_cell_axis(parent)
|
|
216
|
+
in_sub = sub_full >= 0
|
|
217
|
+
sub_markers = ds.run_marker_search(sub, features=run["feature_universe"]) # one count pass
|
|
218
|
+
print(ds.get_markers(marker=sub_markers).groupby("group_id", sort=False)["feature_name"]
|
|
219
|
+
.agg(n="size", top=lambda s: ", ".join(s.head(6)))) # groups missing here have 0 markers
|
|
220
|
+
print(pd.crosstab(sub_full[in_sub], parent_full[in_sub]))
|
|
221
|
+
print(pd.DataFrame({"sub": sub_full, "counts": ds.cells.fetch_all("RNA_nCounts"), "doublet":
|
|
222
|
+
run.cells.fetch_all("doublet_score")})[in_sub].groupby("sub").median().round(2))
|
|
223
|
+
sub_2k = ds.run_leiden_clustering(build_graph(sub_cells, top_n=2000), resolution=sub_resolution)
|
|
224
|
+
print("ARI vs 2000 HVGs", round(ds.metric_label_concordance(sub, sub_2k), 3))
|
|
225
|
+
|
|
226
|
+
combined = np.where(in_sub, np.char.add("T.", sub_full.astype(str)), parent_full.astype(str))
|
|
227
|
+
combined = np.where(parent_full < 0, "not analyzed", combined).astype(object)
|
|
228
|
+
ds.cells.insert("clusters_tsub_v1", combined, overwrite=True) # new suffix for each new version
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
(10x 5K PBMC docs dataset) The T subgraph at 0.3 split naive and memory CD4, naive CD8,
|
|
232
|
+
cytotoxic and FOXP3/CTLA4 groups, with ARI 0.94 at 2,000 HVGs. The parent had no FOXP3 group.
|
|
233
|
+
|
|
234
|
+
## Parameters that matter
|
|
235
|
+
|
|
236
|
+
| Parameter | Default | Change when |
|
|
237
|
+
| --- | --- | --- |
|
|
238
|
+
| `select_hvgs(top_n=)` / `hvg_count` | `1000` | Weak or rare populations (raise); noisy programs dominate (lower). |
|
|
239
|
+
| `select_hvgs(blacklist=)` | `DEFAULT_HVG_BLACKLIST` | A family dominates the HVGs or is the question; what to add: `gene-blacklists.md`. |
|
|
240
|
+
| `select_hvgs(min_cells=, max_cells=)` | `20`, `None` (= selected cells - 20) | Tiny subsets; `max_cells=np.inf` keeps ubiquitous genes. |
|
|
241
|
+
| `run_pca(dims=)` / `pca_dims` | `21` | After a variance and stability check; `0` in the pipeline means no PCA. |
|
|
242
|
+
| `run_pca(pca_cell_selection=)` | `None` | Fit on a subset (for example one batch) and project all cells. |
|
|
243
|
+
| `query_neighbors(k=)` / `neighbors_k` | `11` (clamped to cells - 1) | Larger data or smoother graphs (15 to 30); smaller to resolve rare groups. |
|
|
244
|
+
| `load_graph(symmetric=, use_k=)` | `False`, all `k` | `True` for degree and connectivity checks; `use_k` to thin edges. |
|
|
245
|
+
|
|
246
|
+
## Check before moving on
|
|
247
|
+
|
|
248
|
+
- HVGs: count equals `top_n`; expected lineage markers are selected; the top of the list is not
|
|
249
|
+
one contaminant or technical program. If canonical lineage genes are missing, or one family
|
|
250
|
+
(Ig, haemoglobin, platelet) leads the list, branch with more HVGs and an explicit blacklist
|
|
251
|
+
(`gene-blacklists.md`) and compare partitions.
|
|
252
|
+
- PCA and `k`: a second setting gives a similar partition, or the differences are explained by
|
|
253
|
+
markers. Boundaries that move with `k` or `dims` are provisional.
|
|
254
|
+
- Graph: symmetric degree min is at least `k`; degree barely tracks counts. A strong correlation
|
|
255
|
+
means depth is shaping the graph.
|
|
256
|
+
- Subclusters: keep one only when it is seed-stable, has its own named markers, is not explained
|
|
257
|
+
by counts or doublet score, and survives the HVG change. Merge the rest into the parent label.
|
|
258
|
+
Write labels to a versioned column and keep the parent labels.
|
|
259
|
+
|
|
260
|
+
## Pitfalls
|
|
261
|
+
|
|
262
|
+
- Reading in-degree from the directed graph as isolation. `(G != 0).sum(axis=0)` is zero for 85
|
|
263
|
+
cells (10x 5K PBMC docs dataset), yet every cell has `k` out-edges; use `symmetric=True`.
|
|
264
|
+
- Passing a feature selection to UMAP, clustering or markers on a graph: graph consumers take the
|
|
265
|
+
graph ref. `query_neighbors(coordinates=)` only checks equality; it cannot retarget an index.
|
|
266
|
+
- `show_elbow_plot=True` draws only on a new PCA fit. More than 4096 features or several narrow
|
|
267
|
+
blocks switch PCA to IncrementalPCA, and `batch_size` then joins the artifact identity.
|
|
268
|
+
- Subclustering on the parent graph, or carrying subcluster IDs across artifacts. Rebuild inside
|
|
269
|
+
the subset, join on the cell axis, and keep the parent labels. `select_cells(include=[])`
|
|
270
|
+
raises; integer cluster artifacts need integer `include` values.
|
|
271
|
+
- `select_hvgs` is RNA only. ATAC uses `select_prevalent_peaks` then `run_lsi`.
|
|
272
|
+
|
|
273
|
+
## See also
|
|
274
|
+
|
|
275
|
+
- `gene-blacklists.md`: which gene families to pass as `blacklist=`, and why.
|
|
276
|
+
- `quality-control.md`: building the cell selection this chain starts from.
|
|
277
|
+
- `clustering-and-embedding.md`: Leiden, Paris, UMAP and t-SNE on the graph; choosing a partition.
|
|
278
|
+
- `integration-and-comparisons.md`: `run_harmony` between PCA and the ANN index.
|
|
279
|
+
- `pipeline-runs-and-artifacts.md`: `inspect_artifact`, lineage and reuse.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Gene blacklists for HVG selection
|
|
2
|
+
|
|
3
|
+
What `select_hvgs(blacklist=...)` removes, how to check it on your store, the corrected strings to
|
|
4
|
+
pass today, and when to add the clonotype or haemoglobin add-ons. Docs:
|
|
5
|
+
<https://scarf.readthedocs.io/en/latest/tutorials/feature_selection.html>, <https://scarf.readthedocs.io/en/latest/reference/api/pipeline.html>.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Before the baseline run on any new store, to see what the default blacklist matches there.
|
|
10
|
+
- The store uses current HGNC or MGI histone symbols (`H2AC*`, `H3C*`, `H4C*`, `H1-*`), as Cytebase
|
|
11
|
+
datasets do. The default `^HIST` then matches nothing; only older `HIST1H*` references match.
|
|
12
|
+
- Fibroblast, smooth-muscle, endothelial or tumour data, where `CCN1`/`CCN2` (CYR61/CTGF) or
|
|
13
|
+
`CCND1`/`CCND2` carry biology that the default `^CCN` hides.
|
|
14
|
+
- Plasma-cell-rich or B-cell-rich samples, mouse data, or haemoglobin genes among the top HVGs.
|
|
15
|
+
|
|
16
|
+
## Key concepts
|
|
17
|
+
|
|
18
|
+
- The blacklist is one regular expression, matched case-insensitively with `re.match` against
|
|
19
|
+
feature `names` (never `ids`). Each `|` alternative is anchored at the start of the name.
|
|
20
|
+
- It only removes HVG candidates. Markers, plots, gene scores and QC still see every gene.
|
|
21
|
+
- The string and the matched-gene fingerprint are part of the HVG artifact identity. Changing the
|
|
22
|
+
string creates new HVG, normalization, PCA and graph artifacts; the same string reuses them.
|
|
23
|
+
- HVG selection also drops ubiquitous genes (`max_cells` = selected cells - 20), so most `MT-` and
|
|
24
|
+
cytosolic ribosomal genes never compete. On the 10x 5K PBMC docs dataset, unblocking either
|
|
25
|
+
family added no gene to the top 1000.
|
|
26
|
+
- `RNA_percentMito` and `RNA_percentRibo` were fixed at first open from the `^MT-` and
|
|
27
|
+
`^RPS|^RPL|^MRPS|^MRPL` patterns; `blacklist=` never changes them. The RPS6K kinases and other
|
|
28
|
+
off-targets added under 1% of `percentRibo` counts in testing, so the column needs no correction.
|
|
29
|
+
- Names that are Ensembl IDs match nothing: no blacklist effect, no error, and no
|
|
30
|
+
`percentMito`/`percentRibo` columns (only a warning at first open). Build the string from the
|
|
31
|
+
family genes' IDs instead (`^ENSG00000198888$|...`), and percentages with `run_feature_percentage`.
|
|
32
|
+
- Default families and verdicts:
|
|
33
|
+
|
|
34
|
+
| Family | Default | Problem | Recommended |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| mitochondrial | `^MT-` | none; mouse `mt-` matches | keep |
|
|
37
|
+
| ribosomal | `^RPS\|^RPL\|^MRPS\|^MRPL` | also RPS6KA/B kinases, `RPS19BP1`, `L` paralogs, -AS1/-DT RNAs | exact protein symbols |
|
|
38
|
+
| cellCycleCcn | `^CCN` | hits CCN1-6 (CTGF, CYR61, NOV, WISP), CCND1-3, CCNT/K/H/L; misses MKI67, TOP2A | drop; score the cell cycle instead |
|
|
39
|
+
| hla / h2 | `^HLA-` / `^H2-` | whole MHC I and II; removal did not create donor clusters in testing | keep |
|
|
40
|
+
| histone | `^HIST` | matches only pre-2020 symbols | old and current replication-dependent names |
|
|
41
|
+
| sexLinked | 10 human names | mouse `Eif2s3y`, `Zfy1/2` missed | add mouse genes |
|
|
42
|
+
|
|
43
|
+
## Recipes
|
|
44
|
+
|
|
45
|
+
### Recommended strings
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import re
|
|
49
|
+
|
|
50
|
+
import numpy as np
|
|
51
|
+
import pandas as pd
|
|
52
|
+
import scarf
|
|
53
|
+
from scarf.features.gene_families import GENE_FAMILY_PATTERNS
|
|
54
|
+
from scarf.features.variability import DEFAULT_HVG_BLACKLIST
|
|
55
|
+
|
|
56
|
+
MITO = r"^MT-"
|
|
57
|
+
RIBO = r"^RP[SL]\d+[AXY]?\d*$|^RPLP[0-2]$|^RPSA$|^MRP[SL]\d+[A-C]?$"
|
|
58
|
+
HISTONE = (r"^HIST[1-4]H|^H1-[1-6]$|^H1F[1-6]$|^H2AC\d+$|^H2BC\d+$|^H3C\d+$|^H4C\d+$"
|
|
59
|
+
r"|^H2AW$|^H3-4$|^H4-16$") # replication-dependent; replacement histones kept
|
|
60
|
+
HUMAN_BLACKLIST = "|".join([MITO, RIBO, r"^HLA-", HISTONE,
|
|
61
|
+
r"^XIST$|^DDX3Y$|^USP9Y$|^EIF1AY$|^KDM5D$|^SRY$|^ZFY$|^UTY$|^TMSB4Y$|^NLGN4Y$"])
|
|
62
|
+
MOUSE_BLACKLIST = "|".join([MITO, RIBO, r"^H2-", HISTONE,
|
|
63
|
+
r"^XIST$|^TSIX$|^DDX3Y$|^EIF2S3Y$|^KDM5D$|^UTY$|^ZFY[12]$|^SRY$"])
|
|
64
|
+
CLONOTYPE_ADDON = r"^IG[HKL]V|^IG[HKL]J\d|^IGHD\d" # V, J, D segments; not IGHD or IGKC/IGLC
|
|
65
|
+
HAEMOGLOBIN_ADDON = r"^HB[ABDEGMQZ]\d*$|^HB[AB]-" # spares HBEGF, HBP1, HBS1L
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Add-ons are appended: `HUMAN_BLACKLIST + "|" + CLONOTYPE_ADDON`. Patterns are case-insensitive,
|
|
69
|
+
so the human string also works on title-case names; prefer the species string anyway.
|
|
70
|
+
|
|
71
|
+
### Inspect what the default removes on this store
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
scarf.configure_output(level="WARNING", progress=False)
|
|
75
|
+
ds = scarf.DataStore("analysis.zarr", min_features_per_cell=-1) # reopen; see data-access.md
|
|
76
|
+
names = ds.RNA.feats.fetch_all("names").astype(str)
|
|
77
|
+
print("Ensembl-like names:", int(pd.Series(names).str.match(r"ENS[A-Z]*G\d").sum()), "of", len(names))
|
|
78
|
+
for family, pattern in GENE_FAMILY_PATTERNS.items():
|
|
79
|
+
hits = ds.RNA.feats.grep(pattern) # upper-cased names
|
|
80
|
+
print(f"{family:13s} {len(hits):4d}", hits[:6])
|
|
81
|
+
print("current histone symbols the default misses:",
|
|
82
|
+
len(ds.RNA.feats.grep(r"^H2AC\d|^H2BC\d|^H3C\d|^H4C\d|^H1-[1-6]$")))
|
|
83
|
+
print("default-only matches:",
|
|
84
|
+
sorted(set(ds.RNA.feats.grep(DEFAULT_HVG_BLACKLIST)) - set(ds.RNA.feats.grep(HUMAN_BLACKLIST))))
|
|
85
|
+
|
|
86
|
+
run = ds.pipeline.open(label="baseline")
|
|
87
|
+
cells = run["analysis_cell_selection"]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def selected(ref):
|
|
91
|
+
return set(names[np.asarray(ds.load_artifact(ref)["values"][:], dtype=bool)])
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
hvg_default = run["highly_variable_features"] # same as select_hvgs(cells, top_n=1000)
|
|
95
|
+
hvg_open = ds.select_hvgs(cells, top_n=1000, blacklist="", show_plot=False) # no name filter
|
|
96
|
+
print("removed by the default:", sorted(selected(hvg_open) - selected(hvg_default)))
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The last line is what the default actually costs. 10x 5K PBMC docs dataset: 17 genes, namely 9
|
|
100
|
+
MHC class II, 6 histones, `CCND3` and `CCNL1`.
|
|
101
|
+
|
|
102
|
+
### Apply a string and check the HVGs
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
hvg_fixed = ds.select_hvgs(cells, top_n=1000, blacklist=HUMAN_BLACKLIST, show_plot=False)
|
|
106
|
+
print("entered:", sorted(selected(hvg_fixed) - selected(hvg_default)))
|
|
107
|
+
print("left:", sorted(selected(hvg_default) - selected(hvg_fixed)))
|
|
108
|
+
blocked = re.compile(HUMAN_BLACKLIST, re.IGNORECASE)
|
|
109
|
+
assert not any(blocked.match(gene) for gene in selected(hvg_fixed))
|
|
110
|
+
|
|
111
|
+
run_fixed = ds.pipeline.run(label="baseline_blacklist_fixed",
|
|
112
|
+
params={"hvg": {"blacklist": HUMAN_BLACKLIST}})
|
|
113
|
+
print(run_fixed["highly_variable_features"] == hvg_fixed,
|
|
114
|
+
ds.inspect_artifact(run_fixed["highly_variable_features"]).parameters["blacklist"]
|
|
115
|
+
== HUMAN_BLACKLIST)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The pipeline returns the very ref of the explicit call when its cells match. Compare the two
|
|
119
|
+
runs' partitions with `clustering-and-embedding.md`. 10x 5K PBMC docs dataset: `CCND3` and `CCNL1`
|
|
120
|
+
entered, and the Leiden 1.0 partitions agreed (ARI 0.82) about as well as two Leiden seeds of the
|
|
121
|
+
baseline (0.85).
|
|
122
|
+
|
|
123
|
+
### Decide on the add-ons with marker evidence
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
top = ds.get_markers(marker=run_fixed["markers"]).groupby("group_id", sort=False).head(10)
|
|
127
|
+
clonal = top[top["feature_name"].str.match(CLONOTYPE_ADDON, case=False)]
|
|
128
|
+
print("V/J genes among HVGs:", sum(bool(re.match(CLONOTYPE_ADDON, g, re.I)) for g in selected(hvg_fixed)))
|
|
129
|
+
print(clonal.groupby("group_id")["feature_name"].agg(", ".join)) # clusters led by V genes
|
|
130
|
+
panel = ["JCHAIN", "MZB1", "IGKC", "IGLC2", "IGHA1", "IGHG1", "IGHM", "MKI67",
|
|
131
|
+
"HBB", "HBA1", "ALAS2", "CA1", "AHSP", "SLC4A1"]
|
|
132
|
+
markers = ds.get_markers(marker=run_fixed["markers"], min_score=-1, min_frac_exp=-1)
|
|
133
|
+
frac = markers[markers["feature_name"].isin(panel)].pivot_table(
|
|
134
|
+
index="group_id", columns="feature_name", values="frac_exp")
|
|
135
|
+
print(frac.round(2).to_string())
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- Clonotype add-on: use it when two or more clusters share plasma or B markers (`JCHAIN`, `MZB1`,
|
|
139
|
+
`CD79A`) and differ mainly by `IGKC` versus `IGLC*` or by V genes in their top markers. Rerun
|
|
140
|
+
with `HUMAN_BLACKLIST + "|" + CLONOTYPE_ADDON` under a new label and confirm that the plasma
|
|
141
|
+
clusters now differ by isotype (`IGHA1`, `IGHG1`, `IGHM`) or state (`MKI67`), not light chain.
|
|
142
|
+
Skip it when expanded clones are the question; use VDJ data then.
|
|
143
|
+
- Haemoglobin add-on: use it when `HBB`/`HBA1` are among the HVGs and are detected at low
|
|
144
|
+
fractions across clusters that lack `ALAS2`, `CA1`, `AHSP` and `SLC4A1` (ambient RNA). Do not
|
|
145
|
+
use it when a cluster expresses those erythroid markers; remove contaminating red cells at QC.
|
|
146
|
+
|
|
147
|
+
## Parameters that matter
|
|
148
|
+
|
|
149
|
+
| Parameter | Default | Change when |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| `select_hvgs(blacklist=)` | `DEFAULT_HVG_BLACKLIST` | Always pass a species string on current-symbol stores. |
|
|
152
|
+
| `params={"hvg": {"blacklist": ...}}` | default regex | Same string through `ds.pipeline.run`; new label per string. |
|
|
153
|
+
| `blacklist=""` | | Diagnostics only: shows what the name filter removes. |
|
|
154
|
+
| `select_hvgs(max_cells=)` | selected cells - 20 | `np.inf` lets ubiquitous genes compete; the blacklist then matters more. |
|
|
155
|
+
|
|
156
|
+
## Check before moving on
|
|
157
|
+
|
|
158
|
+
- The store has symbol names; every family you rely on matches the genes you expect.
|
|
159
|
+
- `inspect_artifact(hvg).parameters["blacklist"]` equals the string you recorded in the log.
|
|
160
|
+
- No HVG matches the string; the entered and left genes are listed in the analysis log.
|
|
161
|
+
- With the clonotype add-on, no cluster's top markers are V genes and plasma clusters are not
|
|
162
|
+
split by `IGKC` versus `IGLC*`.
|
|
163
|
+
|
|
164
|
+
## Pitfalls
|
|
165
|
+
|
|
166
|
+
- Trusting the default on current symbols: replication-dependent histones become HVGs unnoticed.
|
|
167
|
+
- Writing `^IGHD` for D segments: it also removes the IgD constant gene. Use `^IGHD\d`.
|
|
168
|
+
- `^TR[ABDG]D` also matches `TRADD`; TCR V genes such as `TRDV2`, `TRGV9` and `TRAV1-2` mark
|
|
169
|
+
gamma-delta and MAIT cells. T-cell clusters did not follow TRBV usage in testing, so no TCR
|
|
170
|
+
add-on is recommended; consider `^TR[AB]V` only when a cluster is led by one TRAV/TRBV pair.
|
|
171
|
+
- `^HB` also removes `HBEGF`. Small clusters (under about 50 cells) move with any HVG change;
|
|
172
|
+
compare across Leiden seeds before attributing a change to the blacklist.
|
|
173
|
+
- Run `groupby(...).head` on default-filtered marker tables; on a full table (`min_score=-1`) it
|
|
174
|
+
crashed pandas 3.0 with a segmentation fault in testing.
|
|
175
|
+
- A blacklist never fixes QC: high `percentMito` cells or red-cell contamination need filtering.
|
|
176
|
+
|
|
177
|
+
## See also
|
|
178
|
+
|
|
179
|
+
- `features-and-graphs.md`: HVG diagnostics and branching the graph.
|
|
180
|
+
- `quality-control.md`: `percentMito`, `percentRibo` and `run_feature_percentage`.
|
|
181
|
+
- `markers-and-annotation.md`: marker tables and panels used for the add-on decisions.
|
|
182
|
+
- `pipeline-runs-and-artifacts.md`: `params`, labels and artifact identity.
|