Skip to content

scdecon.deconvolution

deconvolution

Deconvolution: estimate cell-type proportions from bulk expression.

The solvers (:class:~scdecon.deconvolution.base.Solver, e.g. :class:~scdecon.deconvolution.nnls.NNLSSolver, :class:~scdecon.deconvolution.nusvr.NuSVRSolver, :class:~scdecon.deconvolution.robust.RobustSolver) are format-agnostic and operate purely on NumPy arrays. Aligning a labelled signature and bulk matrix onto shared genes (:mod:scdecon.deconvolution.align) and orchestrating per-sample solving into a labelled result (:func:~scdecon.deconvolution.deconvolve.deconvolve) are separate, pandas-aware concerns.

AlignedInputs dataclass

AlignedInputs(signature: NDArray[float64], bulk: NDArray[float64], genes: list[str], cell_types: list[str], sample_names: list[str])

Gene-aligned deconvolution inputs, ready for a :class:Solver.

Attributes:

Name Type Description
signature NDArray[float64]

Aligned signature matrix, shape (n_shared_genes, n_cell_types).

bulk NDArray[float64]

Aligned bulk matrix, shape (n_shared_genes, n_samples).

genes list[str]

Shared gene identifiers, in signature row order.

cell_types list[str]

Cell-type labels (signature columns).

sample_names list[str]

Bulk sample labels (bulk columns).

Solver

Bases: ABC

Abstract base class for deconvolution solvers.

A solver estimates the cell-type proportion vector p for a single bulk sample by (approximately) solving b ~= S @ p subject to p >= 0 and sum(p) == 1. Implementations must enforce both constraints as part of their contract.

fit abstractmethod

fit(signature: NDArray[float64], bulk: NDArray[float64]) -> NDArray[np.float64]

Estimate cell-type proportions for one bulk sample.

Parameters:

Name Type Description Default
signature NDArray[float64]

Signature matrix of shape (n_genes, n_cell_types). Rows are genes already aligned to bulk; columns are cell types.

required
bulk NDArray[float64]

Bulk expression vector of shape (n_genes,), gene-aligned to signature.

required

Returns:

Type Description
ndarray

Proportion vector of shape (n_cell_types,) with p >= 0 and sum(p) == 1.

BenchmarkResult dataclass

BenchmarkResult(reports: Mapping[str, ValidationReport], runtimes: Mapping[str, float])

Comparison of solvers over one shared pseudobulk set.

Composes the M5 :class:~scdecon.validation.ValidationReport per solver plus a runtime; introduces no second reporting abstraction. Solver names are exactly the caller-supplied mapping keys.

Attributes:

Name Type Description
reports Mapping[str, ValidationReport]

Solver name -> :class:~scdecon.validation.ValidationReport.

runtimes Mapping[str, float]

Solver name -> wall-clock seconds for the solving loop (informational).

to_frame

to_frame() -> pd.DataFrame

Return the comparison table (index = solver names, exactly as given).

Columns: overall_rmse, mean_pearson, mean_spearman, runtime_s.

best

best(by: str = 'overall_rmse') -> str

Return the name of the best solver by a metric.

Lower is better for overall_rmse and runtime_s; higher is better for mean_pearson and mean_spearman.

Raises:

Type Description
ValueError

If by is not a known metric.

render

render() -> str

Return a human-readable comparison table.

NNLSSolver

Bases: Solver

Deconvolution via non-negative least squares (see module docstring).

fit

fit(signature: NDArray[float64], bulk: NDArray[float64]) -> NDArray[np.float64]

Estimate cell-type proportions for one bulk sample.

Solves min ‖S x − b‖₂ subject to x ≥ 0 (scipy.optimize.nnls), then normalises to Σ p = 1.

Parameters:

Name Type Description Default
signature NDArray[float64]

Signature matrix S of shape (n_genes, n_cell_types).

required
bulk NDArray[float64]

Bulk expression vector b of shape (n_genes,), gene-aligned to signature.

required

Returns:

Type Description
ndarray

Proportion vector of shape (n_cell_types,) with p >= 0 and sum(p) == 1. Freshly allocated by the normalisation step; neither signature nor bulk is copied or modified.

Raises:

Type Description
ValueError

If NNLS returns the all-zero solution (its coefficients sum to zero), which cannot be normalised into proportions. This typically means the bulk sample has ~zero expression on the signature genes, the signature and bulk are on incompatible scales, or gene alignment left almost no usable signal. Check that the bulk was preprocessed on the same scale as the signature and that gene identifiers match.

NuSVRSolver

NuSVRSolver(config: NuSVRConfig | None = None)

Bases: Solver

Deconvolution via linear nu support-vector regression (see module docstring).

fit

fit(signature: NDArray[float64], bulk: NDArray[float64]) -> NDArray[np.float64]

Estimate cell-type proportions for one bulk sample.

Fits a linear nu-SVR of bulk on signature's columns, then clips negative coefficients to zero and renormalises to sum(p) == 1.

Parameters:

Name Type Description Default
signature NDArray[float64]

Signature matrix S of shape (n_genes, n_cell_types).

required
bulk NDArray[float64]

Bulk expression vector b of shape (n_genes,), gene-aligned to signature.

required

Returns:

Type Description
ndarray

Proportion vector of shape (n_cell_types,) with p >= 0 and sum(p) == 1. Neither input is modified.

Raises:

Type Description
ValueError

If every coefficient is non-positive (nothing to normalise). Likely causes mirror :class:~scdecon.deconvolution.nnls.NNLSSolver: near-zero bulk on the signature genes, incompatible scales, or too little usable signal.

NuSVRConfig dataclass

NuSVRConfig(nu: float = 0.5)

Configuration for :class:~scdecon.deconvolution.nusvr.NuSVRSolver.

Attributes:

Name Type Description
nu float

The nu parameter of nu-SVR, an upper bound on the fraction of margin errors and a lower bound on the fraction of support vectors, in (0, 1].

__post_init__

__post_init__() -> None

Validate parameters, failing loudly on nonsensical values.

RobustConfig dataclass

RobustConfig(loss: RobustLoss = RobustLoss.SOFT_L1, f_scale: float = 1.0)

Configuration for :class:~scdecon.deconvolution.robust.RobustSolver.

Attributes:

Name Type Description
loss RobustLoss

Robust loss function (:class:RobustLoss); soft_l1 by default.

f_scale float

The soft-margin scale of the robust loss (scipy.optimize.least_squares f_scale): residuals below this scale are treated as inliers. Must be positive.

__post_init__

__post_init__() -> None

Validate parameters, failing loudly on nonsensical values.

RobustLoss

Bases: StrEnum

Robust loss for :class:~scdecon.deconvolution.robust.RobustSolver.

Values are the loss names accepted by scipy.optimize.least_squares.

RobustSolver

RobustSolver(config: RobustConfig | None = None)

Bases: Solver

Deconvolution via robust non-negative least squares (see module docstring).

fit

fit(signature: NDArray[float64], bulk: NDArray[float64]) -> NDArray[np.float64]

Estimate cell-type proportions for one bulk sample.

Solves min sum_g rho((S @ p - b)_g) subject to p >= 0 with a robust loss, then renormalises to sum(p) == 1.

Parameters:

Name Type Description Default
signature NDArray[float64]

Signature matrix S of shape (n_genes, n_cell_types).

required
bulk NDArray[float64]

Bulk expression vector b of shape (n_genes,), gene-aligned to signature.

required

Returns:

Type Description
ndarray

Proportion vector of shape (n_cell_types,) with p >= 0 and sum(p) == 1. Neither input is modified.

Raises:

Type Description
ValueError

If the bulk has no positive expression to fit, or the solution is all zero (nothing to normalise). Unlike NNLS/nu-SVR -- whose solvers return exact zeros for a degenerate bulk -- the trust-region optimiser converges to a tiny non-zero interior point, so the degenerate case is caught at the input (a bulk with no positive signal) rather than only at the solution.

align_signature_and_bulk

align_signature_and_bulk(signature: DataFrame, bulk: DataFrame, *, min_overlap: float = DEFAULT_MIN_OVERLAP) -> AlignedInputs

Restrict a signature and bulk matrix to their shared genes.

Genes common to both are selected in signature row order (deterministic). A warning is emitted if the fraction of signature genes found in the bulk matrix falls below min_overlap.

Parameters:

Name Type Description Default
signature DataFrame

Signature matrix (genes x cell types), gene-indexed.

required
bulk DataFrame

Bulk expression matrix (genes x samples), gene-indexed.

required
min_overlap float

Minimum fraction of signature genes that must be present in bulk before a low-overlap warning is logged. In [0, 1].

DEFAULT_MIN_OVERLAP

Returns:

Type Description
AlignedInputs

Aligned NumPy arrays plus gene / cell-type / sample labels.

Raises:

Type Description
ValueError

If min_overlap is outside [0, 1], either matrix has duplicate gene identifiers, or the signature and bulk share no genes.

run_benchmark

run_benchmark(signature: DataFrame, bulk: DataFrame, truth: DataFrame, solvers: Mapping[str, Solver], *, min_overlap: float = DEFAULT_MIN_OVERLAP) -> BenchmarkResult

Benchmark solvers over one shared, once-aligned pseudobulk set.

Parameters:

Name Type Description Default
signature DataFrame

Signature matrix (genes x cell types), gene-indexed.

required
bulk DataFrame

Bulk expression matrix (genes x samples), gene-indexed.

required
truth DataFrame

Ground-truth proportions (cell types x samples).

required
solvers Mapping[str, Solver]

Mapping of caller-chosen name -> :class:Solver. Required; the harness never constructs solvers itself. Names are preserved exactly.

required
min_overlap float

Passed to :func:~scdecon.deconvolution.align.align_signature_and_bulk.

DEFAULT_MIN_OVERLAP

Returns:

Type Description
BenchmarkResult

Per-solver validation report and runtime.

Raises:

Type Description
ValueError

If solvers is empty, or the inputs cannot be aligned / evaluated.