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