Skip to main content

stancounts logo

Recover raw counts from log1p-normalized single-cell expression matrices

Python 3.9+ License: MIT PyPI Platform


Why

Many single-cell tools and public datasets distribute only the log1p-normalized expression matrix, discarding the raw counts. Some downstream methods (e.g., RNA velocity, differential expression via negative-binomial models, scVI) require integer counts.

stancounts reverses the normalization — exactly, in one line — without needing to know the original target_sum.

How it works

Standard scRNA-seq normalization:

y = log1p(count / library_size × target_sum)

The key insight: in every cell, the minimum nonzero value after expm1 corresponds to a gene with count = 1 (near-universal in sparse scRNA-seq data). This gives us the per-cell scale factor, and division + rounding recovers exact integer counts.

scale_factor = min_nonzero(expm1(y))     # per cell
counts       = round(expm1(y) / scale_factor)

The algorithm is target_sum agnostic — it works with any normalization target (1e4, 1e6, median, etc.) and any per-cell size factor scheme (scanpy, scran, Seurat).

Installation

Python package

pip install git+https://github.com/chansigit/stancounts.git

Or from source:

git clone https://github.com/chansigit/stancounts.git
cd stancounts
pip install -e .

Claude Code plugin

If you use Claude Code, stancounts is also available as a plugin with a built-in skill that guides Claude through the detection and recovery workflow:

/plugins add chansigit/stancounts

Once installed, simply ask Claude to "recover counts" or "reverse log1p" and the skill activates automatically.

Quick start

import stancounts
import scanpy as sc

adata = sc.read_h5ad("my_dataset.h5ad")

# One-liner: recover counts into a new layer
stancounts.reverse_log1p_anndata(adata)
# → adata.layers["counts_recovered"]
# → adata.obs["stancounts_scale_factor"]
# → adata.obs["stancounts_library_size"]

Auto-detect normalization

det = stancounts.detect_normalization(adata.X)
print(det)
# {'is_log1p': True, 'base': 'e', 'scores': {'e': 1.0, '2': 0.32, '10': 0.31},
#  'is_integer': False, 'max_value': 7.26}

if det["is_log1p"]:
    stancounts.reverse_log1p_anndata(adata, base=det["base"])

Low-level API

result = stancounts.reverse_log1p(
    adata.X,          # sparse or dense matrix
    base="e",         # "e", "2", or "10"
    robust=True,      # handle rare min_count > 1 cells
)

counts        = result["counts"]          # recovered count matrix
scale_factors = result["scale_factors"]   # per-cell scale factors
library_sizes = result["library_sizes"]   # recovered library sizes
corrections   = result["corrections"]     # robust correction flags

Validation

Tested on 256,000+ real cells with ground-truth counts:

Dataset Cells Genes Recovery
PBMC3k 2,638 13,714 100%
Purified PBMC 105,753 19,512 100%
CITE-seq PBMC 147,582 20,729 100%

All three datasets: every element, every cell, exactly recovered.

Robustness

Scenario Result
target_sum = 100, 1e3, 1e4, 1e5, 1e6 100%
target_sum = median(library_size) 100%
scran-style arbitrary size factors 100%
log2(1+x) and log10(1+x) 100%
Cells with min_count > 1 (robust mode) 100%
Deeply sequenced cells (high counts) 100%

API reference

stancounts.reverse_log1p(X, *, base="e", robust=True)

Reverse log1p normalization to recover raw counts.

Parameters:

  • X — expression matrix (cells × genes), sparse or dense
  • base — log base: "e" (default), "2", or "10"
  • robust — detect and correct cells whose minimum nonzero count > 1

Returns dict with keys: counts, scale_factors, library_sizes, corrections

stancounts.reverse_log1p_anndata(adata, *, source="X", target_layer="counts_recovered", base="e", robust=True)

Convenience wrapper for AnnData objects. Stores recovered counts in adata.layers[target_layer] and scale factors in adata.obs.

stancounts.detect_normalization(X, *, n_sample=200, seed=0)

Auto-detect whether data is log1p-normalized and determine the log base.

Returns dict with keys: is_log1p, base, scores, is_integer, max_value

stancounts.is_log1p_normalized(X, **kwargs)

Convenience wrapper: returns True if data appears log1p-normalized.

When it won't work

  • Batch-corrected data (e.g., Harmony, scVI latent space) — the linear count→normalized relationship is broken
  • scTransform (Pearson residuals) — not a log1p(scaled_counts) transform
  • Aggregated/pseudobulk data — may still work if the aggregation preserves the structure
  • Fractional counts (e.g., from imputation) — no integer counts to recover

Dependencies

  • numpy >= 1.22
  • scipy >= 1.7
  • anndata >= 0.8 (optional, for AnnData integration)

License

MIT

Download files

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

Source Distribution

stancounts-0.3.0.tar.gz (26.1 kB view details)

Uploaded Source

Built Distribution

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

stancounts-0.3.0-py3-none-any.whl (11.6 kB view details)

Uploaded Python 3

File details

Details for the file stancounts-0.3.0.tar.gz.

File metadata

  • Download URL: stancounts-0.3.0.tar.gz
  • Upload date:
  • Size: 26.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.1

File hashes

Hashes for stancounts-0.3.0.tar.gz
Algorithm Hash digest
SHA256 ec5837544add81337524dc27aefb4d4609642c552ccef84a69ba3bfc558d4e2d
MD5 a0560ee29ff3fb37fe69a15b1a8b3569
BLAKE2b-256 66cf899fda8eb6ebfca4339b3bddc2cbd71a41e51999a27f9fe2e38e69054cde

See more details on using hashes here.

File details

Details for the file stancounts-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: stancounts-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 11.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.1

File hashes

Hashes for stancounts-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d060b49f78b9163677fa5b11a1dcffbd2428f7268899e171ca365b80d0667e3a
MD5 73ba3fc7b122e69779948cdb95e4e222
BLAKE2b-256 89f7ce42e5550d274deebb45b6d66ea1ae336e3da0166ee6d67e9f81ca50f484

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.0 This release

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