File size: 913 Bytes
fc329a3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | """Bulk deconvolution via constrained least squares."""
import numpy as np
from scipy.optimize import nnls
import logging
log = logging.getLogger(__name__)
def nnls_deconv(bulk: np.ndarray, signature: np.ndarray) -> np.ndarray:
"""Deconvolve bulk expression into cell type proportions via NNLS.
Solves: bulk_i ≈ signature @ p_i, s.t. p_i >= 0, then normalizes to simplex.
Args:
bulk: (n_samples, n_genes)
signature: (n_genes, K)
Returns:
proportions_hat: (n_samples, K), rows sum to 1
"""
n = bulk.shape[0]
K = signature.shape[1]
props = np.zeros((n, K))
for i in range(n):
coef, _ = nnls(signature, bulk[i])
total = coef.sum()
if total > 0:
props[i] = coef / total
else:
props[i] = 1.0 / K # fallback to uniform
log.info(f"Deconvolved {n} samples into {K} types")
return props
|