Skip to main content

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-lite is 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:

  1. your precomputed RNA-side Leiden clustering — read as-is from adata.obs[cluster_col], never recomputed;
  2. 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 — THE headline identifier, rank 0 = main core, strictly descending by size within each parent; _umap_partition = the raw UMAP-side partition this fragment came from). Ranked names are byte-compatible with standissect's (c{cluster}_{rank}), so outputs interoperate.

Only use subcluster/rank for anything ranking-related. _umap_partition (u0, u1, …) is ranked globally across the whole embedding, not within any one RNA cluster — u0 is not generally each parent's largest fragment, and two different parents can share a _umap_partition id. It's underscore-prefixed and kept only for traceability (it's what the overlap crosstab's columns are); building a combined per-cell label from it directly (e.g. f"{parent}_{u}") does not have the rank-0-is-largest property people expect from c{parent}_{rank} naming — that's exactly what subcluster already gives you for free.

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: subcluster / rank / is_main (+ _umap_partition, raw)
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

standissect_lite-0.2.0.tar.gz (14.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

standissect_lite-0.2.0-py3-none-any.whl (12.4 kB view details)

Uploaded Python 3

File details

Details for the file standissect_lite-0.2.0.tar.gz.

File metadata

  • Download URL: standissect_lite-0.2.0.tar.gz
  • Upload date:
  • Size: 14.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.1

File hashes

Hashes for standissect_lite-0.2.0.tar.gz
Algorithm Hash digest
SHA256 e3f71c6d14b3e14dd4cd4f3a1ac2582384dc5b557711f5f089fad31ac8811a79
MD5 c91e29845411cfa086692c8e77f6eaf9
BLAKE2b-256 24c66df81da131b07d716a25cd4773c255370898aec47cba122607f99775c3e7

See more details on using hashes here.

File details

Details for the file standissect_lite-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for standissect_lite-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1626cbe4e4de9e14751c62c0d0cfd1a91d06ee831516f02981769a70b3c29477
MD5 2b0da63454b977538caf538ffcfad689
BLAKE2b-256 09a38a820b3dc35def252c5e5fcb516614c7f82783c096e24c6837c5158df7e3

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page