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.

Metadata

Release files for standissect-lite 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for standissect-lite 0.2.0
File Size Uploaded Provenance
standissect_lite-0.2.0.tar.gz 14.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for standissect-lite 0.2.0
File Interpreter ABI Platform
standissect_lite-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 26.6 kB

Release files / standissect_lite-0.2.0.tar.gz

Download URL standissect_lite-0.2.0.tar.gz
Size 14.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e3f71c6d14b3e14dd4cd4f3a1ac2582384dc5b557711f5f089fad31ac8811a79
BLAKE2b-256 checksum
How to use checksums
24c66df81da131b07d716a25cd4773c255370898aec47cba122607f99775c3e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.1

Release files / standissect_lite-0.2.0-py3-none-any.whl

Download URL standissect_lite-0.2.0-py3-none-any.whl
Size 12.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1626cbe4e4de9e14751c62c0d0cfd1a91d06ee831516f02981769a70b3c29477
BLAKE2b-256 checksum
How to use checksums
09a38a820b3dc35def252c5e5fcb516614c7f82783c096e24c6837c5158df7e3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.1

Security for standissect-lite 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

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