Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cumap-0.0.1.tar.gz (21.6 kB view details)

Uploaded Source

Built Distribution

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

cumap-0.0.1-py3-none-any.whl (17.9 kB view details)

Uploaded Python 3

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

Hashes for cumap-0.0.1.tar.gz
Algorithm Hash digest
SHA256 2d3e27ff953fd0d7ca8d0e2259190ac63b0e21805d8691660a9e097aedfcf4a4
MD5 d42926bb84f1c9ef79727107ba4a9b4e
BLAKE2b-256 2efd564ea4f0b7194ef45662037dd64f384f54068c9e766471deaa23662dbde3

See more details on using hashes here.

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

Hashes for cumap-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0a5535e40227842532f2ad1f8c5986159cf6b5bcc7356e07964a84657e20b509
MD5 8d423cb723718ae67373ac3818dc24e9
BLAKE2b-256 7dfb5cb5466e7092177c66fdc1b9e1f1726a3acd77f4647220af7fe7084e1e27

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page