Explainable dimension reduction for scRNA-seq via biologically-driven distance fusion
Project description
cumap
Explainable dimension reduction for single-cell RNA-seq data via biologically-driven distance fusion.
Pre-release (v0.0.1). Core API stable; tests passing. Not yet on PyPI.
Why cumap?
Most scRNA-seq dimension reduction methods (PCA, t-SNE, UMAP, scVI) treat the input as a generic numerical matrix and rely on Euclidean distance or learned black-box embeddings. cumap takes a different approach: it fuses three biologically meaningful distance metrics, each addressing a specific layer of cellular heterogeneity:
| Distance | Captures | Biological question |
|---|---|---|
| Yule | gene on/off status | Which genes are expressed? |
| L-Chebyshev (SVD k=15) | top-expressed gene levels | What are the levels of the top-expressed genes? |
| Fractional (f=1/4) | whole-gene expression levels | What are the levels of all genes? |
The fused pairwise distance matrix is then embedded via t-SNE or UMAP (both supported via the precomputed metric option).
This design provides a fully interpretable alternative to black-box VAE/autoencoder methods, while achieving competitive or superior clustering performance on standard scRNA-seq benchmarks.
Installation
# From source (PyPI coming soon)
git clone https://github.com/LuyuNiu/CUMAP.git
cd CUMAP
pip install -e . # Core: distances + fusion + DR + weight search
pip install -e ".[cluster]" # Extras: Leiden support (igraph + leidenalg)
pip install -e ".[dev]" # Dev: pytest, build, twine
Quick start
import numpy as np
from cumap import CTSNE
# X: raw scRNA-seq count matrix (n_cells, n_genes). NO normalization, NO QC filtering.
# y: ground-truth cell-type labels (integer codes), optional.
model = CTSNE(
distances=['yule', 'l_chebyshev'], # any subset of yule / fractional / l_chebyshev
weights='auto', # 'auto' triggers grid-search; or pass [0.9, 0.1]
fusion='sum', # 'sum' (low sparsity) or 'max' (high sparsity)
backend='umap', # 'umap' or 'tsne'
search_metric='nmi', # supervised; needs y. Use 'silhouette' for unsupervised.
)
# Fit + cluster in one call:
labels = model.fit_predict(X, y=y, cluster='kmeans') # or cluster='leiden' (needs [cluster] extras)
# Inspect results:
emb = model.embedding_ # (n_cells, 2)
print('Best weights:', model.weights_) # e.g. (0.9, 0.1)
print('Best NMI: ', model.score(y)) # NMI(predicted labels, y_true)
For a runnable demo on synthetic data:
python examples/quickstart.py
Expected output (deterministic with seed 42):
Input: 120 cells x 200 genes, 3 true clusters
Embedding shape : (120, 2)
Best weights : Yule=1.00, L-Chebyshev=0.00
Best NMI (search) : 1.0000
NMI(KMeans labels, y_true): 1.0000
Skip distance recomputation
Distance computation (especially the SVD step in L-Chebyshev) is the slowest
part of the pipeline. If you've already computed distance matrices once
(e.g. saved as .npy files), reuse them via fit_transform_precomputed:
import numpy as np
from cumap import CTSNE
# Distances computed earlier — load instead of recomputing
D_yule = np.load("dist_yule.npy")
D_l_chebyshev = np.load("dist_l_chebyshev.npy")
model = CTSNE(distances=["yule", "l_chebyshev"], weights="auto", backend="umap")
emb = model.fit_transform_precomputed([D_yule, D_l_chebyshev], y=y_true)
# Same return values as fit_transform:
print(model.weights_, model.best_score_, model.embedding_.shape)
The matrices in the list must be in the same order as self.distances.
Available variants: fit_precomputed, fit_transform_precomputed,
fit_predict_precomputed.
Custom distance / backend (sklearn-style)
The distances list and the backend argument accept callables, so you can
plug in any metric or dimension-reduction method without modifying cumap:
from functools import partial
from scipy.spatial.distance import pdist, squareform
from cumap import CTSNE
def jaccard_distance(X):
"""Pairwise Jaccard distance on binarized counts."""
return squareform(pdist((X > 0).astype(float), metric="jaccard"))
def phate_backend(D, random_state=42, n_components=2):
import phate
return phate.PHATE(knn_dist="precomputed", random_state=random_state,
n_components=n_components).fit_transform(D)
model = CTSNE(
distances=["yule", jaccard_distance, "l_chebyshev"], # str + callable mix
weights=[0.4, 0.3, 0.3],
backend=phate_backend, # callable backend
)
emb = model.fit_transform(X_raw)
Callable distances receive X (n_cells, n_genes) and must return an (n, n)
symmetric distance matrix. Wrap with functools.partial if your callable
needs extra parameters bound.
API layers
cumap exposes three layers; pick the one matching your control needs:
# Layer 1: high-level estimator (most users)
from cumap import CTSNE
# Layer 2: pipeline functions
from cumap import fuse_distances, embed, search_weights
# Layer 3: individual distance metrics
from cumap import yule_distance, fractional_distance, l_chebyshev_distance
Tests
pip install -e ".[dev]"
pytest tests/ # 63 tests, ~15s
Roadmap
- Phase 1: Project skeleton, packaging, license
- Phase 2: Core modules (distances, fusion, embedding, search, core)
- Phase 3: Tests (63 passing), quickstart example, README
- Custom distances/backends as callables (sklearn-style)
- Skip distance recomputation via
fit_*_precomputed - Phase 4: Publish to PyPI, GitHub Actions CI, full H1 reproduction notebook
Citation
If you use cumap, please cite:
TODO (Han et al., bioRxiv 2022, doi:10.1101/2022.01.12.476084)
License
MIT
Project details
Release history Release notifications | RSS feed
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 cumap-0.0.1.tar.gz.
File metadata
- Download URL: cumap-0.0.1.tar.gz
- Upload date:
- Size: 21.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d3e27ff953fd0d7ca8d0e2259190ac63b0e21805d8691660a9e097aedfcf4a4
|
|
| MD5 |
d42926bb84f1c9ef79727107ba4a9b4e
|
|
| BLAKE2b-256 |
2efd564ea4f0b7194ef45662037dd64f384f54068c9e766471deaa23662dbde3
|
File details
Details for the file cumap-0.0.1-py3-none-any.whl.
File metadata
- Download URL: cumap-0.0.1-py3-none-any.whl
- Upload date:
- Size: 17.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a5535e40227842532f2ad1f8c5986159cf6b5bcc7356e07964a84657e20b509
|
|
| MD5 |
8d423cb723718ae67373ac3818dc24e9
|
|
| BLAKE2b-256 |
7dfb5cb5466e7092177c66fdc1b9e1f1726a3acd77f4647220af7fe7084e1e27
|