standissect-lite
Find each cluster's minor siblings — the small stray fragments carrying its own label — and nothing else.
Why (出发点)
Single-cell annotation and quality control both lean on clustering — yet clustering followed by subset-and-recluster often fails to cleanly separate tiny subpopulations and low-quality subgroups from their parent cluster.
In practice analysts fall back on the UMAP: spot a small island sitting apart from its cluster's main blob, and lasso it by hand. UMAP is rightly criticised against over-interpretation; still, local separations in the embedding can provide useful candidate structure, because UMAP is built to preserve aspects of the high-dimensional neighbourhood graph. Analysts already treat UMAP islands as hypotheses — this package turns that visual heuristic into a systematic candidate-detection procedure: it clusters the UMAP coordinates (granularity-matched to the RNA-side clustering), crosses the two partitions, and hands the islands back as named, ranked fragments, while explicitly leaving validation to downstream evidence.
Two boundaries are deliberate:
- Candidates, not conclusions. Whether a detected tiny cluster is biologically meaningful (or a doublet pocket / low-quality tail) is decided by downstream evidence, not by this method. Its only job is to replace inefficient manual lassoing with something reproducible.
- Light by construction. The full
standissect adds that
interpretation layer (DEG, QC drift, LLM diagnosis, reports, review
server).
standissect-liteis the detection step alone, extracted so other projects can reuse it without the heavy stack: no scanpy, no DEG, no LLM, no server. One module, six light dependencies, pure functions.
What it does
It crosses two partitions of the same cells:
- your precomputed RNA-side Leiden clustering — read as-is from
adata.obs[cluster_col], never recomputed; - a UMAP-side clustering computed here — kNN graph on the 2-D UMAP coordinates + Leiden on that graph.
Naming happens in two steps. Step 1 — cartesian product: each non-empty
(RNA cluster × UMAP cluster) combination is a fragment; think of it as the
concatenated name c3u5 = cells in RNA cluster 3 ∩ UMAP cluster u5.
Step 2 — rank by size: within each RNA cluster, fragments are sorted by
cell count and renamed c{cluster}_{rank} — the largest becomes the main
core, the rest are its minor siblings:
step 1: product names step 2: sort by size, rename
───────────────────── ────────────────────────────
┌── u2 ( 412 cells) → c3u2 ─┐ ┌ c3_0 = c3u5 (8,021) main core
cluster 3 ────────┼── u5 (8,021 cells) → c3u5 ─┼───────▶┤ c3_1 = c3u2 ( 412) minor sibling
(8,800 cells) └── u9 ( 367 cells) → c3u9 ─┘ └ c3_2 = c3u9 ( 367) minor sibling
A minor sibling is a small fragment carrying the same RNA label as its main core — "sibling" stresses that it belongs to the same parent cluster, not that it is some other small cluster elsewhere on the map.
The fragments table keeps both identities per row (subcluster = the
ranked name, umap_label = the UMAP side of the product). Ranked names are
byte-compatible with standissect's (c{cluster}_{rank}, rank 0 = main core),
so outputs interoperate.
Usage
import anndata as ad
from standissect_lite import dissect_partition
adata = ad.read_h5ad("data.h5ad") # needs obs['leiden'] + obsm['X_umap']
res = dissect_partition(adata, cluster_col="leiden", umap_key="X_umap")
# or hand a precomputed UMAP matrix directly (obsm not consulted;
# one row per cell in adata order, >=2 columns):
res = dissect_partition(adata, cluster_col="leiden", umap_Nx2_mat=my_umap)
res.fragments[res.fragments.is_minor_sibling] # minor siblings: parent, size, rank, ...
res.overlap # RNA × UMAP cell-count crosstab
res.labels # per-cell: umap_cluster / subcluster / rank
res.info # UMAP-side resolution search diagnostics
dissect_partition is a pure function: it never modifies adata.
Write-back is the caller's explicit one-liner:
adata.obs["original_cluster_split"] = res.labels["subcluster"]
The lower-level umap_leiden_partition(umap_xy, ...) is also exported for
callers holding bare coordinates. All tuning knobs are documented in the
dissect_partition docstring; defaults are sane and rarely worth touching.
Install
pip install -e /path/to/standissect-lite # or add the parent to PYTHONPATH
Dependencies: numpy, pandas, scikit-learn, python-igraph, leidenalg,
anndata. No scanpy.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file standissect_lite-0.1.0.tar.gz.
File metadata
- Download URL: standissect_lite-0.1.0.tar.gz
- Upload date:
- Size: 13.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2f090aed0dad389529acc18861c33ae584a2cc28ada059f1cb9b9034b61dde5
|
|
| MD5 |
30e92d79131d8f4c897e4b9db296be4d
|
|
| BLAKE2b-256 |
516f3c7799ca99e7fdf34971ff5b9142058e69474f16eb4f43c7c74ddcc7acd4
|
File details
Details for the file standissect_lite-0.1.0-py3-none-any.whl.
File metadata
- Download URL: standissect_lite-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
22606b538e7f6b22e34bb177696ded7b496447340e960d31d4894295d995d324
|
|
| MD5 |
f8899a22bf87a2bf8589e4839a0d1159
|
|
| BLAKE2b-256 |
d495a46b2968120dd54f2e1ed7a73c040623cda93a41e90d3e60ae7b457d0114
|