From 78ac2788782161457acd6b58959305fa5c5087b7 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 28 Mar 2024 14:55:02 -0400 Subject: [PATCH 001/103] remove files Plan: - make Salamander compatible with anndata - reimplement NMF models one at a time Remove all algorithms except KL-NMF. The goal is to avoid having a broken software along the refactorization. --- src/salamander/__init__.py | 5 +- .../nmf_framework/_utils_corrnmf.py | 289 ------- src/salamander/nmf_framework/corrnmf.py | 547 ------------ src/salamander/nmf_framework/corrnmf_det.py | 285 ------- .../nmf_framework/multimodal_corrnmf.py | 794 ------------------ src/salamander/nmf_framework/mvnmf.py | 262 ------ tests/test_corrnmf.py | 221 ----- .../corrnmf/L_init_nsigs1_dim1.npy | Bin 136 -> 0 bytes .../corrnmf/L_init_nsigs2_dim2.npy | Bin 160 -> 0 bytes .../corrnmf/L_updated_nsigs1_dim1.npy | Bin 136 -> 0 bytes .../corrnmf/L_updated_nsigs2_dim2.npy | Bin 160 -> 0 bytes .../corrnmf/U_init_nsigs1_dim1.npy | Bin 208 -> 0 bytes .../corrnmf/U_init_nsigs2_dim2.npy | Bin 288 -> 0 bytes .../corrnmf/U_updated_nsigs1_dim1.npy | Bin 208 -> 0 bytes .../corrnmf/U_updated_nsigs2_dim2.npy | Bin 288 -> 0 bytes .../corrnmf/W_init_nsigs1_dim1.npy | Bin 896 -> 0 bytes .../corrnmf/W_init_nsigs2_dim2.npy | Bin 1664 -> 0 bytes .../corrnmf/W_updated_nsigs1_dim1.npy | Bin 896 -> 0 bytes .../corrnmf/W_updated_nsigs2_dim2.npy | Bin 1664 -> 0 bytes .../corrnmf/alpha_init_nsigs1_dim1.npy | Bin 208 -> 0 bytes .../corrnmf/alpha_init_nsigs2_dim2.npy | Bin 208 -> 0 bytes .../corrnmf/alpha_updated_nsigs1_dim1.npy | Bin 208 -> 0 bytes .../corrnmf/alpha_updated_nsigs2_dim2.npy | Bin 208 -> 0 bytes .../corrnmf/beta_init_nsigs1_dim1.npy | Bin 136 -> 0 bytes .../corrnmf/beta_init_nsigs2_dim2.npy | Bin 144 -> 0 bytes .../corrnmf/beta_updated_nsigs1_dim1.npy | Bin 136 -> 0 bytes .../corrnmf/beta_updated_nsigs2_dim2.npy | Bin 144 -> 0 bytes .../nmf_framework/corrnmf/counts.csv | 97 --- .../corrnmf/objective_init_nsigs1_dim1.npy | Bin 136 -> 0 bytes .../corrnmf/objective_init_nsigs2_dim2.npy | Bin 136 -> 0 bytes .../nmf_framework/corrnmf/p_nsigs1_dim1.npy | Bin 7808 -> 0 bytes .../nmf_framework/corrnmf/p_nsigs2_dim2.npy | Bin 15488 -> 0 bytes .../corrnmf/sigma_sq_init_nsigs1_dim1.npy | Bin 136 -> 0 bytes .../corrnmf/sigma_sq_init_nsigs2_dim2.npy | Bin 136 -> 0 bytes .../corrnmf/sigma_sq_updated_nsigs1_dim1.npy | Bin 136 -> 0 bytes .../corrnmf/sigma_sq_updated_nsigs2_dim2.npy | Bin 136 -> 0 bytes .../surrogate_objective_init_nsigs1_dim1.npy | Bin 136 -> 0 bytes .../surrogate_objective_init_nsigs2_dim2.npy | Bin 136 -> 0 bytes .../multimodal_corrnmf/U_init.npy | Bin 288 -> 0 bytes .../multimodal_corrnmf/U_updated.npy | Bin 288 -> 0 bytes .../multimodal_corrnmf/model0_L_init.npy | Bin 160 -> 0 bytes .../multimodal_corrnmf/model0_L_updated.npy | Bin 160 -> 0 bytes .../multimodal_corrnmf/model0_W_init.npy | Bin 1664 -> 0 bytes .../multimodal_corrnmf/model0_W_updated.npy | Bin 1664 -> 0 bytes .../multimodal_corrnmf/model0_alpha_init.npy | Bin 208 -> 0 bytes .../model0_alpha_updated.npy | Bin 208 -> 0 bytes .../multimodal_corrnmf/model0_beta_init.npy | Bin 144 -> 0 bytes .../model0_beta_updated.npy | Bin 144 -> 0 bytes .../multimodal_corrnmf/model0_counts.csv | 97 --- .../multimodal_corrnmf/model0_p.npy | Bin 15488 -> 0 bytes .../multimodal_corrnmf/model1_L_init.npy | Bin 176 -> 0 bytes .../multimodal_corrnmf/model1_L_updated.npy | Bin 176 -> 0 bytes .../multimodal_corrnmf/model1_W_init.npy | Bin 2432 -> 0 bytes .../multimodal_corrnmf/model1_W_updated.npy | Bin 2432 -> 0 bytes .../multimodal_corrnmf/model1_alpha_init.npy | Bin 208 -> 0 bytes .../model1_alpha_updated.npy | Bin 208 -> 0 bytes .../multimodal_corrnmf/model1_beta_init.npy | Bin 152 -> 0 bytes .../model1_beta_updated.npy | Bin 152 -> 0 bytes .../multimodal_corrnmf/model1_counts.csv | 97 --- .../multimodal_corrnmf/model1_p.npy | Bin 23168 -> 0 bytes .../multimodal_corrnmf/objective_init.npy | Bin 136 -> 0 bytes .../multimodal_corrnmf/sigma_sq_init.npy | Bin 136 -> 0 bytes .../multimodal_corrnmf/sigma_sq_updated.npy | Bin 136 -> 0 bytes .../surrogate_objective_init.npy | Bin 136 -> 0 bytes .../nmf_framework/mvnmf/H_init_nsigs1.npy | Bin 208 -> 0 bytes .../nmf_framework/mvnmf/H_init_nsigs2.npy | Bin 288 -> 0 bytes .../nmf_framework/mvnmf/H_updated_nsigs1.npy | Bin 208 -> 0 bytes .../nmf_framework/mvnmf/H_updated_nsigs2.npy | Bin 288 -> 0 bytes .../nmf_framework/mvnmf/W_init_nsigs1.npy | Bin 896 -> 0 bytes .../nmf_framework/mvnmf/W_init_nsigs2.npy | Bin 1664 -> 0 bytes .../nmf_framework/mvnmf/W_updated_nsigs1.npy | Bin 896 -> 0 bytes .../nmf_framework/mvnmf/W_updated_nsigs2.npy | Bin 1664 -> 0 bytes .../test_data/nmf_framework/mvnmf/counts.csv | 97 --- .../mvnmf/objective_init_nsigs1.npy | Bin 136 -> 0 bytes .../mvnmf/objective_init_nsigs2.npy | Bin 136 -> 0 bytes tests/test_multimodal_corrnmf.py | 323 ------- tests/test_mvnmf.py | 78 -- 77 files changed, 1 insertion(+), 3191 deletions(-) delete mode 100644 src/salamander/nmf_framework/_utils_corrnmf.py delete mode 100644 src/salamander/nmf_framework/corrnmf.py delete mode 100644 src/salamander/nmf_framework/corrnmf_det.py delete mode 100755 src/salamander/nmf_framework/multimodal_corrnmf.py delete mode 100644 src/salamander/nmf_framework/mvnmf.py delete mode 100644 tests/test_corrnmf.py delete mode 100644 tests/test_data/nmf_framework/corrnmf/L_init_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/L_init_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/L_updated_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/L_updated_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/U_init_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/U_init_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/U_updated_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/U_updated_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/W_init_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/W_init_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/W_updated_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/W_updated_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/alpha_init_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/alpha_init_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/alpha_updated_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/alpha_updated_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/beta_init_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/beta_init_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/beta_updated_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/beta_updated_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/counts.csv delete mode 100644 tests/test_data/nmf_framework/corrnmf/objective_init_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/objective_init_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/p_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/p_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/sigma_sq_init_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/sigma_sq_init_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/sigma_sq_updated_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/sigma_sq_updated_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/surrogate_objective_init_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/surrogate_objective_init_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/U_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/U_updated.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model0_L_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model0_L_updated.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model0_W_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model0_W_updated.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model0_alpha_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model0_alpha_updated.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model0_beta_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model0_beta_updated.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model0_counts.csv delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model0_p.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model1_L_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model1_L_updated.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model1_W_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model1_W_updated.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model1_alpha_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model1_alpha_updated.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model1_beta_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model1_beta_updated.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model1_counts.csv delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/model1_p.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/objective_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/sigma_sq_init.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/sigma_sq_updated.npy delete mode 100644 tests/test_data/nmf_framework/multimodal_corrnmf/surrogate_objective_init.npy delete mode 100644 tests/test_data/nmf_framework/mvnmf/H_init_nsigs1.npy delete mode 100644 tests/test_data/nmf_framework/mvnmf/H_init_nsigs2.npy delete mode 100644 tests/test_data/nmf_framework/mvnmf/H_updated_nsigs1.npy delete mode 100644 tests/test_data/nmf_framework/mvnmf/H_updated_nsigs2.npy delete mode 100644 tests/test_data/nmf_framework/mvnmf/W_init_nsigs1.npy delete mode 100644 tests/test_data/nmf_framework/mvnmf/W_init_nsigs2.npy delete mode 100644 tests/test_data/nmf_framework/mvnmf/W_updated_nsigs1.npy delete mode 100644 tests/test_data/nmf_framework/mvnmf/W_updated_nsigs2.npy delete mode 100644 tests/test_data/nmf_framework/mvnmf/counts.csv delete mode 100644 tests/test_data/nmf_framework/mvnmf/objective_init_nsigs1.npy delete mode 100644 tests/test_data/nmf_framework/mvnmf/objective_init_nsigs2.npy delete mode 100644 tests/test_multimodal_corrnmf.py delete mode 100644 tests/test_mvnmf.py diff --git a/src/salamander/__init__.py b/src/salamander/__init__.py index 59d4511..8be1da5 100644 --- a/src/salamander/__init__.py +++ b/src/salamander/__init__.py @@ -2,14 +2,11 @@ Salamander: a non-negative matrix factorization framework for signature analysis ================================================================================ """ -from .nmf_framework.corrnmf_det import CorrNMFDet from .nmf_framework.klnmf import KLNMF -from .nmf_framework.multimodal_corrnmf import MultimodalCorrNMF -from .nmf_framework.mvnmf import MvNMF from .plot import set_salamander_style __version__ = "0.3.2" -__all__ = ["CorrNMFDet", "KLNMF", "MvNMF", "MultimodalCorrNMF"] +__all__ = ["KLNMF"] set_salamander_style() diff --git a/src/salamander/nmf_framework/_utils_corrnmf.py b/src/salamander/nmf_framework/_utils_corrnmf.py deleted file mode 100644 index 288f52c..0000000 --- a/src/salamander/nmf_framework/_utils_corrnmf.py +++ /dev/null @@ -1,289 +0,0 @@ -import numpy as np -from numba import njit - -EPSILON = np.finfo(np.float32).eps - - -@njit -def update_alpha( - X: np.ndarray, beta: np.ndarray, L: np.ndarray, U: np.ndarray -) -> np.ndarray: - """ - Compute the new sample biases alpha according to the update rule of CorrNMF. - - Parameters - ---------- - X : np.ndarray of shape (n_features, n_samples) - data matrix - - beta : np.ndarray of shape (n_signatures,) - signature biases - - L : np.ndarray of shape (dim_embeddings, n_signatures) - signature embeddings - - U : np.ndarray of shape (dim_embeddings, n_samples) - sample embeddings - - Returns - ------- - alpha : np.ndarray of shape (n_samples,) - The new sample biases - """ - second_term = np.exp(beta[:, np.newaxis] + L.T @ U) - alpha = np.log(np.sum(X, axis=0)) - np.log(np.sum(second_term, axis=0)) - return alpha - - -@njit -def update_beta( - X: np.ndarray, p: np.ndarray, alpha: np.ndarray, L: np.ndarray, U: np.ndarray -) -> np.ndarray: - """ - Compute the new signature biases beta according to the update rule of CorrNMF. - - Parameters - ---------- - X : np.ndarray of shape (n_features, n_samples) - data matrix - - alpha : np.ndarray of shape (n_samples,) - sample biases - - L : np.ndarray of shape (dim_embeddings, n_signatures) - signature embeddings - - U : np.ndarray of shape (dim_embeddings, n_samples) - sample embeddings - - Returns - ------- - beta : np.ndarray of shape (n_signatures,) - The new signature biases - """ - n_features, n_signatures, n_samples = p.shape - first_term = np.zeros(n_signatures) - - for k in range(n_signatures): - for v in range(n_features): - for d in range(n_samples): - first_term[k] += X[v, d] * p[v, k, d] - - second_term = np.exp(alpha + L.T @ U) - beta = np.log(first_term) - np.log(np.sum(second_term, axis=1)) - return beta - - -@njit -def update_p_unnormalized(W: np.ndarray, H: np.ndarray) -> np.ndarray: - """ - Compute the new auxiliary parameters according to the update rule of CorrNMF. - The normalization per mutation type and sample is not performed yet. - - Parameters - ---------- - W : np.ndarray of shape (n_features, n_signatures) - signature matrix - - H : np.ndarray of shape (n_signatures, n_samples) - exposure matrix - - Returns - ------- - p: np.ndarray of shape (n_features, n_signatures, n_samples) - """ - n_features, n_signatures = W.shape - n_samples = H.shape[1] - p = np.zeros((n_features, n_signatures, n_samples)) - - for v in range(n_features): - for k in range(n_signatures): - for d in range(n_samples): - p[v, k, d] = W[v, k] * H[k, d] - - return p - - -@njit -def objective_function_embedding( - embedding, - embeddings_other, - alpha, - beta, - sigma_sq, - aux_vec, - add_penalty=True, -): - r""" - The negative objective function of a signature or sample embedding in CorrNMF. - - Parameters - ---------- - embedding : np.ndarray of shape (dim_embeddings,) - The signature or sample embedding - - embeddings_other : np.ndarray of shape (dim_embeddings, n_samples | n_signatures) - If 'embedding' is a signature embedding, 'embeddings_other' are - all sample embeddings. If 'embedding' is a sample embedding, - 'embeddings_other' are all signature embeddings. - - alpha : float | np.narray of shape (n_samples,) - If 'embedding' is a signature embedding, 'alpha' are - all sample biases. If 'embedding' is a sample embedding, - 'alpha' is the bias of the corresponding sample. - - beta : np.array of shape (n_signatures,) | float - If 'embedding' is a signature embedding, 'beta' is - the corresponding signature bias. If 'embedding' is a sample embedding, - 'beta' are all signature biases. - - sigma_sq : float - model variance - - aux_vec : np.ndarray of shape (n_signatures | n_samples,) - A row or column of - aux[k, d] = \sum_v X_vd * p_vkd, - where X is the data matrix and p are the auxiliary parameters of CorrNMF. - If 'embedding' is a signature embedding, the corresponding row is provided. - If 'embedding' is a sample embedding, the corresponding column is provided. - - add_penalty : bool, default=True - Set to True, the norm of the embedding will be penalized. - This argument is useful for the implementation of multimodal CorrNMF. - """ - n_embeddings_other = embeddings_other.shape[1] - of_value = 0.0 - scalar_products = embeddings_other.T.dot(embedding) - - # aux_vec not necessarily contiguous: - # np.dot(scalar_products, aux_vec) doesn't work - for i in range(n_embeddings_other): - of_value += scalar_products[i] * aux_vec[i] - - # works for alpha and beta being a scalar or vector - of_value -= np.sum(np.exp(alpha + beta + scalar_products)) - - if add_penalty: - of_value -= np.dot(embedding, embedding) / (2 * sigma_sq) - - return -of_value - - -@njit -def gradient_embedding( - embedding, - embeddings_other, - alpha, - beta, - sigma_sq, - summand_grad, - add_penalty=True, -): - r""" - The negative gradient of the objective function w.r.t. a signature or - sample embedding in CorrNMF. - - Parameters - ---------- - embedding : np.ndarray of shape (dim_embeddings,) - The signature or sample embedding - - embeddings_other : np.ndarray of shape (dim_embeddings, n_samples | n_signatures) - If 'embedding' is a signature embedding, 'embeddings_other' are - all sample embeddings. If 'embedding' is a sample embedding, - 'embeddings_other' are all signature embeddings. - - alpha : float | np.narray of shape (n_samples,) - If 'embedding' is a signature embedding, 'alpha' are - all sample biases. If 'embedding' is a sample embedding, - 'alpha' is the bias of the corresponding sample. - - beta : np.array of shape (n_signatures,) | float - If 'embedding' is a signature embedding, 'beta' is - the corresponding signature bias. If 'embedding' is a sample embedding, - 'beta' are all signature biases. - - sigma_sq : float - model variance - - summand_grad : np.ndarray of shape (dim_embeddings,) - A signature/sample-independent summand of the gradient. - - add_penalty : bool, default=True - Set to True, the norm of the embedding will be penalized. - This argument is useful for the implementation of multimodal CorrNMF. - """ - scalar_products = embeddings_other.T.dot(embedding) - # works for alpha and beta being a scalar or vector - gradient = -np.sum( - np.exp(alpha + beta + scalar_products) * embeddings_other, axis=1 - ) - gradient += summand_grad - - if add_penalty: - gradient -= embedding / sigma_sq - - return -gradient - - -@njit -def hessian_embedding( - embedding, - embeddings_other, - alpha, - beta, - sigma_sq, - outer_prods_embeddings_other, - add_penalty=True, -): - r""" - The negative Hessian of the objective function w.r.t. a signature or - sample embedding in CorrNMF. - - Parameters - ---------- - embedding : np.ndarray of shape (dim_embeddings,) - The signature or sample embedding - - embeddings_other : np.ndarray of shape (dim_embeddings, n_samples | n_signatures) - If 'embedding' is a signature embedding, 'embeddings_other' are - all sample embeddings. If 'embedding' is a sample embedding, - 'embeddings_other' are all signature embeddings. - - alpha : float | np.narray of shape (n_samples,) - If 'embedding' is a signature embedding, 'alpha' are - all sample biases. If 'embedding' is a sample embedding, - 'alpha' is the bias of the corresponding sample. - - beta : np.array of shape (n_signatures,) | float - If 'embedding' is a signature embedding, 'beta' is - the corresponding signature bias. If 'embedding' is a sample embedding, - 'beta' are all signature biases. - - sigma_sq : float - model variance - - aux_vec : np.ndarray of shape (n_signatures | n_samples,) - A row or column of - aux[k, d] = \sum_v X_vd * p_vkd, - where X is the data matrix and p are the auxiliary parameters of CorrNMF. - If 'embedding' is a signature embedding, the corresponding row is provided. - If 'embedding' is a sample embedding, the corresponding column is provided. - - add_penalty : bool, default=True - Set to True, the norm of the embedding will be penalized. - This argument is useful for the implementation of multimodal CorrNMF. - """ - dim_embeddings, n_embeddings_other = embeddings_other.shape - scalar_products = embeddings_other.T.dot(embedding) - scalings = np.exp(alpha + beta + scalar_products) - hessian = np.zeros((dim_embeddings, dim_embeddings)) - - for m1 in range(dim_embeddings): - for m2 in range(dim_embeddings): - for i in range(n_embeddings_other): - hessian[m1, m2] -= scalings[i] * outer_prods_embeddings_other[i, m1, m2] - if add_penalty and m1 == m2: - hessian[m1, m2] -= 1 / sigma_sq - - return -hessian diff --git a/src/salamander/nmf_framework/corrnmf.py b/src/salamander/nmf_framework/corrnmf.py deleted file mode 100644 index f399f84..0000000 --- a/src/salamander/nmf_framework/corrnmf.py +++ /dev/null @@ -1,547 +0,0 @@ -from abc import abstractmethod - -import numpy as np -import pandas as pd -from scipy.spatial.distance import squareform -from scipy.special import gammaln - -from ..utils import match_signatures_pair, shape_checker, type_checker -from ._utils_klnmf import kl_divergence, poisson_llh, samplewise_kl_divergence -from .initialization import initialize -from .signature_nmf import SignatureNMF - -EPSILON = np.finfo(np.float32).eps - - -class CorrNMF(SignatureNMF): - r""" - The abstract class CorrNMF unifies the structure of deterministic and - stochastic algorithms to fit the parameters of correlated NMF (CorrNMF). - - The model parameters are the signature and sample biases, the variance, and the - signature matrix. The latent variables are the signature and sample embeddings. - - Overview: - - Every child class has to implement the following methods: - - - _update_alpha: - update the sample exposure biases \alpha - - - _update_beta: - update the signature exposure biases \beta - - - _update_sigma_sq: - update the embedding distribution variance \sigma^2 - - - _update_W: - update the signature matrix W - - - _update_p: - update the auxiliary parameters p - - - _update_l: - update a single signature embedding l - - - _update_u: - update a single sample embedding u - - - fit: - Run CorrNMF for a given mutation count data. - - - The following attributes are implemented in CorrNMF: - - - signatures: pd.DataFrame - The signature matrix including mutation type names and signature names - - - exposures: pd.DataFrame - The exposure matrix including the signature names and sample names - - - reconstruction_error: float - The reconstruction error between the count matrix - and the reconstructed count matrix. - - - samplewise_reconstruction_error: np.ndarray - The samplewise reconstruction error between the sample counts - and the reconstructed sample counts. - - - _n_parameters: - The number of parameters fitted in CorrNMF - - - objective: str - "minimize" or "maximize". CorrNMF maximizes the objective function. - - - corr_signatures: pd.DataFrame - The signature correlation matrix induced by the signature embeddings - - - corr_samples: pd.DataFrame - The sample correlation matrix induced by the sample embeddings - - - The following methods are implemented in CorrNMF: - - - objective_function: - The evidence lower bound (ELBO) of the log-likelihood. - Note: The ELBO is sometimes called the variational lower bound. - - - _surrogate_objective_function: - A surrogate lower bound of the ELBO after introducing the - auxiliary parameters p. In contrast to the original objective_function, - the surrogate is strictly convex in the signature and sample embeddings - - - loglikelihood: - The loglikelihood of the underyling generative model - - - _initialize: - Initialize all model parameters and latent variables depending on the - initialization method chosen - - - _get_embedding_data: - A helper function for the embedding plot that returns the signature - and sample embeddings - - - _get_default_embedding_annotations: - A helper function for the embedding plot that returns the signature names - - More specific docstrings are written for the respective attributes and methods. - """ - - def __init__( - self, - n_signatures=1, - dim_embeddings=None, - init_method="nndsvd", - min_iterations=500, - max_iterations=10000, - conv_test_freq=10, - tol=1e-7, - ): - """ - Input: - ------ - n_signatures: int - The number of underlying signatures that are assumed to - have generated the mutation count data - - dim_embeddings: int - The assumed dimension of the signature and sample embeddings. - Should be smaller or equal to the number of signatures as a dimension - equal to the number of signatures covers the case of independent - signatures. The smaller the embedding dimension, the stronger the - enforced correlation structure on both signatures and samples. - - init_method: str - One of "custom", "flat", "hierarchical_cluster", "nndsvd", - "nndsvda", "nndsvdar" "random" and "separableNMF". - See the initialization module for further details. - - min_iterations: int - The minimum number of iterations to perform during inference - - max_iterations: int - The maximum number of iterations to perform during inference - - conv_test_freq: int - The frequency at which the algorithm is tested for convergence. - The objective function value is only computed every 'conv_test_freq' - many iterations, which also affects a potentially saved history of - the objective function values. - - tol: float - The CorrNMF algorithm is converged when the relative change of the - surrogate objective function of one iteration is smaller - than the tolerance 'tol'. - """ - super().__init__( - n_signatures, - init_method, - min_iterations, - max_iterations, - conv_test_freq, - tol, - ) - - if dim_embeddings is None: - dim_embeddings = n_signatures - - self.dim_embeddings = dim_embeddings - - # initialize data/fitting-dependent attributes - self.W = None - self.alpha = None - self.beta = None - self.L = None - self.U = None - self.sigma_sq = None - - @property - def signatures(self) -> pd.DataFrame: - signatures = pd.DataFrame( - self.W, index=self.mutation_types, columns=self.signature_names - ) - return signatures - - @property - def exposures(self) -> pd.DataFrame: - """ - In contrast to the classical NMF framework, the exposure matrix is - restructured and determined by the signature & sample biases and - embeddings. - """ - exposures = pd.DataFrame( - np.exp(self.alpha + self.beta[:, np.newaxis] + self.L.T @ self.U), - index=self.signature_names, - columns=self.sample_names, - ) - return exposures - - @property - def reconstruction_error(self): - return kl_divergence(self.X, self.W, self.exposures.values) - - @property - def samplewise_reconstruction_error(self): - return samplewise_kl_divergence(self.X, self.W, self.exposures.values) - - def objective_function(self, penalize_sample_embeddings=True) -> float: - """ - The evidence lower bound (ELBO) - """ - elbo = poisson_llh(self.X, self.signatures.values, self.exposures.values) - elbo -= ( - 0.5 - * self.dim_embeddings - * self.n_signatures - * np.log(2 * np.pi * self.sigma_sq) - ) - elbo -= np.sum(self.L**2) / (2 * self.sigma_sq) - - if penalize_sample_embeddings: - elbo -= ( - 0.5 - * self.dim_embeddings - * self.n_samples - * np.log(2 * np.pi * self.sigma_sq) - ) - elbo -= np.sum(self.U**2) / (2 * self.sigma_sq) - - return elbo - - @property - def objective(self) -> str: - return "maximize" - - def _surrogate_objective_function(self, penalize_sample_embeddings=True) -> float: - """ - The surrogate lower bound of the ELBO. - """ - p = self._update_p() - exposures = self.exposures.values - aux = np.log(self.W)[:, :, None] + np.log(exposures)[None, :, :] - np.log(p) - sof_value = np.einsum("VD,VKD,VKD->", self.X, p, aux, optimize="greedy").item() - sof_value -= np.sum(gammaln(1 + self.X)) - sof_value -= np.sum(exposures) - sof_value -= ( - 0.5 - * self.dim_embeddings - * self.n_signatures - * np.log(2 * np.pi * self.sigma_sq) - ) - sof_value -= np.sum(self.L**2) / (2 * self.sigma_sq) - - if penalize_sample_embeddings: - sof_value -= ( - 0.5 - * self.dim_embeddings - * self.n_samples - * np.log(2 * np.pi * self.sigma_sq) - ) - sof_value -= np.sum(self.U**2) / (2 * self.sigma_sq) - - return sof_value - - def loglikelihood(self): - return self.objective_function() - - @property - def _n_parameters(self): - """ - There are n_features * n_signatures parameters corresponding to - the signature matrix, each embedding corresponds to dim_embeddings parameters, - and each signature & sample has a real valued bias. - Finally, the model variance is a single positive real number. - """ - n_parameters_signatures = self.n_features * self.n_signatures - n_parameters_embeddings = self.dim_embeddings * ( - self.n_signatures + self.n_samples - ) - n_parameters_biases = self.n_samples + self.n_signatures - n_parameters_exposures = n_parameters_embeddings + n_parameters_biases - n_parameters = n_parameters_signatures + n_parameters_exposures + 1 - - return n_parameters - - @abstractmethod - def _update_alpha(self): - pass - - @abstractmethod - def _update_sigma_sq(self): - pass - - @abstractmethod - def _update_W(self): - pass - - @abstractmethod - def _update_p(self): - pass - - @abstractmethod - def _update_l(self, index, aux_row, outer_prods_U): - r""" - Input: - ------ - index: int - The index of the signature whose embedding is updated - - aux_row: nd.ndarray - Row of the following matrix: - aux_kd = \sum_v X_vd * p_vkd. - This auxiliary matrix is used for updating the signatures - and the sample embeddidngs. The aux_row argument - is the k-th row of aux, where k is equal to 'index'. - - outer_prods_U: np.ndarray - All outer products of the sample embeddings. - shape: (n_samples, dim_embeddings, dim_embeddings) - """ - - @abstractmethod - def _update_u(self, index, aux_col, outer_prods_L): - r""" - Input: - ------ - index: int - The index of the sample whose embedding is updated - - aux_col: nd.ndarray - Column of the following matrix: - aux_kd = \sum_v X_vd * p_vkd. - This auxiliary matrix is used for updating the signatures - and the sample embeddidngs. The aux_col argument - is the d-th row of aux, where d is equal to 'index'. - - outer_prods_L: np.ndarray - All outer products of the signature embeddings. - shape: (n_signatures, dim_embeddings, dim_embeddings) - """ - - def _check_given_biases(self, given_biases, expected_n_biases, name): - type_checker(name, given_biases, np.ndarray) - shape_checker(name, given_biases, (expected_n_biases,)) - - def _check_given_embeddings(self, given_embeddings, expected_n_embeddings, name): - type_checker(name, given_embeddings, np.ndarray) - shape_checker( - name, given_embeddings, (self.dim_embeddings, expected_n_embeddings) - ) - - def _check_given_parameters( - self, - given_signatures, - given_signature_biases, - given_signature_embeddings, - given_sample_biases, - given_sample_embeddings, - given_variance, - ): - if given_signatures is not None: - self._check_given_signatures(given_signatures) - - if given_signature_biases is not None: - self._check_given_biases( - given_signature_biases, self.n_signatures, "given_signature_biases" - ) - - if given_signature_embeddings is not None: - self._check_given_embeddings( - given_signature_embeddings, - self.n_signatures, - "given_signature_embeddings", - ) - - if given_sample_biases is not None: - self._check_given_biases( - given_sample_biases, self.n_samples, "given_sample_biases" - ) - - if given_sample_embeddings is not None: - self._check_given_embeddings( - given_sample_embeddings, self.n_samples, "given_sample_embeddings" - ) - - if given_variance is not None: - type_checker("given_variance", given_variance, [float, int]) - if given_variance <= 0.0: - raise ValueError("The variance has to be a positive real number.") - - def _initialize( - self, - given_signatures=None, - given_signature_biases=None, - given_signature_embeddings=None, - given_sample_biases=None, - given_sample_embeddings=None, - given_variance=None, - init_kwargs=None, - ): - """ - Initialize the signature matrix W, sample biases alpha, signature biases beta, - the variance, and the signature and sample embeddings. - The signatures or signature embeddings can also be provided by the user. - - Parameters - ---------- - given_signatures: pd.DataFrame, default=None - A priori known signatures. The number of given signatures has - to be less or equal to the number of signatures of NMF - algorithm instance, and the mutation type names have to match - the mutation types of the count data. - - given_signature_biases : np.ndarray, default=None - Known signature biases of shape (n_signatures,) that will be fixed - during model fitting. - - given_signature_embeddings : np.ndarray, default=None - A priori known signature embeddings of shape (dim_embeddings, n_signatures). - - given_sample_biases : np.ndarray, default=None - Known sample biases of shape (n_samples,) that will be fixed - during model fitting. - - given_sample_embeddings : np.ndarray, default=None - A priori known sample embeddings of shape (dim_embeddings, n_samples). - - given_variance : float, default=None - A priori known model variance of the embeddings. - - init_kwargs : dict - Any further keyword arguments to pass to the initialization method. - This includes, for example, a possible 'seed' keyword argument - for all stochastic methods. - """ - self._check_given_parameters( - given_signatures, - given_signature_biases, - given_signature_embeddings, - given_sample_biases, - given_sample_embeddings, - given_variance, - ) - - if given_signatures is not None: - self.n_given_signatures = len(given_signatures.columns) - else: - self.n_given_signatures = 0 - - init_kwargs = {} if init_kwargs is None else init_kwargs.copy() - self.W, _, self.signature_names = initialize( - self.X, self.n_signatures, self.init_method, given_signatures, **init_kwargs - ) - - if given_variance is None: - self.sigma_sq = 1.0 - else: - self.sigma_sq = float(given_variance) - - if given_signature_biases is None: - self.beta = np.zeros(self.n_signatures) - else: - self.beta = given_signature_biases - - if given_signature_embeddings is None: - self.L = np.random.multivariate_normal( - np.zeros(self.dim_embeddings), - self.sigma_sq * np.identity(self.dim_embeddings), - size=self.n_signatures, - ).T - else: - self.L = given_signature_embeddings - - if given_sample_biases is None: - self.alpha = np.zeros(self.n_samples) - else: - self.alpha = given_sample_biases - - if given_sample_embeddings is None: - self.U = np.random.multivariate_normal( - np.zeros(self.dim_embeddings), - self.sigma_sq * np.identity(self.dim_embeddings), - size=self.n_samples, - ).T - else: - self.U = given_sample_embeddings - - @property - def corr_signatures(self) -> pd.DataFrame: - norms = np.sqrt(np.sum(self.L**2, axis=0)) - - corr_vector = np.array( - [ - np.dot(l1, l2) / (norms[k1] * norms[k1 + k2 + 1]) - for k1, l1 in enumerate(self.L.T) - for k2, l2 in enumerate(self.L[:, (k1 + 1) :].T) - ] - ) - corr_matrix = squareform(corr_vector) + np.identity(self.n_signatures) - corr = pd.DataFrame( - corr_matrix, index=self.signature_names, columns=self.signature_names - ) - - return corr - - @property - def corr_samples(self) -> pd.DataFrame: - norms = np.sqrt(np.sum(self.U**2, axis=0)) - - corr_vector = np.array( - [ - np.dot(u1, u2) / (norms[d1] * norms[d1 + d2 + 1]) - for d1, u1 in enumerate(self.U.T) - for d2, u2 in enumerate(self.U[:, (d1 + 1) :].T) - ] - ) - corr_matrix = squareform(corr_vector) + np.identity(self.n_samples) - corr = pd.DataFrame( - corr_matrix, index=self.sample_names, columns=self.sample_names - ) - - return corr - - def reorder(self, other_signatures, metric="cosine", keep_names=False): - reordered_indices = match_signatures_pair( - other_signatures, self.signatures, metric=metric - ) - self.W = self.W[:, reordered_indices] - self.beta = self.beta[reordered_indices] - self.L = self.L[:, reordered_indices] - - if keep_names: - self.signature_names = self.signature_names[reordered_indices] - - return reordered_indices - - def _get_embedding_data(self) -> np.ndarray: - """ - In CorrNMF, the data for the embedding plot are the (transpoed) signature and - sample embeddings. - """ - return np.concatenate([self.L, self.U], axis=1).T.copy() - - def _get_default_embedding_annotations(self) -> np.ndarray: - """ - The embedding plot defaults to annotating the signature embeddings. - """ - return self.signature_names diff --git a/src/salamander/nmf_framework/corrnmf_det.py b/src/salamander/nmf_framework/corrnmf_det.py deleted file mode 100644 index 3eda6f9..0000000 --- a/src/salamander/nmf_framework/corrnmf_det.py +++ /dev/null @@ -1,285 +0,0 @@ -# This implementation relies on helper functions in corrnmf.py. -# In particular, functions with leading '_'' are accessed -# pylint: disable=protected-access - -import numpy as np -import pandas as pd -from scipy import optimize - -from . import _utils_corrnmf -from ._utils_klnmf import update_W -from .corrnmf import CorrNMF - -EPSILON = np.finfo(np.float32).eps - - -class CorrNMFDet(CorrNMF): - r""" - The CorrNMFDet class implements the deterministic batch version of - a variant of the correlated NMF (CorrNMF) algorithm devolped in - "Bayesian Nonnegative Matrix Factorization with Stochastic Variational - Inference" by Paisley et al. - - The following methods are implemented to match the structure of CorrNMF: - - - _update_alpha: - update the sample biases \alpha - - - _update_beta: - update the signature biases \beta - - - _update_sigma_sq: - update the variance \sigma^2 - - - _update_W: - update the signature matrix W - - - _update_p: - update the auxiliary parameters p - - - _update_l: - update a single signature embedding l - - - _update_u: - update a single sample embedding u - - The following method is implemented to match the structure of SignatureNMF: - - - fit: - Inference of the CorrNMF parameters for a given mutation count data - """ - - def _update_alpha(self, given_sample_biases=None): - if given_sample_biases is None: - self.alpha = _utils_corrnmf.update_alpha(self.X, self.beta, self.L, self.U) - - def _update_beta(self, p, given_signature_biases=None): - if given_signature_biases is None: - self.beta = _utils_corrnmf.update_beta( - self.X, p, self.alpha, self.L, self.U - ) - - def _update_sigma_sq(self, given_variance=None): - if given_variance is None: - embeddings = np.concatenate([self.L, self.U], axis=1) - self.sigma_sq = np.mean(embeddings**2) - self.sigma_sq = np.clip(self.sigma_sq, EPSILON, None) - - def _update_W(self): - self.W = update_W( - self.X, - self.W, - self.exposures.values, - n_given_signatures=self.n_given_signatures, - ) - - def _update_p(self): - p = _utils_corrnmf.update_p_unnormalized(self.W, self.exposures.values) - p /= np.sum(p, axis=1, keepdims=True) - p = p.clip(EPSILON) - return p - - def _update_l(self, index, aux_row, outer_prods_U): - beta = self.beta[index] - - def objective_fun(l): - return _utils_corrnmf.objective_function_embedding( - l, self.U, self.alpha, beta, self.sigma_sq, aux_row - ) - - summand_grad = np.sum(aux_row * self.U, axis=1) - - def gradient(l): - return _utils_corrnmf.gradient_embedding( - l, self.U, self.alpha, beta, self.sigma_sq, summand_grad - ) - - def hessian(l): - return _utils_corrnmf.hessian_embedding( - l, self.U, self.alpha, beta, self.sigma_sq, outer_prods_U - ) - - l = optimize.minimize( - fun=objective_fun, - x0=self.L[:, index], - method="Newton-CG", - jac=gradient, - hess=hessian, - ).x - l[(0 < l) & (l < EPSILON)] = EPSILON - l[(-EPSILON < l) & (l < 0)] = -EPSILON - self.L[:, index] = l - - def _update_L(self, aux, outer_prods_U=None): - r""" - Update all signature embeddings by optimizing - the surrogate objective function using scipy.optimize.minimize - with the 'Newton-CG' method (strictly convex for each embedding). - - aux: np.ndarray - aux_kd = \sum_v X_vd * p_vkd - is used for updating the signatures and the sample embeddidngs. - """ - if outer_prods_U is None: - outer_prods_U = np.einsum("mD,nD->Dmn", self.U, self.U) - - for k, aux_row in enumerate(aux): - self._update_l(k, aux_row, outer_prods_U) - - def _update_u(self, index, aux_col, outer_prods_L): - alpha = self.alpha[index] - - def objective_fun(u): - return _utils_corrnmf.objective_function_embedding( - u, self.L, alpha, self.beta, self.sigma_sq, aux_col - ) - - summand_grad = np.sum(aux_col * self.L, axis=1) - - def gradient(u): - return _utils_corrnmf.gradient_embedding( - u, self.L, alpha, self.beta, self.sigma_sq, summand_grad - ) - - def hessian(u): - return _utils_corrnmf.hessian_embedding( - u, self.L, alpha, self.beta, self.sigma_sq, outer_prods_L - ) - - u = optimize.minimize( - fun=objective_fun, - x0=self.U[:, index], - method="Newton-CG", - jac=gradient, - hess=hessian, - options={"maxiter": 3}, - ).x - u[(0 < u) & (u < EPSILON)] = EPSILON - u[(-EPSILON < u) & (u < 0)] = -EPSILON - self.U[:, index] = u - - def _update_U(self, aux): - r""" - Update all sample embeddings by optimizing - the surrogate objective function using scipy.optimize.minimize - with the 'Newton-CG' method (strictly convex for each embedding). - - aux: np.ndarray - aux_kd = \sum_v X_vd * p_vkd - is used for updating the signatures and the sample embeddidngs. - """ - outer_prods_L = np.einsum("mK,nK->Kmn", self.L, self.L) - - for d, aux_col in enumerate(aux.T): - self._update_u(d, aux_col, outer_prods_L) - - def _update_LU( - self, p, given_signature_embeddings=None, given_sample_embeddings=None - ): - aux = np.einsum("vd,vkd->kd", self.X, p) - - if given_signature_embeddings is None: - self._update_L(aux) - - if given_sample_embeddings is None: - self._update_U(aux) - - def fit( - self, - data: pd.DataFrame, - given_signatures=None, - given_signature_biases=None, - given_signature_embeddings=None, - given_sample_biases=None, - given_sample_embeddings=None, - given_variance=None, - init_kwargs=None, - history=False, - verbose=0, - ): - """ - Maximize the surrogate objective function of correlated NMF (CNMF). - - Input: - ------ - data: pd.DataFrame - The mutation count data - - given_signatures: pd.DataFrame, default=None - Known signatures that will be fixed during model fitting. - - given_signature_biases : np.ndarray, default=None - Known signature biases of shape (n_signatures,) that will be fixed - during model fitting. - - given_signature_embeddings: np.ndarray, default=None - Known signature embeddings that will be fixed during model fitting. - - given_sample_biases : np.ndarray, default=None - Known sample biases of shape (n_samples,) that will be fixed - during model fitting. - - given_sample_embeddings : np.ndarray, default=None - Known sample embeddings that will be fixed during model fitting. - - given_variance : float, default=None - Known model variance that will be fixed during model fitting. - - init_kwargs: dict - Any further keywords arguments to be passed to the initialization method. - This includes, for example, a possible 'seed' keyword argument - for all stochastic methods. - - history: bool - When set to true, the history of the objective function and - surrogate objective function will be stored in a dictionary. - - verbose: int - Every 100th iteration number will be printed when set unequal to zero. - - Returns - ------- - self : object - Returns the instance itself. - """ - self._setup_data_parameters(data) - self._initialize( - given_signatures=given_signatures, - given_signature_biases=given_signature_biases, - given_signature_embeddings=given_signature_embeddings, - given_sample_biases=given_sample_biases, - given_sample_embeddings=given_sample_embeddings, - given_variance=given_variance, - init_kwargs=init_kwargs, - ) - of_values = [self.objective_function()] - n_iteration = 0 - converged = False - - while not converged: - n_iteration += 1 - - if verbose and n_iteration % 100 == 0: - print(f"iteration: {n_iteration}; objective: {of_values[-1]:.2f}") - - self._update_alpha(given_sample_biases) - p = self._update_p() - self._update_beta(p, given_signature_biases) - self._update_LU(p, given_signature_embeddings, given_sample_embeddings) - self._update_sigma_sq(given_variance) - - if self.n_given_signatures < self.n_signatures: - self._update_W() - - if n_iteration % self.conv_test_freq == 0: - prev_of_value = of_values[-1] - of_values.append(self.objective_function()) - rel_change = (of_values[-1] - prev_of_value) / np.abs(prev_of_value) - converged = rel_change < self.tol and n_iteration >= self.min_iterations - - converged |= n_iteration >= self.max_iterations - - if history: - self.history["objective_function"] = of_values[1:] - - return self diff --git a/src/salamander/nmf_framework/multimodal_corrnmf.py b/src/salamander/nmf_framework/multimodal_corrnmf.py deleted file mode 100755 index cdf2f56..0000000 --- a/src/salamander/nmf_framework/multimodal_corrnmf.py +++ /dev/null @@ -1,794 +0,0 @@ -""" -Multimodal correlated NMF (MultiCorrNMF) fits multiple correlated NMF (CorrNMF) -models jointly in the following manner: -Assuming that the input data for each modality originates from the identical samples, -MultiCorrNMF fixes the sample embeddings accross modalities and learns signature -embeddings for all modalities in the same embedding space. -""" -# This implementation heavily relies on the implementaion of CorrNMF in -# corrnmf_det.py. In particular, CorrNMFDet methods with a leading '_' -# are accessed. -# pylint: disable=protected-access - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from scipy import optimize -from scipy.spatial.distance import squareform - -from ..plot import ( - _get_sample_order, - corr_plot, - embeddings_plot, - history_plot, - signatures_plot, -) -from ..utils import type_checker, value_checker -from . import _utils_corrnmf -from .corrnmf_det import CorrNMFDet - -EPSILON = np.finfo(np.float32).eps - - -class MultimodalCorrNMF: - def __init__( - self, - n_modalities, - ns_signatures=None, - dim_embeddings=None, - init_method="nndsvd", - min_iterations=500, - max_iterations=10000, - conv_test_freq=10, - tol=1e-7, - ): - self.n_modalities = n_modalities - - if ns_signatures is None: - ns_signatures = np.ones(n_modalities, dtype=int) - - self.ns_signatures = ns_signatures - - if dim_embeddings is None: - dim_embeddings = np.max(ns_signatures) - - self.dim_embeddings = dim_embeddings - self.init_method = init_method - self.min_iterations = min_iterations - self.max_iterations = max_iterations - self.conv_test_freq = conv_test_freq - self.tol = tol - self.models = [ - CorrNMFDet(n_signatures, dim_embeddings, init_method) - for n_signatures in ns_signatures - ] - - # initialize data/fitting dependent attributes - self.modality_names = np.empty(n_modalities, dtype=str) - self.n_samples = 0 - self.history = {} - - @property - def signatures(self) -> dict: - return { - name: model.signatures - for name, model in zip(self.modality_names, self.models) - } - - @property - def exposures(self) -> dict: - return { - name: model.exposures - for name, model in zip(self.modality_names, self.models) - } - - @property - def data_reconstructed(self) -> dict: - return { - name: model.data_reconstructred - for name, model in zip(self.modality_names, self.models) - } - - @property - def Xs_reconstructed(self) -> np.ndarray: - return { - name: model.X_reconstructed - for name, model in zip(self.modality_names, self.models) - } - - @property - def reconstruction_errors(self) -> float: - return { - name: model.reconstruction_error - for name, model in zip(self.modality_names, self.models) - } - - @property - def samplewise_reconstruction_errors(self) -> np.ndarray: - return { - name: model.samplewise_reconstruction_error - for name, model in zip(self.modality_names, self.models) - } - - def objective_function(self) -> float: - """ - The objective function to be optimized during fitting. - """ - elbo = np.sum( - [ - model.objective_function(penalize_sample_embeddings=False) - for model in self.models - ] - ) - elbo -= ( - 0.5 - * self.dim_embeddings - * self.n_samples - * np.log(2 * np.pi * self.models[0].sigma_sq) - ) - elbo -= np.sum(self.models[0].U ** 2) / (2 * self.models[0].sigma_sq) - return elbo - - @property - def objective(self) -> str: - return "maximize" - - def _surrogate_objective_function(self) -> float: - """ - The surrogate lower bound of the ELBO. - """ - ps = self._update_ps() - sof_value = np.sum( - [ - model._surrogate_objective_function(penalize_sample_embeddings=False) - for model, p in zip(self.models, ps) - ] - ) - sof_value -= ( - 0.5 - * self.dim_embeddings - * self.n_samples - * np.log(2 * np.pi * self.models[0].sigma_sq) - ) - sof_value -= np.sum(self.models[0].U ** 2) / (2 * self.models[0].sigma_sq) - return sof_value - - def loglikelihood(self) -> float: - """ - The log-likelihood of the underlying generative model. - """ - return self.objective_function() - - @property - def _n_parameters(self) -> int: - n_parameters_signatures = np.sum( - [model.n_features * model.n_signatures for model in self.models] - ) - n_parameters_embeddings = self.dim_embeddings * ( - np.sum(self.ns_signatures) + self.n_samples - ) - n_parameters_biases = self.n_modalities * self.n_samples + np.sum( - self.ns_signatures - ) - n_parameters_exposures = n_parameters_embeddings + n_parameters_biases - n_parameters = n_parameters_signatures + n_parameters_exposures + 1 - - return n_parameters - - @property - def bic(self) -> float: - return self._n_parameters * np.log(self.n_samples) - 2 * self.loglikelihood() - - def _update_alphas(self, given_sample_biases): - for model, given_sam_biases in zip(self.models, given_sample_biases): - model._update_alpha(given_sam_biases) - - def _update_betas(self, ps, given_signature_biases): - for model, p, given_sig_biases in zip(self.models, ps, given_signature_biases): - model._update_beta(p, given_sig_biases) - - def _update_sigma_sq(self, given_variance): - if given_variance is None: - Ls = np.concatenate([model.L for model in self.models], axis=1) - embeddings = np.concatenate([Ls, self.models[0].U], axis=1) - sigma_sq = np.mean(embeddings**2) - sigma_sq = np.clip(sigma_sq, EPSILON, None) - - for model in self.models: - model.sigma_sq = sigma_sq - - def _update_Ws(self): - for model in self.models: - if model.n_given_signatures < model.n_signatures: - model._update_W() - - def _update_ps(self): - return [model._update_p() for model in self.models] - - def _objective_fun_u(self, u, index, aux_cols): - sigma_sq = self.models[0].sigma_sq - s = -np.sum( - [ - _utils_corrnmf.objective_function_embedding( - u, - model.L, - model.alpha[index], - model.beta, - sigma_sq, - aux_col, - add_penalty=False, - ) - for model, aux_col in zip(self.models, aux_cols) - ] - ) - s -= np.dot(u, u) / (2 * sigma_sq) - return -s - - def _gradient_u(self, u, index, s_grads): - sigma_sq = self.models[0].sigma_sq - s = -np.sum( - [ - _utils_corrnmf.gradient_embedding( - u, - model.L, - model.alpha[index], - model.beta, - sigma_sq, - s_grad, - add_penalty=False, - ) - for model, s_grad in zip(self.models, s_grads) - ], - axis=0, - ) - s -= u / sigma_sq - return -s - - def _hessian_u(self, u, index, outer_prods_Ls): - sigma_sq = self.models[0].sigma_sq - s = -np.sum( - [ - _utils_corrnmf.hessian_embedding( - u, - model.L, - model.alpha[index], - model.beta, - sigma_sq, - outer_prods_L, - add_penalty=False, - ) - for model, outer_prods_L in zip(self.models, outer_prods_Ls) - ], - axis=0, - ) - s -= np.diag(np.full(self.dim_embeddings, 1 / sigma_sq)) - return -s - - def _update_u(self, index, aux_cols, outer_prods_Ls): - def objective_fun(u): - return self._objective_fun_u(u, index, aux_cols) - - s_grads = np.array( - [ - np.sum(aux_col * model.L, axis=1) - for model, aux_col in zip(self.models, aux_cols) - ] - ) - - def gradient(u): - return self._gradient_u(u, index, s_grads) - - def hessian(u): - return self._hessian_u(u, index, outer_prods_Ls) - - u = optimize.minimize( - fun=objective_fun, - x0=self.models[0].U[:, index], - method="Newton-CG", - jac=gradient, - hess=hessian, - options={"maxiter": 3}, - ).x - u[(0 < u) & (u < EPSILON)] = EPSILON - u[(-EPSILON < u) & (u < 0)] = -EPSILON - - for model in self.models: - model.U[:, index] = u - - def _update_U(self, auxs): - outer_prods_Ls = [ - np.einsum("mK,nK->Kmn", model.L, model.L) for model in self.models - ] - - for d in range(self.n_samples): - aux_cols = [aux[:, d] for aux in auxs] - self._update_u(d, aux_cols, outer_prods_Ls) - - def _update_Ls(self, auxs, outer_prods_U, given_signature_embeddings): - for model, aux, given_sig_embs in zip( - self.models, auxs, given_signature_embeddings - ): - if given_sig_embs is None: - model._update_L(aux, outer_prods_U) - - def _update_LsU(self, ps, given_signature_embeddings, given_sample_embeddings): - auxs = [ - np.einsum("vd,vkd->kd", model.X, p) for model, p in zip(self.models, ps) - ] - outer_prods_U = np.einsum("mD,nD->Dmn", self.models[0].U, self.models[0].U) - self._update_Ls(auxs, outer_prods_U, given_signature_embeddings) - - if given_sample_embeddings is None: - self._update_U(auxs) - - def _setup_data_parameters(self, data: list): - type_checker("data", data, list) - - if len(data) != self.n_modalities: - raise ValueError( - f"The input data has to be {self.n_modalities} " - "many named pandas dataframes." - ) - - for df in data: - type_checker("input dataframe", df, pd.DataFrame) - - if df.index.name is None: - raise ValueError( - "You have to set 'df.index.name' to a " - "meaningful name for every input dataframe." - ) - - self.modality_names = np.array([df.index.name for df in data]) - self.n_samples = data[0].shape[1] - - for model, df in zip(self.models, data): - model._setup_data_parameters(df) - - def _initialize( - self, - given_signatures=None, - given_signature_biases=None, - given_signature_embeddings=None, - given_sample_biases=None, - given_sample_embeddings=None, - given_variance=None, - init_kwargs=None, - ): - if given_signatures is None: - given_signatures = [None for _ in range(self.n_modalities)] - - if given_variance is None: - variance = 1.0 - else: - variance = given_variance - - if given_signature_biases is None: - given_signature_biases = [None for _ in range(self.n_modalities)] - - if given_signature_embeddings is None: - given_signature_embeddings = [None for _ in range(self.n_modalities)] - - if given_sample_biases is None: - given_sample_biases = [None for _ in range(self.n_modalities)] - - if given_sample_embeddings is None: - U = np.random.multivariate_normal( - np.zeros(self.dim_embeddings), - variance * np.identity(self.dim_embeddings), - size=self.n_samples, - ).T - else: - U = given_sample_embeddings - - for ( - model, - modality_name, - given_sigs, - given_sig_biases, - given_sig_embs, - given_sam_biases, - ) in zip( - self.models, - self.modality_names, - given_signatures, - given_signature_biases, - given_signature_embeddings, - given_sample_biases, - ): - model._initialize( - given_signatures=given_sigs, - given_signature_biases=given_sig_biases, - given_signature_embeddings=given_sig_embs, - given_sample_biases=given_sam_biases, - given_sample_embeddings=U, - given_variance=variance, - init_kwargs=init_kwargs, - ) - model.signature_names = np.concatenate( - [ - model.signature_names[: model.n_given_signatures], - np.char.add( - modality_name + " ", - model.signature_names[model.n_given_signatures :], - ), - ] - ) - return ( - given_signature_biases, - given_signature_embeddings, - given_sample_biases, - ) - - def fit( - self, - data: list, - given_signatures=None, - given_signature_biases=None, - given_signature_embeddings=None, - given_sample_biases=None, - given_sample_embeddings=None, - given_variance=None, - init_kwargs=None, - history=False, - verbose=0, - ): - self._setup_data_parameters(data) - ( - given_signature_biases, - given_signature_embeddings, - given_sample_biases, - ) = self._initialize( - given_signatures=given_signatures, - given_signature_biases=given_signature_biases, - given_signature_embeddings=given_signature_embeddings, - given_sample_biases=given_sample_biases, - given_sample_embeddings=given_sample_embeddings, - given_variance=given_variance, - init_kwargs=init_kwargs, - ) - of_values = [self.objective_function()] - n_iteration = 0 - converged = False - - while not converged: - n_iteration += 1 - - if verbose and n_iteration % 100 == 0: - print(f"iteration: {n_iteration}; objective: {of_values[-1]:.2f}") - - self._update_alphas(given_sample_biases) - ps = self._update_ps() - self._update_betas(ps, given_signature_biases) - self._update_LsU(ps, given_signature_embeddings, given_sample_embeddings) - self._update_sigma_sq(given_variance) - self._update_Ws() - - if n_iteration % self.conv_test_freq == 0: - prev_of_value = of_values[-1] - of_values.append(self.objective_function()) - rel_change = (of_values[-1] - prev_of_value) / np.abs(prev_of_value) - converged = rel_change < self.tol and n_iteration >= self.min_iterations - - converged |= n_iteration >= self.max_iterations - - if history: - self.history["objective_function"] = of_values[1:] - - return self - - def plot_history(self, ax=None, min_iteration=0, outfile=None, **kwargs): - if not self.history: - raise ValueError( - "No history available, the model has to be fitted first. " - "Remember to set 'history' to 'True' when calling 'fit()'." - ) - - history_plot( - self.history["objective_function"], - self.conv_test_freq, - min_iteration=min_iteration, - ax=ax, - **kwargs, - ) - - if outfile is not None: - plt.savefig(outfile, bbox_inches="tight") - - return ax - - def plot_signatures( - self, - colors=None, - annotate_mutation_types=False, - figsize=None, - outfile=None, - **kwargs, - ): - if colors is None: - colors = [None for _ in range(self.n_modalities)] - - max_n_signatures = np.max(self.ns_signatures) - - if figsize is None: - figsize = (8 * self.n_modalities, 2 * max_n_signatures) - - fig, axes = plt.subplots(max_n_signatures, self.n_modalities, figsize=figsize) - - for axs, model, cols in zip(axes.T, self.models, colors): - model.plot_signatures( - colors=cols, - annotate_mutation_types=annotate_mutation_types, - axes=axs[: model.n_signatures], - **kwargs, - ) - - for ax in axs[model.n_signatures :]: - fig.delaxes(ax) - - plt.tight_layout() - - if outfile is not None: - plt.savefig(outfile, bbox_inches="tight") - - return axes - - def plot_exposures( - self, - sample_order=None, - reorder_signatures=True, - annotate_samples=True, - colors=None, - ncol_legend=1, - axes=None, - outfile=None, - **kwargs, - ): - """ - Visualize the exposures as a stacked bar chart, - see plot.py for the implementation. - - Input: - ------ - **kwargs: - arguments to be passed to exposure_plot - """ - if axes is None: - _, axes = plt.subplots( - self.n_modalities, figsize=(20, 3 * self.n_modalities) - ) - - if colors is None: - colors = [None for _ in range(self.n_modalities)] - - if sample_order is None: - all_exposures = [model.exposures for model in self.models] - all_exposures_normalized = pd.concat( - [df / df.sum(axis=0) for df in all_exposures] - ) - sample_order = _get_sample_order(all_exposures_normalized) - - for n, (ax, model, cols) in enumerate(zip(axes, self.models, colors)): - if n < self.n_modalities - 1: - annotate = False - else: - annotate = annotate_samples - - ax = model.plot_exposures( - sample_order=sample_order, - reorder_signatures=reorder_signatures, - annotate_samples=annotate, - colors=cols, - ncol_legend=ncol_legend, - ax=ax, - **kwargs, - ) - ax.set_title(f"{self.modality_names[n]} signature exposures") - - plt.tight_layout() - - if outfile is not None: - plt.savefig(outfile, bbox_inches="tight") - - return axes - - @property - def corr_signatures(self) -> pd.DataFrame: - Ls = np.concatenate([model.L for model in self.models], axis=1) - signature_names = np.concatenate( - [model.signature_names for model in self.models] - ) - norms = np.sqrt(np.sum(Ls**2, axis=0)) - - corr_vector = np.array( - [ - np.dot(l1, l2) / (norms[k1] * norms[k1 + k2 + 1]) - for k1, l1 in enumerate(Ls.T) - for k2, l2 in enumerate(Ls[:, k1 + 1 :].T) - ] - ) - corr_matrix = squareform(corr_vector) + np.identity(np.sum(self.ns_signatures)) - corr = pd.DataFrame(corr_matrix, index=signature_names, columns=signature_names) - - return corr - - @property - def corr_samples(self) -> pd.DataFrame: - return self.models[0].corr_samples - - def plot_correlation(self, data="signatures", annot=False, outfile=None, **kwargs): - """ - Plot the correlation matrix of the signatures or samples. - See plot.py for the implementation of corr_plot. - - Input: - ------ - *args, **kwargs: - arguments to be passed to corr_plot - """ - value_checker("data", data, ["signatures", "samples"]) - - if data == "signatures": - corr = self.corr_signatures - - else: - corr = self.corr_samples - - clustergrid = corr_plot(corr, annot=annot, **kwargs) - - if outfile is not None: - plt.savefig(outfile, bbox_inches="tight") - - return clustergrid - - def plot_embeddings(self, annotations=None, outfile=None, **kwargs): - """ - Plot the signature and sample embeddings. If the embedding dimension - is two, the embeddings will be plotted directly, ignoring the chosen method. - See plot.py for the implementation of 'embeddings_plot'. - - Parameters - ---------- - annotations : list[str], default=None - Annotations per data point, e.g. the sample names. If None, - all signatures are annotated. - Note that there are sum('ns_signatures') + 'n_samples' data points, - i.e. the first sum('ns_signatures') elements in 'annotations' - are the signature annotations, not any sample annotations. - - outfile : str, default=None - If not None, the figure will be saved in the specified file path. - - **kwargs : - keyword arguments to pass to seaborn's scatterplot - - Returns - ------- - ax : matplotlib.axes.Axes - The matplotlib axes containing the plot. - """ - Ls = np.concatenate([model.L for model in self.models], axis=1) - embedding_data = np.concatenate([Ls, self.models[0].U], axis=1).T.copy() - - if annotations is None: - annotations = np.concatenate( - [model.signature_names for model in self.models] - ) - - ax = embeddings_plot(data=embedding_data, annotations=annotations, **kwargs) - - if outfile is not None: - plt.savefig(outfile, bbox_inches="tight") - - return ax - - def feature_change(self, in_modality=None, out_modalities="all", normalize=True): - if in_modality is None: - in_modality = self.modality_names[0] - - in_model = self.models[list(self.modality_names).index(in_modality)] - - if out_modalities == "all": - out_modalities = self.modality_names - - if type(out_modalities) is str: - out_modalities = [out_modalities] - - out_modalities = [name for name in out_modalities if name != in_modality] - out_modalities_indices = [ - n for n, name in enumerate(self.modality_names) if name in out_modalities - ] - results = [in_model.signatures] - - for n in out_modalities_indices: - result = self.models[n].signatures @ np.exp(self.models[n].L.T @ in_model.L) - result.columns = in_model.signature_names - - if normalize: - result = result / result.sum(axis=0) - - results.append(result) - - return results - - def plot_feature_change( - self, - in_modality=None, - out_modalities="all", - colors=None, - annotate_mutation_types=False, - figsize=None, - outfile=None, - **kwargs, - ): - """ - For the signatures of one modality, plot the co-occuring spectra - in other modalities. This is achieved by interpreting a signature - embedding as a sample embedding and using the resulting exposures to - compute distributions over mutation types in different modalities. - - Parameters - ---------- - in_modality : str - The modality name of the signatures of interest, e.g. "SBS". - - out_modalities : list or str, default="all" - A list of modalities to convert the 'in_modality' signatures - into, e.g. ["Indel", "SV"]. A single string can also be provided - to select only one 'out_modality'. By default, all modalities - other than the 'in_modality' are selected. - - colors : list, default=None - A list of length '1 + len(out_modalities)' of colors to use - for the signature plots of the input modalitiy signatures - and the co-occuring spectra in the output modalites. - - annotate_mutation_types : bool, default=False - If True, the x-axis of the spectra plots will be annotated - with the mutation types of the respective modalities. - - figsize : tuple, default=None - The size of the matplotlib figure. If None, the figure size - is computed internally based on the number of input - signatures and output modalities. - - outfile : str, default=None - If not None, the figure will be saved to the provided path. - - kwargs : dict - Any keyword arguments to be passed to matplotlibs ax.bar. - - Returns - ------- - axes : np.ndarray - An array of matplotlib axes containing the plots. - """ - # result[0] are the 'in_modality' signatures - results = self.feature_change(in_modality, out_modalities) - n_signatures = results[0].shape[1] - n_feature_spaces = len(results) - - if colors is None: - colors = [None for _ in range(n_feature_spaces)] - - if figsize is None: - figsize = (4 * n_feature_spaces, n_signatures) - - fig, axes = plt.subplots(n_signatures, n_feature_spaces, figsize=figsize) - fig.suptitle("Signature feature change") - - for axs, result, cols in zip(axes.T, results, colors): - signatures_plot( - result, - colors=cols, - annotate_mutation_types=annotate_mutation_types, - axes=axs, - **kwargs, - ) - - plt.tight_layout() - - if outfile is not None: - plt.savefig(outfile, bbox_inches="tight") - - return axes diff --git a/src/salamander/nmf_framework/mvnmf.py b/src/salamander/nmf_framework/mvnmf.py deleted file mode 100644 index ab5336c..0000000 --- a/src/salamander/nmf_framework/mvnmf.py +++ /dev/null @@ -1,262 +0,0 @@ -import numpy as np -import pandas as pd -from numba import njit - -from ..utils import normalize_WH -from ._utils_klnmf import kl_divergence, poisson_llh, samplewise_kl_divergence, update_H -from .nmf import NMF - -EPSILON = np.finfo(np.float32).eps - - -@njit -def volume_logdet(W: np.ndarray, delta: float) -> float: - n_signatures = W.shape[1] - diag = np.diag(np.full(n_signatures, delta)) - volume = np.log(np.linalg.det(W.T @ W + diag)) - - return volume - - -@njit -def kl_divergence_penalized( - X: np.ndarray, W: np.ndarray, H: np.ndarray, lam: float, delta: float -) -> float: - reconstruction_error = kl_divergence(X, W, H) - volume = volume_logdet(W, delta) - loss = reconstruction_error + lam * volume - - return loss - - -@njit -def update_W_unconstrained( - X: np.ndarray, - W: np.ndarray, - H: np.ndarray, - lam: float, - delta: float, - n_given_signatures: int = 0, -) -> np.ndarray: - n_signatures = W.shape[1] - diag = np.diag(np.full(n_signatures, delta)) - Y = np.linalg.inv(W.T @ W + diag) - Y_minus = np.maximum(0, -Y) - Y_abs = np.abs(Y) - WY_minus = W @ Y_minus - WY_abs = W @ Y_abs - - rowsums_H = np.sum(H, axis=1) - discriminant_s1 = (rowsums_H - 4 * lam * WY_minus) ** 2 - discriminant_s2 = 8 * lam * WY_abs * ((X / (W @ H)) @ H.T) - numerator_s1 = np.sqrt(discriminant_s1 + discriminant_s2) - numerator_s2 = -rowsums_H + 4 * lam * WY_minus - numerator = numerator_s1 + numerator_s2 - denominator = 4 * lam * WY_abs - W_unconstrained = W * numerator / denominator - W_unconstrained[:, :n_given_signatures] = W[:, :n_given_signatures].copy() - W_unconstrained[:, n_given_signatures:] = W_unconstrained[ - :, n_given_signatures: - ].clip(EPSILON) - - return W_unconstrained - - -@njit -def line_search( - X: np.ndarray, - W: np.ndarray, - H: np.ndarray, - lam: float, - delta: float, - gamma: float, - W_unconstrained: np.ndarray, -) -> tuple[np.ndarray, np.ndarray, float]: - prev_of_value = kl_divergence_penalized(X, W, H, lam, delta) - W_new, H_new = normalize_WH(W_unconstrained, H) - W_new, H_new = W_new.clip(EPSILON), H_new.clip(EPSILON) - of_value = kl_divergence_penalized(X, W_new, H_new, lam, delta) - - while of_value > prev_of_value and gamma > 1e-16: - gamma *= 0.8 - W_new = (1 - gamma) * W + gamma * W_unconstrained - W_new, H_new = normalize_WH(W_new, H) - W_new, H_new = W_new.clip(EPSILON), H_new.clip(EPSILON) - of_value = kl_divergence_penalized(X, W_new, H_new, lam, delta) - - gamma = min(1.0, 1.2 * gamma) - - return W_new, H_new, gamma - - -class MvNMF(NMF): - """ - Min-volume non-negative matrix factorization. This algorithms is a volume- - regularized version of NMF with the generalized Kullback-Leibler (KL) - divergence. - - Parameters - ---------- - n_signatures: int - Number of signatures to decipher. - - init_method : str, default=nndsvd - One of "custom", "flat", "hierarchical_cluster", "nndsvd", - "nndsvda", "nndsvdar" "random" and "separableNMF". Please see the initialization - module for further details on each method. - - lam : float, default=1.0 - Objective function volume penalty weight. - - delta : float, default=1.0 - Objective function hyperparameter. - - min_iterations : int, default=500 - Minimum number of iterations. - - max_iterations : int, default=10000 - Maximum number of iterations. - - conv_test_freq: int - The frequency at which the algorithm is tested for convergence. - The objective function value is only computed every 'conv_test_freq' - many iterations, which also affects a potentially saved history of - the objective function values. - - tol : float, default=1e-7 - Tolerance of the stopping condition. - - Reference - --------- - Leplat, V., Gillis, N. and Ang, A.M., 2020. - Blind audio source separation with minimum-volume beta-divergence NMF. - IEEE Transactions on Signal Processing, 68, pp.3400-3410. - """ - - def __init__( - self, - n_signatures=1, - init_method="nndsvd", - lam=1.0, - delta=1.0, - min_iterations=500, - max_iterations=10000, - conv_test_freq=10, - tol=1e-7, - ): - super().__init__( - n_signatures, - init_method, - min_iterations, - max_iterations, - conv_test_freq, - tol, - ) - self.lam = lam - self.delta = delta - self._gamma = None - - @property - def reconstruction_error(self): - return kl_divergence(self.X, self.W, self.H) - - @property - def samplewise_reconstruction_error(self): - return samplewise_kl_divergence(self.X, self.W, self.H) - - def objective_function(self): - return kl_divergence_penalized(self.X, self.W, self.H, self.lam, self.delta) - - @property - def objective(self) -> str: - return "minimize" - - def loglikelihood(self) -> float: - return poisson_llh(self.X, self.W, self.H) - - def _update_H(self): - self.H = update_H(self.X, self.W, self.H) - - def _update_W_unconstrained(self): - return update_W_unconstrained( - self.X, self.W, self.H, self.lam, self.delta, self.n_given_signatures - ) - - def _line_search(self, W_unconstrained): - self.W, self.H, self._gamma = line_search( - self.X, - self.W, - self.H, - self.lam, - self.delta, - self._gamma, - W_unconstrained, - ) - - def _update_W(self): - W_unconstrained = self._update_W_unconstrained() - self._line_search(W_unconstrained) - - def fit( - self, - data: pd.DataFrame, - given_signatures=None, - init_kwargs=None, - history=False, - verbose=0, - ): - """ - Parameters - ---------- - data : pd.DataFrame - Input count data. - - given_signatures : pd.DataFrame, default=None - Signatures to fix during the inference. - - init_kwargs: dict - Any further keyword arguments to be passed to the initialization method. - This includes, for example, a possible 'seed' keyword argument - for all stochastic methods. - - history : bool, default=True - If true, the objective function value of each iteration is saved. - - verbose : int, default=0 - Verbosity level. - - Returns - ------- - self : object - Returns the instance itself. - """ - self._setup_data_parameters(data) - self._initialize(given_signatures, init_kwargs) - self._gamma = 1.0 - of_values = [self.objective_function()] - n_iteration = 0 - converged = False - - while not converged: - n_iteration += 1 - - if verbose and n_iteration % 1000 == 0: - print(f"iteration: {n_iteration}; objective: {of_values[-1]:.2f}") - - self._update_H() - - if self.n_given_signatures < self.n_signatures: - self._update_W() - - if n_iteration % self.conv_test_freq == 0: - prev_of_value = of_values[-1] - of_values.append(self.objective_function()) - rel_change = (prev_of_value - of_values[-1]) / prev_of_value - converged = rel_change < self.tol and n_iteration >= self.min_iterations - - converged |= n_iteration >= self.max_iterations - - if history: - self.history["objective_function"] = of_values[1:] - - return self diff --git a/tests/test_corrnmf.py b/tests/test_corrnmf.py deleted file mode 100644 index 481c9d7..0000000 --- a/tests/test_corrnmf.py +++ /dev/null @@ -1,221 +0,0 @@ -import numpy as np -import pandas as pd -import pytest - -from salamander.nmf_framework import corrnmf_det - -PATH = "tests/test_data" -PATH_TEST_DATA = f"{PATH}/nmf_framework/corrnmf" - - -@pytest.fixture -def counts(): - return pd.read_csv(f"{PATH_TEST_DATA}/counts.csv", index_col=0) - - -@pytest.fixture(params=[1, 2]) -def n_signatures(request): - return request.param - - -@pytest.fixture -def dim_embeddings(n_signatures): - return n_signatures - - -@pytest.fixture -def path_suffix(n_signatures, dim_embeddings): - return f"nsigs{n_signatures}_dim{dim_embeddings}.npy" - - -@pytest.fixture -def W_init(path_suffix): - return np.load(f"{PATH_TEST_DATA}/W_init_{path_suffix}") - - -@pytest.fixture -def alpha_init(path_suffix): - return np.load(f"{PATH_TEST_DATA}/alpha_init_{path_suffix}") - - -@pytest.fixture -def beta_init(path_suffix): - return np.load(f"{PATH_TEST_DATA}/beta_init_{path_suffix}") - - -@pytest.fixture -def _p(path_suffix): - return np.load(f"{PATH_TEST_DATA}/p_{path_suffix}") - - -@pytest.fixture -def _aux(counts, _p): - return np.einsum("vd,vkd->kd", counts.values, _p) - - -@pytest.fixture -def L_init(path_suffix): - return np.load(f"{PATH_TEST_DATA}/L_init_{path_suffix}") - - -@pytest.fixture -def U_init(path_suffix): - return np.load(f"{PATH_TEST_DATA}/U_init_{path_suffix}") - - -@pytest.fixture -def sigma_sq_init(path_suffix): - return np.load(f"{PATH_TEST_DATA}/sigma_sq_init_{path_suffix}") - - -@pytest.fixture -def model_init(counts, W_init, alpha_init, beta_init, L_init, U_init, sigma_sq_init): - n_signatures, dim_embeddings = L_init.shape - model = corrnmf_det.CorrNMFDet( - n_signatures=n_signatures, dim_embeddings=dim_embeddings - ) - model.X = counts.values - model.W = W_init - model.alpha = alpha_init - model.beta = beta_init - model.L = L_init - model.U = U_init - model.sigma_sq = sigma_sq_init - model.mutation_types = counts.index - model.signature_names = ["_" for _ in range(n_signatures)] - model.sample_names = counts.columns - model.n_samples = len(counts.columns) - model.given_signature_embeddings = None - return model - - -@pytest.fixture -def objective_init(path_suffix): - return np.load(f"{PATH_TEST_DATA}/objective_init_{path_suffix}") - - -def test_objective_function(model_init, objective_init): - assert np.allclose(model_init.objective_function(), objective_init) - - -@pytest.fixture -def surrogate_objective_init(path_suffix): - return np.load(f"{PATH_TEST_DATA}/surrogate_objective_init_{path_suffix}") - - -def test_surrogate_objective_function(model_init, surrogate_objective_init): - assert np.allclose( - model_init._surrogate_objective_function(), surrogate_objective_init - ) - - -@pytest.fixture -def W_updated(path_suffix): - return np.load(f"{PATH_TEST_DATA}/W_updated_{path_suffix}") - - -@pytest.fixture -def alpha_updated(path_suffix): - return np.load(f"{PATH_TEST_DATA}/alpha_updated_{path_suffix}") - - -@pytest.fixture -def beta_updated(path_suffix): - return np.load(f"{PATH_TEST_DATA}/beta_updated_{path_suffix}") - - -@pytest.fixture -def L_updated(path_suffix): - return np.load(f"{PATH_TEST_DATA}/L_updated_{path_suffix}") - - -@pytest.fixture -def U_updated(path_suffix): - return np.load(f"{PATH_TEST_DATA}/U_updated_{path_suffix}") - - -@pytest.fixture -def sigma_sq_updated(path_suffix): - return np.load(f"{PATH_TEST_DATA}/sigma_sq_updated_{path_suffix}") - - -class TestUpdatesCorrNMFDet: - def test_update_W(self, model_init, W_updated): - model_init._update_W() - assert np.allclose(model_init.W, W_updated) - - def test_update_alpha(self, model_init, alpha_updated): - model_init._update_alpha() - assert np.allclose(model_init.alpha, alpha_updated) - - def test_update_beta(self, model_init, _p, beta_updated): - model_init._update_beta(_p) - assert np.allclose(model_init.beta, beta_updated) - - def test_p(self, model_init, _p): - p_computed = model_init._update_p() - assert np.allclose(p_computed, _p) - - def test_update_L(self, model_init, _aux, L_updated): - model_init._update_L(_aux) - assert np.allclose(model_init.L, L_updated) - - def test_update_U(self, model_init, _aux, U_updated): - model_init._update_U(_aux) - assert np.allclose(model_init.U, U_updated) - - def test_update_sigma_sq(self, model_init, sigma_sq_updated): - model_init._update_sigma_sq() - assert np.allclose(model_init.sigma_sq, sigma_sq_updated) - - -@pytest.mark.parametrize("n_signatures,dim_embeddings", [(1, 1), (2, 1), (2, 2)]) -class TestGivenParametersCorrNMFDet: - @pytest.fixture - def model(self, n_signatures, dim_embeddings): - return corrnmf_det.CorrNMFDet( - n_signatures=n_signatures, - dim_embeddings=dim_embeddings, - min_iterations=3, - max_iterations=3, - ) - - def test_given_signatures(self, model, counts): - for n_given_signatures in range(1, model.n_signatures + 1): - given_signatures = counts.iloc[:, :n_given_signatures].astype(float).copy() - given_signatures /= given_signatures.sum(axis=0) - model.fit(counts, given_signatures=given_signatures) - assert np.allclose( - given_signatures, model.signatures.iloc[:, :n_given_signatures] - ) - - def test_given_signature_biases(self, model, counts): - given_signature_biases = np.random.uniform(size=model.n_signatures) - model.fit(counts, given_signature_biases=given_signature_biases) - assert np.allclose(given_signature_biases, model.beta) - - def test_given_signature_embeddings(self, model, counts): - given_signature_embeddings = np.random.uniform( - size=(model.dim_embeddings, model.n_signatures) - ) - model.fit(counts, given_signature_embeddings=given_signature_embeddings) - assert np.allclose(given_signature_embeddings, model.L) - - def test_given_sample_biases(self, model, counts): - n_samples = counts.shape[1] - given_sample_biases = np.random.uniform(size=n_samples) - model.fit(counts, given_sample_biases=given_sample_biases) - assert np.allclose(given_sample_biases, model.alpha) - - def test_given_sample_embeddings(self, model, counts): - n_samples = len(counts.columns) - given_sample_embeddings = np.random.uniform( - size=(model.dim_embeddings, n_samples) - ) - model.fit(counts, given_sample_embeddings=given_sample_embeddings) - assert np.allclose(given_sample_embeddings, model.U) - - def test_given_variance(self, model, counts): - given_variance = 3 - model.fit(counts, given_variance=given_variance) - assert np.allclose(given_variance, model.sigma_sq) diff --git a/tests/test_data/nmf_framework/corrnmf/L_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/L_init_nsigs1_dim1.npy deleted file mode 100644 index 79ccd51ad2c6b740b7431dd3c4fcb05db2fce536..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I$-ItqrGItsN4WCO14Z*$wloln{W07oMqX8-^I diff --git a/tests/test_data/nmf_framework/corrnmf/L_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/L_init_nsigs2_dim2.npy deleted file mode 100644 index ece11a6283763552b6074676abb1b037d1e596ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Pzeb3bhL41FkEVSf!f3U)jIz!jl8>^B>u3w`~ZIT=;eW42uRc diff --git a/tests/test_data/nmf_framework/corrnmf/L_updated_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/L_updated_nsigs1_dim1.npy deleted file mode 100644 index a60643c2280523f52649ce2ddfb8950095bb9e40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= fXCxM+0{I$-ItqrGItsN4WCN}%+@8gJNhj?AK-wOF diff --git a/tests/test_data/nmf_framework/corrnmf/L_updated_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/L_updated_nsigs2_dim2.npy deleted file mode 100644 index 7db8c45a471f746cf4363854c66f5540ac26fdf1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Pzeb3bhL41Fi_~l*d-zuk3GL;}myq{v&&{idURd7ku3xYP{_fb949B|1`*$iXVN^fy z#D2p|Lr;G1hxVDWr*aUnhm;rl^&r_4S*b7k8v`e~1sXiq F4*=$!Ll*!5 diff --git a/tests/test_data/nmf_framework/corrnmf/U_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/U_init_nsigs2_dim2.npy deleted file mode 100644 index da10265c5fa88f220a5f5189b05a76d6969805f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Wf%nItsN4WCJdPcP6F34*a*DU7YB3I9R}e|4mH8?8jUVD~|E+Hce-6 z;C`W%rL>5{f$!T7dpU+z_6avm>mE@Ma)^(6rS16TwSDT|OZU@M1RUZ_-SfhHgdC*i zEZKj@{eyk5f0yHxNk8qyO8EBGO0YUas^8dh#DvY^=V$8&rRV?KuetGxT~nLQf%{L? iK7D5{hi?A*=lyED4hwp}JMuneaZrB9Hp@1e*#Q8NAYb+X diff --git a/tests/test_data/nmf_framework/corrnmf/U_updated_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/U_updated_nsigs1_dim1.npy deleted file mode 100644 index aa11556311b9791a06cf66a2e7fa2295ed26c5bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqpcnmP)#3giN=k21I3MP!|}?{)uw^4ZSY_6}=IFMr(fc)za9`^5i$ zp4jvBe6D?;`p}-AAxEpP`rrOwXK78==EwUpkH2?VFz4d_XNyaiB+b99coc`Z_N&WMO=RE}+p43j*DzuTyAvw#dZ;C5} z!}r)4St7}wVwjLKMFc< z8->qaYX8B$GH$_IfzF@y_1g?}wtQ!CD1O0wO<$VL;p^=kEh(G-+wW(Zy6?FRn}bE~ jq~xeuoDMxMS5(vQ@HnhB>220O#^NBWX}kZIE3*RtbG>Fz diff --git a/tests/test_data/nmf_framework/corrnmf/W_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/W_init_nsigs1_dim1.npy deleted file mode 100644 index dd4d1fbe56f6fe8b16da9769aa22ef1102f1763e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 896 zcmbVK`%?=591qPRd6itLvr9E&c@#?0#|e4#Ad)z=*483h+M2aTrafw1lO}BvqRbJ8 zGOwic^2*LTo!BuQJ-pJoE^pn=AK>%L_r9MWzTfZX`}ypThz?&JJH&=>BO)g-xb#$V zAc-8r3?NfUWF{w-mr7%A<)kJs{_@Lc$y~-Cp39=8F#hyY=g*^(W>csn3F&_^GLMp0 z%u#FLK6+bp`A;>90{BX2V>}uk2H}7A)eHMSpM!Xg{B18J=PG^oBnV9`cT!tqu4CiHye3fstg1ky-Iy8PRA9m zCjX#Yi71Nf6F;IVP|p+2sCd9dowW|VVsp`CiGtJmVs>7;2$Lh+XyRCUJ>OVql7u< zho#d`1Esn*ljog*jwch%HBB-|o_ZH=a1di<gsfCvuWoSM1y}#-_A3ddv zT5YfZ(rC4z?}!W^&u2Ufe=JAC<^lh{oqMeJ@IkCgENHLE#_&P^`Ka_Yh4*YqfPIRw z=-cQtET&|6E1vVAH%!}Rdw`BjQ$Bx)W3upzj@Ym^kk%o<@k@f**Y-z4~W5LSFO2;es8yBxm(O~i(V diff --git a/tests/test_data/nmf_framework/corrnmf/W_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/W_init_nsigs2_dim2.npy deleted file mode 100644 index b79c09a88c2d5b19acb1f7d2c66ec59265d4ca4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1664 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 diff --git a/tests/test_data/nmf_framework/corrnmf/W_updated_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/W_updated_nsigs2_dim2.npy deleted file mode 100644 index 0d8f2f67053ebe3a9546431ebb82938dc9377623..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1664 zcmbV}dsGty8^$M)=;R`KLqvt-YviziIDkg)8(@5fV0eT;<-^v12qt42AT$@ZT^Kh9 zgIxsW=A3bvCOSyK1rZlDLMASgo2Z54Wq1ImL&^Gs{{No;-se2O^FHT1;yq!(A^VpQ zQVDEQbPS8iB<*k@`NVpYTpUQTbY?P>l6Z*DjE?!+51`OkF|U1A93?5{_3gaf)5XF4 z9Tx|V!~ZLyiyOi9XKg92sH%?dUUwYMUhq9=e^Z3>S2+pEV#tDalyYx26uJ8-6;kKU0WN4cS9`UgUsc`l~+Ad@k}MqK5~!mw}M1d(V13 z1zWSiy(UiNgVljs!qf6xteXj3*%Mg>Hy;VVP4Rl&Kb&r|VTTIHDIaHyTNWe#E@3v? zsuKn&a++21zyFo+&hgQPatwGy@t6`T;Y<6sIT@RUXuiO6Pqyy?t@P+GBX4m~qm-X4 zdnf)rzF zn`bhzJq8X`IIbiYQNAdoyHH-VDJ+KnxtSZJU z#9+;fu}U~BZdtqX+A+)`)LKe5l>QIIb(Pc#Zcp8gc-in!$5t<2+Q*0Vo$vOQf67_RSL&v0Y0NBa@L#^|aFrOk z2BMy%^Q2IUHy?;@90!|=*M-Di0@ zI3Ie)**3cgc)Z#J%Hdp0T=&3BHzJ1{YbH;GUS!~eYg1+8Y(0p(CT6YIGqBUFZTAM^ zagfy+*{5|(oTLZZG3x4}?~lWQx+|GzJ!~4hZC@KK{rcRM=dD6)GdjDO{Vs=&sW|h1 zDK_3O8&Gq)MbNq1RADi}z}da+nE{#Q(8wydcgb)J8DreA5Dyt_oZND?rneLy&rFi^ zE9B7lvW@Y9Yc{^2N@Eq7Rzo?lH~8pkA)fWsJ&_-kL3Pj*iy(R_nm-Gb?KGZ-++yu; z;=@d|d=%ifYN!a}kIIMR-sj`kh`p~~+X0s6*Ja=1WMD;JD`SXV47)s+5kpmc{5hEF zlq*!i>Xh+<+;|@TbV*FsERn&c7xP;mrlsRtfnfJ@P=IYkxMhVG8xQFW%|5G3Aw!bM zy|t8&@(av(S#K4DiP{FuC<0W)UQlNz2;t8}(%pix9JKXoj<|182{(+q1$}EehK*CL z)#E2Y;y}}O_vRyc+)-Y$tO^Rfx?E+O)3J$lF-}ZWz^)g4O8sxKC{mrJ{*Wn$%hDp_ zrfV#G<&bJgh{!+%^;h3YMLKSrNl_&_$>8gVholdTF<7*%m-FpO4P2)Um()-bu*Q47 zW5Ko?YWk(Tv7sdVb0C>z`c#56pVaF*S{nX%@xNV$PBmolgGV%VU!d646g$GzU_*D? zH@74T&A!r=dEBWlra)pfo>KbfBG$dyC5KiJhIWDSRL4jJW`7MZc;C>y+>p Dcy#DC diff --git a/tests/test_data/nmf_framework/corrnmf/alpha_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/alpha_init_nsigs1_dim1.npy deleted file mode 100644 index 4ec304fa0fd29f088041b82238818e3c8aae353e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41FqtC+Lx}1C^~#DTb5h3TET%i(kJ+3g_1+DXPXE67iot% z#*s^}aLGBWxEivdWva4+R&wI(51$nr7XP^Tz2lUE!_D;Chue?HJGhz~TyYjqasU93 Cm_J7V diff --git a/tests/test_data/nmf_framework/corrnmf/alpha_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/alpha_init_nsigs2_dim2.npy deleted file mode 100644 index 9bf62371a876a4ed66b60e0aec43eb4d301808de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41FlW(-kCq%NI7hmedPanx`KnuO_hjPb7hBI6E21}9R&vu zo2U0fc_bYeRM#we&Zg+VA?9?yOij)qV!4{a9u`@LIgi)*iWW#Y*oyp=(wwj00065U BH?9Bx diff --git a/tests/test_data/nmf_framework/corrnmf/alpha_updated_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/alpha_updated_nsigs1_dim1.npy deleted file mode 100644 index 3c0755146817f59a5e4d235e3ed029df9b487459..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL411@3N|Dk_H6de?LDzk2^R&da=oL#f5Ldk)(JKIj|hqS}H zj#(-XdE^|v9J{mZ^%P}?tv-P*$)6P*G;+EkJx(b&JbCfX#QT7}!{wS|7Z?SU8~{bV BJ~99R diff --git a/tests/test_data/nmf_framework/corrnmf/alpha_updated_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/alpha_updated_nsigs2_dim2.npy deleted file mode 100644 index 064d254febfba1fdfaf41887c6e6a3690e948d8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41Fm-$t_Eqmm2&9cvSAYA3t<8 diff --git a/tests/test_data/nmf_framework/corrnmf/beta_updated_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/beta_updated_nsigs1_dim1.npy deleted file mode 100644 index 0256105f0b2f6f8359791585d6c4c783fc3669e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= eXCxM+0{I$-I+{8PwF(pfE{2BF(myrq_X7YuM;_q- diff --git a/tests/test_data/nmf_framework/corrnmf/beta_updated_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/beta_updated_nsigs2_dim2.npy deleted file mode 100644 index 8410c6edde0b8b7cac43d4c4dc759d1ec574671f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= mXCxM+0{I$7I+{8PwF(pfE(V68@Ye?=>=_tzm75=#*#iJFULaEd diff --git a/tests/test_data/nmf_framework/corrnmf/counts.csv b/tests/test_data/nmf_framework/corrnmf/counts.csv deleted file mode 100644 index 05310ed..0000000 --- a/tests/test_data/nmf_framework/corrnmf/counts.csv +++ /dev/null @@ -1,97 +0,0 @@ -Type,SP9251,SP6730,SP10084,SP5381,SP10635,SP2714,SP11235,SP8085,SP4593,SP4820 -A[C>A]A,94,54,239,28,35,180,78,103,32,112 -A[C>A]C,60,63,199,25,23,129,60,58,24,131 -A[C>A]G,10,10,28,4,8,17,10,7,5,22 -A[C>A]T,72,42,222,17,24,148,43,60,20,106 -C[C>A]A,57,47,163,19,24,159,78,65,17,119 -C[C>A]C,73,35,161,18,13,143,41,33,10,121 -C[C>A]G,13,9,16,6,9,15,10,10,3,20 -C[C>A]T,68,57,189,7,20,173,62,65,14,116 -G[C>A]A,75,69,122,23,22,89,100,95,20,65 -G[C>A]C,45,45,84,14,16,83,27,50,15,92 -G[C>A]G,8,6,13,5,8,16,10,11,5,16 -G[C>A]T,49,66,101,10,13,109,72,63,13,68 -T[C>A]A,65,87,171,19,33,204,99,135,30,84 -T[C>A]C,86,61,132,18,24,202,60,82,33,84 -T[C>A]G,6,12,24,6,11,10,11,16,4,13 -T[C>A]T,91,134,250,24,28,265,151,182,44,100 -A[C>G]A,95,24,95,8,17,150,46,26,10,105 -A[C>G]C,40,19,61,8,7,70,36,19,14,60 -A[C>G]G,11,4,25,0,0,35,3,5,5,23 -A[C>G]T,96,18,118,7,9,145,45,35,16,112 -C[C>G]A,66,15,47,2,10,151,12,17,13,60 -C[C>G]C,52,10,36,3,9,92,23,15,11,77 -C[C>G]G,22,11,14,3,3,39,6,11,2,42 -C[C>G]T,79,19,50,7,7,203,28,31,15,106 -G[C>G]A,36,6,28,3,4,78,18,6,8,68 -G[C>G]C,30,9,43,3,8,64,22,8,7,61 -G[C>G]G,8,1,6,0,1,14,8,3,1,19 -G[C>G]T,51,8,34,6,10,107,23,18,9,93 -T[C>G]A,119,61,131,8,20,687,68,116,24,93 -T[C>G]C,85,32,80,9,11,448,52,56,13,110 -T[C>G]G,15,11,13,1,1,45,11,14,1,16 -T[C>G]T,239,90,236,14,28,1125,136,214,35,207 -A[C>T]A,126,91,238,24,46,187,80,70,54,140 -A[C>T]C,61,56,103,17,26,83,43,47,35,79 -A[C>T]G,149,272,257,92,119,185,147,168,134,145 -A[C>T]T,92,51,181,18,32,157,33,57,43,119 -C[C>T]A,75,76,112,21,46,140,53,66,40,100 -C[C>T]C,69,67,89,28,39,97,59,59,54,77 -C[C>T]G,93,163,139,45,72,108,110,108,85,88 -C[C>T]T,107,94,185,27,49,220,68,75,56,162 -G[C>T]A,68,75,86,13,37,99,74,58,38,123 -G[C>T]C,46,61,95,22,44,103,80,45,44,79 -G[C>T]G,90,230,176,71,81,155,124,118,102,116 -G[C>T]T,74,55,129,10,22,110,35,49,38,93 -T[C>T]A,139,178,198,28,63,520,136,224,97,106 -T[C>T]C,126,97,155,24,40,341,98,95,79,98 -T[C>T]G,80,128,117,35,68,101,79,103,53,87 -T[C>T]T,152,128,244,26,72,382,109,147,116,137 -A[T>A]A,43,66,115,17,16,86,44,26,13,57 -A[T>A]C,25,19,75,20,24,46,31,21,27,48 -A[T>A]G,37,30,99,10,17,76,22,29,16,64 -A[T>A]T,63,61,168,21,32,120,49,38,18,117 -C[T>A]A,31,32,85,4,9,61,15,16,3,63 -C[T>A]C,32,17,65,2,16,71,19,22,7,101 -C[T>A]G,55,24,105,9,14,108,34,22,9,68 -C[T>A]T,63,39,182,6,9,117,24,23,17,90 -G[T>A]A,22,17,42,5,4,47,10,9,7,40 -G[T>A]C,20,14,39,3,8,38,14,12,10,38 -G[T>A]G,23,16,33,5,14,48,18,15,5,39 -G[T>A]T,41,16,99,2,9,102,18,17,10,71 -T[T>A]A,31,63,124,16,29,122,51,60,18,76 -T[T>A]C,30,21,95,3,10,59,25,21,12,44 -T[T>A]G,19,15,57,2,5,43,9,9,7,39 -T[T>A]T,76,38,240,9,13,146,41,41,13,116 -A[T>C]A,90,101,150,29,49,189,79,57,52,157 -A[T>C]C,42,29,68,10,23,87,36,19,18,85 -A[T>C]G,58,47,85,10,24,108,55,31,26,131 -A[T>C]T,99,95,118,19,46,200,95,58,40,167 -C[T>C]A,39,50,57,7,15,69,44,14,20,73 -C[T>C]C,55,27,92,11,15,139,28,20,13,109 -C[T>C]G,43,37,42,10,14,59,28,25,15,88 -C[T>C]T,59,42,68,5,13,105,56,37,22,113 -G[T>C]A,40,63,79,11,32,101,50,22,12,81 -G[T>C]C,29,27,35,12,11,62,37,18,14,47 -G[T>C]G,26,41,32,11,9,58,39,17,15,64 -G[T>C]T,57,49,70,14,36,103,55,28,20,73 -T[T>C]A,56,83,106,7,23,93,41,28,23,76 -T[T>C]C,47,52,73,14,16,91,50,24,9,73 -T[T>C]G,25,29,57,4,13,61,18,21,13,54 -T[T>C]T,54,75,92,17,39,133,55,46,27,99 -A[T>G]A,29,25,49,2,13,61,29,18,13,49 -A[T>G]C,12,12,15,4,5,26,11,4,7,35 -A[T>G]G,43,16,40,6,6,74,17,19,6,44 -A[T>G]T,37,24,57,13,13,56,24,15,9,46 -C[T>G]A,18,7,12,5,3,30,10,9,3,29 -C[T>G]C,23,13,21,8,4,42,6,10,3,27 -C[T>G]G,43,11,46,1,6,100,21,25,7,62 -C[T>G]T,20,14,49,8,9,70,14,25,7,64 -G[T>G]A,7,10,17,0,1,27,7,7,5,26 -G[T>G]C,9,6,12,3,6,19,6,3,4,18 -G[T>G]G,25,15,37,11,4,76,16,12,8,57 -G[T>G]T,27,6,24,6,5,63,20,11,5,41 -T[T>G]A,39,25,39,2,9,66,21,8,9,52 -T[T>G]C,19,9,30,3,4,37,15,6,8,31 -T[T>G]G,39,18,73,4,10,86,21,18,4,45 -T[T>G]T,58,38,81,10,20,110,48,38,16,109 diff --git a/tests/test_data/nmf_framework/corrnmf/objective_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/objective_init_nsigs1_dim1.npy deleted file mode 100644 index a525012b9a0947caef14bf467c36acec0aceb975..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alXGwO{x9F5G?q06zyHi~s-t diff --git a/tests/test_data/nmf_framework/corrnmf/objective_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/objective_init_nsigs2_dim2.npy deleted file mode 100644 index 1f8c79825cf35784379e43c307716a1a5a1de0b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46ag;gizfbF(wh$e05gXkZU6uP diff --git a/tests/test_data/nmf_framework/corrnmf/p_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/p_nsigs1_dim1.npy deleted file mode 100644 index 1ca7a583c5d704174cd39dabc313778b2f433591..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7808 zcmeIyu?oUK41nQt^(nGN;Raob7NMJ)#l^u%j`oU!*m5T>(x>Q)`Ut)D5!{471VS=~ z3m(7xA|!8V-++PJv2MDqD>oK!98znsQolQx<~YB9`CO6sbB3gE8CQtwUu6e ziKUt2TTrJBum|pdJKzs+00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zDX G*MSiMrHnKH diff --git a/tests/test_data/nmf_framework/corrnmf/p_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/p_nsigs2_dim2.npy deleted file mode 100644 index 5fe2958383ed94463965b9fccbfa45eeb995fa4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15488 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 diff --git a/tests/test_data/nmf_framework/corrnmf/sigma_sq_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/sigma_sq_init_nsigs1_dim1.npy deleted file mode 100644 index 8269ea423951502f72555638b0e149b55b10f048..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC!@qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= XXCxM+0{I%6ItsN46ag+q1_%HED6|_x diff --git a/tests/test_data/nmf_framework/corrnmf/sigma_sq_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/sigma_sq_init_nsigs2_dim2.npy deleted file mode 100644 index 8269ea423951502f72555638b0e149b55b10f048..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC!@qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= XXCxM+0{I%6ItsN46ag+q1_%HED6|_x diff --git a/tests/test_data/nmf_framework/corrnmf/sigma_sq_updated_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/sigma_sq_updated_nsigs1_dim1.npy deleted file mode 100644 index a2346a97cbb51a391265e553dfd3f4799f00efca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alU$vNs;YD?POb06d%@od5s; diff --git a/tests/test_data/nmf_framework/corrnmf/sigma_sq_updated_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/sigma_sq_updated_nsigs2_dim2.npy deleted file mode 100644 index 7cbc02e77dfd28adfd49d96eb0a3245f7ce21ed4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= bXCxM+0{I%6ItsN46alWyY(9zh|79EiH8vhU diff --git a/tests/test_data/nmf_framework/corrnmf/surrogate_objective_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/surrogate_objective_init_nsigs1_dim1.npy deleted file mode 100644 index b6ff6e089550943ded27a6b5dc5e7fae07a60267..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alXEwO{x9F5G?q06qsGYybcN diff --git a/tests/test_data/nmf_framework/corrnmf/surrogate_objective_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/surrogate_objective_init_nsigs2_dim2.npy deleted file mode 100644 index 9ae80de447f3344971b111fe9d016e9b8dc59835..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alWylQ*+^NpC&?065(r6#xJL diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/U_init.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/U_init.npy deleted file mode 100644 index 2016f3585f4198e8322d08f8160997efb747221a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Wf%nItsN4WCN~{xzdIcl78)%EqZ=V>+)^;Og_f7l7D~OznuNM%uM{Y z{RO4&6&EIa-+%GHZHiCh<^6wtb6%HN|7E}Hvk8R)x9-`W+Tay6ukF?T1(7?NX5am} z|9JKc{;Q8Z+NZqubl~chH~Tl|3NaihKd^tob)l-Ap3nC;aD0?EKk;(^?efij+Mh1% mXHcKHGw#Lj{b60H&!1&2v6o(QQ22l4#{DI6I=v=RckKZlV|HZ# diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/U_updated.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/U_updated.npy deleted file mode 100644 index 9707161c10f1679266cf286471401489efac1b89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Wf%nItsN4WCJe6?tjafgMaUDulY8UZSie;`~NR?rak&?ulOPUOvc>X z_Kro<+M7;%+iw^3fPVq^#r^RMa=v(P|GGc1oXa)X^`5b( zcimm>&Uf$a-&CLe6Q1*W|Jk_;O-tps?QdP6e&$ia^Zj=nfBVNAd%j=Ct<1C{;PQU% lnNze^`v2aa*spzQ>F;g!e0Gm*U)*22e|g!&=_R#y>;bnCb$I{) diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model0_L_init.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model0_L_init.npy deleted file mode 100644 index fd1976e3d396a0bd86cf2680acf858c324d1f47c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Pzeb3bhL41Flb;Mz42>Y_Wg-*z5VGMIY@g_SK57y?MZXOZ)0M#WtVp E0o{@<3jhEB diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model0_L_updated.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model0_L_updated.npy deleted file mode 100644 index 2078ae46cae687e58f7d0b7c1b67a8dca6c7dbb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Pzeb3bhL411_s|c56AVueU!rscPo>x1a1ot?#Cs5Iks~b92j9wUZz0 E0n6$vl>h($ diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model0_W_init.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model0_W_init.npy deleted file mode 100644 index bd982238393ff192eab5e2d00c7e943569b2a866..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1664 zcmbW0`9Bm01IF2Udlexj4ZW_e8OPfy-jL%#8q}1dC7QephQTmqOk;)^gCtkhNabi% zCfBZ8#2amP+h$Okm3Ozi)#TW+#qvgnYwf?V&u`E3EEz zEjlwB@{%!KBbD^r%trP!vA3-=0Ru6YDDiQb;L%sYcBKbGHl{_oWo zALr6OGcigO!WsS11pI$zzg(j^18^j)&}&%uZ)|7Prf<^F*5#?W#?le2BZ>tLRT?zU zk36_7D?kfP^jqbF5Xb|j9(`Muh4%f~nt_)i@G3AydQFs$sFTgD23`L~#zw|EqDv5_ z?o@lN@$g22n(FlX{L?7a)+(*#aiG&>Wc@ULcd&r_X{x%Q14R#H&- zD3Dl%PPBFZaO2m}4(xm9dacc%2)q9lz5aJa32ZDSPQ!$F_>53?yuZzZ{f&dI>V6@3 zDeL9d)XLydW0xFmoCMC=od=kE3lPxx%csI6F3NizF7J4fi1vD(fxpQYAij>=;eNCn zyjI4`TgT6WBTy~dXCOvXWuqwkU?Ca@%9e)nPD5pWj509JLA~Kbm%3RR%#3s*FID8? zvA1=_5BeF1nIuWO>NCMLr3AXF=726~>|Q#Vh-K&cx*+*kR8QQB$yU#Yo?Z^s`WYQ- z`&ErtYs=v=s%8J>&um0??zWgthIV$AYC~h|dSFlkr6GF>KYfp!(z9tpui{K|0!_FFpjP)3wQHeFF-{U_FM6uaLAJsvqSS{cs@-tRpcdt(rDaKu402+ispdaS`3M; zY|E+2IE-(}&zE(uVgB8t`08dSq<03;6vpX@%zLqIlAnQ5Er~$WH3&Hz3s(v=1+PB} z-@Fu&(Rn2772!kz`kzUORjCYIzLsrwsAko#mAQF3DFNd|dxlG68h8nAWtS?#Fm@(b zpDpKLGGo!fhs=Y({8_%1fRCX^C-nW_dct(O_cV){g|dgEdY*O>=$d=z6y0_JlFx;bBycs3&l+rQnL(UaZU6oUJ@%K4@CmdMy{o^@a%1$w;|+&G0HLXIvi z7B_gJ?}fRy)d?1OLpEszeNp(Z5OdSvdMIX;mekZ(9E^XKd(p&Q9X+W%D4*kFa#w(Z Wob3nA$g5jQh%}6dQWkfqr{fP|QQXS_ diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model0_W_updated.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model0_W_updated.npy deleted file mode 100644 index 57d3796a0bd73a0bae30419e6a4375f3a88756c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1664 zcmbV||34E51IK+Q;weNqX_bt}@YvH4I-h6Rh*~RO(zN-u&3p?p?M7^En6O0TX_70Y zI|<=ZkMrVCN)ENFPFmluTwEt5`sQ5sFFfzx-tQmZ<=evO+nL&0BCUNyE>FlwCwdcz z8{;+*Nd#hCYI;U`OiEO0I+yo%_m4>y@;-NAd`ueevpafukO*Wa5+R51|6-i4D(zd7 zxF3?T?tcCLBB)jg7*u%{B25O#-AV;MTDOdKQzFqc^v|re$q-y=S*mC+-48PxN%LFD zeh4`}O*v6nNFLw3OVE~wIl0zO9bOu;{T6E1`{u%=?n0?`I|bLe2yey`SZFKJeX5Bn z!K&1%2%W)5cv?4DfALa*Z3nuzBhwr>)-=_6&dMMh^XXf0kO{gbq`q@y6e#~NQkAb} zpj}o%uKqR~g<^6m<9aF*G$u9PFDo(p`dG-55Eb4%&7>+f=E7d;wR%#g6#Pwh;>gPd zFrgnTw$$^$tDANIvp8FY9}5hr_1AOp_Nr;u=HPu;#*iL0b(3Pnf}?JliwI}tR=zc& z$3gG8U)ySfI2^FJJjuOALAd>rUJ0=PSG$f53Ae^!oOsRj*eN+~%c9vj+0oDrYCI%8 znGVu#)G{;og*f^D%ywkDcP8Q)XM+|+hXe79XxzXu z6{Zt*$9O40Xt1M|*rW(SuI_NXzjh~BrYWC{uTbGz8Nq$FhKe#bqZh;OGK7c}GS9s~ zfbmK=6ViQ-??%p4xhwZl#gbA z!bGIp^Zc{~oo5}_Ssf~cGbN+Gq9+6F#JyjdP_nSDdP}%Ch>8BrB0)Rr1O|=SPh?LE zU_iOv|2~T%%$XC5iz9MyhUfiV?{ER`mHoatF z=O8fOqM--@a2EG}%X}n7;G3FVJKk-^nlQC`+FOZ@@-^G~x;Mdgtsiap%XCy<^wUl# zk3nS5$u|GdXx!LQf8pUBDVpt;M@+rThHWA@kZK-^`S3Gr?ssws+v&lq`^gyhxy*l0 zOGD$IzckPPn-2TIMt2%j0Nqf^55DFM1lFu4^t`9y;*c9Xp+*R!fsDlGto=ChCcaAeYVro-DqSEZ<(_dpg+@18cStkWq`x0N#1s>jr#s{ygC6Y+Mv}O z3NX;S)o^M>62wn09U@68P+`%>YT@qy!{YG3&ZMuQI&I)jo!JAI#m57K22$bZaIS|o zAVaafR#TiuIZhNj9@f{yAmG8DIws$^gLW=^>Sb3BcF!vcZW;K(PN_IH=9P>8l#D-k zT_FIEJ0r2UvIjY4`-Y}(QlTumVC55(j|KgOt&25sw0^@7w4P$Z@^;}@k62lFVX!nl zMrSP?e*Kr(y?GH_lNKf|Z^vS%6Y-2CO$ev3WtCKp2u_RS{O2cRSQ4%uTE>k5GeSIZ zcaac=Wa^Sxh7hM8O=x<0LRKQw8a|J%ord`2yIxxy z1kgL)ThX*38m_;+s100Ag(T~p(ZYjhREO5t=oz|UOgG9~)tH4k7x&qbx#gHJA1(>4 z3P$oT1h`z?gJN;ZQAI%#mbG_od!ikSJmQolz$^u%Jm#}{z9GU&*WEuZ-U|!5mHou! VV5oaPxpw(SBVq3fO|~=-e*sex%x(Yx diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model0_alpha_init.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model0_alpha_init.npy deleted file mode 100644 index b2861acbbde0b3fb0defd0b1132ed615fba4980e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41FjB(o%a|#2eMy z7fK3F&Q@~Rx1L>vXP=ydVy5Yn(*G(BKaa<|KM7ED*emKK@^6)_gP7{`o636?9RMME BJXQbz diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model0_alpha_updated.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model0_alpha_updated.npy deleted file mode 100644 index b038d1ec3b8533e134990fdfcc57e5222e1907e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL411^P55$S|bd50TcGZ)NEm2@zCce6Qoj=Y0h+ngERWpWOJ z{yxlmQt<8 diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model0_beta_updated.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model0_beta_updated.npy deleted file mode 100644 index 5e6d3b0abc68e469bb4fc3ff33c3411e6d6d0e5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= mXCxM+0{I$7I+{8PwF(pfE{2}0Kaxso_A_Yz(%EXe!X5xZ10o*) diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model0_counts.csv b/tests/test_data/nmf_framework/multimodal_corrnmf/model0_counts.csv deleted file mode 100644 index 459da55..0000000 --- a/tests/test_data/nmf_framework/multimodal_corrnmf/model0_counts.csv +++ /dev/null @@ -1,97 +0,0 @@ -modality0,SP5448,SP117113,SP2826,SP11171,SP117724,SP117454,SP3631,SP4820,SP116947,SP124197 -A[C>A]A,76,51,59,154,39,22,99,113,53,279 -A[C>A]C,53,21,34,117,47,28,62,132,45,245 -A[C>A]G,5,6,8,23,10,5,15,23,9,31 -A[C>A]T,44,25,25,126,26,22,56,107,28,258 -C[C>A]A,43,34,41,111,17,14,82,120,24,255 -C[C>A]C,41,16,20,107,20,12,47,122,27,192 -C[C>A]G,11,4,5,16,7,4,7,21,4,24 -C[C>A]T,47,28,44,105,24,12,46,117,35,263 -G[C>A]A,58,50,32,77,31,16,58,66,31,172 -G[C>A]C,27,22,22,65,27,12,32,93,16,131 -G[C>A]G,14,13,8,14,7,11,10,17,10,21 -G[C>A]T,41,25,23,65,22,15,30,69,14,174 -T[C>A]A,99,41,67,81,33,23,844,85,31,793 -T[C>A]C,88,31,36,89,54,19,333,85,47,340 -T[C>A]G,14,5,9,14,8,7,69,14,5,49 -T[C>A]T,140,89,70,108,57,27,397,101,37,533 -A[C>G]A,51,10,49,80,22,13,120,106,16,169 -A[C>G]C,43,5,23,53,14,8,37,61,19,94 -A[C>G]G,15,4,14,23,7,5,8,24,7,33 -A[C>G]T,37,13,43,79,17,6,90,113,21,190 -C[C>G]A,40,4,33,56,9,14,118,61,16,131 -C[C>G]C,29,7,21,58,18,10,36,78,15,96 -C[C>G]G,7,6,11,28,5,4,15,43,8,26 -C[C>G]T,52,5,35,76,19,13,132,107,19,172 -G[C>G]A,14,5,14,39,10,10,40,69,9,64 -G[C>G]C,22,12,12,20,9,12,23,62,11,70 -G[C>G]G,6,3,8,13,4,5,6,20,2,20 -G[C>G]T,34,7,20,55,14,9,56,94,7,117 -T[C>G]A,467,9,73,79,47,26,5724,94,27,2286 -T[C>G]C,157,15,41,69,27,11,1074,111,21,564 -T[C>G]G,22,2,10,17,4,5,245,17,9,104 -T[C>G]T,537,17,117,149,103,40,6300,208,71,3000 -A[C>T]A,105,35,68,117,62,31,174,141,66,269 -A[C>T]C,55,20,29,65,37,25,48,80,41,112 -A[C>T]G,225,133,129,162,196,145,133,146,149,120 -A[C>T]T,79,38,53,122,44,23,85,120,38,252 -C[C>T]A,119,48,47,62,56,30,291,101,51,213 -C[C>T]C,79,50,36,53,55,21,80,78,49,122 -C[C>T]G,139,83,83,73,114,87,154,89,102,101 -C[C>T]T,102,44,64,126,55,26,187,163,56,272 -G[C>T]A,101,47,40,74,41,37,163,124,49,166 -G[C>T]C,86,39,32,71,60,21,81,80,52,130 -G[C>T]G,197,140,117,100,156,95,121,117,109,90 -G[C>T]T,60,37,44,73,39,30,103,94,45,161 -T[C>T]A,623,67,103,96,82,44,7108,107,110,3738 -T[C>T]C,235,63,67,71,83,34,1283,99,70,749 -T[C>T]G,161,77,62,67,77,61,804,88,77,305 -T[C>T]T,387,70,92,99,92,48,3227,138,66,1728 -A[T>A]A,24,14,29,57,27,21,44,58,23,93 -A[T>A]C,22,10,21,42,29,9,26,49,41,92 -A[T>A]G,37,16,22,54,17,22,35,65,24,76 -A[T>A]T,63,39,44,72,26,20,51,118,30,153 -C[T>A]A,19,9,21,44,11,11,21,64,10,79 -C[T>A]C,29,12,19,82,10,13,18,102,14,147 -C[T>A]G,25,10,27,67,14,10,29,69,17,91 -C[T>A]T,37,8,39,119,15,7,34,91,24,178 -G[T>A]A,14,9,10,40,9,3,22,41,6,57 -G[T>A]C,17,12,12,37,14,4,14,39,13,62 -G[T>A]G,28,15,14,41,10,7,22,40,12,59 -G[T>A]T,30,7,32,71,8,4,17,72,12,105 -T[T>A]A,76,20,35,63,33,22,45,77,33,97 -T[T>A]C,25,8,20,28,13,6,23,45,10,83 -T[T>A]G,16,5,9,36,12,4,10,40,7,49 -T[T>A]T,41,26,39,121,21,10,43,117,21,199 -A[T>C]A,98,41,85,113,54,42,88,158,73,153 -A[T>C]C,57,18,23,56,19,17,36,86,15,86 -A[T>C]G,84,15,33,57,30,24,58,132,34,99 -A[T>C]T,100,35,67,116,71,39,76,168,79,152 -C[T>C]A,47,12,31,41,22,11,24,74,21,63 -C[T>C]C,53,15,20,81,19,14,25,110,19,96 -C[T>C]G,46,13,27,46,21,15,28,89,22,44 -C[T>C]T,42,13,35,66,33,20,45,114,27,111 -G[T>C]A,73,22,30,46,45,18,50,82,33,67 -G[T>C]C,42,16,18,36,18,12,23,48,20,49 -G[T>C]G,47,15,24,50,24,10,26,65,30,55 -G[T>C]T,67,16,44,66,37,13,37,74,32,67 -T[T>C]A,62,28,37,40,34,15,52,77,38,83 -T[T>C]C,62,21,26,45,28,14,37,74,20,89 -T[T>C]G,35,21,20,20,15,10,22,55,18,36 -T[T>C]T,68,24,48,77,33,22,82,100,37,94 -A[T>G]A,22,7,12,35,11,6,17,50,16,52 -A[T>G]C,8,4,13,19,9,10,12,36,9,24 -A[T>G]G,14,5,11,51,11,10,20,45,6,75 -A[T>G]T,33,8,20,30,22,17,28,47,13,50 -C[T>G]A,15,1,9,21,10,4,15,30,8,26 -C[T>G]C,10,5,11,28,7,7,7,28,10,41 -C[T>G]G,32,6,16,40,12,12,23,63,11,68 -C[T>G]T,31,13,15,34,15,14,31,65,11,131 -G[T>G]A,6,1,13,23,5,3,9,27,2,24 -G[T>G]C,9,2,7,19,6,3,7,19,4,23 -G[T>G]G,17,7,11,44,7,31,15,58,9,84 -G[T>G]T,23,3,21,29,9,5,18,42,5,61 -T[T>G]A,25,5,23,31,14,11,15,53,12,56 -T[T>G]C,21,3,10,17,9,8,13,32,6,40 -T[T>G]G,27,4,16,57,10,8,27,46,15,67 -T[T>G]T,48,18,34,80,27,22,57,110,27,123 diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model0_p.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model0_p.npy deleted file mode 100644 index 315c4d52bb52ce59f01233d2b36e4d8cb0951ae4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15488 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 OIwnS}Z_H&WvhaVD47b$) diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model1_L_init.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model1_L_init.npy deleted file mode 100644 index 5dd78e56027d798b4a9de77fae86d023206d7265..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3dWi`3bhL41FrQ>cTHS>T;8ANCV5_Y-+%ju?wzHZ%^vPQmiuo5=Yr$= VTYKKh&pY+bo+onVhh_GU?E(BCHZK4G diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model1_L_updated.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model1_L_updated.npy deleted file mode 100644 index 2673ea36b5cb847e3d8169f6d08f862a4040a3e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3dWi`3bhL411?dfY%STGYx}ihUdya5|7X9ML&<&V#|QhpKKFI)`*Uo6 V%(VoUHoLd>OVu7Umo9p24*;AfG!_5= diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model1_W_init.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model1_W_init.npy deleted file mode 100644 index 4e0ee57747ed3221b780d7fa5adbcb3b0a083a80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2432 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 Ca6Q}r diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model1_alpha_updated.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model1_alpha_updated.npy deleted file mode 100644 index aa41a8b6107be3475e7a66affd78ac142b75c7f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41FlbU_ivmJ5_izJDd)6orKH0K-J`GnO;B*Sl9POl?TffW z%2cN(tCA]A,25,126,270,42,119,51,127,27,99,121 -A[C>A]C,21,63,220,26,76,23,90,20,62,125 -A[C>A]G,7,16,45,12,15,12,12,4,15,17 -A[C>A]T,19,48,157,32,61,38,60,24,56,130 -C[C>A]A,21,105,174,33,115,40,79,16,82,111 -C[C>A]C,15,41,137,21,67,20,65,9,47,96 -C[C>A]G,1,29,27,13,22,7,25,6,7,20 -C[C>A]T,21,77,149,24,108,20,57,24,46,111 -G[C>A]A,19,123,150,21,117,47,82,18,58,90 -G[C>A]C,16,47,101,18,48,24,49,6,32,84 -G[C>A]G,3,19,21,7,13,8,18,4,10,16 -G[C>A]T,17,81,140,21,86,23,58,15,30,89 -T[C>A]A,35,160,223,36,147,228,136,28,844,108 -T[C>A]C,36,97,180,42,119,80,126,26,333,100 -T[C>A]G,6,20,29,14,23,17,25,5,69,12 -T[C>A]T,30,247,263,39,217,123,165,26,397,141 -A[C>G]A,18,32,187,21,63,22,104,11,120,184 -A[C>G]C,11,30,104,22,40,10,64,6,37,77 -A[C>G]G,2,26,59,20,15,3,21,3,8,49 -A[C>G]T,22,52,165,19,59,23,97,7,90,165 -C[C>G]A,11,41,98,16,38,17,34,8,118,100 -C[C>G]C,9,18,90,26,27,12,44,6,36,83 -C[C>G]G,6,25,57,24,16,5,22,2,15,44 -C[C>G]T,17,26,177,22,51,26,67,5,132,120 -G[C>G]A,10,20,104,10,34,15,39,1,40,84 -G[C>G]C,10,24,70,11,23,11,41,4,23,78 -G[C>G]G,4,8,28,8,8,3,6,1,6,26 -G[C>G]T,16,19,132,11,35,15,54,8,56,134 -T[C>G]A,141,65,411,58,127,1007,146,6,5724,205 -T[C>G]C,53,41,232,28,67,191,100,10,1074,117 -T[C>G]G,10,9,40,8,5,35,13,2,245,26 -T[C>G]T,170,107,731,84,236,1123,200,7,6300,283 -A[C>T]A,32,118,252,60,125,67,175,35,174,161 -A[C>T]C,19,58,120,24,78,40,66,21,48,88 -A[C>T]G,60,203,283,102,379,102,268,89,133,165 -A[C>T]T,27,86,225,32,85,38,90,28,85,154 -C[C>T]A,23,85,165,34,96,81,136,23,291,110 -C[C>T]C,27,59,130,26,87,53,107,31,80,63 -C[C>T]G,50,131,164,65,225,67,191,57,154,120 -C[C>T]T,42,99,217,34,95,82,102,30,187,130 -G[C>T]A,16,72,187,41,89,82,123,21,163,144 -G[C>T]C,19,77,137,34,111,56,100,32,81,76 -G[C>T]G,45,155,162,88,336,96,197,61,121,126 -G[C>T]T,20,69,154,30,98,40,114,30,103,92 -T[C>T]A,161,124,476,63,192,2105,202,32,7108,160 -T[C>T]C,59,107,263,50,185,399,163,35,1283,104 -T[C>T]G,44,90,179,69,185,194,174,38,804,98 -T[C>T]T,104,95,382,63,158,998,169,35,3227,129 -A[T>A]A,21,45,99,10,62,11,49,16,44,71 -A[T>A]C,24,29,67,20,35,20,33,17,26,57 -A[T>A]G,13,28,100,9,32,22,41,7,35,61 -A[T>A]T,19,86,168,30,105,22,71,19,51,87 -C[T>A]A,10,19,79,10,19,5,27,3,21,65 -C[T>A]C,11,35,74,12,30,11,30,7,18,74 -C[T>A]G,12,19,104,12,28,14,46,5,29,67 -C[T>A]T,11,25,138,20,35,13,55,10,34,128 -G[T>A]A,9,9,77,8,29,10,16,3,22,42 -G[T>A]C,8,13,45,5,15,5,19,4,14,27 -G[T>A]G,7,30,92,12,22,10,23,7,22,47 -G[T>A]T,6,27,77,12,30,6,32,6,17,72 -T[T>A]A,22,91,152,22,91,23,72,19,45,76 -T[T>A]C,10,30,77,10,28,12,36,10,23,45 -T[T>A]G,7,11,57,13,17,11,34,4,10,30 -T[T>A]T,14,72,166,33,77,23,78,16,43,113 -A[T>C]A,41,105,287,42,156,63,121,29,88,186 -A[T>C]C,17,53,106,13,40,12,34,16,36,128 -A[T>C]G,27,52,160,27,64,35,75,21,58,114 -A[T>C]T,42,93,300,55,120,50,91,37,76,186 -C[T>C]A,13,35,98,19,60,13,43,11,24,94 -C[T>C]C,4,35,106,27,43,16,49,14,25,114 -C[T>C]G,16,39,96,23,39,19,34,10,28,95 -C[T>C]T,18,40,154,24,59,19,65,11,45,116 -G[T>C]A,18,57,146,22,87,29,81,10,50,103 -G[T>C]C,15,31,65,14,55,19,57,8,23,66 -G[T>C]G,11,34,85,22,38,13,40,5,26,60 -G[T>C]T,18,65,167,28,76,22,71,23,37,89 -T[T>C]A,13,61,156,27,104,24,74,16,52,125 -T[T>C]C,14,59,124,24,67,26,53,10,37,116 -T[T>C]G,11,49,70,18,40,19,33,11,22,61 -T[T>C]T,14,80,170,40,105,24,91,25,82,123 -A[T>G]A,11,16,65,13,45,12,32,6,17,57 -A[T>G]C,4,15,20,1,23,6,11,7,12,30 -A[T>G]G,10,18,65,11,25,10,24,3,20,73 -A[T>G]T,11,31,59,17,32,11,49,4,28,59 -C[T>G]A,5,9,56,5,18,8,21,2,15,35 -C[T>G]C,4,20,48,8,11,8,21,2,7,39 -C[T>G]G,12,24,94,12,24,12,33,8,23,63 -C[T>G]T,19,36,105,11,41,17,35,6,31,85 -G[T>G]A,4,13,54,7,12,6,9,3,9,37 -G[T>G]C,6,7,20,6,16,9,8,3,7,19 -G[T>G]G,10,17,99,9,27,6,22,6,15,70 -G[T>G]T,9,21,65,8,21,7,28,5,18,66 -T[T>G]A,12,32,109,12,33,17,34,1,15,70 -T[T>G]C,5,20,51,8,22,10,32,6,13,46 -T[T>G]G,10,20,81,12,23,9,30,9,27,82 -T[T>G]T,26,60,145,31,73,24,77,7,57,146 diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/model1_p.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/model1_p.npy deleted file mode 100644 index 70327d5434b074fe9fa3b5812a64bfe1fd567a56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23168 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 XRCwdT6&TWDsO=4#C@!8cu3Y>No>lKR diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/objective_init.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/objective_init.npy deleted file mode 100644 index 75b4e8de4e88cc3421f3216dda0cb2cc82e9acec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alVY`+Xc&>RucG06A?R%K!iX diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/sigma_sq_init.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/sigma_sq_init.npy deleted file mode 100644 index c4f032c5d522e968eedd177b02f91ccb263bdb1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alV(@45LNc)qg-06fhfn*aa+ diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/sigma_sq_updated.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/sigma_sq_updated.npy deleted file mode 100644 index c4f032c5d522e968eedd177b02f91ccb263bdb1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alV(@45LNc)qg-06fhfn*aa+ diff --git a/tests/test_data/nmf_framework/multimodal_corrnmf/surrogate_objective_init.npy b/tests/test_data/nmf_framework/multimodal_corrnmf/surrogate_objective_init.npy deleted file mode 100644 index 72d34adc1b40cd028c199d0e19611a891b676122..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alXN_WL-l)V(+W06P62{Qv*} diff --git a/tests/test_data/nmf_framework/mvnmf/H_init_nsigs1.npy b/tests/test_data/nmf_framework/mvnmf/H_init_nsigs1.npy deleted file mode 100644 index 7a5e615e80dc9f4d107501676a59cc2149e80aeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqpcnmP)#3giMV1`xQr#Q{w3-RSTi1oj?;(7}@-;#cNF=%2?S^xO>) PIsrwK;ID zzZM$Ww$tHjfaa=I+1ni6>h4H)__@?UeM!GTed9ET<&R&9F~3>ru+c*6($D2n9a39^ j3+Em1ad^Gas_pNB1rGb%AG}`9vBjaaz+Phm*J1|%Lq=$$ diff --git a/tests/test_data/nmf_framework/mvnmf/H_updated_nsigs1.npy b/tests/test_data/nmf_framework/mvnmf/H_updated_nsigs1.npy deleted file mode 100644 index 384de57f1f251cdb93f97de177b5a98beb19dc61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqpcnmP)#3giN={~&N@ivt4)?A_=9=I=cSp@Szw#IMYU@P8bK&~r9G U==co|e?S_97drd`(Y<>e082Six&QzG diff --git a/tests/test_data/nmf_framework/mvnmf/H_updated_nsigs2.npy b/tests/test_data/nmf_framework/mvnmf/H_updated_nsigs2.npy deleted file mode 100644 index 3a1a6a8da82aeb279e5e5e1872d8af639fc6007c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Wf%nItsN4WCN}{)rJ0=Df1nKoZKT`v#)Zf{Ql{`FY7{w77kv9O(lyQ zCcF|^b1HU~!;Y@6E2Vt5In3PP6Vm3?=8)vSd39xKgM-EVz_nHnCOb?x7_+qNdAoyj zY>Y{l^e%_{ix!_Qy|&e%VAUJVhuljY6j!UQv5lGGu*bSO=#cDEhsF-h=xUeg4lV!w jX0lyRbTD!_U?l%`zC+;e`z)3BHaWy_I(*}eU+Mq=v~*=# diff --git a/tests/test_data/nmf_framework/mvnmf/W_init_nsigs1.npy b/tests/test_data/nmf_framework/mvnmf/W_init_nsigs1.npy deleted file mode 100644 index 22a19838d6497d68d3b2da51f405933d2314078d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 896 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 bZ1iGHY`_Ub4|t9JeoKN5={0k!q7;7v5ygU- diff --git a/tests/test_data/nmf_framework/mvnmf/W_updated_nsigs2.npy b/tests/test_data/nmf_framework/mvnmf/W_updated_nsigs2.npy deleted file mode 100644 index 831cd6b542c202d664585dd98076b0cad1417904..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1664 zcmbV~`!^H{0>_n0Ue$_{oC$F}HdBfUr}|b`?!7L<)(m-U-s3q8Gb7J2Gsa+I#=Mx3 zFzP1ZdW25)Znk10k3Cm;RBnZH&*$flj70JE4_c#@u9a;c9m9x9 zv-hyFKS*`6ce1jlrlc{`B9g;W(xPMj*S#W=7%^WvBQAm-^VJ>P_d8kbb#$`IwfcWC zu(7Q$vEC}gU>$qSF-IlLb}a3gBb-I>@;viGdKrd9qR-*`Jn z7WEuYXq$z=m#d#15rWYAai94XJu222=qO#ZQ}O0@AHC(E48(2DbMd=5*tpKo&!Z^^ z9cJraxCF&vtT1_tM39fn>wR8j;i>SgETA0O6NC^|Le4{XBAPzb-LaTjFb8UxqP#jc2XM=H?g1`5CuS7A1Q9O0-k(E zzVl<@R!ZFx`FJ!g&);H1>PJH7os-a6%!2u7%DheX*RMFO70sC^F|-<o$dqo0p7h+xaa~L>xV%_JB_hmR0qtVS6 zip2JonWbHKrC^=y&K;|iz=shn=^?V% z9b=PR!39G;5({11qzwY>s8J1S+PSFsxw@tCT{m`*>X_ZYDK$hCWYNDVYT8v6zD2{t?jnu;bun0K%b}(HU~KNMq?#- z-V^E99v+Laoy%4Wvk`b^6Pcy0;9+p_z;8XoV$kle8&>>^up>%q?X{0=xCX!4a@#Wz zrF;|Ct9yS1VLYaO+lzRFpUL+;yp<1%Hh;n0+#Tt$m-}^{co?Jq)7-~75)zXD*Q>gz z@DUL#c%k1y{q{`I1A}lxd6J&!v*TfHxAm9oi(4TB5~4Jd1+}W2}iyU{V|pl1Cx;X-AXSurf8Hfg#iPa TYfT??CZ$Mye7f>4_NVY)A]A,94,54,239,28,35,180,78,103,32,112 -A[C>A]C,60,63,199,25,23,129,60,58,24,131 -A[C>A]G,10,10,28,4,8,17,10,7,5,22 -A[C>A]T,72,42,222,17,24,148,43,60,20,106 -C[C>A]A,57,47,163,19,24,159,78,65,17,119 -C[C>A]C,73,35,161,18,13,143,41,33,10,121 -C[C>A]G,13,9,16,6,9,15,10,10,3,20 -C[C>A]T,68,57,189,7,20,173,62,65,14,116 -G[C>A]A,75,69,122,23,22,89,100,95,20,65 -G[C>A]C,45,45,84,14,16,83,27,50,15,92 -G[C>A]G,8,6,13,5,8,16,10,11,5,16 -G[C>A]T,49,66,101,10,13,109,72,63,13,68 -T[C>A]A,65,87,171,19,33,204,99,135,30,84 -T[C>A]C,86,61,132,18,24,202,60,82,33,84 -T[C>A]G,6,12,24,6,11,10,11,16,4,13 -T[C>A]T,91,134,250,24,28,265,151,182,44,100 -A[C>G]A,95,24,95,8,17,150,46,26,10,105 -A[C>G]C,40,19,61,8,7,70,36,19,14,60 -A[C>G]G,11,4,25,0,0,35,3,5,5,23 -A[C>G]T,96,18,118,7,9,145,45,35,16,112 -C[C>G]A,66,15,47,2,10,151,12,17,13,60 -C[C>G]C,52,10,36,3,9,92,23,15,11,77 -C[C>G]G,22,11,14,3,3,39,6,11,2,42 -C[C>G]T,79,19,50,7,7,203,28,31,15,106 -G[C>G]A,36,6,28,3,4,78,18,6,8,68 -G[C>G]C,30,9,43,3,8,64,22,8,7,61 -G[C>G]G,8,1,6,0,1,14,8,3,1,19 -G[C>G]T,51,8,34,6,10,107,23,18,9,93 -T[C>G]A,119,61,131,8,20,687,68,116,24,93 -T[C>G]C,85,32,80,9,11,448,52,56,13,110 -T[C>G]G,15,11,13,1,1,45,11,14,1,16 -T[C>G]T,239,90,236,14,28,1125,136,214,35,207 -A[C>T]A,126,91,238,24,46,187,80,70,54,140 -A[C>T]C,61,56,103,17,26,83,43,47,35,79 -A[C>T]G,149,272,257,92,119,185,147,168,134,145 -A[C>T]T,92,51,181,18,32,157,33,57,43,119 -C[C>T]A,75,76,112,21,46,140,53,66,40,100 -C[C>T]C,69,67,89,28,39,97,59,59,54,77 -C[C>T]G,93,163,139,45,72,108,110,108,85,88 -C[C>T]T,107,94,185,27,49,220,68,75,56,162 -G[C>T]A,68,75,86,13,37,99,74,58,38,123 -G[C>T]C,46,61,95,22,44,103,80,45,44,79 -G[C>T]G,90,230,176,71,81,155,124,118,102,116 -G[C>T]T,74,55,129,10,22,110,35,49,38,93 -T[C>T]A,139,178,198,28,63,520,136,224,97,106 -T[C>T]C,126,97,155,24,40,341,98,95,79,98 -T[C>T]G,80,128,117,35,68,101,79,103,53,87 -T[C>T]T,152,128,244,26,72,382,109,147,116,137 -A[T>A]A,43,66,115,17,16,86,44,26,13,57 -A[T>A]C,25,19,75,20,24,46,31,21,27,48 -A[T>A]G,37,30,99,10,17,76,22,29,16,64 -A[T>A]T,63,61,168,21,32,120,49,38,18,117 -C[T>A]A,31,32,85,4,9,61,15,16,3,63 -C[T>A]C,32,17,65,2,16,71,19,22,7,101 -C[T>A]G,55,24,105,9,14,108,34,22,9,68 -C[T>A]T,63,39,182,6,9,117,24,23,17,90 -G[T>A]A,22,17,42,5,4,47,10,9,7,40 -G[T>A]C,20,14,39,3,8,38,14,12,10,38 -G[T>A]G,23,16,33,5,14,48,18,15,5,39 -G[T>A]T,41,16,99,2,9,102,18,17,10,71 -T[T>A]A,31,63,124,16,29,122,51,60,18,76 -T[T>A]C,30,21,95,3,10,59,25,21,12,44 -T[T>A]G,19,15,57,2,5,43,9,9,7,39 -T[T>A]T,76,38,240,9,13,146,41,41,13,116 -A[T>C]A,90,101,150,29,49,189,79,57,52,157 -A[T>C]C,42,29,68,10,23,87,36,19,18,85 -A[T>C]G,58,47,85,10,24,108,55,31,26,131 -A[T>C]T,99,95,118,19,46,200,95,58,40,167 -C[T>C]A,39,50,57,7,15,69,44,14,20,73 -C[T>C]C,55,27,92,11,15,139,28,20,13,109 -C[T>C]G,43,37,42,10,14,59,28,25,15,88 -C[T>C]T,59,42,68,5,13,105,56,37,22,113 -G[T>C]A,40,63,79,11,32,101,50,22,12,81 -G[T>C]C,29,27,35,12,11,62,37,18,14,47 -G[T>C]G,26,41,32,11,9,58,39,17,15,64 -G[T>C]T,57,49,70,14,36,103,55,28,20,73 -T[T>C]A,56,83,106,7,23,93,41,28,23,76 -T[T>C]C,47,52,73,14,16,91,50,24,9,73 -T[T>C]G,25,29,57,4,13,61,18,21,13,54 -T[T>C]T,54,75,92,17,39,133,55,46,27,99 -A[T>G]A,29,25,49,2,13,61,29,18,13,49 -A[T>G]C,12,12,15,4,5,26,11,4,7,35 -A[T>G]G,43,16,40,6,6,74,17,19,6,44 -A[T>G]T,37,24,57,13,13,56,24,15,9,46 -C[T>G]A,18,7,12,5,3,30,10,9,3,29 -C[T>G]C,23,13,21,8,4,42,6,10,3,27 -C[T>G]G,43,11,46,1,6,100,21,25,7,62 -C[T>G]T,20,14,49,8,9,70,14,25,7,64 -G[T>G]A,7,10,17,0,1,27,7,7,5,26 -G[T>G]C,9,6,12,3,6,19,6,3,4,18 -G[T>G]G,25,15,37,11,4,76,16,12,8,57 -G[T>G]T,27,6,24,6,5,63,20,11,5,41 -T[T>G]A,39,25,39,2,9,66,21,8,9,52 -T[T>G]C,19,9,30,3,4,37,15,6,8,31 -T[T>G]G,39,18,73,4,10,86,21,18,4,45 -T[T>G]T,58,38,81,10,20,110,48,38,16,109 diff --git a/tests/test_data/nmf_framework/mvnmf/objective_init_nsigs1.npy b/tests/test_data/nmf_framework/mvnmf/objective_init_nsigs1.npy deleted file mode 100644 index 195659322b0184bfb151aad80c023de474af6eec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46ag;2yu+N6JfAoK05q~5h5!Hn diff --git a/tests/test_data/nmf_framework/mvnmf/objective_init_nsigs2.npy b/tests/test_data/nmf_framework/mvnmf/objective_init_nsigs2.npy deleted file mode 100644 index fc046ed2fa76a0af020c49f60f03c79a5316e983..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46ag-g#gotTWZrN905?M(kd", data.values, p) for data, p in zip(counts, _ps)] - - -@pytest.fixture -def Ls_init(): - return [ - np.load(f"{PATH_TEST_DATA}/model{n}_L_init.npy") for n in range(N_MODALITIES) - ] - - -@pytest.fixture -def U_init(): - """ - Initial joint sample embeddings. - """ - return np.load(f"{PATH_TEST_DATA}/U_init.npy") - - -@pytest.fixture -def sigma_sq_init(): - """ - Initial joint variance. - """ - return np.load(f"{PATH_TEST_DATA}/sigma_sq_init.npy") - - -@pytest.fixture -def multi_model_init( - counts, Ws_init, alphas_init, betas_init, Ls_init, U_init, sigma_sq_init -): - models = [] - - for n, n_signatures in enumerate(NS_SIGNATURES): - model = corrnmf_det.CorrNMFDet( - n_signatures=n_signatures, dim_embeddings=DIM_EMBEDDINGS - ) - model.X = counts[n].values - model.W = Ws_init[n] - model.alpha = alphas_init[n] - model.beta = betas_init[n] - model.L = Ls_init[n] - model.U = U_init - model.sigma_sq = sigma_sq_init - model.mutation_types = counts[n].index - model.signature_names = [f"Sig {k}" for k in range(n_signatures)] - model.sample_names = counts[n].columns - models.append(model) - - multi_model = multimodal_corrnmf.MultimodalCorrNMF( - n_modalities=N_MODALITIES, - ns_signatures=NS_SIGNATURES, - dim_embeddings=DIM_EMBEDDINGS, - ) - multi_model.models = models - multi_model.n_samples = len(counts[0].columns) - return multi_model - - -@pytest.fixture -def objective_init(): - return np.load(f"{PATH_TEST_DATA}/objective_init.npy") - - -def test_objective_function(multi_model_init, objective_init): - assert np.allclose(multi_model_init.objective_function(), objective_init) - - -@pytest.fixture -def surrogate_objective_init(): - return np.load(f"{PATH_TEST_DATA}/surrogate_objective_init.npy") - - -def test_surrogate_objective_function(multi_model_init, surrogate_objective_init): - assert np.allclose( - multi_model_init._surrogate_objective_function(), - surrogate_objective_init, - ) - - -@pytest.fixture -def Ws_updated(): - return [ - np.load(f"{PATH_TEST_DATA}/model{n}_W_updated.npy") for n in range(N_MODALITIES) - ] - - -@pytest.fixture -def alphas_updated(): - return [ - np.load(f"{PATH_TEST_DATA}/model{n}_alpha_updated.npy") - for n in range(N_MODALITIES) - ] - - -@pytest.fixture -def betas_updated(): - return [ - np.load(f"{PATH_TEST_DATA}/model{n}_beta_updated.npy") - for n in range(N_MODALITIES) - ] - - -@pytest.fixture -def Ls_updated(): - return [ - np.load(f"{PATH_TEST_DATA}/model{n}_L_updated.npy") for n in range(N_MODALITIES) - ] - - -@pytest.fixture -def U_updated(): - return np.load(f"{PATH_TEST_DATA}/U_updated.npy") - - -@pytest.fixture -def sigma_sq_updated(): - return np.load(f"{PATH_TEST_DATA}/sigma_sq_updated.npy") - - -class TestUpdatesMultimodalCorrNMF: - def test_update_W(self, multi_model_init, Ws_updated): - multi_model_init._update_Ws() - - for model, W_updated in zip(multi_model_init.models, Ws_updated): - assert np.allclose(model.W, W_updated) - - def test_update_alpha(self, multi_model_init, alphas_updated): - n_modalities = multi_model_init.n_modalities - given_sample_biases = [None for _ in range(n_modalities)] - multi_model_init._update_alphas(given_sample_biases) - - for model, alpha_updated in zip(multi_model_init.models, alphas_updated): - assert np.allclose(model.alpha, alpha_updated) - - def test_update_beta(self, multi_model_init, _ps, betas_updated): - n_modalities = multi_model_init.n_modalities - given_signature_biases = [None for _ in range(n_modalities)] - multi_model_init._update_betas(_ps, given_signature_biases) - - for model, beta_updated in zip(multi_model_init.models, betas_updated): - assert np.allclose(model.beta, beta_updated) - - def test_p(self, multi_model_init, _ps): - ps_computed = multi_model_init._update_ps() - - for p1, p2 in zip(ps_computed, _ps): - assert np.allclose(p1, p2) - - def test_update_L(self, multi_model_init, _auxs, U_init, Ls_updated): - outer_prods_U = np.einsum("mD,nD->Dmn", U_init, U_init) - given_signature_embeddings = [None for _ in range(N_MODALITIES)] - multi_model_init._update_Ls(_auxs, outer_prods_U, given_signature_embeddings) - - for model, L_updated in zip(multi_model_init.models, Ls_updated): - assert np.allclose(model.L, L_updated) - - def test_update_U(self, multi_model_init, _auxs, U_updated): - multi_model_init._update_U(_auxs) - - for model in multi_model_init.models: - assert np.allclose(model.U, U_updated) - - def test_update_sigma_sq(self, multi_model_init, sigma_sq_updated): - given_variance = None - multi_model_init._update_sigma_sq(given_variance) - - for model in multi_model_init.models: - assert np.allclose(model.sigma_sq, sigma_sq_updated) - - -@pytest.mark.parametrize( - "ns_signatures,dim_embeddings", [([1, 2], 1), ([2, 2], 1), ([2, 2], 2)] -) -class TestGivenParametersMultimodalCorrNMF: - @pytest.fixture() - def multi_model(self, ns_signatures, dim_embeddings): - model = multimodal_corrnmf.MultimodalCorrNMF( - n_modalities=2, - ns_signatures=ns_signatures, - dim_embeddings=dim_embeddings, - min_iterations=3, - max_iterations=3, - ) - return model - - def test_given_signatures(self, multi_model, counts): - n_signatures0 = multi_model.ns_signatures[0] - for n_given_signatures in range(1, n_signatures0 + 1): - given_signatures0 = ( - counts[0].iloc[:, :n_given_signatures].astype(float).copy() - ) - given_signatures0 /= given_signatures0.sum(axis=0) - given_signatures = [given_signatures0, None] - multi_model.fit(counts, given_signatures=given_signatures) - assert np.allclose( - given_signatures0, - multi_model.models[0].signatures.iloc[:, :n_given_signatures], - ) - assert not np.allclose( - given_signatures0, - multi_model.models[1].signatures.iloc[:, :n_given_signatures], - ) - - def test_given_signature_biases(self, multi_model, counts): - n_signatures0 = multi_model.ns_signatures[0] - given_signature_biases0 = np.random.uniform(size=n_signatures0) - given_signature_biases = [given_signature_biases0, None] - multi_model.fit(counts, given_signature_biases=given_signature_biases) - assert np.allclose(given_signature_biases0, multi_model.models[0].beta) - assert not np.allclose(given_signature_biases0, multi_model.models[1].beta) - - def test_given_signature_embeddings(self, multi_model, counts): - n_signatures0 = multi_model.ns_signatures[0] - given_signature_embeddings0 = np.random.uniform( - size=(multi_model.dim_embeddings, n_signatures0) - ) - given_signature_embeddings = [given_signature_embeddings0, None] - multi_model.fit(counts, given_signature_embeddings=given_signature_embeddings) - assert np.allclose(given_signature_embeddings0, multi_model.models[0].L) - assert not np.allclose(given_signature_embeddings0, multi_model.models[1].L) - - def test_given_sample_biases(self, multi_model, counts): - n_samples = len(counts[0].columns) - given_sample_biases0 = np.random.uniform(size=n_samples) - given_sample_biases = [given_sample_biases0, None] - multi_model.fit(counts, given_sample_biases=given_sample_biases) - assert np.allclose(given_sample_biases0, multi_model.models[0].alpha) - assert not np.allclose(given_sample_biases0, multi_model.models[1].alpha) - - def test_given_sample_embeddings(self, multi_model, counts): - n_samples = len(counts[0].columns) - given_sample_embeddings = np.random.uniform( - size=(multi_model.dim_embeddings, n_samples) - ) - multi_model.fit(counts, given_sample_embeddings=given_sample_embeddings) - - for model in multi_model.models: - assert np.allclose(given_sample_embeddings, model.U) - - def test_given_variance(self, multi_model, counts): - given_variance = 3 - multi_model.fit(counts, given_variance=given_variance) - - for model in multi_model.models: - assert np.allclose(given_variance, model.sigma_sq) diff --git a/tests/test_mvnmf.py b/tests/test_mvnmf.py deleted file mode 100644 index cd11a2a..0000000 --- a/tests/test_mvnmf.py +++ /dev/null @@ -1,78 +0,0 @@ -import numpy as np -import pandas as pd -import pytest - -from salamander.nmf_framework import mvnmf - -PATH = "tests/test_data" -PATH_TEST_DATA = f"{PATH}/nmf_framework/mvnmf" - - -@pytest.fixture -def counts(): - return pd.read_csv(f"{PATH_TEST_DATA}/counts.csv", index_col=0) - - -@pytest.fixture(params=[1, 2]) -def n_signatures(request): - return request.param - - -@pytest.fixture -def W_init(n_signatures): - return np.load(f"{PATH_TEST_DATA}/W_init_nsigs{n_signatures}.npy") - - -@pytest.fixture -def H_init(n_signatures): - return np.load(f"{PATH_TEST_DATA}/H_init_nsigs{n_signatures}.npy") - - -@pytest.fixture -def model_init(counts, W_init, H_init): - n_signatures = W_init.shape[1] - model = mvnmf.MvNMF(n_signatures=n_signatures, lam=1.0, delta=1.0) - model.X = counts.values - model.W = W_init - model.H = H_init - model._gamma = 1.0 - return model - - -@pytest.fixture -def objective_init(n_signatures): - return np.load(f"{PATH_TEST_DATA}/objective_init_nsigs{n_signatures}.npy") - - -def test_objective_function(model_init, objective_init): - assert np.allclose(model_init.objective_function(), objective_init) - - -class TestUpdatesMVNMF: - @pytest.fixture - def W_updated(self, n_signatures): - return np.load(f"{PATH_TEST_DATA}/W_updated_nsigs{n_signatures}.npy") - - @pytest.fixture - def H_updated(self, n_signatures): - return np.load(f"{PATH_TEST_DATA}/H_updated_nsigs{n_signatures}.npy") - - def test_update_W(self, model_init, W_updated): - model_init._update_W() - assert np.allclose(model_init.W, W_updated) - - def test_update_H(self, model_init, H_updated): - model_init._update_H() - assert np.allclose(model_init.H, H_updated) - - def test_given_signatures(self, n_signatures, counts): - for n_given_signatures in range(1, n_signatures + 1): - given_signatures = counts.iloc[:, :n_given_signatures].astype(float).copy() - given_signatures /= given_signatures.sum(axis=0) - model = mvnmf.MvNMF( - n_signatures=n_signatures, min_iterations=3, max_iterations=3 - ) - model.fit(counts, given_signatures=given_signatures) - assert np.allclose( - given_signatures, model.signatures.iloc[:, :n_given_signatures] - ) From b7cde90c1d1da8d84821196b0c35362eb25dbb39 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 28 Mar 2024 14:55:50 -0400 Subject: [PATCH 002/103] update lock file --- poetry.lock | 409 ++++++++++++++++++++++++++-------------------------- 1 file changed, 202 insertions(+), 207 deletions(-) diff --git a/poetry.lock b/poetry.lock index a94baf8..5553451 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,13 +17,13 @@ numpy = "*" [[package]] name = "cachetools" -version = "5.3.2" +version = "5.3.3" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, - {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, + {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, + {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, ] [[package]] @@ -224,76 +224,76 @@ test = ["scipy (>=1.6.3)"] [[package]] name = "filelock" -version = "3.13.1" +version = "3.13.3" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, + {file = "filelock-3.13.3-py3-none-any.whl", hash = "sha256:5ffa845303983e7a0b7ae17636509bc97997d58afeafa72fb141a17b152284cb"}, + {file = "filelock-3.13.3.tar.gz", hash = "sha256:a79895a25bbefdf55d1a2a0a80968f7dbb28edcd6d4234a0afb3f37ecde4b546"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] name = "fonttools" -version = "4.47.0" +version = "4.50.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.47.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d2404107626f97a221dc1a65b05396d2bb2ce38e435f64f26ed2369f68675d9"}, - {file = "fonttools-4.47.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01f409be619a9a0f5590389e37ccb58b47264939f0e8d58bfa1f3ba07d22671"}, - {file = "fonttools-4.47.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d986b66ff722ef675b7ee22fbe5947a41f60a61a4da15579d5e276d897fbc7fa"}, - {file = "fonttools-4.47.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8acf6dd0434b211b3bd30d572d9e019831aae17a54016629fa8224783b22df8"}, - {file = "fonttools-4.47.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:495369c660e0c27233e3c572269cbe520f7f4978be675f990f4005937337d391"}, - {file = "fonttools-4.47.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c59227d7ba5b232281c26ae04fac2c73a79ad0e236bca5c44aae904a18f14faf"}, - {file = "fonttools-4.47.0-cp310-cp310-win32.whl", hash = "sha256:59a6c8b71a245800e923cb684a2dc0eac19c56493e2f896218fcf2571ed28984"}, - {file = "fonttools-4.47.0-cp310-cp310-win_amd64.whl", hash = "sha256:52c82df66201f3a90db438d9d7b337c7c98139de598d0728fb99dab9fd0495ca"}, - {file = "fonttools-4.47.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:854421e328d47d70aa5abceacbe8eef231961b162c71cbe7ff3f47e235e2e5c5"}, - {file = "fonttools-4.47.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:511482df31cfea9f697930f61520f6541185fa5eeba2fa760fe72e8eee5af88b"}, - {file = "fonttools-4.47.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0e2c88c8c985b7b9a7efcd06511fb0a1fe3ddd9a6cd2895ef1dbf9059719d7"}, - {file = "fonttools-4.47.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7a0a8848726956e9d9fb18c977a279013daadf0cbb6725d2015a6dd57527992"}, - {file = "fonttools-4.47.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e869da810ae35afb3019baa0d0306cdbab4760a54909c89ad8904fa629991812"}, - {file = "fonttools-4.47.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dd23848f877c3754f53a4903fb7a593ed100924f9b4bff7d5a4e2e8a7001ae11"}, - {file = "fonttools-4.47.0-cp311-cp311-win32.whl", hash = "sha256:bf1810635c00f7c45d93085611c995fc130009cec5abdc35b327156aa191f982"}, - {file = "fonttools-4.47.0-cp311-cp311-win_amd64.whl", hash = "sha256:61df4dee5d38ab65b26da8efd62d859a1eef7a34dcbc331299a28e24d04c59a7"}, - {file = "fonttools-4.47.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e3f4d61f3a8195eac784f1d0c16c0a3105382c1b9a74d99ac4ba421da39a8826"}, - {file = "fonttools-4.47.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:174995f7b057e799355b393e97f4f93ef1f2197cbfa945e988d49b2a09ecbce8"}, - {file = "fonttools-4.47.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea592e6a09b71cb7a7661dd93ac0b877a6228e2d677ebacbad0a4d118494c86d"}, - {file = "fonttools-4.47.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40bdbe90b33897d9cc4a39f8e415b0fcdeae4c40a99374b8a4982f127ff5c767"}, - {file = "fonttools-4.47.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:843509ae9b93db5aaf1a6302085e30bddc1111d31e11d724584818f5b698f500"}, - {file = "fonttools-4.47.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9acfa1cdc479e0dde528b61423855913d949a7f7fe09e276228298fef4589540"}, - {file = "fonttools-4.47.0-cp312-cp312-win32.whl", hash = "sha256:66c92ec7f95fd9732550ebedefcd190a8d81beaa97e89d523a0d17198a8bda4d"}, - {file = "fonttools-4.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8fa20748de55d0021f83754b371432dca0439e02847962fc4c42a0e444c2d78"}, - {file = "fonttools-4.47.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c75e19971209fbbce891ebfd1b10c37320a5a28e8d438861c21d35305aedb81c"}, - {file = "fonttools-4.47.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e79f1a3970d25f692bbb8c8c2637e621a66c0d60c109ab48d4a160f50856deff"}, - {file = "fonttools-4.47.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:562681188c62c024fe2c611b32e08b8de2afa00c0c4e72bed47c47c318e16d5c"}, - {file = "fonttools-4.47.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a77a60315c33393b2bd29d538d1ef026060a63d3a49a9233b779261bad9c3f71"}, - {file = "fonttools-4.47.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4fabb8cc9422efae1a925160083fdcbab8fdc96a8483441eb7457235df625bd"}, - {file = "fonttools-4.47.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2a78dba8c2a1e9d53a0fb5382979f024200dc86adc46a56cbb668a2249862fda"}, - {file = "fonttools-4.47.0-cp38-cp38-win32.whl", hash = "sha256:e6b968543fde4119231c12c2a953dcf83349590ca631ba8216a8edf9cd4d36a9"}, - {file = "fonttools-4.47.0-cp38-cp38-win_amd64.whl", hash = "sha256:4a9a51745c0439516d947480d4d884fa18bd1458e05b829e482b9269afa655bc"}, - {file = "fonttools-4.47.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:62d8ddb058b8e87018e5dc26f3258e2c30daad4c87262dfeb0e2617dd84750e6"}, - {file = "fonttools-4.47.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5dde0eab40faaa5476133123f6a622a1cc3ac9b7af45d65690870620323308b4"}, - {file = "fonttools-4.47.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4da089f6dfdb822293bde576916492cd708c37c2501c3651adde39804630538"}, - {file = "fonttools-4.47.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:253bb46bab970e8aae254cebf2ae3db98a4ef6bd034707aa68a239027d2b198d"}, - {file = "fonttools-4.47.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1193fb090061efa2f9e2d8d743ae9850c77b66746a3b32792324cdce65784154"}, - {file = "fonttools-4.47.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:084511482dd265bce6dca24c509894062f0117e4e6869384d853f46c0e6d43be"}, - {file = "fonttools-4.47.0-cp39-cp39-win32.whl", hash = "sha256:97620c4af36e4c849e52661492e31dc36916df12571cb900d16960ab8e92a980"}, - {file = "fonttools-4.47.0-cp39-cp39-win_amd64.whl", hash = "sha256:e77bdf52185bdaf63d39f3e1ac3212e6cfa3ab07d509b94557a8902ce9c13c82"}, - {file = "fonttools-4.47.0-py3-none-any.whl", hash = "sha256:d6477ba902dd2d7adda7f0fd3bfaeb92885d45993c9e1928c9f28fc3961415f7"}, - {file = "fonttools-4.47.0.tar.gz", hash = "sha256:ec13a10715eef0e031858c1c23bfaee6cba02b97558e4a7bfa089dba4a8c2ebf"}, + {file = "fonttools-4.50.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effd303fb422f8ce06543a36ca69148471144c534cc25f30e5be752bc4f46736"}, + {file = "fonttools-4.50.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7913992ab836f621d06aabac118fc258b9947a775a607e1a737eb3a91c360335"}, + {file = "fonttools-4.50.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0a1c5bd2f63da4043b63888534b52c5a1fd7ae187c8ffc64cbb7ae475b9dab"}, + {file = "fonttools-4.50.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40fc98540fa5360e7ecf2c56ddf3c6e7dd04929543618fd7b5cc76e66390562"}, + {file = "fonttools-4.50.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fff65fbb7afe137bac3113827855e0204482727bddd00a806034ab0d3951d0d"}, + {file = "fonttools-4.50.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1aeae3dd2ee719074a9372c89ad94f7c581903306d76befdaca2a559f802472"}, + {file = "fonttools-4.50.0-cp310-cp310-win32.whl", hash = "sha256:e9623afa319405da33b43c85cceb0585a6f5d3a1d7c604daf4f7e1dd55c03d1f"}, + {file = "fonttools-4.50.0-cp310-cp310-win_amd64.whl", hash = "sha256:778c5f43e7e654ef7fe0605e80894930bc3a7772e2f496238e57218610140f54"}, + {file = "fonttools-4.50.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3dfb102e7f63b78c832e4539969167ffcc0375b013080e6472350965a5fe8048"}, + {file = "fonttools-4.50.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e58fe34cb379ba3d01d5d319d67dd3ce7ca9a47ad044ea2b22635cd2d1247fc"}, + {file = "fonttools-4.50.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c673ab40d15a442a4e6eb09bf007c1dda47c84ac1e2eecbdf359adacb799c24"}, + {file = "fonttools-4.50.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b3ac35cdcd1a4c90c23a5200212c1bb74fa05833cc7c14291d7043a52ca2aaa"}, + {file = "fonttools-4.50.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8844e7a2c5f7ecf977e82eb6b3014f025c8b454e046d941ece05b768be5847ae"}, + {file = "fonttools-4.50.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f849bd3c5c2249b49c98eca5aaebb920d2bfd92b3c69e84ca9bddf133e9f83f0"}, + {file = "fonttools-4.50.0-cp311-cp311-win32.whl", hash = "sha256:39293ff231b36b035575e81c14626dfc14407a20de5262f9596c2cbb199c3625"}, + {file = "fonttools-4.50.0-cp311-cp311-win_amd64.whl", hash = "sha256:c33d5023523b44d3481624f840c8646656a1def7630ca562f222eb3ead16c438"}, + {file = "fonttools-4.50.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b4a886a6dbe60100ba1cd24de962f8cd18139bd32808da80de1fa9f9f27bf1dc"}, + {file = "fonttools-4.50.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b2ca1837bfbe5eafa11313dbc7edada79052709a1fffa10cea691210af4aa1fa"}, + {file = "fonttools-4.50.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0493dd97ac8977e48ffc1476b932b37c847cbb87fd68673dee5182004906828"}, + {file = "fonttools-4.50.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77844e2f1b0889120b6c222fc49b2b75c3d88b930615e98893b899b9352a27ea"}, + {file = "fonttools-4.50.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3566bfb8c55ed9100afe1ba6f0f12265cd63a1387b9661eb6031a1578a28bad1"}, + {file = "fonttools-4.50.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:35e10ddbc129cf61775d58a14f2d44121178d89874d32cae1eac722e687d9019"}, + {file = "fonttools-4.50.0-cp312-cp312-win32.whl", hash = "sha256:cc8140baf9fa8f9b903f2b393a6c413a220fa990264b215bf48484f3d0bf8710"}, + {file = "fonttools-4.50.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ccc85fd96373ab73c59833b824d7a73846670a0cb1f3afbaee2b2c426a8f931"}, + {file = "fonttools-4.50.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e270a406219af37581d96c810172001ec536e29e5593aa40d4c01cca3e145aa6"}, + {file = "fonttools-4.50.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac2463de667233372e9e1c7e9de3d914b708437ef52a3199fdbf5a60184f190c"}, + {file = "fonttools-4.50.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47abd6669195abe87c22750dbcd366dc3a0648f1b7c93c2baa97429c4dc1506e"}, + {file = "fonttools-4.50.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:074841375e2e3d559aecc86e1224caf78e8b8417bb391e7d2506412538f21adc"}, + {file = "fonttools-4.50.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0743fd2191ad7ab43d78cd747215b12033ddee24fa1e088605a3efe80d6984de"}, + {file = "fonttools-4.50.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3d7080cce7be5ed65bee3496f09f79a82865a514863197ff4d4d177389e981b0"}, + {file = "fonttools-4.50.0-cp38-cp38-win32.whl", hash = "sha256:a467ba4e2eadc1d5cc1a11d355abb945f680473fbe30d15617e104c81f483045"}, + {file = "fonttools-4.50.0-cp38-cp38-win_amd64.whl", hash = "sha256:f77e048f805e00870659d6318fd89ef28ca4ee16a22b4c5e1905b735495fc422"}, + {file = "fonttools-4.50.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b6245eafd553c4e9a0708e93be51392bd2288c773523892fbd616d33fd2fda59"}, + {file = "fonttools-4.50.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a4062cc7e8de26f1603323ef3ae2171c9d29c8a9f5e067d555a2813cd5c7a7e0"}, + {file = "fonttools-4.50.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34692850dfd64ba06af61e5791a441f664cb7d21e7b544e8f385718430e8f8e4"}, + {file = "fonttools-4.50.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678dd95f26a67e02c50dcb5bf250f95231d455642afbc65a3b0bcdacd4e4dd38"}, + {file = "fonttools-4.50.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4f2ce7b0b295fe64ac0a85aef46a0f2614995774bd7bc643b85679c0283287f9"}, + {file = "fonttools-4.50.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d346f4dc2221bfb7ab652d1e37d327578434ce559baf7113b0f55768437fe6a0"}, + {file = "fonttools-4.50.0-cp39-cp39-win32.whl", hash = "sha256:a51eeaf52ba3afd70bf489be20e52fdfafe6c03d652b02477c6ce23c995222f4"}, + {file = "fonttools-4.50.0-cp39-cp39-win_amd64.whl", hash = "sha256:8639be40d583e5d9da67795aa3eeeda0488fb577a1d42ae11a5036f18fb16d93"}, + {file = "fonttools-4.50.0-py3-none-any.whl", hash = "sha256:48fa36da06247aa8282766cfd63efff1bb24e55f020f29a335939ed3844d20d3"}, + {file = "fonttools-4.50.0.tar.gz", hash = "sha256:fa5cf61058c7dbb104c2ac4e782bf1b2016a8cf2f69de6e4dd6a865d2c969bb5"}, ] [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] interpolatable = ["munkres", "pycairo", "scipy"] -lxml = ["lxml (>=4.0,<5)"] +lxml = ["lxml (>=4.0)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] @@ -305,13 +305,13 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "identify" -version = "2.5.33" +version = "2.5.35" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, - {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, + {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, + {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, ] [package.extras] @@ -319,13 +319,13 @@ license = ["ukkonen"] [[package]] name = "importlib-resources" -version = "6.1.1" +version = "6.4.0" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, - {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, + {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, + {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, ] [package.dependencies] @@ -333,7 +333,7 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] +testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] [[package]] name = "iniconfig" @@ -505,39 +505,39 @@ files = [ [[package]] name = "matplotlib" -version = "3.8.2" +version = "3.8.3" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7"}, - {file = "matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367"}, - {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18"}, - {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31"}, - {file = "matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a"}, - {file = "matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a"}, - {file = "matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63"}, - {file = "matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8"}, - {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6"}, - {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788"}, - {file = "matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0"}, - {file = "matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717"}, - {file = "matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627"}, - {file = "matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4"}, - {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d"}, - {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331"}, - {file = "matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213"}, - {file = "matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630"}, - {file = "matplotlib-3.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:deaed9ad4da0b1aea77fe0aa0cebb9ef611c70b3177be936a95e5d01fa05094f"}, - {file = "matplotlib-3.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:172f4d0fbac3383d39164c6caafd3255ce6fa58f08fc392513a0b1d3b89c4f89"}, - {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7d36c2209d9136cd8e02fab1c0ddc185ce79bc914c45054a9f514e44c787917"}, - {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864bdd7da445e4e5e011b199bb67168cdad10b501750367c496420f2ad00843"}, - {file = "matplotlib-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8345b48e95cee45ff25192ed1f4857273117917a4dcd48e3905619bcd9c9b8"}, - {file = "matplotlib-3.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:7c48d9e221b637c017232e3760ed30b4e8d5dfd081daf327e829bf2a72c731b4"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa11b3c6928a1e496c1a79917d51d4cd5d04f8a2e75f21df4949eeefdf697f4b"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1095fecf99eeb7384dabad4bf44b965f929a5f6079654b681193edf7169ec20"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bddfb1db89bfaa855912261c805bd0e10218923cc262b9159a49c29a7a1c1afa"}, - {file = "matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1"}, + {file = "matplotlib-3.8.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cf60138ccc8004f117ab2a2bad513cc4d122e55864b4fe7adf4db20ca68a078f"}, + {file = "matplotlib-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f557156f7116be3340cdeef7f128fa99b0d5d287d5f41a16e169819dcf22357"}, + {file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f386cf162b059809ecfac3bcc491a9ea17da69fa35c8ded8ad154cd4b933d5ec"}, + {file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c5f96f57b0369c288bf6f9b5274ba45787f7e0589a34d24bdbaf6d3344632f"}, + {file = "matplotlib-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:83e0f72e2c116ca7e571c57aa29b0fe697d4c6425c4e87c6e994159e0c008635"}, + {file = "matplotlib-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:1c5c8290074ba31a41db1dc332dc2b62def469ff33766cbe325d32a3ee291aea"}, + {file = "matplotlib-3.8.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5184e07c7e1d6d1481862ee361905b7059f7fe065fc837f7c3dc11eeb3f2f900"}, + {file = "matplotlib-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d7e7e0993d0758933b1a241a432b42c2db22dfa37d4108342ab4afb9557cbe3e"}, + {file = "matplotlib-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b36ad07eac9740fc76c2aa16edf94e50b297d6eb4c081e3add863de4bb19a7"}, + {file = "matplotlib-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c42dae72a62f14982f1474f7e5c9959fc4bc70c9de11cc5244c6e766200ba65"}, + {file = "matplotlib-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf5932eee0d428192c40b7eac1399d608f5d995f975cdb9d1e6b48539a5ad8d0"}, + {file = "matplotlib-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:40321634e3a05ed02abf7c7b47a50be50b53ef3eaa3a573847431a545585b407"}, + {file = "matplotlib-3.8.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:09074f8057917d17ab52c242fdf4916f30e99959c1908958b1fc6032e2d0f6d4"}, + {file = "matplotlib-3.8.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5745f6d0fb5acfabbb2790318db03809a253096e98c91b9a31969df28ee604aa"}, + {file = "matplotlib-3.8.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97653d869a71721b639714b42d87cda4cfee0ee74b47c569e4874c7590c55c5"}, + {file = "matplotlib-3.8.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:242489efdb75b690c9c2e70bb5c6550727058c8a614e4c7716f363c27e10bba1"}, + {file = "matplotlib-3.8.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:83c0653c64b73926730bd9ea14aa0f50f202ba187c307a881673bad4985967b7"}, + {file = "matplotlib-3.8.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef6c1025a570354297d6c15f7d0f296d95f88bd3850066b7f1e7b4f2f4c13a39"}, + {file = "matplotlib-3.8.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c4af3f7317f8a1009bbb2d0bf23dfaba859eb7dd4ccbd604eba146dccaaaf0a4"}, + {file = "matplotlib-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c6e00a65d017d26009bac6808f637b75ceade3e1ff91a138576f6b3065eeeba"}, + {file = "matplotlib-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7b49ab49a3bea17802df6872f8d44f664ba8f9be0632a60c99b20b6db2165b7"}, + {file = "matplotlib-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6728dde0a3997396b053602dbd907a9bd64ec7d5cf99e728b404083698d3ca01"}, + {file = "matplotlib-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:813925d08fb86aba139f2d31864928d67511f64e5945ca909ad5bc09a96189bb"}, + {file = "matplotlib-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:cd3a0c2be76f4e7be03d34a14d49ded6acf22ef61f88da600a18a5cd8b3c5f3c"}, + {file = "matplotlib-3.8.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fa93695d5c08544f4a0dfd0965f378e7afc410d8672816aff1e81be1f45dbf2e"}, + {file = "matplotlib-3.8.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9764df0e8778f06414b9d281a75235c1e85071f64bb5d71564b97c1306a2afc"}, + {file = "matplotlib-3.8.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5e431a09e6fab4012b01fc155db0ce6dccacdbabe8198197f523a4ef4805eb26"}, + {file = "matplotlib-3.8.3.tar.gz", hash = "sha256:7b416239e9ae38be54b028abbf9048aff5054a9aba5416bef0bd17f9162ce161"}, ] [package.dependencies] @@ -642,13 +642,13 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -786,28 +786,28 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.1.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -816,13 +816,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.6.0" +version = "3.7.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, - {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, + {file = "pre_commit-3.7.0-py2.py3-none-any.whl", hash = "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab"}, + {file = "pre_commit-3.7.0.tar.gz", hash = "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060"}, ] [package.dependencies] @@ -852,13 +852,13 @@ scipy = ">=1.0" [[package]] name = "pyparsing" -version = "3.1.1" +version = "3.1.2" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, ] [package.extras] @@ -907,13 +907,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] @@ -921,13 +921,13 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2023.3.post1" +version = "2024.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] @@ -991,102 +991,97 @@ files = [ [[package]] name = "scikit-learn" -version = "1.3.2" +version = "1.4.1.post1" description = "A set of python modules for machine learning and data mining" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "scikit-learn-1.3.2.tar.gz", hash = "sha256:a2f54c76accc15a34bfb9066e6c7a56c1e7235dda5762b990792330b52ccfb05"}, - {file = "scikit_learn-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e326c0eb5cf4d6ba40f93776a20e9a7a69524c4db0757e7ce24ba222471ee8a1"}, - {file = "scikit_learn-1.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:535805c2a01ccb40ca4ab7d081d771aea67e535153e35a1fd99418fcedd1648a"}, - {file = "scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1215e5e58e9880b554b01187b8c9390bf4dc4692eedeaf542d3273f4785e342c"}, - {file = "scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ee107923a623b9f517754ea2f69ea3b62fc898a3641766cb7deb2f2ce450161"}, - {file = "scikit_learn-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:35a22e8015048c628ad099da9df5ab3004cdbf81edc75b396fd0cff8699ac58c"}, - {file = "scikit_learn-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6fb6bc98f234fda43163ddbe36df8bcde1d13ee176c6dc9b92bb7d3fc842eb66"}, - {file = "scikit_learn-1.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:18424efee518a1cde7b0b53a422cde2f6625197de6af36da0b57ec502f126157"}, - {file = "scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3271552a5eb16f208a6f7f617b8cc6d1f137b52c8a1ef8edf547db0259b2c9fb"}, - {file = "scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4144a5004a676d5022b798d9e573b05139e77f271253a4703eed295bde0433"}, - {file = "scikit_learn-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:67f37d708f042a9b8d59551cf94d30431e01374e00dc2645fa186059c6c5d78b"}, - {file = "scikit_learn-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8db94cd8a2e038b37a80a04df8783e09caac77cbe052146432e67800e430c028"}, - {file = "scikit_learn-1.3.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:61a6efd384258789aa89415a410dcdb39a50e19d3d8410bd29be365bcdd512d5"}, - {file = "scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb06f8dce3f5ddc5dee1715a9b9f19f20d295bed8e3cd4fa51e1d050347de525"}, - {file = "scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b2de18d86f630d68fe1f87af690d451388bb186480afc719e5f770590c2ef6c"}, - {file = "scikit_learn-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:0402638c9a7c219ee52c94cbebc8fcb5eb9fe9c773717965c1f4185588ad3107"}, - {file = "scikit_learn-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a19f90f95ba93c1a7f7924906d0576a84da7f3b2282ac3bfb7a08a32801add93"}, - {file = "scikit_learn-1.3.2-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:b8692e395a03a60cd927125eef3a8e3424d86dde9b2370d544f0ea35f78a8073"}, - {file = "scikit_learn-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15e1e94cc23d04d39da797ee34236ce2375ddea158b10bee3c343647d615581d"}, - {file = "scikit_learn-1.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:785a2213086b7b1abf037aeadbbd6d67159feb3e30263434139c98425e3dcfcf"}, - {file = "scikit_learn-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:64381066f8aa63c2710e6b56edc9f0894cc7bf59bd71b8ce5613a4559b6145e0"}, - {file = "scikit_learn-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6c43290337f7a4b969d207e620658372ba3c1ffb611f8bc2b6f031dc5c6d1d03"}, - {file = "scikit_learn-1.3.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:dc9002fc200bed597d5d34e90c752b74df516d592db162f756cc52836b38fe0e"}, - {file = "scikit_learn-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d08ada33e955c54355d909b9c06a4789a729977f165b8bae6f225ff0a60ec4a"}, - {file = "scikit_learn-1.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763f0ae4b79b0ff9cca0bf3716bcc9915bdacff3cebea15ec79652d1cc4fa5c9"}, - {file = "scikit_learn-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:ed932ea780517b00dae7431e031faae6b49b20eb6950918eb83bd043237950e0"}, + {file = "scikit-learn-1.4.1.post1.tar.gz", hash = "sha256:93d3d496ff1965470f9977d05e5ec3376fb1e63b10e4fda5e39d23c2d8969a30"}, + {file = "scikit_learn-1.4.1.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c540aaf44729ab5cd4bd5e394f2b375e65ceaea9cdd8c195788e70433d91bbc5"}, + {file = "scikit_learn-1.4.1.post1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4310bff71aa98b45b46cd26fa641309deb73a5d1c0461d181587ad4f30ea3c36"}, + {file = "scikit_learn-1.4.1.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f43dd527dabff5521af2786a2f8de5ba381e182ec7292663508901cf6ceaf6e"}, + {file = "scikit_learn-1.4.1.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c02e27d65b0c7dc32f2c5eb601aaf5530b7a02bfbe92438188624524878336f2"}, + {file = "scikit_learn-1.4.1.post1-cp310-cp310-win_amd64.whl", hash = "sha256:629e09f772ad42f657ca60a1a52342eef786218dd20cf1369a3b8d085e55ef8f"}, + {file = "scikit_learn-1.4.1.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6145dfd9605b0b50ae72cdf72b61a2acd87501369a763b0d73d004710ebb76b5"}, + {file = "scikit_learn-1.4.1.post1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1afed6951bc9d2053c6ee9a518a466cbc9b07c6a3f9d43bfe734192b6125d508"}, + {file = "scikit_learn-1.4.1.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce03506ccf5f96b7e9030fea7eb148999b254c44c10182ac55857bc9b5d4815f"}, + {file = "scikit_learn-1.4.1.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ba516fcdc73d60e7f48cbb0bccb9acbdb21807de3651531208aac73c758e3ab"}, + {file = "scikit_learn-1.4.1.post1-cp311-cp311-win_amd64.whl", hash = "sha256:78cd27b4669513b50db4f683ef41ea35b5dddc797bd2bbd990d49897fd1c8a46"}, + {file = "scikit_learn-1.4.1.post1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a1e289f33f613cefe6707dead50db31930530dc386b6ccff176c786335a7b01c"}, + {file = "scikit_learn-1.4.1.post1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0df87de9ce1c0140f2818beef310fb2e2afdc1e66fc9ad587965577f17733649"}, + {file = "scikit_learn-1.4.1.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712c1c69c45b58ef21635360b3d0a680ff7d83ac95b6f9b82cf9294070cda710"}, + {file = "scikit_learn-1.4.1.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1754b0c2409d6ed5a3380512d0adcf182a01363c669033a2b55cca429ed86a81"}, + {file = "scikit_learn-1.4.1.post1-cp312-cp312-win_amd64.whl", hash = "sha256:1d491ef66e37f4e812db7e6c8286520c2c3fc61b34bf5e59b67b4ce528de93af"}, + {file = "scikit_learn-1.4.1.post1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aa0029b78ef59af22cfbd833e8ace8526e4df90212db7ceccbea582ebb5d6794"}, + {file = "scikit_learn-1.4.1.post1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:14e4c88436ac96bf69eb6d746ac76a574c314a23c6961b7d344b38877f20fee1"}, + {file = "scikit_learn-1.4.1.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7cd3a77c32879311f2aa93466d3c288c955ef71d191503cf0677c3340ae8ae0"}, + {file = "scikit_learn-1.4.1.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3ee19211ded1a52ee37b0a7b373a8bfc66f95353af058a210b692bd4cda0dd"}, + {file = "scikit_learn-1.4.1.post1-cp39-cp39-win_amd64.whl", hash = "sha256:234b6bda70fdcae9e4abbbe028582ce99c280458665a155eed0b820599377d25"}, ] [package.dependencies] -joblib = ">=1.1.1" -numpy = ">=1.17.3,<2.0" -scipy = ">=1.5.0" +joblib = ">=1.2.0" +numpy = ">=1.19.5,<2.0" +scipy = ">=1.6.0" threadpoolctl = ">=2.0.0" [package.extras] -benchmark = ["matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "pandas (>=1.0.5)"] -docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.10.1)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] -examples = ["matplotlib (>=3.1.3)", "pandas (>=1.0.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)"] -tests = ["black (>=23.3.0)", "matplotlib (>=3.1.3)", "mypy (>=1.3)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.0.272)", "scikit-image (>=0.16.2)"] +benchmark = ["matplotlib (>=3.3.4)", "memory-profiler (>=0.57.0)", "pandas (>=1.1.5)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.15.0)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] +examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] +tests = ["black (>=23.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.3)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.19.12)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.0.272)", "scikit-image (>=0.17.2)"] [[package]] name = "scipy" -version = "1.11.4" +version = "1.12.0" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710"}, - {file = "scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41"}, - {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4"}, - {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56"}, - {file = "scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446"}, - {file = "scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3"}, - {file = "scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be"}, - {file = "scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8"}, - {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c"}, - {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff"}, - {file = "scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993"}, - {file = "scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd"}, - {file = "scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6"}, - {file = "scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d"}, - {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4"}, - {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79"}, - {file = "scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660"}, - {file = "scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97"}, - {file = "scipy-1.11.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e619aba2df228a9b34718efb023966da781e89dd3d21637b27f2e54db0410d7"}, - {file = "scipy-1.11.4-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f3cd9e7b3c2c1ec26364856f9fbe78695fe631150f94cd1c22228456404cf1ec"}, - {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d10e45a6c50211fe256da61a11c34927c68f277e03138777bdebedd933712fea"}, - {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91af76a68eeae0064887a48e25c4e616fa519fa0d38602eda7e0f97d65d57937"}, - {file = "scipy-1.11.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6df1468153a31cf55ed5ed39647279beb9cfb5d3f84369453b49e4b8502394fd"}, - {file = "scipy-1.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee410e6de8f88fd5cf6eadd73c135020bfbbbdfcd0f6162c36a7638a1ea8cc65"}, - {file = "scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa"}, + {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, + {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, + {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, + {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c"}, + {file = "scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd"}, + {file = "scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2"}, + {file = "scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08"}, + {file = "scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c"}, + {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467"}, + {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a"}, + {file = "scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba"}, + {file = "scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70"}, + {file = "scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372"}, + {file = "scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3"}, + {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc"}, + {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c"}, + {file = "scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338"}, + {file = "scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c"}, + {file = "scipy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35"}, + {file = "scipy-1.12.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067"}, + {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371"}, + {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490"}, + {file = "scipy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc"}, + {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, + {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, ] [package.dependencies] -numpy = ">=1.21.6,<1.28.0" +numpy = ">=1.22.4,<1.29.0" [package.extras] dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "seaborn" -version = "0.13.1" +version = "0.13.2" description = "Statistical data visualization" optional = false python-versions = ">=3.8" files = [ - {file = "seaborn-0.13.1-py3-none-any.whl", hash = "sha256:6baa69b6d1169ae59037971491c450c0b73332b42bd4b23570b62a546bc61cb8"}, - {file = "seaborn-0.13.1.tar.gz", hash = "sha256:bfad65e9c5989e5e1897e61bdbd2f22e62455940ca76fd49eca3ed69345b9179"}, + {file = "seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987"}, + {file = "seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7"}, ] [package.dependencies] @@ -1101,19 +1096,19 @@ stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] [[package]] name = "setuptools" -version = "69.0.3" +version = "69.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, + {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, + {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -1128,13 +1123,13 @@ files = [ [[package]] name = "threadpoolctl" -version = "3.2.0" +version = "3.4.0" description = "threadpoolctl" optional = false python-versions = ">=3.8" files = [ - {file = "threadpoolctl-3.2.0-py3-none-any.whl", hash = "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032"}, - {file = "threadpoolctl-3.2.0.tar.gz", hash = "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355"}, + {file = "threadpoolctl-3.4.0-py3-none-any.whl", hash = "sha256:8f4c689a65b23e5ed825c8436a92b818aac005e0f3715f6a1664d7c7ee29d262"}, + {file = "threadpoolctl-3.4.0.tar.gz", hash = "sha256:f11b491a03661d6dd7ef692dd422ab34185d982466c49c8f98c8f716b5c93196"}, ] [[package]] @@ -1150,40 +1145,40 @@ files = [ [[package]] name = "tox" -version = "4.11.4" +version = "4.14.2" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.8" files = [ - {file = "tox-4.11.4-py3-none-any.whl", hash = "sha256:2adb83d68f27116812b69aa36676a8d6a52249cb0d173649de0e7d0c2e3e7229"}, - {file = "tox-4.11.4.tar.gz", hash = "sha256:73a7240778fabf305aeb05ab8ea26e575e042ab5a18d71d0ed13e343a51d6ce1"}, + {file = "tox-4.14.2-py3-none-any.whl", hash = "sha256:2900c4eb7b716af4a928a7fdc2ed248ad6575294ed7cfae2ea41203937422847"}, + {file = "tox-4.14.2.tar.gz", hash = "sha256:0defb44f6dafd911b61788325741cc6b2e12ea71f987ac025ad4d649f1f1a104"}, ] [package.dependencies] -cachetools = ">=5.3.1" +cachetools = ">=5.3.2" chardet = ">=5.2" colorama = ">=0.4.6" -filelock = ">=3.12.3" -packaging = ">=23.1" -platformdirs = ">=3.10" +filelock = ">=3.13.1" +packaging = ">=23.2" +platformdirs = ">=4.1" pluggy = ">=1.3" pyproject-api = ">=1.6.1" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} -virtualenv = ">=20.24.3" +virtualenv = ">=20.25" [package.extras] -docs = ["furo (>=2023.8.19)", "sphinx (>=7.2.4)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.24)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.1.1)", "devpi-process (>=1)", "diff-cover (>=7.7)", "distlib (>=0.3.7)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.18)", "psutil (>=5.9.5)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-xdist (>=3.3.1)", "re-assert (>=1.1)", "time-machine (>=2.12)", "wheel (>=0.41.2)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.25.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"] +testing = ["build[virtualenv] (>=1.0.3)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=8.0.2)", "distlib (>=0.3.8)", "flaky (>=3.7)", "hatch-vcs (>=0.4)", "hatchling (>=1.21)", "psutil (>=5.9.7)", "pytest (>=7.4.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-xdist (>=3.5)", "re-assert (>=1.1)", "time-machine (>=2.13)", "wheel (>=0.42)"] [[package]] name = "tqdm" -version = "4.66.1" +version = "4.66.2" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, - {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, + {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, + {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, ] [package.dependencies] @@ -1220,13 +1215,13 @@ tbb = ["tbb (>=2019.0)"] [[package]] name = "virtualenv" -version = "20.25.0" +version = "20.25.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, + {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, + {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, ] [package.dependencies] @@ -1240,18 +1235,18 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "zipp" -version = "3.17.0" +version = "3.18.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, + {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, + {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" From e3e29f3cd9030328acba2819f810d34da745077d Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 28 Mar 2024 15:05:58 -0400 Subject: [PATCH 003/103] rename file --- src/salamander/nmf_framework/klnmf.py | 4 ++-- src/salamander/nmf_framework/{nmf.py => standard_nmf.py} | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename src/salamander/nmf_framework/{nmf.py => standard_nmf.py} (97%) diff --git a/src/salamander/nmf_framework/klnmf.py b/src/salamander/nmf_framework/klnmf.py index 0ce4fc1..2f44f87 100644 --- a/src/salamander/nmf_framework/klnmf.py +++ b/src/salamander/nmf_framework/klnmf.py @@ -3,12 +3,12 @@ from ..utils import shape_checker, type_checker, value_checker from . import _utils_klnmf -from .nmf import NMF +from .standard_nmf import StandardNMF EPSILON = np.finfo(np.float32).eps -class KLNMF(NMF): +class KLNMF(StandardNMF): """ Decompose a mutation count matrix X into the product of a signature matrix W and an exposure matrix H by minimizing the weighted diff --git a/src/salamander/nmf_framework/nmf.py b/src/salamander/nmf_framework/standard_nmf.py similarity index 97% rename from src/salamander/nmf_framework/nmf.py rename to src/salamander/nmf_framework/standard_nmf.py index b0c3d4a..2f19274 100644 --- a/src/salamander/nmf_framework/nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -10,13 +10,13 @@ EPSILON = np.finfo(np.float32).eps -class NMF(SignatureNMF): +class StandardNMF(SignatureNMF): """ - The abstract class NMF unifies the structure of NMF algorithms + The abstract class StandardNMF unifies the structure of NMF algorithms with a single signature matrix W and exposure matrix H. - Examples of these algorithms include the standard NMF algorithm + Examples of these algorithms include the NMF algorithms from (Lee and Seung, 1999), minimum volume NMF (mvNMF) or NMF variants - with regularizations on the entries of W and H. + with regularizations on the entries of W or H. All of these NMF algorithms have the same parameters. Therefore, many properties of interest such as the signature correlation martrix or the sample embeddings are computed in the same manner. From ef79bb701cda84393a712a639a5e969a0253edbf Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 5 Apr 2024 01:39:50 -0400 Subject: [PATCH 004/103] update dependencies --- poetry.lock | 746 +++++++++++++++++++++++++++++++------------------ pyproject.toml | 6 +- 2 files changed, 484 insertions(+), 268 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5553451..973ceaf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,6 +15,51 @@ files = [ matplotlib = "*" numpy = "*" +[[package]] +name = "anndata" +version = "0.10.6" +description = "Annotated data." +optional = false +python-versions = ">=3.9" +files = [ + {file = "anndata-0.10.6-py3-none-any.whl", hash = "sha256:2c625149472fab4430f402208e7bf1efa6a7d45fa5c14827c8122305f4dca206"}, + {file = "anndata-0.10.6.tar.gz", hash = "sha256:16ada7ed7d38552ce56d8e8b2348e18a5fc81294ce8a7ea282acf30dc42cd6c2"}, +] + +[package.dependencies] +array-api-compat = ">1.4,<1.5 || >1.5" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +h5py = ">=3.1" +natsort = "*" +numpy = ">=1.23" +packaging = ">=20.0" +pandas = ">=1.4,<2.1.0rc0 || >2.1.0rc0,<2.1.2 || >2.1.2" +scipy = ">1.8" + +[package.extras] +dev = ["pytest-xdist", "setuptools-scm"] +doc = ["awkward (>=2.0.7)", "ipython", "myst-parser", "nbsphinx", "readthedocs-sphinx-search", "scanpydoc[theme,typehints] (>=0.13.4)", "sphinx (>=4.4)", "sphinx-autodoc-typehints (>=1.11.0)", "sphinx-book-theme (>=1.1.0)", "sphinx-copybutton", "sphinx-design (>=0.5.0)", "sphinx-issues", "sphinxext-opengraph", "zarr"] +gpu = ["cupy"] +test = ["awkward (>=2.3)", "boltons", "dask[array,distributed] (>=2022.09.2)", "httpx", "joblib", "loompy (>=3.0.5)", "matplotlib", "openpyxl", "pyarrow", "pytest (>=7.3)", "pytest-cov (>=2.10)", "pytest-memray", "pytest-mock", "scanpy", "scikit-learn", "zarr"] + +[[package]] +name = "array-api-compat" +version = "1.6" +description = "A wrapper around NumPy and other array libraries to make them compatible with the Array API standard" +optional = false +python-versions = "*" +files = [ + {file = "array_api_compat-1.6-py3-none-any.whl", hash = "sha256:0f132e698952bcebccfa941b4fc02cbc754802474b13a564208cf6cdbffb51e1"}, + {file = "array_api_compat-1.6.tar.gz", hash = "sha256:74a1e355cdabb9cf6c48922a5d07976d9230025d65731bef17174151b64876af"}, +] + +[package.extras] +cupy = ["cupy"] +dask = ["dask"] +jax = ["jax"] +numpy = ["numpy"] +pytorch = ["pytorch"] + [[package]] name = "cachetools" version = "5.3.3" @@ -61,64 +106,64 @@ files = [ [[package]] name = "contourpy" -version = "1.2.0" +version = "1.2.1" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.9" files = [ - {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, - {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, - {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, - {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, - {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, - {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, - {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, - {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, - {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, - {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, - {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, + {file = "contourpy-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040"}, + {file = "contourpy-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b"}, + {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd"}, + {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619"}, + {file = "contourpy-1.2.1-cp310-cp310-win32.whl", hash = "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8"}, + {file = "contourpy-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9"}, + {file = "contourpy-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5"}, + {file = "contourpy-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df"}, + {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205"}, + {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8"}, + {file = "contourpy-1.2.1-cp311-cp311-win32.whl", hash = "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec"}, + {file = "contourpy-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922"}, + {file = "contourpy-1.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc"}, + {file = "contourpy-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b"}, + {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce"}, + {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4"}, + {file = "contourpy-1.2.1-cp312-cp312-win32.whl", hash = "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f"}, + {file = "contourpy-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce"}, + {file = "contourpy-1.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b"}, + {file = "contourpy-1.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445"}, + {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02"}, + {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083"}, + {file = "contourpy-1.2.1-cp39-cp39-win32.whl", hash = "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba"}, + {file = "contourpy-1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f"}, + {file = "contourpy-1.2.1.tar.gz", hash = "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c"}, ] [package.dependencies] -numpy = ">=1.20,<2.0" +numpy = ">=1.20" [package.extras] bokeh = ["bokeh", "selenium"] docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.8.0)", "types-Pillow"] test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] @@ -303,6 +348,43 @@ ufo = ["fs (>=2.2.0,<3)"] unicode = ["unicodedata2 (>=15.1.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +[[package]] +name = "h5py" +version = "3.10.0" +description = "Read and write HDF5 files from Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "h5py-3.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b963fb772964fc1d1563c57e4e2e874022ce11f75ddc6df1a626f42bd49ab99f"}, + {file = "h5py-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:012ab448590e3c4f5a8dd0f3533255bc57f80629bf7c5054cf4c87b30085063c"}, + {file = "h5py-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:781a24263c1270a62cd67be59f293e62b76acfcc207afa6384961762bb88ea03"}, + {file = "h5py-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f42e6c30698b520f0295d70157c4e202a9e402406f50dc08f5a7bc416b24e52d"}, + {file = "h5py-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:93dd840bd675787fc0b016f7a05fc6efe37312a08849d9dd4053fd0377b1357f"}, + {file = "h5py-3.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2381e98af081b6df7f6db300cd88f88e740649d77736e4b53db522d8874bf2dc"}, + {file = "h5py-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:667fe23ab33d5a8a6b77970b229e14ae3bb84e4ea3382cc08567a02e1499eedd"}, + {file = "h5py-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90286b79abd085e4e65e07c1bd7ee65a0f15818ea107f44b175d2dfe1a4674b7"}, + {file = "h5py-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c013d2e79c00f28ffd0cc24e68665ea03ae9069e167087b2adb5727d2736a52"}, + {file = "h5py-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:92273ce69ae4983dadb898fd4d3bea5eb90820df953b401282ee69ad648df684"}, + {file = "h5py-3.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c97d03f87f215e7759a354460fb4b0d0f27001450b18b23e556e7856a0b21c3"}, + {file = "h5py-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86df4c2de68257b8539a18646ceccdcf2c1ce6b1768ada16c8dcfb489eafae20"}, + {file = "h5py-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba9ab36be991119a3ff32d0c7cbe5faf9b8d2375b5278b2aea64effbeba66039"}, + {file = "h5py-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c8e4fda19eb769e9a678592e67eaec3a2f069f7570c82d2da909c077aa94339"}, + {file = "h5py-3.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:492305a074327e8d2513011fa9fffeb54ecb28a04ca4c4227d7e1e9616d35641"}, + {file = "h5py-3.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9450464b458cca2c86252b624279115dcaa7260a40d3cb1594bf2b410a2bd1a3"}, + {file = "h5py-3.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd6f6d1384a9f491732cee233b99cd4bfd6e838a8815cc86722f9d2ee64032af"}, + {file = "h5py-3.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3074ec45d3dc6e178c6f96834cf8108bf4a60ccb5ab044e16909580352010a97"}, + {file = "h5py-3.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:212bb997a91e6a895ce5e2f365ba764debeaef5d2dca5c6fb7098d66607adf99"}, + {file = "h5py-3.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5dfc65ac21fa2f630323c92453cadbe8d4f504726ec42f6a56cf80c2f90d6c52"}, + {file = "h5py-3.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4682b94fd36ab217352be438abd44c8f357c5449b8995e63886b431d260f3d3"}, + {file = "h5py-3.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aece0e2e1ed2aab076c41802e50a0c3e5ef8816d60ece39107d68717d4559824"}, + {file = "h5py-3.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43a61b2c2ad65b1fabc28802d133eed34debcc2c8b420cb213d3d4ef4d3e2229"}, + {file = "h5py-3.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae2f0201c950059676455daf92700eeb57dcf5caaf71b9e1328e6e6593601770"}, + {file = "h5py-3.10.0.tar.gz", hash = "sha256:d93adc48ceeb33347eb24a634fb787efc7ae4644e6ea4ba733d099605045c049"}, +] + +[package.dependencies] +numpy = ">=1.17.3" + [[package]] name = "identify" version = "2.5.35" @@ -472,72 +554,69 @@ files = [ [[package]] name = "llvmlite" -version = "0.40.1" +version = "0.42.0" description = "lightweight wrapper around basic LLVM functionality" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "llvmlite-0.40.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84ce9b1c7a59936382ffde7871978cddcda14098e5a76d961e204523e5c372fb"}, - {file = "llvmlite-0.40.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3673c53cb21c65d2ff3704962b5958e967c6fc0bd0cff772998face199e8d87b"}, - {file = "llvmlite-0.40.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bba2747cf5b4954e945c287fe310b3fcc484e2a9d1b0c273e99eb17d103bb0e6"}, - {file = "llvmlite-0.40.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbd5e82cc990e5a3e343a3bf855c26fdfe3bfae55225f00efd01c05bbda79918"}, - {file = "llvmlite-0.40.1-cp310-cp310-win32.whl", hash = "sha256:09f83ea7a54509c285f905d968184bba00fc31ebf12f2b6b1494d677bb7dde9b"}, - {file = "llvmlite-0.40.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b37297f3cbd68d14a97223a30620589d98ad1890e5040c9e5fc181063f4ed49"}, - {file = "llvmlite-0.40.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a66a5bd580951751b4268f4c3bddcef92682814d6bc72f3cd3bb67f335dd7097"}, - {file = "llvmlite-0.40.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:467b43836b388eaedc5a106d76761e388dbc4674b2f2237bc477c6895b15a634"}, - {file = "llvmlite-0.40.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c23edd196bd797dc3a7860799054ea3488d2824ecabc03f9135110c2e39fcbc"}, - {file = "llvmlite-0.40.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a36d9f244b6680cb90bbca66b146dabb2972f4180c64415c96f7c8a2d8b60a36"}, - {file = "llvmlite-0.40.1-cp311-cp311-win_amd64.whl", hash = "sha256:5b3076dc4e9c107d16dc15ecb7f2faf94f7736cd2d5e9f4dc06287fd672452c1"}, - {file = "llvmlite-0.40.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a7525db121f2e699809b539b5308228854ccab6693ecb01b52c44a2f5647e20"}, - {file = "llvmlite-0.40.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:84747289775d0874e506f907a4513db889471607db19b04de97d144047fec885"}, - {file = "llvmlite-0.40.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e35766e42acef0fe7d1c43169a8ffc327a47808fae6a067b049fe0e9bbf84dd5"}, - {file = "llvmlite-0.40.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cda71de10a1f48416309e408ea83dab5bf36058f83e13b86a2961defed265568"}, - {file = "llvmlite-0.40.1-cp38-cp38-win32.whl", hash = "sha256:96707ebad8b051bbb4fc40c65ef93b7eeee16643bd4d579a14d11578e4b7a647"}, - {file = "llvmlite-0.40.1-cp38-cp38-win_amd64.whl", hash = "sha256:e44f854dc11559795bcdeaf12303759e56213d42dabbf91a5897aa2d8b033810"}, - {file = "llvmlite-0.40.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f643d15aacd0b0b0dc8b74b693822ba3f9a53fa63bc6a178c2dba7cc88f42144"}, - {file = "llvmlite-0.40.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39a0b4d0088c01a469a5860d2e2d7a9b4e6a93c0f07eb26e71a9a872a8cadf8d"}, - {file = "llvmlite-0.40.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9329b930d699699846623054121ed105fd0823ed2180906d3b3235d361645490"}, - {file = "llvmlite-0.40.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2dbbb8424037ca287983b115a29adf37d806baf7e1bf4a67bd2cffb74e085ed"}, - {file = "llvmlite-0.40.1-cp39-cp39-win32.whl", hash = "sha256:e74e7bec3235a1e1c9ad97d897a620c5007d0ed80c32c84c1d787e7daa17e4ec"}, - {file = "llvmlite-0.40.1-cp39-cp39-win_amd64.whl", hash = "sha256:ff8f31111bb99d135ff296757dc81ab36c2dee54ed4bd429158a96da9807c316"}, - {file = "llvmlite-0.40.1.tar.gz", hash = "sha256:5cdb0d45df602099d833d50bd9e81353a5e036242d3c003c5b294fc61d1986b4"}, + {file = "llvmlite-0.42.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3366938e1bf63d26c34fbfb4c8e8d2ded57d11e0567d5bb243d89aab1eb56098"}, + {file = "llvmlite-0.42.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c35da49666a21185d21b551fc3caf46a935d54d66969d32d72af109b5e7d2b6f"}, + {file = "llvmlite-0.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70f44ccc3c6220bd23e0ba698a63ec2a7d3205da0d848804807f37fc243e3f77"}, + {file = "llvmlite-0.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763f8d8717a9073b9e0246998de89929071d15b47f254c10eef2310b9aac033d"}, + {file = "llvmlite-0.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:8d90edf400b4ceb3a0e776b6c6e4656d05c7187c439587e06f86afceb66d2be5"}, + {file = "llvmlite-0.42.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ae511caed28beaf1252dbaf5f40e663f533b79ceb408c874c01754cafabb9cbf"}, + {file = "llvmlite-0.42.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81e674c2fe85576e6c4474e8c7e7aba7901ac0196e864fe7985492b737dbab65"}, + {file = "llvmlite-0.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb3975787f13eb97629052edb5017f6c170eebc1c14a0433e8089e5db43bcce6"}, + {file = "llvmlite-0.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5bece0cdf77f22379f19b1959ccd7aee518afa4afbd3656c6365865f84903f9"}, + {file = "llvmlite-0.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e0c4c11c8c2aa9b0701f91b799cb9134a6a6de51444eff5a9087fc7c1384275"}, + {file = "llvmlite-0.42.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:08fa9ab02b0d0179c688a4216b8939138266519aaa0aa94f1195a8542faedb56"}, + {file = "llvmlite-0.42.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b2fce7d355068494d1e42202c7aff25d50c462584233013eb4470c33b995e3ee"}, + {file = "llvmlite-0.42.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebe66a86dc44634b59a3bc860c7b20d26d9aaffcd30364ebe8ba79161a9121f4"}, + {file = "llvmlite-0.42.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d47494552559e00d81bfb836cf1c4d5a5062e54102cc5767d5aa1e77ccd2505c"}, + {file = "llvmlite-0.42.0-cp312-cp312-win_amd64.whl", hash = "sha256:05cb7e9b6ce69165ce4d1b994fbdedca0c62492e537b0cc86141b6e2c78d5888"}, + {file = "llvmlite-0.42.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bdd3888544538a94d7ec99e7c62a0cdd8833609c85f0c23fcb6c5c591aec60ad"}, + {file = "llvmlite-0.42.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0936c2067a67fb8816c908d5457d63eba3e2b17e515c5fe00e5ee2bace06040"}, + {file = "llvmlite-0.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a78ab89f1924fc11482209f6799a7a3fc74ddc80425a7a3e0e8174af0e9e2301"}, + {file = "llvmlite-0.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7599b65c7af7abbc978dbf345712c60fd596aa5670496561cc10e8a71cebfb2"}, + {file = "llvmlite-0.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:43d65cc4e206c2e902c1004dd5418417c4efa6c1d04df05c6c5675a27e8ca90e"}, + {file = "llvmlite-0.42.0.tar.gz", hash = "sha256:f92b09243c0cc3f457da8b983f67bd8e1295d0f5b3746c7a1861d7a99403854a"}, ] [[package]] name = "matplotlib" -version = "3.8.3" +version = "3.8.4" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.8.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cf60138ccc8004f117ab2a2bad513cc4d122e55864b4fe7adf4db20ca68a078f"}, - {file = "matplotlib-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f557156f7116be3340cdeef7f128fa99b0d5d287d5f41a16e169819dcf22357"}, - {file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f386cf162b059809ecfac3bcc491a9ea17da69fa35c8ded8ad154cd4b933d5ec"}, - {file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c5f96f57b0369c288bf6f9b5274ba45787f7e0589a34d24bdbaf6d3344632f"}, - {file = "matplotlib-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:83e0f72e2c116ca7e571c57aa29b0fe697d4c6425c4e87c6e994159e0c008635"}, - {file = "matplotlib-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:1c5c8290074ba31a41db1dc332dc2b62def469ff33766cbe325d32a3ee291aea"}, - {file = "matplotlib-3.8.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5184e07c7e1d6d1481862ee361905b7059f7fe065fc837f7c3dc11eeb3f2f900"}, - {file = "matplotlib-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d7e7e0993d0758933b1a241a432b42c2db22dfa37d4108342ab4afb9557cbe3e"}, - {file = "matplotlib-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b36ad07eac9740fc76c2aa16edf94e50b297d6eb4c081e3add863de4bb19a7"}, - {file = "matplotlib-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c42dae72a62f14982f1474f7e5c9959fc4bc70c9de11cc5244c6e766200ba65"}, - {file = "matplotlib-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf5932eee0d428192c40b7eac1399d608f5d995f975cdb9d1e6b48539a5ad8d0"}, - {file = "matplotlib-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:40321634e3a05ed02abf7c7b47a50be50b53ef3eaa3a573847431a545585b407"}, - {file = "matplotlib-3.8.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:09074f8057917d17ab52c242fdf4916f30e99959c1908958b1fc6032e2d0f6d4"}, - {file = "matplotlib-3.8.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5745f6d0fb5acfabbb2790318db03809a253096e98c91b9a31969df28ee604aa"}, - {file = "matplotlib-3.8.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97653d869a71721b639714b42d87cda4cfee0ee74b47c569e4874c7590c55c5"}, - {file = "matplotlib-3.8.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:242489efdb75b690c9c2e70bb5c6550727058c8a614e4c7716f363c27e10bba1"}, - {file = "matplotlib-3.8.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:83c0653c64b73926730bd9ea14aa0f50f202ba187c307a881673bad4985967b7"}, - {file = "matplotlib-3.8.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef6c1025a570354297d6c15f7d0f296d95f88bd3850066b7f1e7b4f2f4c13a39"}, - {file = "matplotlib-3.8.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c4af3f7317f8a1009bbb2d0bf23dfaba859eb7dd4ccbd604eba146dccaaaf0a4"}, - {file = "matplotlib-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c6e00a65d017d26009bac6808f637b75ceade3e1ff91a138576f6b3065eeeba"}, - {file = "matplotlib-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7b49ab49a3bea17802df6872f8d44f664ba8f9be0632a60c99b20b6db2165b7"}, - {file = "matplotlib-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6728dde0a3997396b053602dbd907a9bd64ec7d5cf99e728b404083698d3ca01"}, - {file = "matplotlib-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:813925d08fb86aba139f2d31864928d67511f64e5945ca909ad5bc09a96189bb"}, - {file = "matplotlib-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:cd3a0c2be76f4e7be03d34a14d49ded6acf22ef61f88da600a18a5cd8b3c5f3c"}, - {file = "matplotlib-3.8.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fa93695d5c08544f4a0dfd0965f378e7afc410d8672816aff1e81be1f45dbf2e"}, - {file = "matplotlib-3.8.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9764df0e8778f06414b9d281a75235c1e85071f64bb5d71564b97c1306a2afc"}, - {file = "matplotlib-3.8.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5e431a09e6fab4012b01fc155db0ce6dccacdbabe8198197f523a4ef4805eb26"}, - {file = "matplotlib-3.8.3.tar.gz", hash = "sha256:7b416239e9ae38be54b028abbf9048aff5054a9aba5416bef0bd17f9162ce161"}, + {file = "matplotlib-3.8.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:abc9d838f93583650c35eca41cfcec65b2e7cb50fd486da6f0c49b5e1ed23014"}, + {file = "matplotlib-3.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f65c9f002d281a6e904976007b2d46a1ee2bcea3a68a8c12dda24709ddc9106"}, + {file = "matplotlib-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce1edd9f5383b504dbc26eeea404ed0a00656c526638129028b758fd43fc5f10"}, + {file = "matplotlib-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd79298550cba13a43c340581a3ec9c707bd895a6a061a78fa2524660482fc0"}, + {file = "matplotlib-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:90df07db7b599fe7035d2f74ab7e438b656528c68ba6bb59b7dc46af39ee48ef"}, + {file = "matplotlib-3.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:ac24233e8f2939ac4fd2919eed1e9c0871eac8057666070e94cbf0b33dd9c338"}, + {file = "matplotlib-3.8.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:72f9322712e4562e792b2961971891b9fbbb0e525011e09ea0d1f416c4645661"}, + {file = "matplotlib-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:232ce322bfd020a434caaffbd9a95333f7c2491e59cfc014041d95e38ab90d1c"}, + {file = "matplotlib-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6addbd5b488aedb7f9bc19f91cd87ea476206f45d7116fcfe3d31416702a82fa"}, + {file = "matplotlib-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc4ccdc64e3039fc303defd119658148f2349239871db72cd74e2eeaa9b80b71"}, + {file = "matplotlib-3.8.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b7a2a253d3b36d90c8993b4620183b55665a429da8357a4f621e78cd48b2b30b"}, + {file = "matplotlib-3.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:8080d5081a86e690d7688ffa542532e87f224c38a6ed71f8fbed34dd1d9fedae"}, + {file = "matplotlib-3.8.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6485ac1f2e84676cff22e693eaa4fbed50ef5dc37173ce1f023daef4687df616"}, + {file = "matplotlib-3.8.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c89ee9314ef48c72fe92ce55c4e95f2f39d70208f9f1d9db4e64079420d8d732"}, + {file = "matplotlib-3.8.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50bac6e4d77e4262c4340d7a985c30912054745ec99756ce213bfbc3cb3808eb"}, + {file = "matplotlib-3.8.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f51c4c869d4b60d769f7b4406eec39596648d9d70246428745a681c327a8ad30"}, + {file = "matplotlib-3.8.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b12ba985837e4899b762b81f5b2845bd1a28f4fdd1a126d9ace64e9c4eb2fb25"}, + {file = "matplotlib-3.8.4-cp312-cp312-win_amd64.whl", hash = "sha256:7a6769f58ce51791b4cb8b4d7642489df347697cd3e23d88266aaaee93b41d9a"}, + {file = "matplotlib-3.8.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:843cbde2f0946dadd8c5c11c6d91847abd18ec76859dc319362a0964493f0ba6"}, + {file = "matplotlib-3.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c13f041a7178f9780fb61cc3a2b10423d5e125480e4be51beaf62b172413b67"}, + {file = "matplotlib-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb44f53af0a62dc80bba4443d9b27f2fde6acfdac281d95bc872dc148a6509cc"}, + {file = "matplotlib-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:606e3b90897554c989b1e38a258c626d46c873523de432b1462f295db13de6f9"}, + {file = "matplotlib-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9bb0189011785ea794ee827b68777db3ca3f93f3e339ea4d920315a0e5a78d54"}, + {file = "matplotlib-3.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:6209e5c9aaccc056e63b547a8152661324404dd92340a6e479b3a7f24b42a5d0"}, + {file = "matplotlib-3.8.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c7064120a59ce6f64103c9cefba8ffe6fba87f2c61d67c401186423c9a20fd35"}, + {file = "matplotlib-3.8.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0e47eda4eb2614300fc7bb4657fced3e83d6334d03da2173b09e447418d499f"}, + {file = "matplotlib-3.8.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:493e9f6aa5819156b58fce42b296ea31969f2aab71c5b680b4ea7a3cb5c07d94"}, + {file = "matplotlib-3.8.4.tar.gz", hash = "sha256:8aac397d5e9ec158960e31c381c5ffc52ddd52bd9a47717e2a694038167dffea"}, ] [package.dependencies] @@ -546,12 +625,85 @@ cycler = ">=0.10" fonttools = ">=4.22.0" importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} kiwisolver = ">=1.3.1" -numpy = ">=1.21,<2" +numpy = ">=1.21" packaging = ">=20.0" pillow = ">=8" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" +[[package]] +name = "mypy" +version = "1.9.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, + {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, + {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, + {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, + {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, + {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, + {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, + {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, + {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, + {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, + {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, + {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, + {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, + {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, + {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, + {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, + {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, + {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "natsort" +version = "8.4.0" +description = "Simple yet flexible natural sorting in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c"}, + {file = "natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581"}, +] + +[package.extras] +fast = ["fastnumbers (>=2.0.0)"] +icu = ["PyICU (>=1.0.0)"] + [[package]] name = "nodeenv" version = "1.8.0" @@ -568,76 +720,81 @@ setuptools = "*" [[package]] name = "numba" -version = "0.57.1" +version = "0.59.1" description = "compiling Python code using LLVM" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "numba-0.57.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db8268eb5093cae2288942a8cbd69c9352f6fe6e0bfa0a9a27679436f92e4248"}, - {file = "numba-0.57.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:643cb09a9ba9e1bd8b060e910aeca455e9442361e80fce97690795ff9840e681"}, - {file = "numba-0.57.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e9fab973d9e82c9f8449f75994a898daaaf821d84f06fbb0b9de2293dd9306"}, - {file = "numba-0.57.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c0602e4f896e6a6d844517c3ab434bc978e7698a22a733cc8124465898c28fa8"}, - {file = "numba-0.57.1-cp310-cp310-win32.whl", hash = "sha256:3d6483c27520d16cf5d122868b79cad79e48056ecb721b52d70c126bed65431e"}, - {file = "numba-0.57.1-cp310-cp310-win_amd64.whl", hash = "sha256:a32ee263649aa3c3587b833d6311305379529570e6c20deb0c6f4fb5bc7020db"}, - {file = "numba-0.57.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c078f84b5529a7fdb8413bb33d5100f11ec7b44aa705857d9eb4e54a54ff505"}, - {file = "numba-0.57.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e447c4634d1cc99ab50d4faa68f680f1d88b06a2a05acf134aa6fcc0342adeca"}, - {file = "numba-0.57.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4838edef2df5f056cb8974670f3d66562e751040c448eb0b67c7e2fec1726649"}, - {file = "numba-0.57.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b17fbe4a69dcd9a7cd49916b6463cd9a82af5f84911feeb40793b8bce00dfa7"}, - {file = "numba-0.57.1-cp311-cp311-win_amd64.whl", hash = "sha256:93df62304ada9b351818ba19b1cfbddaf72cd89348e81474326ca0b23bf0bae1"}, - {file = "numba-0.57.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8e00ca63c5d0ad2beeb78d77f087b3a88c45ea9b97e7622ab2ec411a868420ee"}, - {file = "numba-0.57.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ff66d5b022af6c7d81ddbefa87768e78ed4f834ab2da6ca2fd0d60a9e69b94f5"}, - {file = "numba-0.57.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:60ec56386076e9eed106a87c96626d5686fbb16293b9834f0849cf78c9491779"}, - {file = "numba-0.57.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c057ccedca95df23802b6ccad86bb318be624af45b5a38bb8412882be57a681"}, - {file = "numba-0.57.1-cp38-cp38-win32.whl", hash = "sha256:5a82bf37444039c732485c072fda21a361790ed990f88db57fd6941cd5e5d307"}, - {file = "numba-0.57.1-cp38-cp38-win_amd64.whl", hash = "sha256:9bcc36478773ce838f38afd9a4dfafc328d4ffb1915381353d657da7f6473282"}, - {file = "numba-0.57.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae50c8c90c2ce8057f9618b589223e13faa8cbc037d8f15b4aad95a2c33a0582"}, - {file = "numba-0.57.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9a1b2b69448e510d672ff9a6b18d2db9355241d93c6a77677baa14bec67dc2a0"}, - {file = "numba-0.57.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3cf78d74ad9d289fbc1e5b1c9f2680fca7a788311eb620581893ab347ec37a7e"}, - {file = "numba-0.57.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f47dd214adc5dcd040fe9ad2adbd2192133c9075d2189ce1b3d5f9d72863ef05"}, - {file = "numba-0.57.1-cp39-cp39-win32.whl", hash = "sha256:a3eac19529956185677acb7f01864919761bfffbb9ae04bbbe5e84bbc06cfc2b"}, - {file = "numba-0.57.1-cp39-cp39-win_amd64.whl", hash = "sha256:9587ba1bf5f3035575e45562ada17737535c6d612df751e811d702693a72d95e"}, - {file = "numba-0.57.1.tar.gz", hash = "sha256:33c0500170d213e66d90558ad6aca57d3e03e97bb11da82e6d87ab793648cb17"}, + {file = "numba-0.59.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97385a7f12212c4f4bc28f648720a92514bee79d7063e40ef66c2d30600fd18e"}, + {file = "numba-0.59.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0b77aecf52040de2a1eb1d7e314497b9e56fba17466c80b457b971a25bb1576d"}, + {file = "numba-0.59.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3476a4f641bfd58f35ead42f4dcaf5f132569c4647c6f1360ccf18ee4cda3990"}, + {file = "numba-0.59.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:525ef3f820931bdae95ee5379c670d5c97289c6520726bc6937a4a7d4230ba24"}, + {file = "numba-0.59.1-cp310-cp310-win_amd64.whl", hash = "sha256:990e395e44d192a12105eca3083b61307db7da10e093972ca285c85bef0963d6"}, + {file = "numba-0.59.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43727e7ad20b3ec23ee4fc642f5b61845c71f75dd2825b3c234390c6d8d64051"}, + {file = "numba-0.59.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:411df625372c77959570050e861981e9d196cc1da9aa62c3d6a836b5cc338966"}, + {file = "numba-0.59.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2801003caa263d1e8497fb84829a7ecfb61738a95f62bc05693fcf1733e978e4"}, + {file = "numba-0.59.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dd2842fac03be4e5324ebbbd4d2d0c8c0fc6e0df75c09477dd45b288a0777389"}, + {file = "numba-0.59.1-cp311-cp311-win_amd64.whl", hash = "sha256:0594b3dfb369fada1f8bb2e3045cd6c61a564c62e50cf1f86b4666bc721b3450"}, + {file = "numba-0.59.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1cce206a3b92836cdf26ef39d3a3242fec25e07f020cc4feec4c4a865e340569"}, + {file = "numba-0.59.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8c8b4477763cb1fbd86a3be7050500229417bf60867c93e131fd2626edb02238"}, + {file = "numba-0.59.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d80bce4ef7e65bf895c29e3889ca75a29ee01da80266a01d34815918e365835"}, + {file = "numba-0.59.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7ad1d217773e89a9845886401eaaab0a156a90aa2f179fdc125261fd1105096"}, + {file = "numba-0.59.1-cp312-cp312-win_amd64.whl", hash = "sha256:5bf68f4d69dd3a9f26a9b23548fa23e3bcb9042e2935257b471d2a8d3c424b7f"}, + {file = "numba-0.59.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4e0318ae729de6e5dbe64c75ead1a95eb01fabfe0e2ebed81ebf0344d32db0ae"}, + {file = "numba-0.59.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0f68589740a8c38bb7dc1b938b55d1145244c8353078eea23895d4f82c8b9ec1"}, + {file = "numba-0.59.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:649913a3758891c77c32e2d2a3bcbedf4a69f5fea276d11f9119677c45a422e8"}, + {file = "numba-0.59.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9712808e4545270291d76b9a264839ac878c5eb7d8b6e02c970dc0ac29bc8187"}, + {file = "numba-0.59.1-cp39-cp39-win_amd64.whl", hash = "sha256:8d51ccd7008a83105ad6a0082b6a2b70f1142dc7cfd76deb8c5a862367eb8c86"}, + {file = "numba-0.59.1.tar.gz", hash = "sha256:76f69132b96028d2774ed20415e8c528a34e3299a40581bae178f0994a2f370b"}, ] [package.dependencies] -llvmlite = "==0.40.*" -numpy = ">=1.21,<1.25" +llvmlite = "==0.42.*" +numpy = ">=1.22,<1.27" [[package]] name = "numpy" -version = "1.24.4" +version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, - {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, - {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, - {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, - {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, - {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, - {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, - {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, - {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, - {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, - {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] [[package]] @@ -699,81 +856,97 @@ pytz = ">=2020.1" [package.extras] test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] +[[package]] +name = "pandas-stubs" +version = "2.2.1.240316" +description = "Type annotations for pandas" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas_stubs-2.2.1.240316-py3-none-any.whl", hash = "sha256:0126a26451a37cb893ea62357ca87ba3d181bd999ec8ba2ca5602e20207d6682"}, + {file = "pandas_stubs-2.2.1.240316.tar.gz", hash = "sha256:236a4f812fb6b1922e9607ff09e427f6d8540c421c9e5a40e3e4ddf7adac7f05"}, +] + +[package.dependencies] +numpy = {version = ">=1.26.0", markers = "python_version < \"3.13\""} +types-pytz = ">=2022.1.1" + [[package]] name = "pillow" -version = "10.2.0" +version = "10.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, - {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, - {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, - {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, - {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, - {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, - {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, - {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, - {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, - {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, - {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, - {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, - {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, - {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, - {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, - {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, - {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, - {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, + {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, + {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"}, + {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"}, + {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"}, + {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"}, + {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"}, + {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"}, + {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"}, + {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"}, + {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"}, + {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"}, + {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"}, + {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"}, + {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"}, + {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"}, + {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"}, + {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"}, + {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"}, + {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"}, + {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"}, + {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"}, + {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"}, + {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"}, + {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"}, + {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"}, + {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"}, + {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"}, + {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"}, + {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"}, + {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"}, + {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"}, + {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"}, + {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"}, + {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"}, + {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"}, ] [package.extras] @@ -834,13 +1007,13 @@ virtualenv = ">=20.10.0" [[package]] name = "pynndescent" -version = "0.5.11" +version = "0.5.12" description = "Nearest Neighbor Descent" optional = false python-versions = "*" files = [ - {file = "pynndescent-0.5.11-py3-none-any.whl", hash = "sha256:a628f4fc8a67757c8fa15613449ac513fd056258a55b4084e47c06640ec90a8d"}, - {file = "pynndescent-0.5.11.tar.gz", hash = "sha256:6f44ced9d5a9da2c87d9b2fff30bb5308540c0657605e4d5cde7ed3275bbad50"}, + {file = "pynndescent-0.5.12-py3-none-any.whl", hash = "sha256:9023dc5fea520a4e84d0633ae735db97d2509da927bfa86c897e61f3315473c7"}, + {file = "pynndescent-0.5.12.tar.gz", hash = "sha256:0736291fcbbedfd5e0a3a280f71a63f8eb2f8bd9670d4c0b51ac1b4d081adf70"}, ] [package.dependencies] @@ -1033,45 +1206,45 @@ tests = ["black (>=23.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.3)", "numpydoc ( [[package]] name = "scipy" -version = "1.12.0" +version = "1.13.0" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, - {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, - {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, - {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c"}, - {file = "scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd"}, - {file = "scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2"}, - {file = "scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08"}, - {file = "scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c"}, - {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467"}, - {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a"}, - {file = "scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba"}, - {file = "scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70"}, - {file = "scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372"}, - {file = "scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3"}, - {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc"}, - {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c"}, - {file = "scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338"}, - {file = "scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c"}, - {file = "scipy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35"}, - {file = "scipy-1.12.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067"}, - {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371"}, - {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490"}, - {file = "scipy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc"}, - {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, - {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, + {file = "scipy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba419578ab343a4e0a77c0ef82f088238a93eef141b2b8017e46149776dfad4d"}, + {file = "scipy-1.13.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:22789b56a999265431c417d462e5b7f2b487e831ca7bef5edeb56efe4c93f86e"}, + {file = "scipy-1.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f1432ba070e90d42d7fd836462c50bf98bd08bed0aa616c359eed8a04e3922"}, + {file = "scipy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8434f6f3fa49f631fae84afee424e2483289dfc30a47755b4b4e6b07b2633a4"}, + {file = "scipy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dcbb9ea49b0167de4167c40eeee6e167caeef11effb0670b554d10b1e693a8b9"}, + {file = "scipy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:1d2f7bb14c178f8b13ebae93f67e42b0a6b0fc50eba1cd8021c9b6e08e8fb1cd"}, + {file = "scipy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fbcf8abaf5aa2dc8d6400566c1a727aed338b5fe880cde64907596a89d576fa"}, + {file = "scipy-1.13.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5e4a756355522eb60fcd61f8372ac2549073c8788f6114449b37e9e8104f15a5"}, + {file = "scipy-1.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5acd8e1dbd8dbe38d0004b1497019b2dbbc3d70691e65d69615f8a7292865d7"}, + {file = "scipy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ff7dad5d24a8045d836671e082a490848e8639cabb3dbdacb29f943a678683d"}, + {file = "scipy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4dca18c3ffee287ddd3bc8f1dabaf45f5305c5afc9f8ab9cbfab855e70b2df5c"}, + {file = "scipy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a2f471de4d01200718b2b8927f7d76b5d9bde18047ea0fa8bd15c5ba3f26a1d6"}, + {file = "scipy-1.13.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0de696f589681c2802f9090fff730c218f7c51ff49bf252b6a97ec4a5d19e8b"}, + {file = "scipy-1.13.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b2a3ff461ec4756b7e8e42e1c681077349a038f0686132d623fa404c0bee2551"}, + {file = "scipy-1.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf9fe63e7a4bf01d3645b13ff2aa6dea023d38993f42aaac81a18b1bda7a82a"}, + {file = "scipy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e7626dfd91cdea5714f343ce1176b6c4745155d234f1033584154f60ef1ff42"}, + {file = "scipy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:109d391d720fcebf2fbe008621952b08e52907cf4c8c7efc7376822151820820"}, + {file = "scipy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8930ae3ea371d6b91c203b1032b9600d69c568e537b7988a3073dfe4d4774f21"}, + {file = "scipy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5407708195cb38d70fd2d6bb04b1b9dd5c92297d86e9f9daae1576bd9e06f602"}, + {file = "scipy-1.13.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:ac38c4c92951ac0f729c4c48c9e13eb3675d9986cc0c83943784d7390d540c78"}, + {file = "scipy-1.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c74543c4fbeb67af6ce457f6a6a28e5d3739a87f62412e4a16e46f164f0ae5"}, + {file = "scipy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28e286bf9ac422d6beb559bc61312c348ca9b0f0dae0d7c5afde7f722d6ea13d"}, + {file = "scipy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33fde20efc380bd23a78a4d26d59fc8704e9b5fd9b08841693eb46716ba13d86"}, + {file = "scipy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:45c08bec71d3546d606989ba6e7daa6f0992918171e2a6f7fbedfa7361c2de1e"}, + {file = "scipy-1.13.0.tar.gz", hash = "sha256:58569af537ea29d3f78e5abd18398459f195546bb3be23d16677fb26616cc11e"}, ] [package.dependencies] -numpy = ">=1.22.4,<1.29.0" +numpy = ">=1.22.4,<2.3" [package.extras] -dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "seaborn" @@ -1190,14 +1363,53 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "types-pytz" +version = "2024.1.0.20240203" +description = "Typing stubs for pytz" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-pytz-2024.1.0.20240203.tar.gz", hash = "sha256:c93751ee20dfc6e054a0148f8f5227b9a00b79c90a4d3c9f464711a73179c89e"}, + {file = "types_pytz-2024.1.0.20240203-py3-none-any.whl", hash = "sha256:9679eef0365db3af91ef7722c199dbb75ee5c1b67e3c4dd7bfbeb1b8a71c21a3"}, +] + +[[package]] +name = "types-seaborn" +version = "0.13.2.20240311" +description = "Typing stubs for seaborn" +optional = false +python-versions = ">=3.9" +files = [ + {file = "types-seaborn-0.13.2.20240311.tar.gz", hash = "sha256:75b6d94214b8e94591c58e09581f65f46a673c0ee2dab162a76205c46c95057c"}, + {file = "types_seaborn-0.13.2.20240311-py3-none-any.whl", hash = "sha256:96879a786dafa026aa376854d498dd20c345bd8764de8a2a1590b7b423c0a69d"}, +] + +[package.dependencies] +matplotlib = ">=3.8" +numpy = ">=1.20" +pandas-stubs = "*" + +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + [[package]] name = "umap-learn" -version = "0.5.5" +version = "0.5.6" description = "Uniform Manifold Approximation and Projection" optional = false python-versions = "*" files = [ - {file = "umap-learn-0.5.5.tar.gz", hash = "sha256:c54d607364413eade968b73ba07c8b3ea14412817f53cd07b6f720ac957293c4"}, + {file = "umap-learn-0.5.6.tar.gz", hash = "sha256:5b3917a862c23ba0fc83bfcd67a7b719dec85b3d9c01fdc7d894cce455df4e03"}, + {file = "umap_learn-0.5.6-py3-none-any.whl", hash = "sha256:881cc0c2ee845b790bf0455aa1664f9f68b838d9d0fe12a1291b85c5a559c913"}, ] [package.dependencies] @@ -1209,7 +1421,7 @@ scipy = ">=1.3.1" tqdm = "*" [package.extras] -parametric-umap = ["tensorflow (>=2.1)", "tensorflow-probability (>=0.10)"] +parametric-umap = ["tensorflow (>=2.1)"] plot = ["bokeh", "colorcet", "datashader", "holoviews", "matplotlib", "pandas", "scikit-image", "seaborn"] tbb = ["tbb (>=2019.0)"] @@ -1251,4 +1463,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "eb0d4457a0050208acf5820c1af209916ebb95a3fbdb316fb31fbb8d82dcb831" +content-hash = "6a4bed5f74ce07f45874658f9bf8b4399500a88af934e34c4323de900d0e3074" diff --git a/pyproject.toml b/pyproject.toml index 1872680..0eb0549 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,9 +15,10 @@ readme = "README.md" [tool.poetry.dependencies] python = ">=3.9,<3.12" adjustText = "^0.8" +anndata = "^0.10.5" fastcluster = "^1.2.6" matplotlib = "^3.7.1" -numba = "^0.57" +numba = "^0.59" numpy = "^1.24.3" pandas = "^1.5.3" scikit-learn = "^1.3.0" @@ -26,9 +27,12 @@ seaborn = "^0.13.0" umap-learn = "^0.5.4" [tool.poetry.group.dev.dependencies] +mypy = "^1.9" +pandas-stubs = "^2.2.1" pytest = "^7.4.2" pre-commit = "^3.4.0" tox = "^4.11.3" +types-seaborn = "^0.13.2" [tool.pytest.ini_options] # /site-packages/umap/__init__.py:36: DeprecationWarning: pkg_resources is deprecated as an API. From c7673d8d74cb032cf682d194bf68b5f9f7c4249b Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 5 Apr 2024 01:43:40 -0400 Subject: [PATCH 005/103] refactor plotting module The plotting module is now implemented around AnnData objects. Computation and plotting is separated into the tools and plot modules. One major change is that samples now correspond to rows instead of columns of the mutation count matrix. --- src/salamander/plot.py | 725 +++++++++++++++++++++------------------- src/salamander/tools.py | 141 ++++++++ src/salamander/utils.py | 47 ++- 3 files changed, 560 insertions(+), 353 deletions(-) create mode 100644 src/salamander/tools.py diff --git a/src/salamander/plot.py b/src/salamander/plot.py index b23c994..5627513 100644 --- a/src/salamander/plot.py +++ b/src/salamander/plot.py @@ -1,20 +1,27 @@ +from __future__ import annotations + import warnings +from typing import TYPE_CHECKING, Any, Iterable -import fastcluster +import fastcluster # type: ignore import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as sns -import umap -from adjustText import adjust_text +from adjustText import adjust_text # type: ignore from scipy.cluster import hierarchy from scipy.spatial.distance import pdist -from sklearn.decomposition import PCA -from sklearn.manifold import TSNE from .consts import COLORS_INDEL83, COLORS_SBS96, INDEL_TYPES_83, SBS_TYPES_96 -from .utils import match_to_catalog, value_checker +from .utils import _get_basis_obsm, _get_basis_obsp, match_to_catalog, value_checker + +if TYPE_CHECKING: + from anndata import AnnData + from matplotlib.axes import Axes + from matplotlib.colors import Colormap + from matplotlib.typing import ColorType + from seaborn.matrix import ClusterGrid def set_salamander_style(): @@ -22,22 +29,28 @@ def set_salamander_style(): sns.set_style("ticks") params = { "axes.edgecolor": "black", - "axes.labelsize": 14, + "axes.labelsize": "medium", "axes.spines.top": False, "axes.spines.right": False, - "axes.titlesize": 16, + "axes.titlesize": "large", "errorbar.capsize": 3, "font.family": "DejaVu Sans", - "legend.fontsize": 12, + "legend.fontsize": "medium", "lines.markersize": 8, "pdf.fonttype": 42, - "xtick.labelsize": 12, - "ytick.labelsize": 12, + "xtick.labelsize": "small", + "ytick.labelsize": "small", } mpl.rcParams.update(params) -def history_plot(values, conv_test_freq, min_iteration=0, ax=None, **kwargs): +def history_plot( + values: np.ndarray, + conv_test_freq: int, + min_iteration: int = 0, + ax: Axes | None = None, + **kwargs, +) -> Axes: n_values = len(values) ns_iteration = np.arange( conv_test_freq, n_values * conv_test_freq + 1, conv_test_freq @@ -62,14 +75,13 @@ def history_plot(values, conv_test_freq, min_iteration=0, ax=None, **kwargs): def _annotate_plot( - ax, - data, - annotations, - ha="left", - fontsize="medium", - color="black", - adjust_annotations=True, - adjust_kwargs=None, + ax: Axes, + data: np.ndarray, + annotations: Iterable[str], + fontsize: float | str = "small", + color: ColorType = "black", + adjust_annotations: bool = True, + adjust_kwargs: dict[str, Any] | None = None, **kwargs, ): for data_point, annotation in zip(data, annotations): @@ -77,40 +89,47 @@ def _annotate_plot( data_point[0], data_point[1], annotation, - ha=ha, fontsize=fontsize, color=color, **kwargs, ) if adjust_annotations: - if adjust_kwargs is None: - adjust_kwargs = {} if adjust_kwargs is None else adjust_kwargs.copy() - - annotations = [ + adjust_kwargs = {} if adjust_kwargs is None else adjust_kwargs.copy() + texts = [ child for child in ax.get_children() if isinstance(child, mpl.text.Text) ] - annotations = [ - annotation for annotation in annotations if annotation.get_text() - ] - adjust_text(annotations, **adjust_kwargs) + texts_nonempty = [annotation for annotation in texts if annotation.get_text()] + adjust_text(texts_nonempty, **adjust_kwargs) -def _scatter_1d(data: np.ndarray, ax=None, **kwargs): +def _scatter_1d( + data: np.ndarray, xlabel: str | None = None, ax: Axes | None = None, **kwargs +) -> Axes: if data.ndim != 1: raise ValueError(f"The datapoints of {data} (rows) have to be one-dimensional.") if ax is None: - _, ax = plt.subplots(figsize=(6, 1)) + _, ax = plt.subplots(figsize=(4, 1)) ax.spines[["left", "bottom"]].set_visible(False) ax.get_yaxis().set_visible(False) ax.axhline(y=0, color="black", zorder=1) - data_2d = np.vstack([data, np.zeros_like(data)]).T - sns.scatterplot(x=data_2d[:, 0], y=data_2d[:, 1], ax=ax, zorder=2, **kwargs) - return data_2d, ax + sns.scatterplot(x=data, y=np.zeros_like(data), ax=ax, zorder=2, **kwargs) + + if xlabel: + ax.set_xlabel(xlabel) + + return ax -def _scatter_2d(data, ax=None, **kwargs): +def _scatter_2d( + data: np.ndarray, + xlabel: str | None = None, + ylabel: str | None = None, + ticks: bool = True, + ax: Axes | None = None, + **kwargs, +) -> Axes: """ The rows (!) of 'data' are assumed to be the data points. """ @@ -118,160 +137,128 @@ def _scatter_2d(data, ax=None, **kwargs): raise ValueError(f"The datapoints of {data} (rows) have to be two-dimensional.") if ax is None: - _, ax = plt.subplots(figsize=(6, 6)) + _, ax = plt.subplots(figsize=(4, 4)) - ax.set(xlabel="x", ylabel="y") sns.scatterplot(x=data[:, 0], y=data[:, 1], ax=ax, **kwargs) - return data, ax + if xlabel: + ax.set_xlabel(xlabel) -def _pca_2d(data, ax=None, **kwargs): - """ - The rows (!) of 'data' are assumed to be the data points. - """ - if ax is None: - _, ax = plt.subplots(figsize=(6, 6)) - - data_2d = PCA(n_components=2).fit_transform(data) - ax.set(xlabel="PC1", ylabel="PC2") - sns.scatterplot(x=data_2d[:, 0], y=data_2d[:, 1], ax=ax, **kwargs) - return data_2d, ax - - -def _tsne_2d(data, perplexity=30, ax=None, **kwargs): - """ - The rows (!) of 'data' are assumed to be the single data points. - """ - if ax is None: - _, ax = plt.subplots(figsize=(6, 6)) - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - perplexity = min(perplexity, len(data) - 1) - data_2d = TSNE(perplexity=perplexity).fit_transform(data) + if ylabel: + ax.set_ylabel(ylabel) - ax.set(xlabel="t-SNE1", xticks=[], ylabel="t-SNE2", yticks=[]) - sns.scatterplot(x=data_2d[:, 0], y=data_2d[:, 1], ax=ax, **kwargs) - return data_2d, ax + if not ticks: + ax.set(xticks=[], yticks=[]) + return ax -def _umap_2d(data, n_neighbors=15, min_dist=0.1, ax=None, **kwargs): - """ - The rows (!) of 'data' are assumed to be the single data points. - """ - if ax is None: - _, ax = plt.subplots(figsize=(6, 6)) - n_neighbors = min(n_neighbors, len(data) - 1) - data_2d = umap.UMAP(n_neighbors=n_neighbors, min_dist=min_dist).fit_transform(data) +def _scatter( + data: np.ndarray, + xlabel: str | None = None, + ylabel: str | None = None, + ticks: bool = True, + annotations: Iterable[str] | None = None, + annotation_kwargs: dict[str, Any] | None = None, + adjust_annotations: bool = True, + adjust_kwargs: dict[str, Any] | None = None, + ax: Axes | None = None, + **kwargs, +) -> Axes: + if data.ndim == 1 or data.shape[1] == 1: + ax = _scatter_1d(data, xlabel, ax, **kwargs) + data_2d = np.vstack([data.flatten(), np.zeros_like(data.flatten())]).T + elif data.ndim == 2 and data.shape[1] == 2: + ax = _scatter_2d(data, xlabel, ylabel, ticks, ax, **kwargs) + data_2d = data + else: + raise ValueError( + "Scatterplots are only supported for one- or two-dimensional data." + ) - ax.set(xlabel="UMAP1", xticks=[], ylabel="UMAP2", yticks=[]) - sns.scatterplot(x=data_2d[:, 0], y=data_2d[:, 1], ax=ax, **kwargs) - return data_2d, ax + if annotations is not None: + annotation_kwargs = ( + {} if annotation_kwargs is None else annotation_kwargs.copy() + ) + _annotate_plot( + ax, + data_2d, + annotations, + adjust_annotations=adjust_annotations, + adjust_kwargs=adjust_kwargs, + **annotation_kwargs, + ) + return ax -def embeddings_plot( - data: np.ndarray, - method="umap", - normalize=False, - annotations=None, - annotation_kwargs=None, - adjust_annotations=True, - adjust_kwargs=None, - ax=None, - **kwargs, -): - """ - Plot a dimensionality reduction of high-dimensional data. - If the dimension of the data is one or two, it is plotted - directly, ignoring the chosen method. +def scatter(adata: AnnData, x: str, y: str | None = None, **kwargs) -> Axes: + if y is None: + data = adata.obs(x).to_numpy() + else: + data = adata.obs[[x, y]].to_numpy() - Parameters - ---------- - data : np.ndarray - The rows (!) are assumed to be the data points. + ax = _scatter(data, xlabel=x, ylabel=y, **kwargs) + return ax - method : str, default='umap' - Either 'tsne', 'pca' or 'umap'. The respective dimensionality reduction - is applied to plot the data in 2D. If the dimensionality of the data is - less or equal to two, the points are plotted directly. - normalize : bool, default=False - If True, the data points are normalized to have a Euclidean norm of - one before applying the dimensionality reduction. +def _basisobsm2name(basis: str) -> str: + name = ( + "PC" + if basis == "pca" + else "tSNE" + if basis == "tsne" + else "UMAP" + if basis == "umap" + else basis + ) + return name - annotations : list[str], default=None - Annotations of the projected data points. The length of the list has to match - the number of data points. - annotation_kwargs : dict, default=None - keyword arguments to pass to matplotlibs plt.txt() +def embedding( + adata: AnnData, + basis: str, + dimensions: tuple[int, int] = (0, 1), + xlabel: str | None = None, + ylabel: str | None = None, + **kwargs, +) -> Axes: + data = _get_basis_obsm(adata, basis) - adjust_annotations : bool, default=True - If True, the annotations are adjusted to prevent them from overlapping. + if data.ndim == 2 and data.shape[1] > 2: + data = data[dimensions] - adjust_kwargs : dict, default=None - keyword arguments to pass to adjustText's adjust_text. - This is for example useful when arrows connecting the projected - data points and the adjusted positions of the annotations - should be added. + name = _basisobsm2name(basis) + labels = [f"{name}{d+1}" for d in dimensions] - ax : matplotlib.axes.Axes, default=None - Pre-existing axes for the plot. Otherwise, an axes is created. + if xlabel is None: + xlabel = labels[0] - **kwargs : - keyword arguments to pass to seaborn's scatterplot + if ylabel is None: + ylabel = labels[1] - Returns - ------- - ax : matplotlib.axes.Axes - The matplotlib axes containing the plot. - """ - value_checker("method", method, ["pca", "tsne", "umap"]) + ax = _scatter(data, xlabel=xlabel, ylabel=ylabel, **kwargs) + return ax - if normalize: - data /= np.sqrt(np.sum(data**2, axis=1))[:, np.newaxis] - n_dimensions = data.shape[1] +def pca(adata: AnnData, **kwargs) -> Axes: + return embedding(adata, basis="pca", **kwargs) - if n_dimensions in [1, 2]: - warnings.warn( - f"The dimension of the data points is {n_dimensions}. " - f"The method argument '{method}' will be ignored " - "and the embeddings are plotted directly.", - UserWarning, - ) - if n_dimensions == 1: - data_2d, ax = _scatter_1d(data[:, 0], ax=ax, **kwargs) - elif n_dimensions == 2: - data_2d, ax = _scatter_2d(data, ax=ax, **kwargs) - elif method == "tsne": - data_2d, ax = _tsne_2d(data, ax=ax, **kwargs) - elif method == "pca": - data_2d, ax = _pca_2d(data, ax=ax, **kwargs) - else: - data_2d, ax = _umap_2d(data, ax=ax, **kwargs) +def tsne(adata: AnnData, **kwargs) -> Axes: + return embedding(adata, basis="tsne", ticks=False, **kwargs) - if annotations is not None: - annotation_kwargs = ( - {} if annotation_kwargs is None else annotation_kwargs.copy() - ) - _annotate_plot( - ax, - data_2d, - annotations, - adjust_annotations=adjust_annotations, - adjust_kwargs=adjust_kwargs, - **annotation_kwargs, - ) - return ax +def umap(adata: AnnData, **kwargs) -> Axes: + return embedding(adata, basis="umap", ticks=False, **kwargs) -def corr_plot( - corr: pd.DataFrame, figsize=(6, 6), cmap="vlag", annot=True, fmt=".2f", **kwargs -): +def _correlation( + corr: pd.DataFrame, + figsize: tuple[float, float] = (4.0, 4.0), + cmap: Colormap | str | None = "vlag", + fmt: str = ".2f", + **kwargs, +) -> ClusterGrid: linkage = hierarchy.linkage(corr) clustergrid = sns.clustermap( corr, @@ -280,25 +267,32 @@ def corr_plot( vmin=-1, vmax=1, cmap=cmap, - annot=annot, fmt=fmt, **kwargs, ) - return clustergrid -def _get_colors_signature_plot(mutation_types, colors=None): +def correlation(adata: AnnData, **kwargs) -> ClusterGrid: + corr = pd.DataFrame( + _get_basis_obsp(adata, "correlation"), + index=adata.obs_names, + columns=adata.obs_names, + ) + return _correlation(corr, **kwargs) + + +def _get_colors_barplot(var_names, colors=None): """ - Given the mutation types and the colors argument of sigplot_bar, return the - final colors used in the signature bar chart. + Given the variable names / features and the colors argument of barplot, + return the final colors used in the bar chart. """ - n_features = len(mutation_types) + n_vars = len(var_names) if colors == "SBS96" or ( - n_features == 96 and all(mutation_types == SBS_TYPES_96) and colors is None + n_vars == 96 and all(var_names == SBS_TYPES_96) and colors is None ): - if n_features != 96: + if n_vars != 96: raise ValueError( "The standard SBS colors can only be used " "when the signatures have 96 features." @@ -306,9 +300,9 @@ def _get_colors_signature_plot(mutation_types, colors=None): colors = COLORS_SBS96 elif colors == "Indel83" or ( - n_features == 83 and all(mutation_types == INDEL_TYPES_83) and colors is None + n_vars == 83 and all(var_names == INDEL_TYPES_83) and colors is None ): - if n_features != 83: + if n_vars != 83: raise ValueError( "The standard Indel colors can only be used " "when the signatures have 83 features." @@ -316,38 +310,48 @@ def _get_colors_signature_plot(mutation_types, colors=None): colors = COLORS_INDEL83 elif type(colors) in [str, tuple]: - colors = n_features * [colors] + colors = n_vars * [colors] elif type(colors) is list: - if len(colors) != n_features: - raise ValueError( - f"The list of colors must be of length n_features={n_features}." - ) + if len(colors) != n_vars: + raise ValueError(f"The list of colors must be of length n_vars={n_vars}.") else: - colors = n_features * ["gray"] + colors = n_vars * ["gray"] return colors -def _signature_plot( - signature, colors=None, annotate_mutation_types=False, ax=None, **kwargs -): +def _barplot_single( + data: pd.DataFrame, + colors: ColorType | list[ColorType] | None = None, + annotate_vars: bool = False, + ax: Axes | None = None, + **kwargs, +) -> Axes: """ + Plot the relative values of a non-negative dataframe + with a single row. + Inputs: ------- - signature: pd.Signature - Signature with mutation types and name. + data: pd.DataFrame + A dataframe with only one row, typically a single signature + or the feature counts of a single sample. + The columns of data are expected to be the names of the features. - colors: str, tuple or list + colors: Can be set to 'SBS96' or 'Indel83' to use the standard bar colors - for these mutation types. - Otherwise, when a single string or tuple is provided, - all bars will have the same color. Alternatively, - a list can be used to specifiy the color of each bar individually. + for these features. + Otherwise, when a single color is provided, all bars will have + the same color. Alternatively, a list can be used to specifiy + the color of each bar individually. + + annotate_vars: bool, default=False + If True, the x-axis has ticks and annotations. ax: - A single matplotlib Axes in which to draw the plot. + Axes object to draw the plot onto. Otherwise, create an Axes internally. kwargs: dict Any keyword arguments to be passed to matplotlibs ax.bar @@ -355,267 +359,298 @@ def _signature_plot( if ax is None: _, ax = plt.subplots(figsize=(4, 1)) - signature_normalized = signature / signature.sum(axis=0) - mutation_types = signature.index - colors = _get_colors_signature_plot(mutation_types, colors) + data_normalized = data.div(data.sum(axis=1), axis=0) + var_names = data.columns + colors = _get_colors_barplot(var_names, colors) - ax.set_title(signature_normalized.columns[0]) + ax.set_title(data.index[0]) ax.spines["left"].set_visible(False) ax.get_yaxis().set_visible(False) - ax.set_xlim((-1, len(mutation_types))) + ax.set_xlim((-1, len(var_names))) ax.bar( - mutation_types, - signature_normalized.iloc[:, 0], + var_names, + data_normalized.iloc[0, :], linewidth=0, color=colors, **kwargs, ) - if annotate_mutation_types: - ax.set_xticks(mutation_types) + if annotate_vars: + ax.set_xticks(var_names) ax.set_xticklabels( - mutation_types, family="monospace", fontsize=4, ha="center", rotation=90 + var_names, family="monospace", fontsize="x-small", ha="center", rotation=90 ) - else: ax.set_xticks([]) return ax -def signature_plot( - signature, - catalog=None, - colors=None, - annotate_mutation_types=False, - ax=None, +def _barplot_matched( + data: pd.DataFrame, + catalog: pd.DataFrame | None = None, + colors: ColorType | list[ColorType] | None = None, + annotate_vars: bool = False, + ax: Axes | Iterable[Axes] | None = None, **kwargs, -): +) -> Axes | Iterable[Axes]: """ + Plot the relative values of a non-negative dataframe + with a single row. + The closest matching row from a 'catalog' can also be plotted. + Inputs: ------- - signature: pd.Signature - Signature with mutation types and name. + data: pd.DataFrame + A dataframe with only one row, typically a single mutational + signature. catalog: pd.DataFrame - If a catalog is provided, the single best matching catalog signature - will also be plotted. + If a catalog with matching features is provided, the single best + matching row will also be plotted. colors: str, tuple or list Can be set to 'SBS96' or 'Indel83' to use the standard bar colors - for these mutation types. - Otherwise, when a single string or tuple is provided, - all bars will have the same color. Alternatively, - a list can be used to specifiy the color of each bar individually. + for these features. + Otherwise, when a single color is provided, all bars will have + the same color. Alternatively, a list can be used to specifiy + the color of each bar individually. + + annotate_vars: bool, default=False + If True, the x-axis has ticks and annotations. ax: - Axes in which to draw the plot. A single Axes if catalog is None; + Axes object(s) to draw the plot onto. A single Axes if catalog is None; two Axes if a catalog is given. kwargs: dict Any keyword arguments to be passed to matplotlibs ax.bar """ if catalog is None: - if ax is None: - _, ax = plt.subplots(figsize=(4, 1)) - - signatures = [signature] - axes = [ax] + assert isinstance(ax, Axes) or ax is None + return _barplot_single( + data, colors=colors, annotate_vars=annotate_vars, ax=ax, **kwargs + ) + if ax is None: + _, axes = plt.subplots(1, 2, figsize=(8, 1)) else: - if ax is None: - _, ax = plt.subplots(1, 2, figsize=(8, 1)) - - matched_signature = match_to_catalog(signature, catalog, metric="cosine") - signatures = [signature, matched_signature] axes = ax - for sig, axis in zip(signatures, axes): - _signature_plot( - sig, + matched_data = match_to_catalog(data, catalog, metric="cosine") + data_all = [data, matched_data] + + for d, axis in zip(data_all, axes): + _barplot_single( + d, colors=colors, - annotate_mutation_types=annotate_mutation_types, + annotate_vars=annotate_vars, ax=axis, **kwargs, ) - if catalog is None: - return axes[0] - return axes -def signatures_plot( - signatures, - catalog=None, - colors=None, - annotate_mutation_types=False, - axes=None, +def _barplot( + data: pd.DataFrame, + catalog: pd.DataFrame | None = None, + colors: ColorType | list[ColorType] | None = None, + annotate_vars: bool = False, + axes: Axes | Iterable[Axes] | None = None, **kwargs, -): +) -> Axes | Iterable[Axes]: """ + Plot the relative values of the rows of a non-negative dataframe. + The closest matching rows from a 'catalog' can also be plotted. + Inputs: ------- - signatures : pd.DataFrame - Named signatures of shape (n_features, n_signatures) + data : pd.DataFrame + Annotated dataframe of shape (n_obs, n_vars), typically + a collection of mutational signatures. catalog: pd.DataFrame - If a catalog is provided, the best matching catalog signatures - will also be plotted. + If a catalog with matching features is provided, the best matching + rows of the catalog are also plotted. - axes : list - Axes in which to draw the plot. Multiple Axes if more than one signature - is provided or a catalog is given. Otherwise a single axis. - When a catalog is provided, axes is expected to be of shape (n_signatures, 2). + colors: str, tuple or list + Can be set to 'SBS96' or 'Indel83' to use the standard bar colors + for these features. + Otherwise, when a single color is provided, all bars will have + the same color. Alternatively, a list can be used to specifiy + the color of each bar individually. + + annotate_vars: bool, default=False + If True, the x-axis has ticks and annotations. + + axes : Axes | list[Axes] + Axes object(s) to draw the plot onto. Multiple Axes if 'data' has more than + one column or a catalog is given. Otherwise a single Axes. + When a catalog is provided, axes is expected to be of shape (n_obs, 2). """ - n_signatures = signatures.shape[1] + n_obs = data.shape[0] - if n_signatures == 1: - ax = signature_plot( - signatures, + if n_obs == 1: + return _barplot_matched( + data, catalog=catalog, colors=colors, - annotate_mutation_types=annotate_mutation_types, + annotate_vars=annotate_vars, ax=axes, **kwargs, ) - return ax if axes is None: if catalog is None: - _, axes = plt.subplots(n_signatures, 1, figsize=(4, n_signatures)) + _, axes = plt.subplots(n_obs, 1, figsize=(4, n_obs)) else: - _, axes = plt.subplots(n_signatures, 2, figsize=(8, n_signatures)) + _, axes = plt.subplots(n_obs, 2, figsize=(8, n_obs)) + + assert isinstance( + axes, Iterable + ), "Adding multiple barplots to custom 'axes' requires 'axes' to be iterable." - for ax, signature in zip(axes, signatures): - signature_plot( - signatures[[signature]], + for ax, row in zip(axes, data.T): + _barplot_matched( + data.loc[[row], :], catalog=catalog, colors=colors, - annotate_mutation_types=annotate_mutation_types, + annotate_vars=annotate_vars, ax=ax, **kwargs, ) - plt.tight_layout() + plt.tight_layout() return axes -def _get_sample_order(exposures: pd.DataFrame, normalize=True): +def barplot(adata: AnnData, **kwargs): + return _barplot(adata.to_df(), **kwargs) + + +def _get_obs_order(data: pd.DataFrame, normalize: bool = True) -> np.ndarray: """ - Compute the aesthetically most pleasing order of the samples - for a stacked bar chart of the exposures. + Compute the aesthetically most pleasing order of the observations + of a non-negative data array of shape (n_obs, n_dimensions) for a + stacked barchart using hierarchical clustering. Parameters ---------- - exposures : pd.DataFrame of shape (n_signatures, n_samples) - The named exposure matrix + data : pd.DataFrame of shape (n_obs, n_dimensions) + An annotated non-negative data matrix, typically the signature + exposures. normalize : bool, default=True - If True, the exposures are normalized before computing the - hierarchical clustering. + If True, the data is row-normalized before computing the + optimal order. Returns ------- - sample_order : np.ndarray - The ordered sample names + order : np.ndarray + The ordered observations. """ if normalize: - # not in-place - exposures = exposures / exposures.sum(axis=0) + # no in-place + data = data.div(data.sum(axis=1), axis=0) - d = pdist(exposures.T) + d = pdist(data) linkage = fastcluster.linkage(d) - # get the optimal sample order that is consistent + # get the optimal order that is consistent # with the hierarchical clustering linkage - sample_order = hierarchy.leaves_list(hierarchy.optimal_leaf_ordering(linkage, d)) - sample_order = exposures.columns[sample_order].to_numpy() - return sample_order + obs_order = hierarchy.leaves_list(hierarchy.optimal_leaf_ordering(linkage, d)) + obs_order = data.index[obs_order].to_numpy() + return obs_order -def _reorder_exposures( - exposures: pd.DataFrame, sample_order=None, reorder_signatures=True -): +def _reorder_data( + data: pd.DataFrame, + obs_order: np.ndarray | None = None, + normalize: bool = True, + reorder_dimensions: bool = True, +) -> pd.DataFrame: """ - Reorder the samples with hierarchical clustering and - reorder the signatures by their total relative exposure. + Reorder non-negative data using hierarchical clustering and optionally + reorder the dimensions by their total relative sums. Parameters ---------- - exposures : pd.DataFrame of shape (n_signatures, n_samples) - The named exposure matrix + data : pd.DataFrame of shape (n_obs, n_dimensions) + An annotated non-negative data matrix, typically the signature + exposures. - sample_order : np.ndarray, default=None - A predefined order of the samples as a list of sample names. - If None, hierarchical clustering is used to compute the - aesthetically most pleasing order. + obs_order : np.ndarray, default=None + An optional predefined order of the observations. + + normalize : bool, default=True + If True, the data is row-normalized before computing the + optimal order. Only used if 'data_order' is not given. - reorder_signatures : bool, default=True - If True, the signatures will be reordered such that the - total relative exposures of the signatures decrease from the bottom - to the top signature in the stacked bar chart. + reorder_dimensions : bool, default=True + If True, the dimensions/columns will be reordered such that their + total relative sums decrease from the left to the right. Returns ------- - exposures_reordered : pd.DataFrame of shape (n_signatures, n_samples) - The reorderd named exposure matrix + data_reordered : pd.DataFrame of shape (n_obs, n_dimensions) + The reorderd annotated data. """ - if sample_order is None: - sample_order = _get_sample_order(exposures) + if obs_order is None: + obs_order = _get_obs_order(data, normalize=normalize) - exposures_reordered = exposures[sample_order] + data_reordered = data.loc[obs_order, :] - # order the signatures by their total relative exposure - if reorder_signatures: - exposures_normalized = exposures_reordered / exposures_reordered.sum(axis=0) - signature_order = ( - exposures_normalized.sum(axis=1).sort_values(ascending=False).index - ) - exposures_reordered = exposures_reordered.reindex(signature_order) + # order the columns by their total relative contribution + if reorder_dimensions: + data_normalized = data.div(data.sum(axis=1), axis=0) + dim_ordered = data_normalized.sum(axis=0).sort_values(ascending=False).index + data_reordered = data_reordered[dim_ordered] - return exposures_reordered + return data_reordered -def exposures_plot( - exposures: pd.DataFrame, - sample_order=None, - reorder_signatures=True, - annotate_samples=True, - colors=None, - ncol_legend=1, - ax=None, +def stacked_barplot( + data: pd.DataFrame, + obs_order: np.ndarray | None = None, + reorder_dimensions: bool = True, + annotate_obs: bool = True, + colors: Iterable[ColorType] | None = None, + title: str | None = None, + ncol_legend: int = 1, + ax: Axes | None = None, **kwargs, -): +) -> Axes: """ - Visualize the exposures with a stacked bar chart. + Visualize non-negative data with a stacked bar chart, typically + the signature exposures. Parameter --------- - exposures : pd.DataFrame of shape (n_signatures, n_samples) - The named exposure matrix. + data : pd.DataFrame of shape (n_obs, n_dimensions) + An annotated non-negative data matrix, typically the signature + exposures. - sample_order : np.ndarray, default=None - A predefined order of the samples as a list of sample names. + obs_order : np.ndarray, default=None + An optional predefined order of the observations. If None, hierarchical clustering is used to compute the aesthetically most pleasing order. - reorder_signatures : bool, default=True - If True, the signatures will be reordered such that the - total relative exposures of the signatures decrease from the bottom - to the top signature in the stacked bar chart. + reorder_dimensions : bool, default=True + If True, the columns of 'data' will be reordered such that their + total relative contributions in the stacked bar chart is increasing. - annotate_samples : bool, default=True - If True, the x-axis is annotated with the sample names. + annotate_obs : bool, default=True + If True, the x-axis is annotated with the observation names. - colors : list of length n_signatures, default=None - Colors to pass to matplotlibs ax.bar, one per signature. + colors : iterable of length n_dimensions, default=None + Colors to pass to matplotlibs ax.bar, one per dimension. n_col_legend : int, default=1 The number of columns of the legend. ax : matplotlib.axes.Axes, default=None - Pre-existing axes for the plot. Otherwise, create an axis internally. + Axes object to draw the plot onto. Otherwise, create an Axes internally. kwargs : dict Any keyword arguments to be passed to matplotlibs ax.bar. @@ -625,43 +660,43 @@ def exposures_plot( ax : matplotlib.axes.Axes The matplotlib axes containing the plot. """ - n_signatures, n_samples = exposures.shape - # not in-place - exposures = exposures / exposures.sum(axis=0) - exposures = _reorder_exposures( - exposures, sample_order=sample_order, reorder_signatures=reorder_signatures + n_obs, n_dimensions = data.shape + data = data.div(data.sum(axis=1), axis=0) + data = _reorder_data( + data, obs_order=obs_order, reorder_dimensions=reorder_dimensions ) if ax is None: - _, ax = plt.subplots(figsize=(0.3 * n_samples, 4)) + _, ax = plt.subplots(figsize=(0.3 * n_obs, 4)) if colors is None: - colors = list(sns.color_palette("deep")) * (1 + n_signatures // 10) + colors = sns.color_palette("deep") * (1 + n_dimensions // 10) - bottom = np.zeros(n_samples) + bottom = np.zeros(n_obs) - for signature, color in zip(exposures.T, colors): - signature_exposures = exposures.T[signature].to_numpy() + for dimension, color in zip(data, colors): + values = data[dimension].to_numpy() ax.bar( - np.arange(n_samples), - signature_exposures, + np.arange(n_obs), + values, color=color, width=1, - label=signature, + label=dimension, linewidth=0, bottom=bottom, **kwargs, ) - bottom += signature_exposures - - if annotate_samples: - ax.set_xticks(np.arange(n_samples)) - ax.set_xticklabels(exposures.columns, rotation=90, ha="center", fontsize=10) + bottom += values + if annotate_obs: + ax.set_xticks(np.arange(n_obs)) + ax.set_xticklabels(data.index, rotation=90, ha="center", fontsize="x-small") else: ax.get_xaxis().set_visible(False) - ax.set_title("Sample exposures") + if title: + ax.set_title(title) + ax.spines[["left", "bottom"]].set_visible(False) ax.get_yaxis().set_visible(False) ax.legend(loc="center left", bbox_to_anchor=(0.975, 0.5), ncol=ncol_legend) diff --git a/src/salamander/tools.py b/src/salamander/tools.py new file mode 100644 index 0000000..90f2eb5 --- /dev/null +++ b/src/salamander/tools.py @@ -0,0 +1,141 @@ +from __future__ import annotations + +import warnings +from typing import TYPE_CHECKING + +import numpy as np +import pandas as pd + +from .utils import _get_basis_obsm, value_checker + +if TYPE_CHECKING: + from anndata import AnnData + + +def _pca(data: np.ndarray, n_components: int = 2, **kwargs) -> np.ndarray: + from sklearn.decomposition import PCA + + data_reduced_dim = PCA(n_components=n_components, **kwargs).fit_transform(data) + return data_reduced_dim + + +def pca(adata: AnnData, basis: str, **kwargs) -> None: + """ + Compute and store the PCA of the multi-dimensional + observation annotations named 'basis'. + """ + data = _get_basis_obsm(adata, basis) + adata.obsm[f"X_pca"] = _pca(data, **kwargs) + + +def _tsne( + data: np.ndarray, n_components: int = 2, perplexity: float = 30.0, **kwargs +) -> np.ndarray: + from sklearn.manifold import TSNE + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + perplexity = min(perplexity, len(data) - 1) + data_reduced_dim = TSNE( + n_components=n_components, perplexity=perplexity, **kwargs + ).fit_transform(data) + + return data_reduced_dim + + +def tsne(adata: AnnData, basis: str, **kwargs) -> None: + """ + Compute and store the t-SNE of the multi-dimensional + observation annotations named 'basis'. + """ + data = _get_basis_obsm(adata, basis) + adata.obsm[f"X_tsne"] = _tsne(data, **kwargs) + + +def _umap( + data: np.ndarray, n_neighbors: float = 15, min_dist: float = 0.1, **kwargs +) -> np.ndarray: + import umap + + n_neighbors = min(n_neighbors, len(data) - 1.0) + data_reduced_dim = umap.UMAP( + n_neighbors=n_neighbors, min_dist=min_dist, **kwargs + ).fit_transform(data) + + return data_reduced_dim + + +def umap(adata: AnnData, basis: str, **kwargs) -> None: + """ + Compute and store the UMAP of the multi-dimensional + observation annotations named 'basis'. + """ + data = _get_basis_obsm(adata, basis) + adata.obsm[f"X_umap"] = _umap(data, **kwargs) + + +def _reduce_dimension( + data: np.ndarray, method: str = "umap", normalize: bool = False, **kwargs +) -> np.ndarray: + value_checker("method", method, ["pca", "tsne", "umap"]) + + if normalize: + data /= np.sqrt(np.sum(data**2, axis=1))[:, np.newaxis] + + n_dimensions = data.shape[1] + + if n_dimensions in [1, 2]: + warnings.warn( + f"The dimension of the data points is {n_dimensions}. " + "The dimensionality of the data will not be reduced.", + UserWarning, + ) + return data + + if method == "pca": + data_reduced_dim = _pca(data, **kwargs) + elif method == "tsne": + data_reduced_dim = _tsne(data, **kwargs) + else: + data_reduced_dim = _umap(data, **kwargs) + + return data_reduced_dim + + +def reduce_dimension( + adata: AnnData, basis: str, method="umap", normalize: bool = False, **kwargs +) -> None: + """ + Compute and store a dimensionality reduction of the multi-dimensional + observation annotations named 'basis'. + """ + data = _get_basis_obsm(adata, basis) + n_dimensions = data.shape[1] + + if n_dimensions in [1, 2]: + warnings.warn( + f"The dimension of the observation annotations is {n_dimensions}. " + "No dimensionality reduction will be applied.", + UserWarning, + ) + return + + adata.obsm[f"X_{method}"] = _reduce_dimension( + data, method=method, normalize=normalize, **kwargs + ) + + +def _correlation(data: np.ndarray, **kwargs) -> np.ndarray: + """ + Compute the correlation of the rows of the data. + """ + return pd.DataFrame(data.T).corr(**kwargs).values + + +def correlation(adata: AnnData, basis: str, **kwargs) -> None: + """ + Compute and store the correlation of the multi-dimensional + observation annotations named 'basis'. + """ + data = _get_basis_obsm(adata, basis) + adata.obsp[f"X_correlation"] = _correlation(data, **kwargs) diff --git a/src/salamander/utils.py b/src/salamander/utils.py index ecfc0a6..47f7462 100644 --- a/src/salamander/utils.py +++ b/src/salamander/utils.py @@ -1,9 +1,16 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + import numpy as np import pandas as pd from numba import njit from scipy.optimize import linear_sum_assignment from sklearn.metrics import pairwise_distances +if TYPE_CHECKING: + from anndata import AnnData + EPSILON = np.finfo(np.float32).eps @@ -68,6 +75,32 @@ def value_checker(arg_name: str, arg, allowed_values): ) +def _get_basis_obsm(adata: AnnData, basis: str) -> np.ndarray: + """ + Get the multidimensional observation annotations named 'basis'. + Tries to recover 'X_basis' if 'basis' is not a key of adata.obsm. + """ + if basis in adata.obsm: + return adata.obsm[basis] + elif f"X_{basis}" in adata.obsm: + return adata.obsm[f"X_{basis}"] + else: + raise KeyError(f"Could not find '{basis}' or 'X_{basis}' in .obsm") + + +def _get_basis_obsp(adata: AnnData, basis: str) -> np.ndarray: + """ + Get the pairwise observation annotations named 'basis'. + Tries to recover 'X_basis' if 'basis' is not a key of adata.obsp. + """ + if basis in adata.obsp: + return adata.obsp[basis] + elif f"X_{basis}" in adata.obsp: + return adata.obsp[f"X_{basis}"] + else: + raise KeyError(f"Could not find '{basis}' or 'X_{basis}' in .obsp") + + @njit def normalize_WH(W, H): normalization_factor = np.sum(W, axis=0) @@ -78,10 +111,9 @@ def match_to_catalog(signatures: pd.DataFrame, catalog: pd.DataFrame, metric="co """ Find the best matching signatures in catalog for all signatures. """ - cosine_sim = 1 - pairwise_distances(signatures.T, catalog.T, metric=metric) + cosine_sim = 1 - pairwise_distances(signatures, catalog, metric=metric) matches_indices = [np.argmax(row) for row in cosine_sim] - matches = catalog.iloc[:, matches_indices] - + matches = catalog.loc[matches_indices, :] return matches @@ -89,20 +121,19 @@ def match_signatures_pair( signatures1: pd.DataFrame, signatures2: pd.DataFrame, metric="cosine" ): """ - Match a pair of signature catalogs using their pairwise column distances, + Match a pair of signature catalogs using their pairwise distances, see https://en.wikipedia.org/wiki/Assignment_problem. Output: ------ reordered_indices: np.ndarray - The list of column indices such that reordering signatures2 using this list - minimizes the sum of the pairwise column distances between + The list of indices such that reordering signatures2 using this list + minimizes the sum of the pairwise distances between signatures1 and signatures2. """ if signatures1.shape != signatures2.shape: raise ValueError("The signatures must be of the same shape.") - pdist = pairwise_distances(signatures1.T, signatures2.T, metric=metric) + pdist = pairwise_distances(signatures1, signatures2, metric=metric) reordered_indices = linear_sum_assignment(pdist)[1] - return reordered_indices From f39fc7627a42f14663daf17f6b2eee3003102acf Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 5 Apr 2024 01:44:01 -0400 Subject: [PATCH 006/103] add mypy config --- mypy.ini | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..3c340b4 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,23 @@ +[mypy] +warn_unused_configs = True +python_version = 3.11 + +files = + src/salamander/plot.py, + src/salamander/tools.py + +# +# Third party dependencies that don't have types. +# + +[mypy-anndata.*] +ignore_missing_imports = True + +[mypy-scipy.*] +ignore_missing_imports = True + +[mypy-sklearn.*] +ignore_missing_imports = True + +[mypy-umap.*] +ignore_missing_imports = True From addd0a0dcad80ef2990a9ee7b10d952bb86b24c3 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 5 Apr 2024 01:44:33 -0400 Subject: [PATCH 007/103] fix init KLNMF not refactored yet --- src/salamander/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/salamander/__init__.py b/src/salamander/__init__.py index 8be1da5..db76571 100644 --- a/src/salamander/__init__.py +++ b/src/salamander/__init__.py @@ -2,11 +2,11 @@ Salamander: a non-negative matrix factorization framework for signature analysis ================================================================================ """ -from .nmf_framework.klnmf import KLNMF +# from .nmf_framework.klnmf import KLNMF from .plot import set_salamander_style __version__ = "0.3.2" -__all__ = ["KLNMF"] +# __all__ = ["KLNMF"] set_salamander_style() From a9a7b16dbe69d473a694a5ff059ae91bea7359b1 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 5 Apr 2024 13:05:11 -0400 Subject: [PATCH 008/103] update tests Samples now correspond to rows. The variable names are adapted to fit the new plotting module. --- tests/test_plot.py | 61 +++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/tests/test_plot.py b/tests/test_plot.py index ae1878c..0114632 100644 --- a/tests/test_plot.py +++ b/tests/test_plot.py @@ -6,59 +6,58 @@ @pytest.fixture -def exposures(): - mat = np.array([[1, 2, 3, 4], [1, 3, 2, 4]]) - exposures = pd.DataFrame(mat, columns=["a", "b", "c", "d"]) - return exposures +def data(): + counts = np.array([[1, 1], [2, 3], [3, 2], [4, 4]]) + sample_names = ["a", "b", "c", "d"] + data = pd.DataFrame(counts, index=sample_names) + return data -def test_get_sample_order_normalized(exposures): - sample_order = plot._get_sample_order(exposures, normalize=True) +def test_get_obs_order_normalized(data): + obs_order = plot._get_obs_order(data, normalize=True) # A next to D - position_a = np.where(sample_order == "a")[0][0] - position_d = np.where(sample_order == "d")[0][0] + position_a = np.where(obs_order == "a")[0][0] + position_d = np.where(obs_order == "d")[0][0] assert np.abs(position_a - position_d) == 1 # B as far away from C as possible - position_b = np.where(sample_order == "b")[0][0] - position_c = np.where(sample_order == "c")[0][0] + position_b = np.where(obs_order == "b")[0][0] + position_c = np.where(obs_order == "c")[0][0] assert np.abs(position_b - position_c) == 3 -def test_get_sample_order_unnormalized(exposures): - sample_order = plot._get_sample_order(exposures, normalize=False) +def test_get_obs_order_unnormalized(data): + obs_order = plot._get_obs_order(data, normalize=False) - # A as fara away from D as possible - position_a = np.where(sample_order == "a")[0][0] - position_d = np.where(sample_order == "d")[0][0] + # A as far away from D as possible + position_a = np.where(obs_order == "a")[0][0] + position_d = np.where(obs_order == "d")[0][0] assert np.abs(position_a - position_d) == 3 # B next to C - position_b = np.where(sample_order == "b")[0][0] - position_c = np.where(sample_order == "c")[0][0] + position_b = np.where(obs_order == "b")[0][0] + position_c = np.where(obs_order == "c")[0][0] assert np.abs(position_b - position_c) == 1 -def test_reorder_exposures(exposures): - # reordering is based on the relative exposures - exposures_reordered = plot._reorder_exposures(exposures) - sample_order = exposures_reordered.columns.to_numpy() +def test_reorder_data(data): + # reordering is based on the relative values + data_reordered = plot._reorder_data(data) + obs_order = data_reordered.index.to_numpy() # A next to D - position_a = np.where(sample_order == "a")[0][0] - position_d = np.where(sample_order == "d")[0][0] + position_a = np.where(obs_order == "a")[0][0] + position_d = np.where(obs_order == "d")[0][0] assert np.abs(position_a - position_d) == 1 # B as far away from C as possible - position_b = np.where(sample_order == "b")[0][0] - position_c = np.where(sample_order == "c")[0][0] + position_b = np.where(obs_order == "b")[0][0] + position_c = np.where(obs_order == "c")[0][0] assert np.abs(position_b - position_c) == 3 -def test_reorder_custom(exposures): - custom_sample_order = ["b", "a", "c", "d"] - exposures_reordered = plot._reorder_exposures( - exposures, sample_order=custom_sample_order - ) - assert np.array_equal(exposures_reordered.columns, custom_sample_order) +def test_reorder_data_custom(data): + custom_obs_order = ["b", "a", "c", "d"] + data_reordered = plot._reorder_data(data, obs_order=custom_obs_order) + assert np.array_equal(data_reordered.index, custom_obs_order) From 3051c14f34ca866300cea95336d7da340fcaca86 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 5 Apr 2024 17:14:21 -0400 Subject: [PATCH 009/103] add parameter --- src/salamander/tools.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/salamander/tools.py b/src/salamander/tools.py index 90f2eb5..d83456f 100644 --- a/src/salamander/tools.py +++ b/src/salamander/tools.py @@ -53,13 +53,17 @@ def tsne(adata: AnnData, basis: str, **kwargs) -> None: def _umap( - data: np.ndarray, n_neighbors: float = 15, min_dist: float = 0.1, **kwargs + data: np.ndarray, + n_components: int = 2, + n_neighbors: float = 15, + min_dist: float = 0.1, + **kwargs, ) -> np.ndarray: import umap n_neighbors = min(n_neighbors, len(data) - 1.0) data_reduced_dim = umap.UMAP( - n_neighbors=n_neighbors, min_dist=min_dist, **kwargs + n_components=n_components, n_neighbors=n_neighbors, min_dist=min_dist, **kwargs ).fit_transform(data) return data_reduced_dim From e558c9692ec91e471a3aa696ad67ed325db63ac4 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 02:14:45 -0400 Subject: [PATCH 010/103] update lock file --- poetry.lock | 206 ++++++++++++++++++++++++++-------------------------- 1 file changed, 101 insertions(+), 105 deletions(-) diff --git a/poetry.lock b/poetry.lock index 973ceaf..3836a25 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,13 +17,13 @@ numpy = "*" [[package]] name = "anndata" -version = "0.10.6" +version = "0.10.7" description = "Annotated data." optional = false python-versions = ">=3.9" files = [ - {file = "anndata-0.10.6-py3-none-any.whl", hash = "sha256:2c625149472fab4430f402208e7bf1efa6a7d45fa5c14827c8122305f4dca206"}, - {file = "anndata-0.10.6.tar.gz", hash = "sha256:16ada7ed7d38552ce56d8e8b2348e18a5fc81294ce8a7ea282acf30dc42cd6c2"}, + {file = "anndata-0.10.7-py3-none-any.whl", hash = "sha256:8b50af790f9a73414d0b94fd7b8e005f5cc6c53c33eb09206d2bd6720115bffc"}, + {file = "anndata-0.10.7.tar.gz", hash = "sha256:9b186bea18956fefdc35e7464d822a17e9faa456ca2d20c7a0966a4837b822a2"}, ] [package.dependencies] @@ -269,13 +269,13 @@ test = ["scipy (>=1.6.3)"] [[package]] name = "filelock" -version = "3.13.3" +version = "3.13.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.3-py3-none-any.whl", hash = "sha256:5ffa845303983e7a0b7ae17636509bc97997d58afeafa72fb141a17b152284cb"}, - {file = "filelock-3.13.3.tar.gz", hash = "sha256:a79895a25bbefdf55d1a2a0a80968f7dbb28edcd6d4234a0afb3f37ecde4b546"}, + {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, + {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, ] [package.extras] @@ -285,53 +285,53 @@ typing = ["typing-extensions (>=4.8)"] [[package]] name = "fonttools" -version = "4.50.0" +version = "4.51.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.50.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effd303fb422f8ce06543a36ca69148471144c534cc25f30e5be752bc4f46736"}, - {file = "fonttools-4.50.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7913992ab836f621d06aabac118fc258b9947a775a607e1a737eb3a91c360335"}, - {file = "fonttools-4.50.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0a1c5bd2f63da4043b63888534b52c5a1fd7ae187c8ffc64cbb7ae475b9dab"}, - {file = "fonttools-4.50.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40fc98540fa5360e7ecf2c56ddf3c6e7dd04929543618fd7b5cc76e66390562"}, - {file = "fonttools-4.50.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fff65fbb7afe137bac3113827855e0204482727bddd00a806034ab0d3951d0d"}, - {file = "fonttools-4.50.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1aeae3dd2ee719074a9372c89ad94f7c581903306d76befdaca2a559f802472"}, - {file = "fonttools-4.50.0-cp310-cp310-win32.whl", hash = "sha256:e9623afa319405da33b43c85cceb0585a6f5d3a1d7c604daf4f7e1dd55c03d1f"}, - {file = "fonttools-4.50.0-cp310-cp310-win_amd64.whl", hash = "sha256:778c5f43e7e654ef7fe0605e80894930bc3a7772e2f496238e57218610140f54"}, - {file = "fonttools-4.50.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3dfb102e7f63b78c832e4539969167ffcc0375b013080e6472350965a5fe8048"}, - {file = "fonttools-4.50.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e58fe34cb379ba3d01d5d319d67dd3ce7ca9a47ad044ea2b22635cd2d1247fc"}, - {file = "fonttools-4.50.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c673ab40d15a442a4e6eb09bf007c1dda47c84ac1e2eecbdf359adacb799c24"}, - {file = "fonttools-4.50.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b3ac35cdcd1a4c90c23a5200212c1bb74fa05833cc7c14291d7043a52ca2aaa"}, - {file = "fonttools-4.50.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8844e7a2c5f7ecf977e82eb6b3014f025c8b454e046d941ece05b768be5847ae"}, - {file = "fonttools-4.50.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f849bd3c5c2249b49c98eca5aaebb920d2bfd92b3c69e84ca9bddf133e9f83f0"}, - {file = "fonttools-4.50.0-cp311-cp311-win32.whl", hash = "sha256:39293ff231b36b035575e81c14626dfc14407a20de5262f9596c2cbb199c3625"}, - {file = "fonttools-4.50.0-cp311-cp311-win_amd64.whl", hash = "sha256:c33d5023523b44d3481624f840c8646656a1def7630ca562f222eb3ead16c438"}, - {file = "fonttools-4.50.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b4a886a6dbe60100ba1cd24de962f8cd18139bd32808da80de1fa9f9f27bf1dc"}, - {file = "fonttools-4.50.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b2ca1837bfbe5eafa11313dbc7edada79052709a1fffa10cea691210af4aa1fa"}, - {file = "fonttools-4.50.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0493dd97ac8977e48ffc1476b932b37c847cbb87fd68673dee5182004906828"}, - {file = "fonttools-4.50.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77844e2f1b0889120b6c222fc49b2b75c3d88b930615e98893b899b9352a27ea"}, - {file = "fonttools-4.50.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3566bfb8c55ed9100afe1ba6f0f12265cd63a1387b9661eb6031a1578a28bad1"}, - {file = "fonttools-4.50.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:35e10ddbc129cf61775d58a14f2d44121178d89874d32cae1eac722e687d9019"}, - {file = "fonttools-4.50.0-cp312-cp312-win32.whl", hash = "sha256:cc8140baf9fa8f9b903f2b393a6c413a220fa990264b215bf48484f3d0bf8710"}, - {file = "fonttools-4.50.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ccc85fd96373ab73c59833b824d7a73846670a0cb1f3afbaee2b2c426a8f931"}, - {file = "fonttools-4.50.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e270a406219af37581d96c810172001ec536e29e5593aa40d4c01cca3e145aa6"}, - {file = "fonttools-4.50.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac2463de667233372e9e1c7e9de3d914b708437ef52a3199fdbf5a60184f190c"}, - {file = "fonttools-4.50.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47abd6669195abe87c22750dbcd366dc3a0648f1b7c93c2baa97429c4dc1506e"}, - {file = "fonttools-4.50.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:074841375e2e3d559aecc86e1224caf78e8b8417bb391e7d2506412538f21adc"}, - {file = "fonttools-4.50.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0743fd2191ad7ab43d78cd747215b12033ddee24fa1e088605a3efe80d6984de"}, - {file = "fonttools-4.50.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3d7080cce7be5ed65bee3496f09f79a82865a514863197ff4d4d177389e981b0"}, - {file = "fonttools-4.50.0-cp38-cp38-win32.whl", hash = "sha256:a467ba4e2eadc1d5cc1a11d355abb945f680473fbe30d15617e104c81f483045"}, - {file = "fonttools-4.50.0-cp38-cp38-win_amd64.whl", hash = "sha256:f77e048f805e00870659d6318fd89ef28ca4ee16a22b4c5e1905b735495fc422"}, - {file = "fonttools-4.50.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b6245eafd553c4e9a0708e93be51392bd2288c773523892fbd616d33fd2fda59"}, - {file = "fonttools-4.50.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a4062cc7e8de26f1603323ef3ae2171c9d29c8a9f5e067d555a2813cd5c7a7e0"}, - {file = "fonttools-4.50.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34692850dfd64ba06af61e5791a441f664cb7d21e7b544e8f385718430e8f8e4"}, - {file = "fonttools-4.50.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678dd95f26a67e02c50dcb5bf250f95231d455642afbc65a3b0bcdacd4e4dd38"}, - {file = "fonttools-4.50.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4f2ce7b0b295fe64ac0a85aef46a0f2614995774bd7bc643b85679c0283287f9"}, - {file = "fonttools-4.50.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d346f4dc2221bfb7ab652d1e37d327578434ce559baf7113b0f55768437fe6a0"}, - {file = "fonttools-4.50.0-cp39-cp39-win32.whl", hash = "sha256:a51eeaf52ba3afd70bf489be20e52fdfafe6c03d652b02477c6ce23c995222f4"}, - {file = "fonttools-4.50.0-cp39-cp39-win_amd64.whl", hash = "sha256:8639be40d583e5d9da67795aa3eeeda0488fb577a1d42ae11a5036f18fb16d93"}, - {file = "fonttools-4.50.0-py3-none-any.whl", hash = "sha256:48fa36da06247aa8282766cfd63efff1bb24e55f020f29a335939ed3844d20d3"}, - {file = "fonttools-4.50.0.tar.gz", hash = "sha256:fa5cf61058c7dbb104c2ac4e782bf1b2016a8cf2f69de6e4dd6a865d2c969bb5"}, + {file = "fonttools-4.51.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:84d7751f4468dd8cdd03ddada18b8b0857a5beec80bce9f435742abc9a851a74"}, + {file = "fonttools-4.51.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b4850fa2ef2cfbc1d1f689bc159ef0f45d8d83298c1425838095bf53ef46308"}, + {file = "fonttools-4.51.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5b48a1121117047d82695d276c2af2ee3a24ffe0f502ed581acc2673ecf1037"}, + {file = "fonttools-4.51.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:180194c7fe60c989bb627d7ed5011f2bef1c4d36ecf3ec64daec8302f1ae0716"}, + {file = "fonttools-4.51.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:96a48e137c36be55e68845fc4284533bda2980f8d6f835e26bca79d7e2006438"}, + {file = "fonttools-4.51.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:806e7912c32a657fa39d2d6eb1d3012d35f841387c8fc6cf349ed70b7c340039"}, + {file = "fonttools-4.51.0-cp310-cp310-win32.whl", hash = "sha256:32b17504696f605e9e960647c5f64b35704782a502cc26a37b800b4d69ff3c77"}, + {file = "fonttools-4.51.0-cp310-cp310-win_amd64.whl", hash = "sha256:c7e91abdfae1b5c9e3a543f48ce96013f9a08c6c9668f1e6be0beabf0a569c1b"}, + {file = "fonttools-4.51.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a8feca65bab31479d795b0d16c9a9852902e3a3c0630678efb0b2b7941ea9c74"}, + {file = "fonttools-4.51.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ac27f436e8af7779f0bb4d5425aa3535270494d3bc5459ed27de3f03151e4c2"}, + {file = "fonttools-4.51.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e19bd9e9964a09cd2433a4b100ca7f34e34731e0758e13ba9a1ed6e5468cc0f"}, + {file = "fonttools-4.51.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2b92381f37b39ba2fc98c3a45a9d6383bfc9916a87d66ccb6553f7bdd129097"}, + {file = "fonttools-4.51.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5f6bc991d1610f5c3bbe997b0233cbc234b8e82fa99fc0b2932dc1ca5e5afec0"}, + {file = "fonttools-4.51.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9696fe9f3f0c32e9a321d5268208a7cc9205a52f99b89479d1b035ed54c923f1"}, + {file = "fonttools-4.51.0-cp311-cp311-win32.whl", hash = "sha256:3bee3f3bd9fa1d5ee616ccfd13b27ca605c2b4270e45715bd2883e9504735034"}, + {file = "fonttools-4.51.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f08c901d3866a8905363619e3741c33f0a83a680d92a9f0e575985c2634fcc1"}, + {file = "fonttools-4.51.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4060acc2bfa2d8e98117828a238889f13b6f69d59f4f2d5857eece5277b829ba"}, + {file = "fonttools-4.51.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1250e818b5f8a679ad79660855528120a8f0288f8f30ec88b83db51515411fcc"}, + {file = "fonttools-4.51.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76f1777d8b3386479ffb4a282e74318e730014d86ce60f016908d9801af9ca2a"}, + {file = "fonttools-4.51.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b5ad456813d93b9c4b7ee55302208db2b45324315129d85275c01f5cb7e61a2"}, + {file = "fonttools-4.51.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:68b3fb7775a923be73e739f92f7e8a72725fd333eab24834041365d2278c3671"}, + {file = "fonttools-4.51.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8e2f1a4499e3b5ee82c19b5ee57f0294673125c65b0a1ff3764ea1f9db2f9ef5"}, + {file = "fonttools-4.51.0-cp312-cp312-win32.whl", hash = "sha256:278e50f6b003c6aed19bae2242b364e575bcb16304b53f2b64f6551b9c000e15"}, + {file = "fonttools-4.51.0-cp312-cp312-win_amd64.whl", hash = "sha256:b3c61423f22165541b9403ee39874dcae84cd57a9078b82e1dce8cb06b07fa2e"}, + {file = "fonttools-4.51.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1621ee57da887c17312acc4b0e7ac30d3a4fb0fec6174b2e3754a74c26bbed1e"}, + {file = "fonttools-4.51.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d9298be7a05bb4801f558522adbe2feea1b0b103d5294ebf24a92dd49b78e5"}, + {file = "fonttools-4.51.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee1af4be1c5afe4c96ca23badd368d8dc75f611887fb0c0dac9f71ee5d6f110e"}, + {file = "fonttools-4.51.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c18b49adc721a7d0b8dfe7c3130c89b8704baf599fb396396d07d4aa69b824a1"}, + {file = "fonttools-4.51.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de7c29bdbdd35811f14493ffd2534b88f0ce1b9065316433b22d63ca1cd21f14"}, + {file = "fonttools-4.51.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cadf4e12a608ef1d13e039864f484c8a968840afa0258b0b843a0556497ea9ed"}, + {file = "fonttools-4.51.0-cp38-cp38-win32.whl", hash = "sha256:aefa011207ed36cd280babfaa8510b8176f1a77261833e895a9d96e57e44802f"}, + {file = "fonttools-4.51.0-cp38-cp38-win_amd64.whl", hash = "sha256:865a58b6e60b0938874af0968cd0553bcd88e0b2cb6e588727117bd099eef836"}, + {file = "fonttools-4.51.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:60a3409c9112aec02d5fb546f557bca6efa773dcb32ac147c6baf5f742e6258b"}, + {file = "fonttools-4.51.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7e89853d8bea103c8e3514b9f9dc86b5b4120afb4583b57eb10dfa5afbe0936"}, + {file = "fonttools-4.51.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56fc244f2585d6c00b9bcc59e6593e646cf095a96fe68d62cd4da53dd1287b55"}, + {file = "fonttools-4.51.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d145976194a5242fdd22df18a1b451481a88071feadf251221af110ca8f00ce"}, + {file = "fonttools-4.51.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5b8cab0c137ca229433570151b5c1fc6af212680b58b15abd797dcdd9dd5051"}, + {file = "fonttools-4.51.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:54dcf21a2f2d06ded676e3c3f9f74b2bafded3a8ff12f0983160b13e9f2fb4a7"}, + {file = "fonttools-4.51.0-cp39-cp39-win32.whl", hash = "sha256:0118ef998a0699a96c7b28457f15546815015a2710a1b23a7bf6c1be60c01636"}, + {file = "fonttools-4.51.0-cp39-cp39-win_amd64.whl", hash = "sha256:599bdb75e220241cedc6faebfafedd7670335d2e29620d207dd0378a4e9ccc5a"}, + {file = "fonttools-4.51.0-py3-none-any.whl", hash = "sha256:15c94eeef6b095831067f72c825eb0e2d48bb4cea0647c1b05c981ecba2bf39f"}, + {file = "fonttools-4.51.0.tar.gz", hash = "sha256:dc0673361331566d7a663d7ce0f6fdcbfbdc1f59c6e3ed1165ad7202ca183c68"}, ] [package.extras] @@ -350,36 +350,32 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "h5py" -version = "3.10.0" +version = "3.11.0" description = "Read and write HDF5 files from Python" optional = false python-versions = ">=3.8" files = [ - {file = "h5py-3.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b963fb772964fc1d1563c57e4e2e874022ce11f75ddc6df1a626f42bd49ab99f"}, - {file = "h5py-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:012ab448590e3c4f5a8dd0f3533255bc57f80629bf7c5054cf4c87b30085063c"}, - {file = "h5py-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:781a24263c1270a62cd67be59f293e62b76acfcc207afa6384961762bb88ea03"}, - {file = "h5py-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f42e6c30698b520f0295d70157c4e202a9e402406f50dc08f5a7bc416b24e52d"}, - {file = "h5py-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:93dd840bd675787fc0b016f7a05fc6efe37312a08849d9dd4053fd0377b1357f"}, - {file = "h5py-3.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2381e98af081b6df7f6db300cd88f88e740649d77736e4b53db522d8874bf2dc"}, - {file = "h5py-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:667fe23ab33d5a8a6b77970b229e14ae3bb84e4ea3382cc08567a02e1499eedd"}, - {file = "h5py-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90286b79abd085e4e65e07c1bd7ee65a0f15818ea107f44b175d2dfe1a4674b7"}, - {file = "h5py-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c013d2e79c00f28ffd0cc24e68665ea03ae9069e167087b2adb5727d2736a52"}, - {file = "h5py-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:92273ce69ae4983dadb898fd4d3bea5eb90820df953b401282ee69ad648df684"}, - {file = "h5py-3.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c97d03f87f215e7759a354460fb4b0d0f27001450b18b23e556e7856a0b21c3"}, - {file = "h5py-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86df4c2de68257b8539a18646ceccdcf2c1ce6b1768ada16c8dcfb489eafae20"}, - {file = "h5py-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba9ab36be991119a3ff32d0c7cbe5faf9b8d2375b5278b2aea64effbeba66039"}, - {file = "h5py-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c8e4fda19eb769e9a678592e67eaec3a2f069f7570c82d2da909c077aa94339"}, - {file = "h5py-3.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:492305a074327e8d2513011fa9fffeb54ecb28a04ca4c4227d7e1e9616d35641"}, - {file = "h5py-3.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9450464b458cca2c86252b624279115dcaa7260a40d3cb1594bf2b410a2bd1a3"}, - {file = "h5py-3.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd6f6d1384a9f491732cee233b99cd4bfd6e838a8815cc86722f9d2ee64032af"}, - {file = "h5py-3.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3074ec45d3dc6e178c6f96834cf8108bf4a60ccb5ab044e16909580352010a97"}, - {file = "h5py-3.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:212bb997a91e6a895ce5e2f365ba764debeaef5d2dca5c6fb7098d66607adf99"}, - {file = "h5py-3.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5dfc65ac21fa2f630323c92453cadbe8d4f504726ec42f6a56cf80c2f90d6c52"}, - {file = "h5py-3.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4682b94fd36ab217352be438abd44c8f357c5449b8995e63886b431d260f3d3"}, - {file = "h5py-3.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aece0e2e1ed2aab076c41802e50a0c3e5ef8816d60ece39107d68717d4559824"}, - {file = "h5py-3.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43a61b2c2ad65b1fabc28802d133eed34debcc2c8b420cb213d3d4ef4d3e2229"}, - {file = "h5py-3.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae2f0201c950059676455daf92700eeb57dcf5caaf71b9e1328e6e6593601770"}, - {file = "h5py-3.10.0.tar.gz", hash = "sha256:d93adc48ceeb33347eb24a634fb787efc7ae4644e6ea4ba733d099605045c049"}, + {file = "h5py-3.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1625fd24ad6cfc9c1ccd44a66dac2396e7ee74940776792772819fc69f3a3731"}, + {file = "h5py-3.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c072655ad1d5fe9ef462445d3e77a8166cbfa5e599045f8aa3c19b75315f10e5"}, + {file = "h5py-3.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77b19a40788e3e362b54af4dcf9e6fde59ca016db2c61360aa30b47c7b7cef00"}, + {file = "h5py-3.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef4e2f338fc763f50a8113890f455e1a70acd42a4d083370ceb80c463d803972"}, + {file = "h5py-3.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbd732a08187a9e2a6ecf9e8af713f1d68256ee0f7c8b652a32795670fb481ba"}, + {file = "h5py-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75bd7b3d93fbeee40860fd70cdc88df4464e06b70a5ad9ce1446f5f32eb84007"}, + {file = "h5py-3.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c416f8eb0daae39dabe71415cb531f95dce2d81e1f61a74537a50c63b28ab3"}, + {file = "h5py-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:083e0329ae534a264940d6513f47f5ada617da536d8dccbafc3026aefc33c90e"}, + {file = "h5py-3.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a76cae64080210389a571c7d13c94a1a6cf8cb75153044fd1f822a962c97aeab"}, + {file = "h5py-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3736fe21da2b7d8a13fe8fe415f1272d2a1ccdeff4849c1421d2fb30fd533bc"}, + {file = "h5py-3.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6ae84a14103e8dc19266ef4c3e5d7c00b68f21d07f2966f0ca7bdb6c2761fb"}, + {file = "h5py-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:21dbdc5343f53b2e25404673c4f00a3335aef25521bd5fa8c707ec3833934892"}, + {file = "h5py-3.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:754c0c2e373d13d6309f408325343b642eb0f40f1a6ad21779cfa9502209e150"}, + {file = "h5py-3.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:731839240c59ba219d4cb3bc5880d438248533366f102402cfa0621b71796b62"}, + {file = "h5py-3.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ec9df3dd2018904c4cc06331951e274f3f3fd091e6d6cc350aaa90fa9b42a76"}, + {file = "h5py-3.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:55106b04e2c83dfb73dc8732e9abad69d83a436b5b82b773481d95d17b9685e1"}, + {file = "h5py-3.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f4e025e852754ca833401777c25888acb96889ee2c27e7e629a19aee288833f0"}, + {file = "h5py-3.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c4b760082626120031d7902cd983d8c1f424cdba2809f1067511ef283629d4b"}, + {file = "h5py-3.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67462d0669f8f5459529de179f7771bd697389fcb3faab54d63bf788599a48ea"}, + {file = "h5py-3.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:d9c944d364688f827dc889cf83f1fca311caf4fa50b19f009d1f2b525edd33a3"}, + {file = "h5py-3.11.0.tar.gz", hash = "sha256:7b7e8f78072a2edec87c9836f25f34203fd492a4475709a18b417a33cfb21fa9"}, ] [package.dependencies] @@ -430,13 +426,13 @@ files = [ [[package]] name = "joblib" -version = "1.3.2" +version = "1.4.0" description = "Lightweight pipelining with Python functions" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"}, - {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"}, + {file = "joblib-1.4.0-py3-none-any.whl", hash = "sha256:42942470d4062537be4d54c83511186da1fc14ba354961a2114da91efa9a4ed7"}, + {file = "joblib-1.4.0.tar.gz", hash = "sha256:1eb0dc091919cd384490de890cb5dfd538410a6d4b3b54eef09fb8c50b409b1c"}, ] [[package]] @@ -1164,37 +1160,37 @@ files = [ [[package]] name = "scikit-learn" -version = "1.4.1.post1" +version = "1.4.2" description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.9" files = [ - {file = "scikit-learn-1.4.1.post1.tar.gz", hash = "sha256:93d3d496ff1965470f9977d05e5ec3376fb1e63b10e4fda5e39d23c2d8969a30"}, - {file = "scikit_learn-1.4.1.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c540aaf44729ab5cd4bd5e394f2b375e65ceaea9cdd8c195788e70433d91bbc5"}, - {file = "scikit_learn-1.4.1.post1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4310bff71aa98b45b46cd26fa641309deb73a5d1c0461d181587ad4f30ea3c36"}, - {file = "scikit_learn-1.4.1.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f43dd527dabff5521af2786a2f8de5ba381e182ec7292663508901cf6ceaf6e"}, - {file = "scikit_learn-1.4.1.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c02e27d65b0c7dc32f2c5eb601aaf5530b7a02bfbe92438188624524878336f2"}, - {file = "scikit_learn-1.4.1.post1-cp310-cp310-win_amd64.whl", hash = "sha256:629e09f772ad42f657ca60a1a52342eef786218dd20cf1369a3b8d085e55ef8f"}, - {file = "scikit_learn-1.4.1.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6145dfd9605b0b50ae72cdf72b61a2acd87501369a763b0d73d004710ebb76b5"}, - {file = "scikit_learn-1.4.1.post1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1afed6951bc9d2053c6ee9a518a466cbc9b07c6a3f9d43bfe734192b6125d508"}, - {file = "scikit_learn-1.4.1.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce03506ccf5f96b7e9030fea7eb148999b254c44c10182ac55857bc9b5d4815f"}, - {file = "scikit_learn-1.4.1.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ba516fcdc73d60e7f48cbb0bccb9acbdb21807de3651531208aac73c758e3ab"}, - {file = "scikit_learn-1.4.1.post1-cp311-cp311-win_amd64.whl", hash = "sha256:78cd27b4669513b50db4f683ef41ea35b5dddc797bd2bbd990d49897fd1c8a46"}, - {file = "scikit_learn-1.4.1.post1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a1e289f33f613cefe6707dead50db31930530dc386b6ccff176c786335a7b01c"}, - {file = "scikit_learn-1.4.1.post1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0df87de9ce1c0140f2818beef310fb2e2afdc1e66fc9ad587965577f17733649"}, - {file = "scikit_learn-1.4.1.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712c1c69c45b58ef21635360b3d0a680ff7d83ac95b6f9b82cf9294070cda710"}, - {file = "scikit_learn-1.4.1.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1754b0c2409d6ed5a3380512d0adcf182a01363c669033a2b55cca429ed86a81"}, - {file = "scikit_learn-1.4.1.post1-cp312-cp312-win_amd64.whl", hash = "sha256:1d491ef66e37f4e812db7e6c8286520c2c3fc61b34bf5e59b67b4ce528de93af"}, - {file = "scikit_learn-1.4.1.post1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aa0029b78ef59af22cfbd833e8ace8526e4df90212db7ceccbea582ebb5d6794"}, - {file = "scikit_learn-1.4.1.post1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:14e4c88436ac96bf69eb6d746ac76a574c314a23c6961b7d344b38877f20fee1"}, - {file = "scikit_learn-1.4.1.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7cd3a77c32879311f2aa93466d3c288c955ef71d191503cf0677c3340ae8ae0"}, - {file = "scikit_learn-1.4.1.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3ee19211ded1a52ee37b0a7b373a8bfc66f95353af058a210b692bd4cda0dd"}, - {file = "scikit_learn-1.4.1.post1-cp39-cp39-win_amd64.whl", hash = "sha256:234b6bda70fdcae9e4abbbe028582ce99c280458665a155eed0b820599377d25"}, + {file = "scikit-learn-1.4.2.tar.gz", hash = "sha256:daa1c471d95bad080c6e44b4946c9390a4842adc3082572c20e4f8884e39e959"}, + {file = "scikit_learn-1.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8539a41b3d6d1af82eb629f9c57f37428ff1481c1e34dddb3b9d7af8ede67ac5"}, + {file = "scikit_learn-1.4.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:68b8404841f944a4a1459b07198fa2edd41a82f189b44f3e1d55c104dbc2e40c"}, + {file = "scikit_learn-1.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81bf5d8bbe87643103334032dd82f7419bc8c8d02a763643a6b9a5c7288c5054"}, + {file = "scikit_learn-1.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36f0ea5d0f693cb247a073d21a4123bdf4172e470e6d163c12b74cbb1536cf38"}, + {file = "scikit_learn-1.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:87440e2e188c87db80ea4023440923dccbd56fbc2d557b18ced00fef79da0727"}, + {file = "scikit_learn-1.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:45dee87ac5309bb82e3ea633955030df9bbcb8d2cdb30383c6cd483691c546cc"}, + {file = "scikit_learn-1.4.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1d0b25d9c651fd050555aadd57431b53d4cf664e749069da77f3d52c5ad14b3b"}, + {file = "scikit_learn-1.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0203c368058ab92efc6168a1507d388d41469c873e96ec220ca8e74079bf62e"}, + {file = "scikit_learn-1.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44c62f2b124848a28fd695db5bc4da019287abf390bfce602ddc8aa1ec186aae"}, + {file = "scikit_learn-1.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:5cd7b524115499b18b63f0c96f4224eb885564937a0b3477531b2b63ce331904"}, + {file = "scikit_learn-1.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90378e1747949f90c8f385898fff35d73193dfcaec3dd75d6b542f90c4e89755"}, + {file = "scikit_learn-1.4.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ff4effe5a1d4e8fed260a83a163f7dbf4f6087b54528d8880bab1d1377bd78be"}, + {file = "scikit_learn-1.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:671e2f0c3f2c15409dae4f282a3a619601fa824d2c820e5b608d9d775f91780c"}, + {file = "scikit_learn-1.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d36d0bc983336bbc1be22f9b686b50c964f593c8a9a913a792442af9bf4f5e68"}, + {file = "scikit_learn-1.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:d762070980c17ba3e9a4a1e043ba0518ce4c55152032f1af0ca6f39b376b5928"}, + {file = "scikit_learn-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9993d5e78a8148b1d0fdf5b15ed92452af5581734129998c26f481c46586d68"}, + {file = "scikit_learn-1.4.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:426d258fddac674fdf33f3cb2d54d26f49406e2599dbf9a32b4d1696091d4256"}, + {file = "scikit_learn-1.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5460a1a5b043ae5ae4596b3126a4ec33ccba1b51e7ca2c5d36dac2169f62ab1d"}, + {file = "scikit_learn-1.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d64ef6cb8c093d883e5a36c4766548d974898d378e395ba41a806d0e824db8"}, + {file = "scikit_learn-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:c97a50b05c194be9146d61fe87dbf8eac62b203d9e87a3ccc6ae9aed2dfaf361"}, ] [package.dependencies] joblib = ">=1.2.0" -numpy = ">=1.19.5,<2.0" +numpy = ">=1.19.5" scipy = ">=1.6.0" threadpoolctl = ">=2.0.0" @@ -1392,13 +1388,13 @@ pandas-stubs = "*" [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] From 0a8888a18c22ddb8ce5bb312de342258c8397756 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 15:59:30 -0400 Subject: [PATCH 011/103] improve type hints --- .../nmf_framework/initialization.py | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/salamander/nmf_framework/initialization.py b/src/salamander/nmf_framework/initialization.py index d22cd2d..dfc6517 100644 --- a/src/salamander/nmf_framework/initialization.py +++ b/src/salamander/nmf_framework/initialization.py @@ -1,13 +1,18 @@ """ Initialization methods for non-negative matrix factorization (NMF) """ +from __future__ import annotations + +from typing import Literal, get_args + import numpy as np +import pandas as pd from sklearn.decomposition import _nmf as sknmf from ..utils import normalize_WH, shape_checker, type_checker, value_checker EPSILON = np.finfo(np.float32).eps -INIT_METHODS = [ +_Init_methods = Literal[ "custom", "flat", "hierarchical_cluster", @@ -17,11 +22,12 @@ "random", "separableNMF", ] +_INIT_METHODS = get_args(_Init_methods) def init_custom( X: np.ndarray, n_signatures: int, W_custom: np.ndarray, H_custom: np.ndarray -): +) -> tuple[np.ndarray, np.ndarray]: """ Perform type and shape checks on custom signature and exposure matrix initializations. @@ -34,7 +40,7 @@ def init_custom( return W_custom, H_custom -def init_flat(X: np.ndarray, n_signatures: int): +def init_flat(X: np.ndarray, n_signatures: int) -> tuple[np.ndarray, np.ndarray]: """ Initialize the signature and exposure matrices with one float, respectively. """ @@ -45,7 +51,12 @@ def init_flat(X: np.ndarray, n_signatures: int): return W, H -def init_nndsvd(X: np.ndarray, n_signatures: int, init: str, seed=None): +def init_nndsvd( + X: np.ndarray, + n_signatures: int, + init: Literal["nndsvd", "nndsvda", "nndsvdar"], + seed: int | None = None, +) -> tuple[np.ndarray, np.ndarray]: """ A wrapper around the non-negative double singular value decomposition (NNDSVD) initialization methods "nndsvd", "nndsvda" and "nndsvdar" from scikit-learn. @@ -64,7 +75,9 @@ def init_nndsvd(X: np.ndarray, n_signatures: int, init: str, seed=None): return W, H -def init_random(X: np.ndarray, n_signatures: int, seed=None): +def init_random( + X: np.ndarray, n_signatures: int, seed: int | None = None +) -> tuple[np.ndarray, np.ndarray]: """ Initialize each signature by drawing from the uniform distribution on the simplex. @@ -83,7 +96,9 @@ def init_random(X: np.ndarray, n_signatures: int, seed=None): return W, H -def init_separableNMF(X: np.ndarray, n_signatures: int, seed=None): +def init_separableNMF( + X: np.ndarray, n_signatures: int, seed: int | None = None +) -> tuple[np.ndarray, np.ndarray]: r""" This code is following Algorithm 1 from "Fast and Robust Recursive Algorithms for Separable Nonnegative Matrix Factorization" @@ -109,15 +124,15 @@ def init_separableNMF(X: np.ndarray, n_signatures: int, seed=None): def initialize( X: np.ndarray, n_signatures: int, - init_method="nndsvd", - given_signatures=None, + init_method: _Init_methods = "nndsvd", + given_signatures: pd.DataFrame | None = None, **kwargs, -): +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """ Initialize the signature and exposure matrices. - Parameters - ---------- + Inputs + ------ X : np.ndarray count matrix @@ -126,12 +141,12 @@ def initialize( init_method : str initialization method. One of 'custom', 'flat', 'hierarchical_cluster', - 'nndsvd', 'nndsvda', 'nndsvdar', 'random', 'separableNMF' + 'nndsvd', 'nndsvda', 'nndsvdar', 'random', 'separableNMF'. - given_signatures : pd.Dataframe, default=None + given_signatures : pd.Dataframe, optional At most 'n_signatures' many signatures can be provided to overwrite some of the initialized signatures. This does not - change the initialized exposurse. + change the initialized exposurses. kwargs : dict Any keyword arguments to be passed to the initialization method. @@ -146,13 +161,13 @@ def initialize( H : np.ndarray exposure matrix - signature_names : list + signature_names : np.ndarray The signature names. By default, the signatures are named 'Sigk', where 'k' is one plus the index of the signature. If 'given_signatures' are provided, the names are adjusted accordingly. """ - value_checker("init_method", init_method, INIT_METHODS) + value_checker("init_method", init_method, _INIT_METHODS) if init_method == "custom": W, H = init_custom(X, n_signatures, **kwargs) @@ -161,7 +176,8 @@ def initialize( W, H = init_flat(X, n_signatures) elif init_method in ["nndsvd", "nndsvda", "nndsvdar"]: - W, H = init_nndsvd(X, n_signatures, init=init_method, **kwargs) + # mypy does not recognize that init_method is compatible with Literal["nndsvd", "nndsvda", "nndsvdar"] + W, H = init_nndsvd(X, n_signatures, init=init_method, **kwargs) # type: ignore[arg-type] elif init_method == "random": W, H = init_random(X, n_signatures, **kwargs) From c741759a952e938a876b4237a7c370d86594bbb1 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 15:59:54 -0400 Subject: [PATCH 012/103] improve type hints --- src/salamander/nmf_framework/_utils_klnmf.py | 22 +++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/salamander/nmf_framework/_utils_klnmf.py b/src/salamander/nmf_framework/_utils_klnmf.py index 97d20cb..2722d41 100644 --- a/src/salamander/nmf_framework/_utils_klnmf.py +++ b/src/salamander/nmf_framework/_utils_klnmf.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import numpy as np from numba import njit from scipy.special import gammaln @@ -6,7 +8,9 @@ @njit(fastmath=True) -def kl_divergence(X: np.ndarray, W: np.ndarray, H: np.ndarray, weights=None) -> float: +def kl_divergence( + X: np.ndarray, W: np.ndarray, H: np.ndarray, weights: np.ndarray | None = None +) -> float: r""" The generalized Kullback-Leibler divergence D_KL(X || WH) = \sum_vd X_vd * ln(X_vd / (WH)_vd) - \sum_vd X_vd + \sum_vd (WH)_vd. @@ -51,7 +55,7 @@ def kl_divergence(X: np.ndarray, W: np.ndarray, H: np.ndarray, weights=None) -> def samplewise_kl_divergence( - X: np.ndarray, W: np.ndarray, H: np.ndarray, weights=None + X: np.ndarray, W: np.ndarray, H: np.ndarray, weights: np.ndarray | None = None ) -> np.ndarray: """ Per sample (weighted) generalized Kullback-Leibler divergence D_KL(x || Wh). @@ -161,7 +165,7 @@ def update_W( X: np.ndarray, W: np.ndarray, H: np.ndarray, - weights_kl=None, + weights_kl: np.ndarray | None = None, n_given_signatures: int = 0, ) -> np.ndarray: """ @@ -214,7 +218,11 @@ def update_W( @njit def update_H( - X: np.ndarray, W: np.ndarray, H: np.ndarray, weights_kl=None, weights_l_half=None + X: np.ndarray, + W: np.ndarray, + H: np.ndarray, + weights_kl: np.ndarray | None = None, + weights_l_half: np.ndarray | None = None, ) -> np.ndarray: """ The multiplicative update rule of the exposure matrix H @@ -274,10 +282,10 @@ def update_WH( X: np.ndarray, W: np.ndarray, H: np.ndarray, - weights_kl=None, - weights_l_half=None, + weights_kl: np.ndarray | None = None, + weights_l_half: np.ndarray | None = None, n_given_signatures: int = 0, -) -> np.ndarray: +) -> tuple[np.ndarray, np.ndarray]: """ A joint update rule for the signature matrix W and the exposure matrix H under the constraint of normalized From 23513a286f981334200b9e46fa9ba66e3a2cf388 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 16:03:48 -0400 Subject: [PATCH 013/103] improve structure Function names now include the names of the input data type if there are multiple versions, e.g., an implementation for numpy arrays and a wrapper around this implementation for AnnData inputs. --- src/salamander/plot.py | 56 +++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/salamander/plot.py b/src/salamander/plot.py index 5627513..4884fa9 100644 --- a/src/salamander/plot.py +++ b/src/salamander/plot.py @@ -1,15 +1,16 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING, Any, Iterable +from typing import TYPE_CHECKING, Iterable -import fastcluster # type: ignore +import fastcluster import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as sns -from adjustText import adjust_text # type: ignore +from adjustText import adjust_text +from matplotlib.axes import Axes from scipy.cluster import hierarchy from scipy.spatial.distance import pdist @@ -17,8 +18,9 @@ from .utils import _get_basis_obsm, _get_basis_obsp, match_to_catalog, value_checker if TYPE_CHECKING: + from typing import Any + from anndata import AnnData - from matplotlib.axes import Axes from matplotlib.colors import Colormap from matplotlib.typing import ColorType from seaborn.matrix import ClusterGrid @@ -33,10 +35,8 @@ def set_salamander_style(): "axes.spines.top": False, "axes.spines.right": False, "axes.titlesize": "large", - "errorbar.capsize": 3, "font.family": "DejaVu Sans", "legend.fontsize": "medium", - "lines.markersize": 8, "pdf.fonttype": 42, "xtick.labelsize": "small", "ytick.labelsize": "small", @@ -44,7 +44,7 @@ def set_salamander_style(): mpl.rcParams.update(params) -def history_plot( +def history( values: np.ndarray, conv_test_freq: int, min_iteration: int = 0, @@ -153,7 +153,7 @@ def _scatter_2d( return ax -def _scatter( +def scatter_numpy( data: np.ndarray, xlabel: str | None = None, ylabel: str | None = None, @@ -197,7 +197,21 @@ def scatter(adata: AnnData, x: str, y: str | None = None, **kwargs) -> Axes: else: data = adata.obs[[x, y]].to_numpy() - ax = _scatter(data, xlabel=x, ylabel=y, **kwargs) + ax = scatter_numpy(data, xlabel=x, ylabel=y, **kwargs) + return ax + + +def embedding_numpy( + data: np.ndarray, + dimensions: tuple[int, int] = (0, 1), + xlabel: str | None = None, + ylabel: str | None = None, + **kwargs, +): + if data.ndim == 2 and data.shape[1] > 2: + data = data[:, dimensions] + + ax = scatter_numpy(data, xlabel=xlabel, ylabel=ylabel, **kwargs) return ax @@ -220,13 +234,10 @@ def embedding( dimensions: tuple[int, int] = (0, 1), xlabel: str | None = None, ylabel: str | None = None, + ticks: bool | None = None, **kwargs, ) -> Axes: data = _get_basis_obsm(adata, basis) - - if data.ndim == 2 and data.shape[1] > 2: - data = data[dimensions] - name = _basisobsm2name(basis) labels = [f"{name}{d+1}" for d in dimensions] @@ -236,7 +247,12 @@ def embedding( if ylabel is None: ylabel = labels[1] - ax = _scatter(data, xlabel=xlabel, ylabel=ylabel, **kwargs) + if ticks is None: + ticks = False if basis in ["tsne", "umap"] else True + + ax = embedding_numpy( + data, dimensions=dimensions, xlabel=xlabel, ylabel=ylabel, ticks=ticks, **kwargs + ) return ax @@ -245,14 +261,14 @@ def pca(adata: AnnData, **kwargs) -> Axes: def tsne(adata: AnnData, **kwargs) -> Axes: - return embedding(adata, basis="tsne", ticks=False, **kwargs) + return embedding(adata, basis="tsne", **kwargs) def umap(adata: AnnData, **kwargs) -> Axes: - return embedding(adata, basis="umap", ticks=False, **kwargs) + return embedding(adata, basis="umap", **kwargs) -def _correlation( +def correlation_pandas( corr: pd.DataFrame, figsize: tuple[float, float] = (4.0, 4.0), cmap: Colormap | str | None = "vlag", @@ -279,7 +295,7 @@ def correlation(adata: AnnData, **kwargs) -> ClusterGrid: index=adata.obs_names, columns=adata.obs_names, ) - return _correlation(corr, **kwargs) + return correlation_pandas(corr, **kwargs) def _get_colors_barplot(var_names, colors=None): @@ -453,7 +469,7 @@ def _barplot_matched( return axes -def _barplot( +def barplot_pandas( data: pd.DataFrame, catalog: pd.DataFrame | None = None, colors: ColorType | list[ColorType] | None = None, @@ -527,7 +543,7 @@ def _barplot( def barplot(adata: AnnData, **kwargs): - return _barplot(adata.to_df(), **kwargs) + return barplot_pandas(adata.to_df(), **kwargs) def _get_obs_order(data: pd.DataFrame, normalize: bool = True) -> np.ndarray: From df58375c65e0ed5516ace5bf5cc1e9da8efd32a8 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 16:04:13 -0400 Subject: [PATCH 014/103] improve docstrings --- src/salamander/plot.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/salamander/plot.py b/src/salamander/plot.py index 4884fa9..dd8048b 100644 --- a/src/salamander/plot.py +++ b/src/salamander/plot.py @@ -349,8 +349,8 @@ def _barplot_single( Plot the relative values of a non-negative dataframe with a single row. - Inputs: - ------- + Inputs + ------ data: pd.DataFrame A dataframe with only one row, typically a single signature or the feature counts of a single sample. @@ -416,8 +416,8 @@ def _barplot_matched( with a single row. The closest matching row from a 'catalog' can also be plotted. - Inputs: - ------- + Inputs + ------ data: pd.DataFrame A dataframe with only one row, typically a single mutational signature. @@ -481,8 +481,8 @@ def barplot_pandas( Plot the relative values of the rows of a non-negative dataframe. The closest matching rows from a 'catalog' can also be plotted. - Inputs: - ------- + Inputs + ------ data : pd.DataFrame Annotated dataframe of shape (n_obs, n_vars), typically a collection of mutational signatures. @@ -552,8 +552,8 @@ def _get_obs_order(data: pd.DataFrame, normalize: bool = True) -> np.ndarray: of a non-negative data array of shape (n_obs, n_dimensions) for a stacked barchart using hierarchical clustering. - Parameters - ---------- + Inputs + ------ data : pd.DataFrame of shape (n_obs, n_dimensions) An annotated non-negative data matrix, typically the signature exposures. @@ -590,8 +590,8 @@ def _reorder_data( Reorder non-negative data using hierarchical clustering and optionally reorder the dimensions by their total relative sums. - Parameters - ---------- + Inputs + ------ data : pd.DataFrame of shape (n_obs, n_dimensions) An annotated non-negative data matrix, typically the signature exposures. @@ -641,8 +641,8 @@ def stacked_barplot( Visualize non-negative data with a stacked bar chart, typically the signature exposures. - Parameter - --------- + Inputs + ------ data : pd.DataFrame of shape (n_obs, n_dimensions) An annotated non-negative data matrix, typically the signature exposures. From 4e1c37e794a5b5de6f7d24c9ada1f59896370de7 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 16:06:25 -0400 Subject: [PATCH 015/103] add type hints --- src/salamander/plot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/salamander/plot.py b/src/salamander/plot.py index dd8048b..1fb861c 100644 --- a/src/salamander/plot.py +++ b/src/salamander/plot.py @@ -83,7 +83,7 @@ def _annotate_plot( adjust_annotations: bool = True, adjust_kwargs: dict[str, Any] | None = None, **kwargs, -): +) -> None: for data_point, annotation in zip(data, annotations): ax.text( data_point[0], @@ -207,7 +207,7 @@ def embedding_numpy( xlabel: str | None = None, ylabel: str | None = None, **kwargs, -): +) -> Axes: if data.ndim == 2 and data.shape[1] > 2: data = data[:, dimensions] @@ -542,7 +542,7 @@ def barplot_pandas( return axes -def barplot(adata: AnnData, **kwargs): +def barplot(adata: AnnData, **kwargs) -> Axes | Iterable[Axes]: return barplot_pandas(adata.to_df(), **kwargs) From 07b1000a8d4467c126a93d1032268df59ece2597 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 16:10:15 -0400 Subject: [PATCH 016/103] add argument n_components is now an explicit argument of the dimensionality reduction. The motivation was to improve the transparency of the code. --- src/salamander/tools.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/salamander/tools.py b/src/salamander/tools.py index d83456f..7e74b27 100644 --- a/src/salamander/tools.py +++ b/src/salamander/tools.py @@ -79,7 +79,11 @@ def umap(adata: AnnData, basis: str, **kwargs) -> None: def _reduce_dimension( - data: np.ndarray, method: str = "umap", normalize: bool = False, **kwargs + data: np.ndarray, + method: str = "umap", + n_components: int = 2, + normalize: bool = False, + **kwargs, ) -> np.ndarray: value_checker("method", method, ["pca", "tsne", "umap"]) @@ -97,17 +101,17 @@ def _reduce_dimension( return data if method == "pca": - data_reduced_dim = _pca(data, **kwargs) + data_reduced_dim = _pca(data, n_components=n_components, **kwargs) elif method == "tsne": - data_reduced_dim = _tsne(data, **kwargs) + data_reduced_dim = _tsne(data, n_components=n_components, **kwargs) else: - data_reduced_dim = _umap(data, **kwargs) + data_reduced_dim = _umap(data, n_components=n_components, **kwargs) return data_reduced_dim def reduce_dimension( - adata: AnnData, basis: str, method="umap", normalize: bool = False, **kwargs + adata: AnnData, basis: str, method="umap", n_components: int = 2, **kwargs ) -> None: """ Compute and store a dimensionality reduction of the multi-dimensional @@ -125,7 +129,7 @@ def reduce_dimension( return adata.obsm[f"X_{method}"] = _reduce_dimension( - data, method=method, normalize=normalize, **kwargs + data, method=method, n_components=n_components, **kwargs ) From 0b183950a831af20595630f5dce6367ef0f75933 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 16:12:05 -0400 Subject: [PATCH 017/103] add function A joint dimensionality reduction of multidimensional observation annotations of multiple anndata objects is useful for models with shared embedding spaces, e.g. (multimodal) correlated NMF. --- src/salamander/tools.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/salamander/tools.py b/src/salamander/tools.py index 7e74b27..e2e6912 100644 --- a/src/salamander/tools.py +++ b/src/salamander/tools.py @@ -9,6 +9,8 @@ from .utils import _get_basis_obsm, value_checker if TYPE_CHECKING: + from typing import Iterable + from anndata import AnnData @@ -133,6 +135,33 @@ def reduce_dimension( ) +def reduce_dimension_multiple( + adatas: Iterable[AnnData], basis: str, method="umap", **kwargs +) -> None: + """ + Compute a joint dimensionality reduction of the same multi-dimensional observation + annotations of multiple AnnData instances. + """ + data = np.concatenate([_get_basis_obsm(adata, basis) for adata in adatas]) + n_dimensions = data.shape[1] + + if n_dimensions in [1, 2]: + warnings.warn( + f"The dimension of the observation annotations is {n_dimensions}. " + "No dimensionality reduction will be applied.", + UserWarning, + ) + return + + data_reduced_dim = _reduce_dimension(data, method=method, **kwargs) + sum_n_obs = 0 + + for adata in adatas: + n_obs = adata.n_obs + adata.obsm[f"X_{method}"] = data_reduced_dim[sum_n_obs : sum_n_obs + n_obs, :] + sum_n_obs += n_obs + + def _correlation(data: np.ndarray, **kwargs) -> np.ndarray: """ Compute the correlation of the rows of the data. From 5a5256f40aa7c4935920c159d2ca1ece4688d213 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 16:13:09 -0400 Subject: [PATCH 018/103] add type hinting --- src/salamander/utils.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/salamander/utils.py b/src/salamander/utils.py index 47f7462..175e292 100644 --- a/src/salamander/utils.py +++ b/src/salamander/utils.py @@ -9,12 +9,16 @@ from sklearn.metrics import pairwise_distances if TYPE_CHECKING: + from typing import Any, Iterable + from anndata import AnnData EPSILON = np.finfo(np.float32).eps -def shape_checker(arg_name: str, arg, allowed_shape): +def shape_checker( + arg_name: str, arg: np.ndarray | pd.DataFrame, allowed_shape: tuple[int, ...] +) -> None: """ A helper function to test the shape of a numpy ndarray or pandas dataframe. @@ -33,7 +37,7 @@ def shape_checker(arg_name: str, arg, allowed_shape): raise ValueError(f"The shape of '{arg_name}' has to be {allowed_shape}.") -def type_checker(arg_name: str, arg, allowed_types): +def type_checker(arg_name: str, arg: Any, allowed_types: type | Iterable[type]) -> None: """ A helper function to test the type of an argument. @@ -53,7 +57,7 @@ def type_checker(arg_name: str, arg, allowed_types): raise TypeError(f"The type of '{arg_name}' has to be one of {allowed_types}.") -def value_checker(arg_name: str, arg, allowed_values): +def value_checker(arg_name: str, arg: Any, allowed_values: Iterable[Any]) -> None: """ A helper function to test the value of an argument. @@ -66,7 +70,7 @@ def value_checker(arg_name: str, arg, allowed_values): allowed_values: A value or list of values allowed for 'arg' """ - if not isinstance(allowed_values, list): + if isinstance(allowed_values, type): allowed_values = [allowed_values] if arg not in allowed_values: @@ -102,12 +106,14 @@ def _get_basis_obsp(adata: AnnData, basis: str) -> np.ndarray: @njit -def normalize_WH(W, H): +def normalize_WH(W: np.ndarray, H: np.ndarray) -> tuple[np.ndarray, np.ndarray]: normalization_factor = np.sum(W, axis=0) return W / normalization_factor, H * normalization_factor[:, None] -def match_to_catalog(signatures: pd.DataFrame, catalog: pd.DataFrame, metric="cosine"): +def match_to_catalog( + signatures: pd.DataFrame, catalog: pd.DataFrame, metric="cosine" +) -> pd.DataFrame: """ Find the best matching signatures in catalog for all signatures. """ @@ -118,8 +124,8 @@ def match_to_catalog(signatures: pd.DataFrame, catalog: pd.DataFrame, metric="co def match_signatures_pair( - signatures1: pd.DataFrame, signatures2: pd.DataFrame, metric="cosine" -): + signatures1: pd.DataFrame, signatures2: pd.DataFrame, metric: str = "cosine" +) -> np.ndarray: """ Match a pair of signature catalogs using their pairwise distances, see https://en.wikipedia.org/wiki/Assignment_problem. From ee6204dfd665a1f5f3dc60f55832ff9b03641bed Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 16:19:19 -0400 Subject: [PATCH 019/103] refactor NMF algorithms are now built around AnnData: both the input data and the signatures are AnnData objects. The .fit() method is now more abstract and implemented in the root class SignatureNMF. --- src/salamander/nmf_framework/klnmf.py | 251 ++----- src/salamander/nmf_framework/signature_nmf.py | 699 +++++++++++------- src/salamander/nmf_framework/standard_nmf.py | 311 +++----- 3 files changed, 612 insertions(+), 649 deletions(-) diff --git a/src/salamander/nmf_framework/klnmf.py b/src/salamander/nmf_framework/klnmf.py index 2f44f87..4d60315 100644 --- a/src/salamander/nmf_framework/klnmf.py +++ b/src/salamander/nmf_framework/klnmf.py @@ -1,11 +1,23 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + import numpy as np import pandas as pd -from ..utils import shape_checker, type_checker, value_checker +from ..utils import shape_checker, type_checker from . import _utils_klnmf from .standard_nmf import StandardNMF -EPSILON = np.finfo(np.float32).eps +if TYPE_CHECKING: + from typing import Any, Literal + + from .initialization import _Init_methods + from .signature_nmf import _Dim_reduction_methods + + +_FITTING_KWARGS = ["weights_kl", "weights_lhalf"] +_DEFAULT_FITTING_KWARGS = {kwarg: None for kwarg in _FITTING_KWARGS} class KLNMF(StandardNMF): @@ -17,37 +29,6 @@ class KLNMF(StandardNMF): The implementation supports a sparstiy-inducing l_half penalty of the exposures. - Parameters - ---------- - n_signatures: int - Number of signatures to decipher. - - init_method : str, default=nndsvd - One of "custom", "flat", "hierarchical_cluster", "nndsvd", - "nndsvda", "nndsvdar" "random" and "separableNMF". Please see the initialization - module for further details on each method. - - update_method : str, default=mu-joint - One of "mu-standard" and "mu-joint". The standard multiplicative - update rules alternates between optimizing the signatures and exposures. - The joint multiplicative update rule updates both matrices at once. - It requires one matrix multiplication less and is faster. - - min_iterations : int, default=500 - Minimum number of iterations. - - max_iterations : int, default=10000 - Maximum number of iterations. - - conv_test_freq: int - The frequency at which the algorithm is tested for convergence. - The objective function value is only computed every 'conv_test_freq' - many iterations, which also affects a potentially saved history of - the objective function values. - - tol : float, default=1e-7 - Tolerance of the stopping condition. - Reference --------- D. Lee, H. Seung: Algorithms for Non-negative Matrix Factorization @@ -57,13 +38,12 @@ class KLNMF(StandardNMF): def __init__( self, - n_signatures=1, - init_method="nndsvd", - update_method="mu-joint", - min_iterations=500, - max_iterations=10000, - conv_test_freq=10, - tol=1e-7, + n_signatures: int = 1, + init_method: _Init_methods = "nndsvd", + min_iterations: int = 500, + max_iterations: int = 10000, + conv_test_freq: int = 10, + tol: float = 1e-7, ): super().__init__( n_signatures, @@ -73,82 +53,73 @@ def __init__( conv_test_freq, tol, ) - value_checker("update method", update_method, ["mu-standard", "mu-joint"]) - self.update_method = update_method self.weights_kl = None self.weights_l_half = None - @property - def reconstruction_error(self) -> float: - """ - The unweighted Kullback-Leibler divergence. - """ - return _utils_klnmf.kl_divergence(self.X, self.W, self.H) - - @property - def samplewise_reconstruction_error(self) -> np.ndarray: + def compute_reconstruction_errors(self) -> None: """ - The unweighted samplewise Kullback-Leibler divergence. + Add the unweighted samplewise Kullback-Leibler divergences + as observation annotations to the AnnData count data. """ - return _utils_klnmf.samplewise_kl_divergence(self.X, self.W, self.H) + errors = _utils_klnmf.samplewise_kl_divergence( + self.adata.X.T, self.asignatures.X.T, self.adata.obsm["exposures"].T + ) + self.adata.obs["reconstruction_error"] = errors def objective_function(self) -> float: """ The sum of the (weighted) Kullback-Leibler divergence and the sparsity penalty. """ - of_value = _utils_klnmf.kl_divergence(self.X, self.W, self.H, self.weights_kl) - + of_value = _utils_klnmf.kl_divergence( + self.adata.X.T, + self.asignatures.X.T, + self.adata.obsm["exposures"].T, + self.weights_kl, + ) if self.weights_l_half is not None: - of_value += np.dot(self.weights_l_half, np.sum(np.sqrt(self.H), axis=0)) - + of_value += np.dot( + self.weights_l_half, + np.sum(np.sqrt(self.adata.obsm["exposures"].T), axis=0), + ) return of_value @property - def objective(self) -> str: + def objective(self) -> Literal["minimize", "maximize"]: return "minimize" - def loglikelihood(self) -> float: - return _utils_klnmf.poisson_llh(self.X, self.W, self.H) + def _update_parameters(self, given_parameters: dict[str, Any]) -> None: + given_asignatures = given_parameters["asignatures"] - def _update_W(self): - self.W = _utils_klnmf.update_W( - self.X, self.W, self.H, self.weights_kl, self.n_given_signatures - ) - - def _update_H(self): - self.H = _utils_klnmf.update_H( - self.X, self.W, self.H, self.weights_kl, self.weights_l_half - ) - - def _update_WH(self): - if self.update_method == "mu-standard": - self._update_H() - if self.n_given_signatures < self.n_signatures: - self._update_W() + if given_asignatures is not None: + n_given_signatures = given_asignatures.n_obs else: - self.W, self.H = _utils_klnmf.update_WH( - self.X, - self.W, - self.H, - self.weights_kl, - self.weights_l_half, - self.n_given_signatures, - ) + n_given_signatures = 0 + + W, H = _utils_klnmf.update_WH( + self.adata.X.T, + self.asignatures.X.T, + self.adata.obsm["exposures"].T, + self.weights_kl, + self.weights_l_half, + n_given_signatures, + ) + self.asignatures.X = W.T + self.adata.obsm["exposures"] = H.T - def _check_weights(self, weights: np.ndarray, name: str = "weights"): + def _check_weights(self, weights: np.ndarray, name: str = "weights") -> None: """ Check if the given sample-specific loss function or l-1/2 penalty weights are compatible with the input data. - weights : np.ndarray of shape (n_samples,) + weights : np.ndarray of shape (n_obs,) Sample-specific KL-divergence or sparsity penalty loss weights name : str, default='weights' Name to be displayed in a potential error message """ type_checker(name, weights, np.ndarray) - shape_checker(name, weights, (self.n_samples,)) + shape_checker(name, weights, (self.adata.n_obs,)) if not all(weights >= 0): raise ValueError( @@ -156,96 +127,28 @@ def _check_weights(self, weights: np.ndarray, name: str = "weights"): "are allowed." ) - def _setup_weight_parameters( - self, weights_kl: np.ndarray, weights_l_half: np.ndarray - ): - for weights, name in zip( - [weights_kl, weights_l_half], ["weights_kl", "weights_l_half"] - ): - if type(weights) in [float, int]: - weights *= np.ones(self.n_samples) + def _setup_fitting_parameters( + self, + fitting_kwargs: dict[str, Any] | None = None, + ) -> None: + if fitting_kwargs is None: + fitting_kwargs = _DEFAULT_FITTING_KWARGS + + for kwarg in fitting_kwargs: + if kwarg not in _FITTING_KWARGS: + raise ValueError( + f"The given fitting keyword arguments include parameters outside of {_FITTING_KWARGS}." + ) + + for name, weights in fitting_kwargs.items(): + if weights is not None: + type_checker(name, weights, [float, int, list, np.ndarray]) + if type(weights) in [float, int]: + weights *= np.ones(self.adata.n_obs) - if type(weights) is list: - weights = np.array(weights) + if type(weights) is list: + weights = np.array(weights) - if weights is not None: self._check_weights(weights, name) setattr(self, name, weights) - - def fit( - self, - data: pd.DataFrame, - given_signatures=None, - weights_kl=None, - weights_l_half=None, - init_kwargs=None, - history=False, - verbose=0, - ): - """ - Minimize the generalized Kullback-Leibler divergence D_KL(X || WH) between - the mutation count matrix X and product of the signature matrix W and - exposure matrix H under the constraint of normalized signatures. - - Parameters - ---------- - data : pd.DataFrame - The mutation count data - - given_signatures : pd.DataFrame, default=None - Known signatures that should be fixed by the algorithm. - The number of known signatures can be less or equal to the - number of signatures specified in the algorithm instance. - - weights_kl : np.ndarray, default=None - Per sample KL-divergence loss weights of shape (n_samples,). - - weights_l_half : np.ndarray, default=None - Per sample l_half penalty weights of shape (n_samples,). - They can be used to induce sparse exposures. - - init_kwargs : dict, default=None - Any further keyword arguments to be passed to the initialization method. - This includes, for example, a possible 'seed' keyword argument - for all stochastic methods. - - history : bool, default=False - If True, the objective function value will be stored after every - 'conv_test_freq' many iterations. - - verbose : int, default=0 - verbosity level - - Returns - ------- - self : object - Returns the instance itself. - """ - self._setup_data_parameters(data) - self._setup_weight_parameters(weights_kl, weights_l_half) - self._initialize(given_signatures, init_kwargs) - of_values = [self.objective_function()] - n_iteration = 0 - converged = False - - while not converged: - n_iteration += 1 - - if verbose and n_iteration % 1000 == 0: - print(f"iteration: {n_iteration}; objective: {of_values[-1]:.2f}") - - self._update_WH() - - if n_iteration % self.conv_test_freq == 0: - prev_of_value = of_values[-1] - of_values.append(self.objective_function()) - rel_change = (prev_of_value - of_values[-1]) / prev_of_value - converged = rel_change < self.tol and n_iteration >= self.min_iterations - - converged |= n_iteration >= self.max_iterations - - if history: - self.history["objective_function"] = of_values[1:] - - return self diff --git a/src/salamander/nmf_framework/signature_nmf.py b/src/salamander/nmf_framework/signature_nmf.py index 54e13c5..0570c28 100644 --- a/src/salamander/nmf_framework/signature_nmf.py +++ b/src/salamander/nmf_framework/signature_nmf.py @@ -1,19 +1,30 @@ +from __future__ import annotations + from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Iterable, Literal, get_args import matplotlib.pyplot as plt import numpy as np import pandas as pd +from anndata import AnnData -from ..plot import ( - corr_plot, - embeddings_plot, - exposures_plot, - history_plot, - signatures_plot, -) +from .. import plot as pl +from .. import tools as tl from ..utils import type_checker, value_checker +from .initialization import _INIT_METHODS, EPSILON, _Init_methods + +if TYPE_CHECKING: + from typing import Any + + from matplotlib.axes import Axes + from seaborn.matrix import ClusterGrid -EPSILON = np.finfo(np.float32).eps +_Dim_reduction_methods = Literal[ + "pca", + "tsne", + "umap", +] +_DIM_REDUCTION_METHODS = get_args(_Dim_reduction_methods) class SignatureNMF(ABC): @@ -26,142 +37,141 @@ class SignatureNMF(ABC): Every child class has to implement the following attributes: - - signatures: pd.DataFrame - The signature matrix including mutation type names and signature names + - objective: Literal["minimize", "maximize"] + Whether the NMF algorithm minimize or maximize the objective function. + Some algorithms maximize a likelihood, others minimize a distance. - - exposures: pd.DataFrames - The exposure matrix including the signature names and sample names + Every child class has to implement the following methods: - - _n_parameters: int - The number of parameters fitted by the NMF algorithm. - This is needed to compute the Bayesian Information Criterion (BIC) + - compute_reconstruction_errors: + Add the samplewise reconstruction errors to adata.obs with the key + 'reconstruction_error'. - - reconstruction_error: float - The reconstruction error between the count matrix and - the reconstructed count matrix. + - objective_fuction: + The objective function to optimize during model training. - - samplewise_reconstruction_error: np.ndarray - The samplewise reconstruction error between the sample counts and - the reconstructed sample counts. + - _initialize: + Initialize all model parameters before model training. - - objective: str - "minimize" or "maximize". Whether the NMF algorithm maximizes or - minimizes the objective function. Some algorithms maximize a likelihood, - others minimize a distance. The distinction is useful for filtering NMF runs - based on the fitted objective function value downstream. + - _setup_fitting_parameters: + Initialize any additional and parameters required to fit + the NMF model. - - corr_signatures: pd.DataFrame - The signature correlation matrix + - _update_parameters: + Update all model parameters. - - corr_samples: pd.DataFrame - The sample correlation matrix + - reorder: + Reorder the model parameters to match the order of another + collection of signatures. + - reduce_dimension_embeddings: + Reduce the dimension of the canonical model embeddings. + These are typically the sample exposures. - Every child class has to implement the following methods: + - _get_embedding_plot_adata: + A helper function for the embedding plot. - - objective_fuction: - The objective function to optimize when running the algorithm + - _get_default_embedding_plot_annotations: + A helper function for the embedding plot. - - loglikelihood: - The loglikelihood of the underyling generative model + The following attributes and methods are implemented in SignatureNMF: - - _initialize: - A method to initialize all model parameters before fitting + - mutation_types: np.ndarray + Wrapper around the var_names of the count data. - - fit: - Run the NMF algorithm for a given mutation count data. Every - fit method should also implement a version that allows fixing - arbitrary many a priori known signatures. + - signature_names: np.ndarray + Wrapper around the obs_names of the signatures AnnData object. - - _get_embedding_data: - A helper function for the embedding plot + - sample_names: np.ndarrray + Wrapper around the obs_names of the count data. - - _get_default_embedding_annotations: - A helper function for the embedding plot + - signatures: pd.DataFrame + Wrapper around the signatures AnnData object to return + the signatures as a dataframe. + - exposures: pd.DataFrame + Wrapper around adata.obsm to return the signature exposures + as a dataframe. - The following attributes and methods are implemented in SignatureNMF: + - compute_reconstruction: None + Add the reconstrcuted counts to adata.obsm with key 'X_reconstructed'. - data_reconstructed: pd.DataFrame - The recovered mutation count data given - the current signatures and exposures. + The recovered mutation count data as a dataframe. + + - reconstruction_error: float + The sum of the samplewise reconstruction errors. - - X_reconstructed: np.ndarray - The recovered mutation count matrix given - the current signatures and exposures + - _setup_adata: + Perform parameter checks on the input AnnData count object and clip zeros. - - bic: float - The value of the Bayesian Information Criterion (BIC) + - _check_given_asignatures: + Perform parameter checks on the optial input AnnData signature object. - - _setup_data_parameters: - Perform parameter checks on the input data and add attributes + - fit: + Fit all model parameters. + + - compute_correlation: + Add sample or signature correlations to the AnnData objects. + + - correlation: + The sample or signature correlation as a dataframe. - plot_history: Plot the history of the objective function values after fitting the model - plot_signatures: - Plot the signatures using the signatures_plot function implemented in - the plot module + Plot the signatures as a barplot. + + - plot_exposures: + Plot the exposures as a stacked barplot. - plot_correlation: - Plot the correlation of either the signatures or exposures - using the corr_plot function implemented in the plot module + Plot the correlation of either the signatures or exposures. - plot_embeddings: Plot the sample (and potentially the signature) embeddings in 2D - using PCA, tSNE or UMAP + using PCA, tSNE or UMAP. """ def __init__( self, - n_signatures=1, - init_method="nndsvd", - min_iterations=500, - max_iterations=10000, - conv_test_freq=10, - tol=1e-7, + n_signatures: int = 1, + init_method: _Init_methods = "nndsvd", + min_iterations: int = 500, + max_iterations: int = 10000, + conv_test_freq: int = 10, + tol: float = 1e-7, ): """ - Input: + Inputs ------ n_signatures: int - The number of underlying signatures that are assumed to - have generated the mutation count data + The number of signatures that are assumed to + have generated the mutation count data. - init_method: str - The initialization method for the NMF algorithm + init_method: str, default='nndsvd' + The model parameter initialization method. - min_iterations: int + min_iterations: int, default=500 The minimum number of iterations to perform by the NMF algorithm - max_iterations: int + max_iterations: int, default=10000 The maximum number of iterations to perform by the NMF algorithm - conv_test_freq: int + conv_test_freq: int, default=10 The frequency at which the algorithm is tested for convergence. The objective function value is only computed every 'conv_test_freq' - many iterations, which also affects a potentially saved history of - the objective function values. + many iterations. tol: float - The NMF algorithm is converged when the relative change of - the objective function of one iteration is smaller - than the tolerance 'tol'. - """ - init_methods = [ - "custom", - "flat", - "hierarchical_cluster", - "nndsvd", - "nndsvda", - "nndsvdar", - "random", - "separableNMF", - ] - value_checker("init_method", init_method, init_methods) + The convergence tolerance. The NMF algorithm is converged + when the relative change of the objective function is smaller + than 'tol'. + """ + value_checker("init_method", init_method, _INIT_METHODS) self.n_signatures = n_signatures - self.signature_names = None self.init_method = init_method self.min_iterations = min_iterations self.max_iterations = max_iterations @@ -169,178 +179,256 @@ def __init__( self.tol = tol # initialize data/fitting dependent attributes - self.X = None - self.n_features = 0 - self.n_given_signatures = 0 - self.n_samples = 0 - self.mutation_types = np.empty(0, dtype=str) - self.sample_names = np.empty(0, dtype=str) - self.history = {} + self.adata = AnnData() + self.asignatures = AnnData() + self.history: dict[str, Any] = {} + + @property + def mutation_types(self) -> np.ndarray: + return self.adata.var_names.to_numpy(dtype=str) + + @property + def signature_names(self) -> np.ndarray: + return self.asignatures.obs_names.to_numpy(dtype=str) + + @property + def sample_names(self) -> np.ndarray: + return self.adata.obs_names.to_numpy(dtype=str) @property - @abstractmethod def signatures(self) -> pd.DataFrame: """ Extract the mutational signatures as a pandas dataframe. """ - pass + return self.asignatures.to_df() @property - @abstractmethod def exposures(self) -> pd.DataFrame: """ - Extract the signature exposures of samples as a pandas dataframe. + Extract the signature exposures as a pandas dataframe. """ - pass + assert ( + "exposures" in self.adata.obsm + ), "Learning the sample exposures requires fitting the NMF model." + exposures_df = pd.DataFrame( + self.adata.obsm["exposures"], + index=self.sample_names, + columns=self.signature_names, + ) + return exposures_df + + def compute_reconstruction(self) -> None: + self.adata.obsm["X_reconstructed"] = ( + self.adata.obsm["exposures"] @ self.asignatures.X + ) @property def data_reconstructed(self) -> pd.DataFrame: - return (self.signatures @ self.exposures).astype(int) + if not "X_reconstructed" in self.adata.obsm: + self.compute_reconstruction() - @property - def X_reconstructed(self) -> np.ndarray: - return self.data_reconstructed.values + return pd.DataFrame( + self.adata.obsm["X_reconstructed"], + index=self.sample_names, + columns=self.mutation_types, + ) - @property @abstractmethod - def reconstruction_error(self) -> float: + def compute_reconstruction_errors(self) -> None: """ - The reconstruction error between the count matrix and - the reconstructed count matrix. + The samplewise reconstruction errors between the data + and the reconstructed data. """ - pass @property - @abstractmethod - def samplewise_reconstruction_error(self) -> np.ndarray: + def reconstruction_error(self) -> float: """ - The samplewise reconstruction error between the sample counts and - the reconstructed sample counts. + The total reconstruction error between the data and + the reconstructed data. """ - pass + if not "reconstruction_error" in self.adata.obs: + self.compute_reconstruction_errors() - @abstractmethod - def objective_function(self) -> float: - """ - The objective function to be optimized during fitting. - """ - pass + return np.sum(self.adata.obs["reconstruction_error"]) + @property @abstractmethod - def loglikelihood(self) -> float: + def objective(self) -> Literal["minimize", "maximize"]: """ - The log-likelihood of the underlying generative model. + Whether the NMF algorithm minimizes or maximizes its objective + function. """ - pass - @property @abstractmethod - def _n_parameters(self) -> int: + def objective_function(self) -> float: """ - Every child class has to implement a function returning - the number of parameters estimated by the respective model. - This is allows to, for example, implement the BIC - (Bayesian information criterion). The BIC can be used to - estimate the optimal number of signatures. + The objective function to be optimized during fitting. """ - pass - @property - def bic(self) -> float: + def _setup_adata(self, adata: AnnData) -> None: """ - Bayesian information criterion (BIC). - Can only be called after the _setup_parameters_fitting function as it - requires the number of samples be an attribute. + Check the type of the input counts and clip them to + avoid floating point errors. + + Inputs + ------ + data: AnnData + The AnnData object with the mutation count matrix. """ - return self._n_parameters * np.log(self.n_samples) - 2 * self.loglikelihood() + type_checker("adata", adata, AnnData) + self.adata = adata + self.adata.X = self.adata.X.clip(EPSILON) - def _check_given_signatures(self, given_signatures: pd.DataFrame): + def _check_given_asignatures(self, given_asignatures: AnnData) -> None: """ Check if the given signatures are compatible with the number of signatures of the algorithm and the mutation types of the input data. + Should be called by implementations of _initialize. - given_signatures: pd.DataFrame + Inputs + ------ + given_asignatures: AnnData Known signatures that should be fixed by the algorithm. The number of known signatures can be less or equal to the - number of signatures specified in the algorithm instance. + number of signatures specified by the algorithm. """ - type_checker("given_signatures", given_signatures, pd.DataFrame) - given_mutation_types = given_signatures.index.to_numpy(dtype=str) + type_checker("given_asignatures", given_asignatures, AnnData) + given_mutation_types = given_asignatures.var_names.to_numpy(dtype=str) compatible = ( np.array_equal(given_mutation_types, self.mutation_types) - and given_signatures.shape[1] <= self.n_signatures + and given_asignatures.n_obs <= self.n_signatures ) - if not compatible: raise ValueError( f"You have to provide at most {self.n_signatures} signatures with " - f"mutation types matching to your data." + "mutation types matching to your data." ) @abstractmethod - def _initialize(self): + def _initialize( + self, + given_parameters: dict[str, Any] | None = None, + init_kwargs: dict[str, Any] | None = None, + ) -> dict[str, Any]: """ - Initialize model parameters and attributes before fitting. - Enforcing the existence of _initialize unifies the implementation of - the NMF algorithms. + Initialize the NMF model parameters and return the potentially + updated 'given_parameters'. Example: - Before running the Lee & Seung NMF multiplicative update rules to - decompose the mutation count matrix X into a signature matrix W and - an exposure matrix H, both W and H have to be initialized. + decompose the mutation count matrix into a signature matrix and + an exposure matrix, both matrices have to be initialized. """ - def _setup_data_parameters(self, data: pd.DataFrame): + @abstractmethod + def _setup_fitting_parameters( + self, fitting_kwargs: dict[str, Any] | None = None + ) -> None: """ - Perform parameter checks before running the fit method. - - Input: - ------ - data: pd.DataFrame - The mutation count pandas dataframe with indices and column names. - Samples are expected to corresponding to columns. + Initialize any additional and parameters required to fit + the NMF model. """ - type_checker("data", data, pd.DataFrame) - self.X = data.values.clip(EPSILON) - self.n_features, self.n_samples = data.shape - self.mutation_types = data.index.values.astype(str) - self.sample_names = data.columns.values.astype(str) @abstractmethod - def fit(self, data: pd.DataFrame, given_signatures=None): + def _update_parameters(self, given_parameters: dict[str, Any]) -> None: + """ + Update all model parameters. """ - Fit the model parameters. Child classes are expected to handle - 'given_signatures' appropriately. - Input: + def fit( + self, + adata: AnnData, + given_parameters: dict[str, Any] | None = None, + init_kwargs: dict[str, Any] | None = None, + fitting_kwargs: dict[str, Any] | None = None, + history: bool = True, + verbose: Literal[0, 1] = 0, + verbosity_freq: int = 1000, + ) -> SignatureNMF: + """ + Fit the model parameters. NMF models are expected to handle + 'given_parameters' appropriately. + + Inputs ------ - data: pd.DataFrame - The named mutation count data of shape (n_features, n_samples). + adata: AnnData + The mutation count matrix as an AnnData object. + + given_parameters: dict, optional + A priori known parameters. The key is expected to be the parameter + name. + + init_kwargs: dict, optional + Keyword arguments to pass to the model parameter initialization, e.g., + a seed when a stochastic initialization method is used. + + fitting_kwargs: dict, optional + Keyword arguments to pass to the initialization of additional fitting + parameters, e.g., sample-specific loss function weights. - given_signatures: pd.DataFrame, by default None - A priori known signatures. The number of given signatures has - to be less or equal to the number of signatures of NMF - algorithm instance, and the mutation type names have to match - the mutation types of the count data. + history: bool, default=True + If True, the objective function values computed during model training + will be stored. + + verbose: Literal[0, 1], default=0 + If True, intermediate objective function values obtained during model + training are printed. + + verbosity_freq: int, default=1000 + The objective function values after every 'verbosity_freq' many + iterations are printed. Only applies if 'verbose' is set to 1. """ + self._setup_adata(adata) + given_parameters = self._initialize(given_parameters, init_kwargs) + self._setup_fitting_parameters(fitting_kwargs) + of_values = [self.objective_function()] + n_iteration = 0 + converged = False - def plot_history(self, ax=None, min_iteration=0, outfile=None, **kwargs): - if not self.history: - raise ValueError( - "No history available, the model has to be fitted first. " - "Remember to set 'history' to 'True' when calling 'fit()'." - ) + while not converged: + n_iteration += 1 - history_plot( - self.history["objective_function"], - self.conv_test_freq, - min_iteration=min_iteration, - ax=ax, + if verbose and n_iteration % verbosity_freq == 0: + print(f"iteration: {n_iteration}; objective: {of_values[-1]:.2f}") + + self._update_parameters(given_parameters) + + if n_iteration % self.conv_test_freq == 0: + prev_of_value = of_values[-1] + of_values.append(self.objective_function()) + rel_change_nominator = np.abs(prev_of_value - of_values[-1]) + rel_change = rel_change_nominator / np.abs(prev_of_value) + converged = rel_change < self.tol and n_iteration >= self.min_iterations + + converged |= n_iteration >= self.max_iterations + + if history: + self.history["objective_function"] = of_values[1:] + + return self + + @abstractmethod + def reorder(self, asignatures_other: AnnData) -> None: + """ + Reorder the model parameters to match the order of another + collection of signatures. + """ + + def plot_history(self, outfile: str | None = None, **kwargs) -> Axes: + """ + Plot the history of the objective function values. See + the implemenation of 'history' in the plotting module. + """ + assert "objective_function" in self.history, ( + "No history available, the model has to be fitted first. " + "Remember to set 'history' to 'True' when calling 'fit()'." + ) + ax = pl.history( + values=self.history["objective_function"], + conv_test_freq=self.conv_test_freq, **kwargs, ) - if outfile is not None: plt.savefig(outfile, bbox_inches="tight") @@ -348,25 +436,19 @@ def plot_history(self, ax=None, min_iteration=0, outfile=None, **kwargs): def plot_signatures( self, - catalog=None, - colors=None, - annotate_mutation_types=False, - axes=None, - outfile=None, + annotate_mutation_types: bool = False, + outfile: str | None = None, **kwargs, - ): + ) -> Axes | Iterable[Axes]: """ - Plot the signatures, see plot.py for the implementation of signatures_plot. + Plot the signatures, see the implementation of 'barplot' in + the plotting module. """ - axes = signatures_plot( - self.signatures, - catalog=catalog, - colors=colors, - annotate_mutation_types=annotate_mutation_types, - axes=axes, + axes = pl.barplot( + self.asignatures, + annotate_vars=annotate_mutation_types, **kwargs, ) - if outfile is not None: plt.savefig(outfile, bbox_inches="tight") @@ -374,27 +456,45 @@ def plot_signatures( def plot_exposures( self, - sample_order=None, - reorder_signatures=True, - annotate_samples=True, - colors=None, - ncol_legend=1, - ax=None, - outfile=None, + sample_order: np.ndarray | None = None, + reorder_signatures: bool = True, + annotate_samples: bool = True, + outfile: str | None = None, **kwargs, - ): + ) -> Axes: """ - Visualize the exposures as a stacked bar chart, - see plot.py for the implementation. - """ - ax = exposures_plot( - exposures=self.exposures, - sample_order=sample_order, - reorder_signatures=reorder_signatures, - annotate_samples=annotate_samples, - colors=colors, - ncol_legend=ncol_legend, - ax=ax, + Visualize the exposures as a stacked bar chart, see + the implementation of 'stacked_barplot' in the plotting + module. + + Inputs + ------ + sample_order: np.ndarray, optional + A pre-defined order of the samples along the x-axis. + + reorder_signatures: bool, default=True + If True, the signatures are ordered by their total + relative contributions. + + annotate_samples: bool, default=True + If True, the x-axis is annotated with the sample names. + + outfile : str, default=None + If not None, the figure will be saved in the specified file path. + + **kwargs: + Any further keyword arguments to pass to 'stacked_barplot'. + + Returns + ------- + ax : matplotlib.axes.Axes + The matplotlib axes containing the plot. + """ + ax = pl.stacked_barplot( + data=self.exposures, + obs_order=sample_order, + reorder_dimensions=reorder_signatures, + annotate_obs=annotate_samples, **kwargs, ) if outfile is not None: @@ -402,41 +502,71 @@ def plot_exposures( return ax - @property - @abstractmethod - def corr_signatures(self) -> pd.DataFrame: + def compute_correlation( + self, data: Literal["samples", "signatures"] = "signatures", **kwargs + ) -> None: """ - Every child class of SignatureNMF has to implement a function that - returns the signature correlation matrix as a pandas dataframe. + Compute the signature or sample correlation and store it in the + respective anndata object. """ + value_checker("data", data, ["samples", "signatures"]) - @property - @abstractmethod - def corr_samples(self) -> pd.DataFrame: - """ - Every child class of SignatureNMF has to implement a function that - returns the sample correlation matrix as a pandas dataframe. - """ + assert ( + "exposures" in self.adata.obsm + ), "Computing the sample or signature correlation requires fitting the NMF model." - def plot_correlation(self, data="signatures", annot=False, outfile=None, **kwargs): - """ - Plot the correlation matrix of the signatures or samples. - See plot.py for the implementation of corr_plot. + values = self.adata.obsm["exposures"] - Input: - ------ - *args, **kwargs: - arguments to be passed to corr_plot + if data == "signatures": + values = values.T + + correlation = tl._correlation(values, **kwargs) + + if data == "samples": + self.adata.obsp["X_correlation"] = correlation + else: + self.asignatures.obsp["correlation"] = correlation + + def correlation( + self, data: Literal["samples", "signatures"] = "signatures" + ) -> pd.DataFrame: """ - value_checker("data", data, ["signatures", "samples"]) + Dataframe of the signature or sample correlation. + """ + value_checker("data", data, ["samples", "signatures"]) - if data == "signatures": - corr = self.corr_signatures + if data == "samples": + if "X_correlation" not in self.adata.obsp: + self.compute_correlation("samples") + values = self.adata.obsp["X_correlation"] + names = self.sample_names else: - corr = self.corr_samples + if "correlation" not in self.asignatures.obsp: + self.compute_correlation("signatures") + values = self.asignatures.obsp["correlation"] + names = self.signature_names - clustergrid = corr_plot(corr, annot=annot, **kwargs) + correlation_df = pd.DataFrame(values, index=names, columns=names) + return correlation_df + + def plot_correlation( + self, + data: Literal["samples", "signatures"] = "signatures", + annot: bool | None = None, + outfile: str | None = None, + **kwargs, + ) -> ClusterGrid: + """ + Plot the signature or sample correlation. + """ + value_checker("data", data, ["samples", "signatures"]) + corr = self.correlation(data=data) + + if annot is None: + annot = False if data == "samples" else True + + clustergrid = pl.correlation_pandas(corr, annot=annot, **kwargs) if outfile is not None: plt.savefig(outfile, bbox_inches="tight") @@ -444,20 +574,40 @@ def plot_correlation(self, data="signatures", annot=False, outfile=None, **kwarg return clustergrid @abstractmethod - def _get_embedding_data(self) -> np.ndarray: + def reduce_dimension_embeddings( + self, method: _Dim_reduction_methods = "umap", **kwargs + ) -> None: + """ + Reduce the dimension of the embeddings. + Usually, the embeddings are the signature exposures of the samples. + But in correlated NMF, they are the signature and sample embeddings + in a shared embedding space. + """ + + @abstractmethod + def _get_embedding_plot_adata( + self, method: _Dim_reduction_methods = "umap" + ) -> tuple[AnnData, str]: """ - Get the data points for the dimensionality reduction / embedding plot. - One data point corresponds to a row of the embedding data. - Usually, these are the transposed exposures. + Get the anndata object containing all embeddings + and the name of the embedding basis. """ @abstractmethod - def _get_default_embedding_annotations(self) -> np.ndarray: + def _get_default_embedding_plot_annotations(self) -> Iterable[str] | None: """ - Get the annotations of the data points in the embedding plot. + Get the default annotations of the data points in the embedding plot. """ - def plot_embeddings(self, annotations=None, outfile=None, **kwargs): + def plot_embeddings( + self, + method: _Dim_reduction_methods = "umap", + n_components: int = 2, + dimensions: tuple[int, int] = (0, 1), + annotations: Iterable[str] | None = None, + outfile: str | None = None, + **kwargs, + ) -> Axes: """ Plot a dimensionality reduction of the exposure representation. In most NMF algorithms, this is just the exposures of the samples. @@ -465,12 +615,20 @@ def plot_embeddings(self, annotations=None, outfile=None, **kwargs): sample and signature embeddings in a shared embedding space. If the embedding dimension is one or two, the embeddings are be plotted - directly, ignoring the chosen method. - See plot.py for the implementation of 'embeddings_plot'. + directly, ignoring the chosen dimensionality reduction method. + + Inputs + ------ + method: str, default='umap' + The dimensionality reduction method. One of ['pca', 'tsne', 'umap']. + + n_components: int, default=2 + The target dimension of the dimensionality reduction. - Parameters - ---------- - annotations : list[str], default=None + dimensions: tuple[int, int], default=(0,1) + The indices of the dimensions to plot. + + annotations : Iterable[str], optional, default=None Annotations per data point, e.g. the sample names. If None, the algorithm-specific default annotations are used. For example, CorrNMF annotates the signature embeddings by default. @@ -482,21 +640,26 @@ def plot_embeddings(self, annotations=None, outfile=None, **kwargs): If not None, the figure will be saved in the specified file path. **kwargs : - keyword arguments to pass to seaborn's scatterplot + keyword arguments to pass to the scatterplot implementation. Returns ------- ax : matplotlib.axes.Axes The matplotlib axes containing the plot. """ - # one data point corresponds to a row of embedding_data - embedding_data = self._get_embedding_data() + self.reduce_dimension_embeddings(method=method, n_components=n_components) + adata, basis = self._get_embedding_plot_adata(method=method) if annotations is None: - annotations = self._get_default_embedding_annotations() - - ax = embeddings_plot(data=embedding_data, annotations=annotations, **kwargs) + annotations = self._get_default_embedding_plot_annotations() + ax = pl.embedding( + adata=adata, + basis=basis, + dimensions=dimensions, + annotations=annotations, + **kwargs, + ) if outfile is not None: plt.savefig(outfile, bbox_inches="tight") diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/nmf_framework/standard_nmf.py index 2f19274..320a61b 100644 --- a/src/salamander/nmf_framework/standard_nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -1,249 +1,146 @@ +from __future__ import annotations + +import warnings from abc import abstractmethod +from typing import TYPE_CHECKING +import anndata as ad import numpy as np import pandas as pd -from ..utils import match_signatures_pair +from .. import tools as tl +from ..utils import _get_basis_obsm, match_signatures_pair, type_checker from .initialization import initialize -from .signature_nmf import SignatureNMF +from .signature_nmf import SignatureNMF, _Dim_reduction_methods + +if TYPE_CHECKING: + from typing import Any, Literal -EPSILON = np.finfo(np.float32).eps + from .signature_nmf import _Dim_reduction_methods + +_PARAMETERS_NAMES = ["asignatures", "exposures"] +_DEFAULT_GIVEN_PARAMETERS = {parameter: None for parameter in _PARAMETERS_NAMES} class StandardNMF(SignatureNMF): """ The abstract class StandardNMF unifies the structure of NMF algorithms - with a single signature matrix W and exposure matrix H. + with a signature and exposure matrix. + Examples of these algorithms include the NMF algorithms from - (Lee and Seung, 1999), minimum volume NMF (mvNMF) or NMF variants + (Lee and Seung, 1999), minimum volume NMF (mvNMF) or any NMF variants with regularizations on the entries of W or H. All of these NMF algorithms have the same parameters. Therefore, - many properties of interest such as the signature correlation martrix - or the sample embeddings are computed in the same manner. - - Overview: - - Every child class has to implement the following attributes: - - - reconstruction_error: float - The reconstruction error between the count matrix and - the reconstructed count matrix. - - - samplewise_reconstruction_error: np.ndarray - The samplewise reconstruction error between the sample counts - and the reconstructed sample counts. - - - objective: str - "minimize" or "maximize". Whether the NMF algorithm maximizes or - minimizes the objective function. - Some algorithms maximize a likelihood, others minimize a distance. - The distinction is useful for filtering NMF runs based on - the fitted objective function value downstream. - - - Every child class has to implement the following methods: - - - objective_function: - The algorithm-specific objective function - - - loglikelihood: - The loglikelihood of the underyling generative model - - - _update_W: - update the signature matrix W - - - _update_H: - update the exposure matrix H - - - fit: - Apply the NMF algorithm for a given mutation count data or - for given signatures and mutation count data - - - The following attributes are implemented in the abstract class NMF: - - - signatures: pd.DataFrame - The signature matrix including mutation type names and signature names - - - exposures: pd.DataFrame - The exposure matrix including the signature names and sample names - - - _n_parameters: - The number of parameters of models with signature and exposure matrices - - - corr_signatures: pd.DataFrame - The signature correlation matrix induced by their sample exposures - - - corr_samples: pd.DataFrame - The sample correlation matrix induced by their signature exposures - - - The following methods are implemented in the abstract class NMF: - - - _initialize: - Initialize all model parameters - - - _get_embedding_data: - A helper function for the embedding plot. Models with signature and - exposure matrices use the exposures as the lower-dimensional - representation of the samples - - - _get_default_embedding_annotations: - A helper function for the embedding plot. By default, no samples are - annotated + their initializations are identical, and the lower-dimensional + representations are the sample exposures. """ - def __init__( + def _initialize( self, - n_signatures=1, - init_method="nndsvd", - min_iterations=500, - max_iterations=10000, - conv_test_freq=10, - tol=1e-7, + given_parameters: dict[str, Any] | None = None, + init_kwargs: dict[str, Any] | None = None, ): """ - Input: - ------ - n_signatures: int - The number of underlying signatures that are assumed to - have generated the mutation count data. - - init_method: str - One of "custom", "flat", "hierarchical_cluster", "nndsvd", - "nndsvda", "nndsvdar" "random" and "separableNMF". - See the initialization module for further details on each method. - - min_iterations: int - The minimum number of iterations to perform during inference - - max_iterations: int - The maximum number of iterations to perform during inference - - conv_test_freq: int - The frequency at which the algorithm is tested for convergence. - The objective function value is only computed every 'conv_test_freq' - many iterations, which also affects a potentially saved history of - the objective function values. - - tol: float - The NMF algorithm is converged when the relative change - of the objective function of one iteration is smaller - than the tolerance 'tol'. - """ - super().__init__( - n_signatures, - init_method, - min_iterations, - max_iterations, - conv_test_freq, - tol, - ) - - # initialize data/fitting dependent attributes - self.W, self.H = None, None - - @property - def signatures(self) -> pd.DataFrame: - signatures = pd.DataFrame( - self.W, index=self.mutation_types, columns=self.signature_names - ) - return signatures - - @property - def exposures(self) -> pd.DataFrame: - exposures = pd.DataFrame( - self.H, index=self.signature_names, columns=self.sample_names - ) - return exposures - - @property - def _n_parameters(self) -> int: - """ - There are n_features * n_signatures parameters corresponding to - the signature matrix and n_signatures * n_samples parameters - corresponding to the exposure matrix. - """ - return self.n_signatures * (self.n_features + self.n_samples) - - @abstractmethod - def _update_W(self): - pass - - @abstractmethod - def _update_H(self): - pass - - def _initialize(self, given_signatures=None, init_kwargs=None): - """ - Initialize the signature matrix W and exposure matrix H. - When the signatures are given, the initialization - of W is overwritten by the given signatures. + Initialize the signatures and exposures. + A subset of the signatures can be given by the user. They will + not be overwritten during fitting. Input: ------ - given_signatures : pd.Dataframe, default=None - At most 'n_signatures' many signatures can be provided to - overwrite some of the initialized signatures. This does not - change the initialized exposurse. + given_parameters : dict, default=None + Optinally given 'asignatures' AnnData signatures object. init_kwargs: dict Any further keywords arguments to be passed to the initialization method. - This includes, for example, a possible 'seed' keyword argument - for all stochastic methods. + This includes, for example, an optional 'seed' for all stochastic methods. """ - if given_signatures is not None: - self._check_given_signatures(given_signatures) - self.n_given_signatures = len(given_signatures.columns) + if given_parameters is None: + given_parameters = _DEFAULT_GIVEN_PARAMETERS + given_signatures = None else: - self.n_given_signatures = 0 + given_parameters = given_parameters.copy() - init_kwargs = {} if init_kwargs is None else init_kwargs.copy() - self.W, self.H, self.signature_names = initialize( - self.X, self.n_signatures, self.init_method, given_signatures, **init_kwargs - ) + type_checker("given_parameters", given_parameters, dict) - @property - def corr_signatures(self) -> pd.DataFrame: - """ - The correlation of two signatures is given by the pearson correlation of - the respective rows of the exposure matrix H. + for parameter in given_parameters: + if parameter not in _PARAMETERS_NAMES: + raise ValueError( + f"The given parameters include parameters outside of {_PARAMETERS_NAMES}." + ) - The pandas dataframe method 'corr' computes the pairwise correlation of columns. - """ - return self.exposures.T.corr(method="pearson") + given_asignatures = given_parameters["asignatures"] - @property - def corr_samples(self) -> pd.DataFrame: - """ - The correlation of two samples is given by the pearson correlation of - the respective columns of the exposure matrix H. - - The pandas dataframe method 'corr' computes the pairwise correlation of columns. - """ - return self.exposures.corr(method="pearson") + if given_asignatures is not None: + self._check_given_asignatures(given_asignatures) + given_signatures = given_asignatures.to_df().T - def reorder(self, other_signatures, metric="cosine", keep_names=False): + init_kwargs = {} if init_kwargs is None else init_kwargs.copy() + # initialize takes counts X of shape (n_features, n_samples), + # and given_signatures of shape (n_features, n_given_signatures) + W, H, signature_names = initialize( + self.adata.X.T, + self.n_signatures, + self.init_method, + given_signatures, + **init_kwargs, + ) + self.asignatures = ad.AnnData(W.T) + self.asignatures.obs_names = signature_names + self.asignatures.var_names = self.mutation_types + + # keep signature annotations + if given_asignatures is not None: + n_given_signatures = given_asignatures.n_obs + asignatures_new = self.asignatures[n_given_signatures:, :] + self.asignatures = ad.concat( + [given_asignatures, asignatures_new], join="outer" + ) + + self.adata.obsm["exposures"] = H.T + return given_parameters + + def reorder( + self, + asignatures_other: ad.AnnData, + metric: str = "cosine", + ) -> None: reordered_indices = match_signatures_pair( - other_signatures, self.signatures, metric=metric + asignatures_other.to_df().T, self.asignatures.to_df().T, metric=metric + ) + self.asignatures = self.asignatures[reordered_indices, :].copy() + self.adata.obsm["exposures"] = self.adata.obsm["exposures"][ + :, reordered_indices + ] + + def reduce_dimension_embeddings( + self, method: _Dim_reduction_methods = "umap", n_components: int = 2, **kwargs + ) -> None: + tl.reduce_dimension( + self.adata, + basis="exposures", + method=method, + n_components=n_components, + **kwargs, ) - self.W = self.W[:, reordered_indices] - self.H = self.H[reordered_indices, :] - - if keep_names: - self.signature_names = self.signature_names[reordered_indices] - - return reordered_indices - def _get_embedding_data(self): + def _get_embedding_plot_adata( + self, method: _Dim_reduction_methods = "umap" + ) -> tuple[ad.AnnData, str]: """ - In most NMF models like KL-NMF or mvNMF, the data for the embedding plot - are just the (transposed) exposures. + Plot the exposures directly if the number of signatures is at most 2. """ - return self.H.T.copy() + if self.n_signatures <= 2: + warnings.warn( + f"There are only {self.n_signatures} many signatures. " + "The exposures are plotted directly.", + UserWarning, + ) + return self.adata, "exposures" + + return self.adata, method - def _get_default_embedding_annotations(self): + def _get_default_embedding_plot_annotations(self) -> None: """ The embedding plot defaults to no annotations. """ - return None + return From f9530acb8d8e00e04430e8d7303a5813623784cf Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 11 Apr 2024 16:19:36 -0400 Subject: [PATCH 020/103] update config --- mypy.ini | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mypy.ini b/mypy.ini index 3c340b4..66ebce1 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,18 +1,27 @@ [mypy] +allow_redefinition = True warn_unused_configs = True python_version = 3.11 files = - src/salamander/plot.py, - src/salamander/tools.py + src # # Third party dependencies that don't have types. # +[mypy-adjustText.*] +ignore_missing_imports = True + [mypy-anndata.*] ignore_missing_imports = True +[mypy-fastcluster.*] +ignore_missing_imports = True + +[mypy-numba.*] +ignore_missing_imports = True + [mypy-scipy.*] ignore_missing_imports = True From b9a73ae0ff3e23f937043bf72511590c9eafc854 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 14:57:07 -0400 Subject: [PATCH 021/103] rename test files no longer support --- ...-joint_nsigs1.npy => H_updated_joint_nsigs1.npy} | Bin ...-joint_nsigs2.npy => H_updated_joint_nsigs2.npy} | Bin ...ard_nsigs1.npy => H_updated_standard_nsigs1.npy} | Bin ...ard_nsigs2.npy => H_updated_standard_nsigs2.npy} | Bin ...-joint_nsigs1.npy => W_updated_joint_nsigs1.npy} | Bin ...-joint_nsigs2.npy => W_updated_joint_nsigs2.npy} | Bin ...ard_nsigs1.npy => W_updated_standard_nsigs1.npy} | Bin ...ard_nsigs2.npy => W_updated_standard_nsigs2.npy} | Bin 8 files changed, 0 insertions(+), 0 deletions(-) rename tests/test_data/nmf_framework/utils_klnmf/{H_updated_mu-joint_nsigs1.npy => H_updated_joint_nsigs1.npy} (100%) rename tests/test_data/nmf_framework/utils_klnmf/{H_updated_mu-joint_nsigs2.npy => H_updated_joint_nsigs2.npy} (100%) rename tests/test_data/nmf_framework/utils_klnmf/{H_updated_mu-standard_nsigs1.npy => H_updated_standard_nsigs1.npy} (100%) rename tests/test_data/nmf_framework/utils_klnmf/{H_updated_mu-standard_nsigs2.npy => H_updated_standard_nsigs2.npy} (100%) rename tests/test_data/nmf_framework/utils_klnmf/{W_updated_mu-joint_nsigs1.npy => W_updated_joint_nsigs1.npy} (100%) rename tests/test_data/nmf_framework/utils_klnmf/{W_updated_mu-joint_nsigs2.npy => W_updated_joint_nsigs2.npy} (100%) rename tests/test_data/nmf_framework/utils_klnmf/{W_updated_mu-standard_nsigs1.npy => W_updated_standard_nsigs1.npy} (100%) rename tests/test_data/nmf_framework/utils_klnmf/{W_updated_mu-standard_nsigs2.npy => W_updated_standard_nsigs2.npy} (100%) diff --git a/tests/test_data/nmf_framework/utils_klnmf/H_updated_mu-joint_nsigs1.npy b/tests/test_data/nmf_framework/utils_klnmf/H_updated_joint_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/H_updated_mu-joint_nsigs1.npy rename to tests/test_data/nmf_framework/utils_klnmf/H_updated_joint_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/H_updated_mu-joint_nsigs2.npy b/tests/test_data/nmf_framework/utils_klnmf/H_updated_joint_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/H_updated_mu-joint_nsigs2.npy rename to tests/test_data/nmf_framework/utils_klnmf/H_updated_joint_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/H_updated_mu-standard_nsigs1.npy b/tests/test_data/nmf_framework/utils_klnmf/H_updated_standard_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/H_updated_mu-standard_nsigs1.npy rename to tests/test_data/nmf_framework/utils_klnmf/H_updated_standard_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/H_updated_mu-standard_nsigs2.npy b/tests/test_data/nmf_framework/utils_klnmf/H_updated_standard_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/H_updated_mu-standard_nsigs2.npy rename to tests/test_data/nmf_framework/utils_klnmf/H_updated_standard_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/W_updated_mu-joint_nsigs1.npy b/tests/test_data/nmf_framework/utils_klnmf/W_updated_joint_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/W_updated_mu-joint_nsigs1.npy rename to tests/test_data/nmf_framework/utils_klnmf/W_updated_joint_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/W_updated_mu-joint_nsigs2.npy b/tests/test_data/nmf_framework/utils_klnmf/W_updated_joint_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/W_updated_mu-joint_nsigs2.npy rename to tests/test_data/nmf_framework/utils_klnmf/W_updated_joint_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/W_updated_mu-standard_nsigs1.npy b/tests/test_data/nmf_framework/utils_klnmf/W_updated_standard_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/W_updated_mu-standard_nsigs1.npy rename to tests/test_data/nmf_framework/utils_klnmf/W_updated_standard_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/W_updated_mu-standard_nsigs2.npy b/tests/test_data/nmf_framework/utils_klnmf/W_updated_standard_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/W_updated_mu-standard_nsigs2.npy rename to tests/test_data/nmf_framework/utils_klnmf/W_updated_standard_nsigs2.npy From 98b66799f44c8380293bddb1a3ea92fdb3ce6181 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 14:57:54 -0400 Subject: [PATCH 022/103] simplify paths --- tests/test_utils_klnmf.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/tests/test_utils_klnmf.py b/tests/test_utils_klnmf.py index 5b15444..56b2b77 100644 --- a/tests/test_utils_klnmf.py +++ b/tests/test_utils_klnmf.py @@ -4,13 +4,13 @@ from salamander.nmf_framework import _utils_klnmf -PATH_TEST_DATA = "tests/test_data" -PATH_TEST_DATA_UTILS_KLNMF = f"{PATH_TEST_DATA}/nmf_framework/utils_klnmf" +PATH = "tests/test_data" +PATH_TEST_DATA = f"{PATH}/nmf_framework/utils_klnmf" @pytest.fixture def counts(): - return pd.read_csv(f"{PATH_TEST_DATA_UTILS_KLNMF}/counts.csv", index_col=0) + return pd.read_csv(f"{PATH_TEST_DATA}/counts.csv", index_col=0) @pytest.fixture(params=[1, 2]) @@ -20,9 +20,8 @@ def n_signatures(request): @pytest.fixture def matrices_input(counts, n_signatures): - W = np.load(f"{PATH_TEST_DATA_UTILS_KLNMF}/W_nsigs{n_signatures}.npy") - H = np.load(f"{PATH_TEST_DATA_UTILS_KLNMF}/H_nsigs{n_signatures}.npy") - + W = np.load(f"{PATH_TEST_DATA}/W_nsigs{n_signatures}.npy") + H = np.load(f"{PATH_TEST_DATA}/H_nsigs{n_signatures}.npy") return (counts.values, W, H) @@ -42,7 +41,7 @@ def weights_l_half(counts): @pytest.fixture def kl_divergence_output(n_signatures): - path = f"{PATH_TEST_DATA_UTILS_KLNMF}/kl_divergence_nsigs{n_signatures}.npy" + path = f"{PATH_TEST_DATA}/kl_divergence_nsigs{n_signatures}.npy" return np.load(path) @@ -61,10 +60,7 @@ def test_kl_divergence_weights(matrices_input, weights_kl, kl_divergence_output) @pytest.fixture def samplewise_kl_divergence_output(n_signatures): - path = ( - f"{PATH_TEST_DATA_UTILS_KLNMF}/" - f"samplewise_kl_divergence_nsigs{n_signatures}.npy" - ) + path = f"{PATH_TEST_DATA}/" f"samplewise_kl_divergence_nsigs{n_signatures}.npy" return np.load(path) @@ -93,7 +89,7 @@ def test_samplewise_kl_divergence_weights( @pytest.fixture def poisson_llh_output(n_signatures): - path = f"{PATH_TEST_DATA_UTILS_KLNMF}/poisson_llh_nsigs{n_signatures}.npy" + path = f"{PATH_TEST_DATA}/poisson_llh_nsigs{n_signatures}.npy" return np.load(path) @@ -103,7 +99,7 @@ def test_poisson_llh(matrices_input, poisson_llh_output): @pytest.fixture def W_updated(n_signatures): - path = f"{PATH_TEST_DATA_UTILS_KLNMF}/W_updated_mu-standard_nsigs{n_signatures}.npy" + path = f"{PATH_TEST_DATA}/W_updated_standard_nsigs{n_signatures}.npy" return np.load(path) @@ -133,7 +129,7 @@ def test_given_signatures_update_W(matrices_input): @pytest.fixture def H_updated(n_signatures): - path = f"{PATH_TEST_DATA_UTILS_KLNMF}/H_updated_mu-standard_nsigs{n_signatures}.npy" + path = f"{PATH_TEST_DATA}/H_updated_standard_nsigs{n_signatures}.npy" return np.load(path) @@ -151,9 +147,9 @@ def test_update_H_weights_l_half(matrices_input, weights_kl, weights_l_half, H_u @pytest.fixture def WH_updated(n_signatures): - suffix = f"_updated_mu-standard_nsigs{n_signatures}.npy" - path_W = f"{PATH_TEST_DATA_UTILS_KLNMF}/W{suffix}" - path_H = f"{PATH_TEST_DATA_UTILS_KLNMF}/H{suffix}" + suffix = f"_updated_joint_nsigs{n_signatures}.npy" + path_W = f"{PATH_TEST_DATA}/W{suffix}" + path_H = f"{PATH_TEST_DATA}/H{suffix}" return np.load(path_W), np.load(path_H) From 5c8f07f6845666fcff852939b78c9172cc7a0773 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 15:02:38 -0400 Subject: [PATCH 023/103] remove test files The KL-NMF implementation now only supports the faster joint update rules. --- ...nt_nsigs1.pkl => WH_updated_joint_nsigs1.pkl} | Bin ...nt_nsigs2.pkl => WH_updated_joint_nsigs2.pkl} | Bin .../klnmf/WH_updated_mu-standard_nsigs1.pkl | Bin 1007 -> 0 bytes .../klnmf/WH_updated_mu-standard_nsigs2.pkl | Bin 1857 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) rename tests/test_data/nmf_framework/klnmf/{WH_updated_mu-joint_nsigs1.pkl => WH_updated_joint_nsigs1.pkl} (100%) rename tests/test_data/nmf_framework/klnmf/{WH_updated_mu-joint_nsigs2.pkl => WH_updated_joint_nsigs2.pkl} (100%) delete mode 100644 tests/test_data/nmf_framework/klnmf/WH_updated_mu-standard_nsigs1.pkl delete mode 100644 tests/test_data/nmf_framework/klnmf/WH_updated_mu-standard_nsigs2.pkl diff --git a/tests/test_data/nmf_framework/klnmf/WH_updated_mu-joint_nsigs1.pkl b/tests/test_data/nmf_framework/klnmf/WH_updated_joint_nsigs1.pkl similarity index 100% rename from tests/test_data/nmf_framework/klnmf/WH_updated_mu-joint_nsigs1.pkl rename to tests/test_data/nmf_framework/klnmf/WH_updated_joint_nsigs1.pkl diff --git a/tests/test_data/nmf_framework/klnmf/WH_updated_mu-joint_nsigs2.pkl b/tests/test_data/nmf_framework/klnmf/WH_updated_joint_nsigs2.pkl similarity index 100% rename from tests/test_data/nmf_framework/klnmf/WH_updated_mu-joint_nsigs2.pkl rename to tests/test_data/nmf_framework/klnmf/WH_updated_joint_nsigs2.pkl diff --git a/tests/test_data/nmf_framework/klnmf/WH_updated_mu-standard_nsigs1.pkl b/tests/test_data/nmf_framework/klnmf/WH_updated_mu-standard_nsigs1.pkl deleted file mode 100644 index db622694a885b40fa64eab13084a5c623bc066dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1007 zcmX|=drZ?;6vugWAPQ4J3LKrFxTH^WRe(udZ=bn4+xnBk4 z?2N0E-Agb!bonk*YyQG-CI zN3i$l)Xrrc>S$uM{}>C`Zp%Dc7&>%p*?G|RvJP2IZ9gUN(n1mIHMozSfjGXq=OvpC zU!Pk%U_4U+WuUOoA`WjId!Rzonazc-yXeu9crZSMRf!`9LIQuR z$^duI;|cc;BiuXhH&Qdy5Cy08$JnGV&OaKjUAmK|!KPbd!iU-->^pxc;t#nL7ehy) zV@9)3pRypo6QRPeP!`#9`@x#-kqo@c`Ey#{{OYyntHq2&bjicA?d?4 z!weee~_2lU*=SMq-+8nb)0Cp@7x?!xvFakk7U2{ctm+zcyTosRqkEN{|f8+9-18)?#!leV#Z6D}wdzHp}2h8H#@nYdGd5M?&wr+o^k%sICn+T<|T!;{Q zw+L@iHDRh>#c1!}aoX)H8z)}<=dW}w4=dXP+f)=O*k79NyA6pjMYnkPH>abd-m7lM zz;P@;3HZkAun;Xnaf)xLc}U&8!EM`9A$a!xn~jJN%3J^TOV<0Y1W8crdRb+rP@2bC z6$jzMrIFfl4ap?K8LotL44IsqoUpQL_JQFfBw37fhO?R^oa0F0svFNW-0-$TflkhA zobIFBm!@v|THK21ii1aL92`II*#B?T!Nx8J)4S-)YyQ5?^k-|-ndl_Xli|$pRFgdK JwN#wQ{{iQo%{c%7 diff --git a/tests/test_data/nmf_framework/klnmf/WH_updated_mu-standard_nsigs2.pkl b/tests/test_data/nmf_framework/klnmf/WH_updated_mu-standard_nsigs2.pkl deleted file mode 100644 index b2062e8530a348541f7d01b16d974bff81e56609..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1857 zcmYL~dt8iJ7{{l^h!K)Y#AZVpZ6cIhI?qJg#UQ$fbf0DvqRTYRsF|8BbIHt<=510! zp%fxEs}O~#L@^>N)VgLBE4QYao#nG{=a2KA@8|dY&Uwx`@A*85q%2!K!jI;MQKW{u zV!|W1R`lIb3@h!B5fw(qq+m&w5=^6Jz(V`QjPW18o{NrMJ| zGy2yO1&ZW%937YyjQjo9&k^zX>0oT&?sw^t5_$0hBVOFxi|e{WU0$2)lX0HVH2)tP zVj#{VT~d~}jTYfNzhtiSo1RQ4s(zjAGGBps^*w6-$8_lEzNR$XQjWUQ57rj4hu1%o zYVu~vyBw%lX+c}ESb{iLdc|%c5d?@jJPF^FaRdZ8YEHnp6y-L^Icf4~6Kq^xZ=D1P)M2ySf$&k3gngMt0j`4tT$qe#2pv z1oGD$6_;s3ft9gfYQo@I)O?|%rPNITv&WiF)Sq4q@5!>2&4ZEf?eEL8GEeM<&fMg` zOj=k_P#bgEbnO{5sdLLcQm+RjyD#vJRv$pDrI$ji4{^aI)O=6DgiPpLri|KYodIqR zTSC6b8(>~b(e%8NLYUe%NzCu7h7HxRc0NskP_bW0E>|amlg=)h{iZ{ZedF9Q@5c|| z5p~gQ=DsZaJel>yO{`cU&K;YTB}JnOaAs2ncdFIt(AVEoD=$kz4$O8(+ASHbpKa}z zmyJ)uc|u5qYDN);^DFoEQQ_;dU?^>C-urcXQ85?{+zcJQe<4}7jNmH4<0DV(u&B75 zgL7@feYWMyVV&3Kn{IpjAdHb(<{!r{z>E#+Oi?igbW+gfHL@cZkmB5*@eMmO zeh<#8Nfsm8YvQ4^)Jh&3myGf~Yx3`|O@i&BI1t z(Dg97h%8LoK37c>gJj`Q5^IeJ6>l%8%$YNsPmHRuG2&_j^c*`N*ft{#bwB9!ocU1( z0_$nbJ3a}J@PwuR6Se~MbqwC-@dU_wu#I0hN&r*eF<0jjC8*_oTt!#>@Ov9=9$#)f zTLF5_-O@Ue9Bp>+!fu^Q!1oVJHkuSSq=2l1xs6#}gNO+frV*cq>y#C*=2h9J!t)X1 z2UrEkC}o+le$TiRc&a(?I?hLk!UPvT4nB#64a9l@)JqZX;GcS~JTC5^<`Ep-nV$l* zDXJj1@u`SS_AnXuG8NYkb7#$P_D+JaT|r;s%u>*qsPgNVr*Ys`9`T*G_I&F6?Vc_t zrGeb7XjQg_1o6Al4BO9B^<62dvx<6W)e*id<;r!(htF}g7Q#bG Date: Fri, 12 Apr 2024 16:00:11 -0400 Subject: [PATCH 024/103] update lock file --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3836a25..781512d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1265,18 +1265,18 @@ stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] [[package]] name = "setuptools" -version = "69.2.0" +version = "69.4.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, + {file = "setuptools-69.4.0-py3-none-any.whl", hash = "sha256:b6df12d754b505e4ca283c61582d5578db83ae2f56a979b3bc9a8754705ae3bf"}, + {file = "setuptools-69.4.tar.gz", hash = "sha256:659e902e587e77fab8212358f5b03977b5f0d18d4724310d4a093929fee4ca1a"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] From bbdcb720cb7f00f567670991d5b96d6d498dabac Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 16:02:52 -0400 Subject: [PATCH 025/103] update klnmf tests update the KL-NMF tests to the AnnData based model structure --- tests/test_klnmf.py | 52 +++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/tests/test_klnmf.py b/tests/test_klnmf.py index a1f6919..c7f0826 100644 --- a/tests/test_klnmf.py +++ b/tests/test_klnmf.py @@ -1,18 +1,22 @@ import pickle +import anndata as ad import numpy as np import pandas as pd import pytest from salamander.nmf_framework import klnmf +from salamander.nmf_framework.standard_nmf import _DEFAULT_GIVEN_PARAMETERS PATH = "tests/test_data" PATH_TEST_DATA = f"{PATH}/nmf_framework/klnmf" @pytest.fixture -def counts(): - return pd.read_csv(f"{PATH_TEST_DATA}/counts.csv", index_col=0) +def adata(): + counts = pd.read_csv(f"{PATH_TEST_DATA}/counts.csv", index_col=0) + adata = ad.AnnData(counts.T) + return adata @pytest.fixture(params=[1, 2]) @@ -25,18 +29,25 @@ def W_init(n_signatures): return np.load(f"{PATH_TEST_DATA}/W_init_nsigs{n_signatures}.npy") +@pytest.fixture +def asignatures_init(W_init, adata): + asignatures = ad.AnnData(W_init.T) + asignatures.var_names = adata.var_names + return asignatures + + @pytest.fixture def H_init(n_signatures): return np.load(f"{PATH_TEST_DATA}/H_init_nsigs{n_signatures}.npy") @pytest.fixture -def model_init(counts, W_init, H_init): - n_signatures = W_init.shape[1] +def model_init(adata, asignatures_init, H_init): + n_signatures = asignatures_init.n_obs model = klnmf.KLNMF(n_signatures=n_signatures) - model.X = counts.values - model.W = W_init - model.H = H_init + model.adata = adata + model.asignatures = asignatures_init + model.adata.obsm["exposures"] = H_init.T return model @@ -49,34 +60,33 @@ def test_objective_function(model_init, objective_init): assert np.allclose(model_init.objective_function(), objective_init) -@pytest.mark.parametrize("update_method", ["mu-standard", "mu-joint"]) class TestUpdatesKLNMF: @pytest.fixture - def WH_updated(self, n_signatures, update_method): + def WH_updated(self, n_signatures): with open( - f"{PATH_TEST_DATA}/WH_updated_{update_method}_nsigs{n_signatures}.pkl", "rb" + f"{PATH_TEST_DATA}/WH_updated_joint_nsigs{n_signatures}.pkl", "rb" ) as f: WH_updated = pickle.load(f) return WH_updated - def test_update_WH(self, model_init, update_method, WH_updated): - model_init.update_method = update_method - model_init._update_WH() + def test_update_parameters(self, model_init, WH_updated): + model_init._update_parameters(given_parameters=_DEFAULT_GIVEN_PARAMETERS) W_updated, H_updated = WH_updated - assert np.allclose(model_init.W, W_updated) - assert np.allclose(model_init.H, H_updated) + assert np.allclose(model_init.asignatures.X.T, W_updated) + assert np.allclose(model_init.adata.obsm["exposures"].T, H_updated) - def test_given_signatures(self, n_signatures, update_method, counts): + def test_given_signatures(self, n_signatures, adata): for n_given_signatures in range(1, n_signatures + 1): - given_signatures = counts.iloc[:, :n_given_signatures].astype(float).copy() - given_signatures /= given_signatures.sum(axis=0) + given_asignatures = adata[:n_given_signatures, :].copy() + given_asignatures.X = given_asignatures.X / np.sum( + given_asignatures.X, axis=1, keepdims=True + ) model = klnmf.KLNMF( n_signatures=n_signatures, - update_method=update_method, min_iterations=3, max_iterations=3, ) - model.fit(counts, given_signatures=given_signatures) + model.fit(adata, given_parameters={"asignatures": given_asignatures}) assert np.allclose( - given_signatures, model.signatures.iloc[:, :n_given_signatures] + given_asignatures.X, model.asignatures.X[:n_given_signatures, :] ) From e2179d8c228472d3993d3b7ab575d6ddfabee98d Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 16:21:08 -0400 Subject: [PATCH 026/103] remove import --- src/salamander/nmf_framework/klnmf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/salamander/nmf_framework/klnmf.py b/src/salamander/nmf_framework/klnmf.py index 4d60315..60e1d2f 100644 --- a/src/salamander/nmf_framework/klnmf.py +++ b/src/salamander/nmf_framework/klnmf.py @@ -13,8 +13,6 @@ from typing import Any, Literal from .initialization import _Init_methods - from .signature_nmf import _Dim_reduction_methods - _FITTING_KWARGS = ["weights_kl", "weights_lhalf"] _DEFAULT_FITTING_KWARGS = {kwarg: None for kwarg in _FITTING_KWARGS} From 3932d82826f26eb301848c2ec62818f69371ae5f Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 16:24:07 -0400 Subject: [PATCH 027/103] improve imports --- src/salamander/nmf_framework/signature_nmf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/salamander/nmf_framework/signature_nmf.py b/src/salamander/nmf_framework/signature_nmf.py index 0570c28..8214b00 100644 --- a/src/salamander/nmf_framework/signature_nmf.py +++ b/src/salamander/nmf_framework/signature_nmf.py @@ -11,7 +11,7 @@ from .. import plot as pl from .. import tools as tl from ..utils import type_checker, value_checker -from .initialization import _INIT_METHODS, EPSILON, _Init_methods +from .initialization import _INIT_METHODS, EPSILON if TYPE_CHECKING: from typing import Any @@ -19,6 +19,8 @@ from matplotlib.axes import Axes from seaborn.matrix import ClusterGrid + from .initialization import _Init_methods + _Dim_reduction_methods = Literal[ "pca", "tsne", From f69b3595de483cc369c5ec6de21858a1d512f221 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 17:30:39 -0400 Subject: [PATCH 028/103] simplify import --- tests/test_klnmf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_klnmf.py b/tests/test_klnmf.py index c7f0826..f831695 100644 --- a/tests/test_klnmf.py +++ b/tests/test_klnmf.py @@ -1,9 +1,9 @@ import pickle -import anndata as ad import numpy as np import pandas as pd import pytest +from anndata import AnnData from salamander.nmf_framework import klnmf from salamander.nmf_framework.standard_nmf import _DEFAULT_GIVEN_PARAMETERS @@ -15,7 +15,7 @@ @pytest.fixture def adata(): counts = pd.read_csv(f"{PATH_TEST_DATA}/counts.csv", index_col=0) - adata = ad.AnnData(counts.T) + adata = AnnData(counts.T) return adata @@ -31,7 +31,7 @@ def W_init(n_signatures): @pytest.fixture def asignatures_init(W_init, adata): - asignatures = ad.AnnData(W_init.T) + asignatures = AnnData(W_init.T) asignatures.var_names = adata.var_names return asignatures From 971498ab65083e059ff994464de0b4c07a557557 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 17:31:56 -0400 Subject: [PATCH 029/103] add mvnmf An implementation of minimum-volume NMF based on the SignatureNMF structure with mutation counts and signatures as anndata objects. --- src/salamander/nmf_framework/mvnmf.py | 216 ++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 src/salamander/nmf_framework/mvnmf.py diff --git a/src/salamander/nmf_framework/mvnmf.py b/src/salamander/nmf_framework/mvnmf.py new file mode 100644 index 0000000..2c27a72 --- /dev/null +++ b/src/salamander/nmf_framework/mvnmf.py @@ -0,0 +1,216 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np +import pandas as pd +from numba import njit + +from ..utils import normalize_WH +from ._utils_klnmf import kl_divergence, poisson_llh, samplewise_kl_divergence, update_H +from .initialization import EPSILON, _Init_methods +from .standard_nmf import StandardNMF + +if TYPE_CHECKING: + from typing import Literal + + from .initialization import _Init_methods + +_DEFAULT_FITTING_KWARGS = None + + +@njit +def volume_logdet(W: np.ndarray, delta: float) -> float: + n_signatures = W.shape[1] + diag = np.diag(np.full(n_signatures, delta)) + volume = np.log(np.linalg.det(W.T @ W + diag)) + return volume + + +@njit +def kl_divergence_penalized( + X: np.ndarray, W: np.ndarray, H: np.ndarray, lam: float, delta: float +) -> float: + reconstruction_error = kl_divergence(X, W, H) + volume = volume_logdet(W, delta) + loss = reconstruction_error + lam * volume + return loss + + +@njit +def update_W_unconstrained( + X: np.ndarray, + W: np.ndarray, + H: np.ndarray, + lam: float, + delta: float, + n_given_signatures: int = 0, +) -> np.ndarray: + n_signatures = W.shape[1] + diag = np.diag(np.full(n_signatures, delta)) + Y = np.linalg.inv(W.T @ W + diag) + Y_minus = np.maximum(0, -Y) + Y_abs = np.abs(Y) + WY_minus = W @ Y_minus + WY_abs = W @ Y_abs + + rowsums_H = np.sum(H, axis=1) + discriminant_s1 = (rowsums_H - 4 * lam * WY_minus) ** 2 + discriminant_s2 = 8 * lam * WY_abs * ((X / (W @ H)) @ H.T) + numerator_s1 = np.sqrt(discriminant_s1 + discriminant_s2) + numerator_s2 = -rowsums_H + 4 * lam * WY_minus + numerator = numerator_s1 + numerator_s2 + denominator = 4 * lam * WY_abs + W_unconstrained = W * numerator / denominator + W_unconstrained[:, :n_given_signatures] = W[:, :n_given_signatures].copy() + W_unconstrained[:, n_given_signatures:] = W_unconstrained[ + :, n_given_signatures: + ].clip(EPSILON) + return W_unconstrained + + +@njit +def line_search( + X: np.ndarray, + W: np.ndarray, + H: np.ndarray, + lam: float, + delta: float, + gamma: float, + W_unconstrained: np.ndarray, +) -> tuple[np.ndarray, np.ndarray, float]: + prev_of_value = kl_divergence_penalized(X, W, H, lam, delta) + W_new, H_new = normalize_WH(W_unconstrained, H) + W_new, H_new = W_new.clip(EPSILON), H_new.clip(EPSILON) + of_value = kl_divergence_penalized(X, W_new, H_new, lam, delta) + + while of_value > prev_of_value and gamma > 1e-16: + gamma *= 0.8 + W_new = (1 - gamma) * W + gamma * W_unconstrained + W_new, H_new = normalize_WH(W_new, H) + W_new, H_new = W_new.clip(EPSILON), H_new.clip(EPSILON) + of_value = kl_divergence_penalized(X, W_new, H_new, lam, delta) + + gamma = min(1.0, 1.2 * gamma) + return W_new, H_new, gamma + + +class MvNMF(StandardNMF): + """ + Min-volume non-negative matrix factorization. This algorithms is a volume- + regularized version of NMF with the generalized Kullback-Leibler (KL) + divergence. + + Parameters + ---------- + lam : float, default=1.0 + Objective function volume penalty weight. + + delta : float, default=1.0 + Objective function hyperparameter, see equation (4) in reference. + + Reference + --------- + Leplat, V., Gillis, N. and Ang, A.M., 2020. + Blind audio source separation with minimum-volume beta-divergence NMF. + IEEE Transactions on Signal Processing, 68, pp.3400-3410. + """ + + def __init__( + self, + n_signatures: int = 1, + init_method: _Init_methods = "nndsvd", + lam: float = 1.0, + delta: float = 1.0, + min_iterations: int = 500, + max_iterations: int = 10000, + conv_test_freq: int = 10, + tol: float = 1e-7, + ): + super().__init__( + n_signatures, + init_method, + min_iterations, + max_iterations, + conv_test_freq, + tol, + ) + self.lam = lam + self.delta = delta + self._gamma = None + + def compute_reconstruction_errors(self) -> None: + """ + Add the samplewise Kullback-Leibler divergences + as observation annotations to the AnnData count data. + """ + errors = _utils_klnmf.samplewise_kl_divergence( + self.adata.X.T, self.asignatures.X.T, self.adata.obsm["exposures"].T + ) + self.adata.obs["reconstruction_error"] = errors + + def objective_function(self) -> float: + return kl_divergence_penalized( + self.adata.X.T, + self.asignatures.X.T, + self.adata.obsm["exposures"].T, + self.lam, + self.delta, + ) + + @property + def objective(self) -> Literal["minimize", "maximize"]: + return "minimize" + + def _update_H(self): + self.adata.obsm["exposures"] = update_H( + self.adata.X.T, self.asignatures.X.T, self.adata.obsm["exposures"].T + ).T + + def _update_W_unconstrained(self, n_given_signatures: int) -> np.ndarray: + return update_W_unconstrained( + self.adata.X.T, + self.asignatures.X.T, + self.adata.obsm["exposures"].T, + self.lam, + self.delta, + n_given_signatures, + ) + + def _line_search(self, W_unconstrained: np.ndarray) -> None: + W, H, self._gamma = line_search( + self.adata.X.T, + self.asignatures.X.T, + self.adata.obsm["exposures"].T, + self.lam, + self.delta, + self._gamma, + W_unconstrained, + ) + self.asignatures.X = W.T + self.adata.obsm["exposures"] = H.T + + def _update_W(self, n_given_signatures: int) -> None: + W_unconstrained = self._update_W_unconstrained(n_given_signatures) + self._line_search(W_unconstrained) + + def _update_parameters(self, given_parameters: dict[str, Any]) -> None: + given_asignatures = given_parameters["asignatures"] + + if given_asignatures is not None: + n_given_signatures = given_asignatures.n_obs + else: + n_given_signatures = 0 + + self._update_H() + + if n_given_signatures < self.n_signatures: + self._update_W(n_given_signatures) + + def _setup_fitting_parameters( + self, fitting_kwargs: dict[str, Any] | None = None + ) -> None: + if fitting_kwargs is None: + fitting_kwargs = _DEFAULT_FITTING_KWARGS # still None + + self._gamma = 1.0 From 3bb2378a8091e1fc30ccecdb5871fa12d6523309 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 17:32:17 -0400 Subject: [PATCH 030/103] add test data --- .../nmf_framework/mvnmf/H_init_nsigs1.npy | Bin 0 -> 208 bytes .../nmf_framework/mvnmf/H_init_nsigs2.npy | Bin 0 -> 288 bytes .../nmf_framework/mvnmf/H_updated_nsigs1.npy | Bin 0 -> 208 bytes .../nmf_framework/mvnmf/H_updated_nsigs2.npy | Bin 0 -> 288 bytes .../nmf_framework/mvnmf/W_init_nsigs1.npy | Bin 0 -> 896 bytes .../nmf_framework/mvnmf/W_init_nsigs2.npy | Bin 0 -> 1664 bytes .../nmf_framework/mvnmf/W_updated_nsigs1.npy | Bin 0 -> 896 bytes .../nmf_framework/mvnmf/W_updated_nsigs2.npy | Bin 0 -> 1664 bytes .../test_data/nmf_framework/mvnmf/counts.csv | 97 ++++++++++++++++++ .../mvnmf/objective_init_nsigs1.npy | Bin 0 -> 136 bytes .../mvnmf/objective_init_nsigs2.npy | Bin 0 -> 136 bytes 11 files changed, 97 insertions(+) create mode 100644 tests/test_data/nmf_framework/mvnmf/H_init_nsigs1.npy create mode 100644 tests/test_data/nmf_framework/mvnmf/H_init_nsigs2.npy create mode 100644 tests/test_data/nmf_framework/mvnmf/H_updated_nsigs1.npy create mode 100644 tests/test_data/nmf_framework/mvnmf/H_updated_nsigs2.npy create mode 100644 tests/test_data/nmf_framework/mvnmf/W_init_nsigs1.npy create mode 100644 tests/test_data/nmf_framework/mvnmf/W_init_nsigs2.npy create mode 100644 tests/test_data/nmf_framework/mvnmf/W_updated_nsigs1.npy create mode 100644 tests/test_data/nmf_framework/mvnmf/W_updated_nsigs2.npy create mode 100644 tests/test_data/nmf_framework/mvnmf/counts.csv create mode 100644 tests/test_data/nmf_framework/mvnmf/objective_init_nsigs1.npy create mode 100644 tests/test_data/nmf_framework/mvnmf/objective_init_nsigs2.npy diff --git a/tests/test_data/nmf_framework/mvnmf/H_init_nsigs1.npy b/tests/test_data/nmf_framework/mvnmf/H_init_nsigs1.npy new file mode 100644 index 0000000000000000000000000000000000000000..7a5e615e80dc9f4d107501676a59cc2149e80aeb GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqpcnmP)#3giMV1`xQr#Q{w3-RSTi1oj?;(7}@-;#cNF=%2?S^xO>) PIsrwK;ID zzZM$Ww$tHjfaa=I+1ni6>h4H)__@?UeM!GTed9ET<&R&9F~3>ru+c*6($D2n9a39^ j3+Em1ad^Gas_pNB1rGb%AG}`9vBjaaz+Phm*J1|%Lq=$$ literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/mvnmf/H_updated_nsigs1.npy b/tests/test_data/nmf_framework/mvnmf/H_updated_nsigs1.npy new file mode 100644 index 0000000000000000000000000000000000000000..384de57f1f251cdb93f97de177b5a98beb19dc61 GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqpcnmP)#3giN={~&N@ivt4)?A_=9=I=cSp@Szw#IMYU@P8bK&~r9G U==co|e?S_97drd`(Y<>e082Six&QzG literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/mvnmf/H_updated_nsigs2.npy b/tests/test_data/nmf_framework/mvnmf/H_updated_nsigs2.npy new file mode 100644 index 0000000000000000000000000000000000000000..3a1a6a8da82aeb279e5e5e1872d8af639fc6007c GIT binary patch literal 288 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Wf%nItsN4WCN}{)rJ0=Df1nKoZKT`v#)Zf{Ql{`FY7{w77kv9O(lyQ zCcF|^b1HU~!;Y@6E2Vt5In3PP6Vm3?=8)vSd39xKgM-EVz_nHnCOb?x7_+qNdAoyj zY>Y{l^e%_{ix!_Qy|&e%VAUJVhuljY6j!UQv5lGGu*bSO=#cDEhsF-h=xUeg4lV!w jX0lyRbTD!_U?l%`zC+;e`z)3BHaWy_I(*}eU+Mq=v~*=# literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/mvnmf/W_init_nsigs1.npy b/tests/test_data/nmf_framework/mvnmf/W_init_nsigs1.npy new file mode 100644 index 0000000000000000000000000000000000000000..22a19838d6497d68d3b2da51f405933d2314078d GIT binary patch literal 896 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 bZ1iGHY`_Ub4|t9JeoKN5={0k!q7;7v5ygU- literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/mvnmf/W_updated_nsigs2.npy b/tests/test_data/nmf_framework/mvnmf/W_updated_nsigs2.npy new file mode 100644 index 0000000000000000000000000000000000000000..831cd6b542c202d664585dd98076b0cad1417904 GIT binary patch literal 1664 zcmbV~`!^H{0>_n0Ue$_{oC$F}HdBfUr}|b`?!7L<)(m-U-s3q8Gb7J2Gsa+I#=Mx3 zFzP1ZdW25)Znk10k3Cm;RBnZH&*$flj70JE4_c#@u9a;c9m9x9 zv-hyFKS*`6ce1jlrlc{`B9g;W(xPMj*S#W=7%^WvBQAm-^VJ>P_d8kbb#$`IwfcWC zu(7Q$vEC}gU>$qSF-IlLb}a3gBb-I>@;viGdKrd9qR-*`Jn z7WEuYXq$z=m#d#15rWYAai94XJu222=qO#ZQ}O0@AHC(E48(2DbMd=5*tpKo&!Z^^ z9cJraxCF&vtT1_tM39fn>wR8j;i>SgETA0O6NC^|Le4{XBAPzb-LaTjFb8UxqP#jc2XM=H?g1`5CuS7A1Q9O0-k(E zzVl<@R!ZFx`FJ!g&);H1>PJH7os-a6%!2u7%DheX*RMFO70sC^F|-<o$dqo0p7h+xaa~L>xV%_JB_hmR0qtVS6 zip2JonWbHKrC^=y&K;|iz=shn=^?V% z9b=PR!39G;5({11qzwY>s8J1S+PSFsxw@tCT{m`*>X_ZYDK$hCWYNDVYT8v6zD2{t?jnu;bun0K%b}(HU~KNMq?#- z-V^E99v+Laoy%4Wvk`b^6Pcy0;9+p_z;8XoV$kle8&>>^up>%q?X{0=xCX!4a@#Wz zrF;|Ct9yS1VLYaO+lzRFpUL+;yp<1%Hh;n0+#Tt$m-}^{co?Jq)7-~75)zXD*Q>gz z@DUL#c%k1y{q{`I1A}lxd6J&!v*TfHxAm9oi(4TB5~4Jd1+}W2}iyU{V|pl1Cx;X-AXSurf8Hfg#iPa TYfT??CZ$Mye7f>4_NVY)A]A,94,54,239,28,35,180,78,103,32,112 +A[C>A]C,60,63,199,25,23,129,60,58,24,131 +A[C>A]G,10,10,28,4,8,17,10,7,5,22 +A[C>A]T,72,42,222,17,24,148,43,60,20,106 +C[C>A]A,57,47,163,19,24,159,78,65,17,119 +C[C>A]C,73,35,161,18,13,143,41,33,10,121 +C[C>A]G,13,9,16,6,9,15,10,10,3,20 +C[C>A]T,68,57,189,7,20,173,62,65,14,116 +G[C>A]A,75,69,122,23,22,89,100,95,20,65 +G[C>A]C,45,45,84,14,16,83,27,50,15,92 +G[C>A]G,8,6,13,5,8,16,10,11,5,16 +G[C>A]T,49,66,101,10,13,109,72,63,13,68 +T[C>A]A,65,87,171,19,33,204,99,135,30,84 +T[C>A]C,86,61,132,18,24,202,60,82,33,84 +T[C>A]G,6,12,24,6,11,10,11,16,4,13 +T[C>A]T,91,134,250,24,28,265,151,182,44,100 +A[C>G]A,95,24,95,8,17,150,46,26,10,105 +A[C>G]C,40,19,61,8,7,70,36,19,14,60 +A[C>G]G,11,4,25,0,0,35,3,5,5,23 +A[C>G]T,96,18,118,7,9,145,45,35,16,112 +C[C>G]A,66,15,47,2,10,151,12,17,13,60 +C[C>G]C,52,10,36,3,9,92,23,15,11,77 +C[C>G]G,22,11,14,3,3,39,6,11,2,42 +C[C>G]T,79,19,50,7,7,203,28,31,15,106 +G[C>G]A,36,6,28,3,4,78,18,6,8,68 +G[C>G]C,30,9,43,3,8,64,22,8,7,61 +G[C>G]G,8,1,6,0,1,14,8,3,1,19 +G[C>G]T,51,8,34,6,10,107,23,18,9,93 +T[C>G]A,119,61,131,8,20,687,68,116,24,93 +T[C>G]C,85,32,80,9,11,448,52,56,13,110 +T[C>G]G,15,11,13,1,1,45,11,14,1,16 +T[C>G]T,239,90,236,14,28,1125,136,214,35,207 +A[C>T]A,126,91,238,24,46,187,80,70,54,140 +A[C>T]C,61,56,103,17,26,83,43,47,35,79 +A[C>T]G,149,272,257,92,119,185,147,168,134,145 +A[C>T]T,92,51,181,18,32,157,33,57,43,119 +C[C>T]A,75,76,112,21,46,140,53,66,40,100 +C[C>T]C,69,67,89,28,39,97,59,59,54,77 +C[C>T]G,93,163,139,45,72,108,110,108,85,88 +C[C>T]T,107,94,185,27,49,220,68,75,56,162 +G[C>T]A,68,75,86,13,37,99,74,58,38,123 +G[C>T]C,46,61,95,22,44,103,80,45,44,79 +G[C>T]G,90,230,176,71,81,155,124,118,102,116 +G[C>T]T,74,55,129,10,22,110,35,49,38,93 +T[C>T]A,139,178,198,28,63,520,136,224,97,106 +T[C>T]C,126,97,155,24,40,341,98,95,79,98 +T[C>T]G,80,128,117,35,68,101,79,103,53,87 +T[C>T]T,152,128,244,26,72,382,109,147,116,137 +A[T>A]A,43,66,115,17,16,86,44,26,13,57 +A[T>A]C,25,19,75,20,24,46,31,21,27,48 +A[T>A]G,37,30,99,10,17,76,22,29,16,64 +A[T>A]T,63,61,168,21,32,120,49,38,18,117 +C[T>A]A,31,32,85,4,9,61,15,16,3,63 +C[T>A]C,32,17,65,2,16,71,19,22,7,101 +C[T>A]G,55,24,105,9,14,108,34,22,9,68 +C[T>A]T,63,39,182,6,9,117,24,23,17,90 +G[T>A]A,22,17,42,5,4,47,10,9,7,40 +G[T>A]C,20,14,39,3,8,38,14,12,10,38 +G[T>A]G,23,16,33,5,14,48,18,15,5,39 +G[T>A]T,41,16,99,2,9,102,18,17,10,71 +T[T>A]A,31,63,124,16,29,122,51,60,18,76 +T[T>A]C,30,21,95,3,10,59,25,21,12,44 +T[T>A]G,19,15,57,2,5,43,9,9,7,39 +T[T>A]T,76,38,240,9,13,146,41,41,13,116 +A[T>C]A,90,101,150,29,49,189,79,57,52,157 +A[T>C]C,42,29,68,10,23,87,36,19,18,85 +A[T>C]G,58,47,85,10,24,108,55,31,26,131 +A[T>C]T,99,95,118,19,46,200,95,58,40,167 +C[T>C]A,39,50,57,7,15,69,44,14,20,73 +C[T>C]C,55,27,92,11,15,139,28,20,13,109 +C[T>C]G,43,37,42,10,14,59,28,25,15,88 +C[T>C]T,59,42,68,5,13,105,56,37,22,113 +G[T>C]A,40,63,79,11,32,101,50,22,12,81 +G[T>C]C,29,27,35,12,11,62,37,18,14,47 +G[T>C]G,26,41,32,11,9,58,39,17,15,64 +G[T>C]T,57,49,70,14,36,103,55,28,20,73 +T[T>C]A,56,83,106,7,23,93,41,28,23,76 +T[T>C]C,47,52,73,14,16,91,50,24,9,73 +T[T>C]G,25,29,57,4,13,61,18,21,13,54 +T[T>C]T,54,75,92,17,39,133,55,46,27,99 +A[T>G]A,29,25,49,2,13,61,29,18,13,49 +A[T>G]C,12,12,15,4,5,26,11,4,7,35 +A[T>G]G,43,16,40,6,6,74,17,19,6,44 +A[T>G]T,37,24,57,13,13,56,24,15,9,46 +C[T>G]A,18,7,12,5,3,30,10,9,3,29 +C[T>G]C,23,13,21,8,4,42,6,10,3,27 +C[T>G]G,43,11,46,1,6,100,21,25,7,62 +C[T>G]T,20,14,49,8,9,70,14,25,7,64 +G[T>G]A,7,10,17,0,1,27,7,7,5,26 +G[T>G]C,9,6,12,3,6,19,6,3,4,18 +G[T>G]G,25,15,37,11,4,76,16,12,8,57 +G[T>G]T,27,6,24,6,5,63,20,11,5,41 +T[T>G]A,39,25,39,2,9,66,21,8,9,52 +T[T>G]C,19,9,30,3,4,37,15,6,8,31 +T[T>G]G,39,18,73,4,10,86,21,18,4,45 +T[T>G]T,58,38,81,10,20,110,48,38,16,109 diff --git a/tests/test_data/nmf_framework/mvnmf/objective_init_nsigs1.npy b/tests/test_data/nmf_framework/mvnmf/objective_init_nsigs1.npy new file mode 100644 index 0000000000000000000000000000000000000000..195659322b0184bfb151aad80c023de474af6eec GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46ag;2yu+N6JfAoK05q~5h5!Hn literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/mvnmf/objective_init_nsigs2.npy b/tests/test_data/nmf_framework/mvnmf/objective_init_nsigs2.npy new file mode 100644 index 0000000000000000000000000000000000000000..fc046ed2fa76a0af020c49f60f03c79a5316e983 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46ag-g#gotTWZrN905?M( Date: Fri, 12 Apr 2024 17:33:17 -0400 Subject: [PATCH 031/103] add mvnmf tests --- tests/test_mvnmf.py | 90 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tests/test_mvnmf.py diff --git a/tests/test_mvnmf.py b/tests/test_mvnmf.py new file mode 100644 index 0000000..f07aa71 --- /dev/null +++ b/tests/test_mvnmf.py @@ -0,0 +1,90 @@ +import numpy as np +import pandas as pd +import pytest +from anndata import AnnData + +from salamander.nmf_framework import mvnmf + +PATH = "tests/test_data" +PATH_TEST_DATA = f"{PATH}/nmf_framework/mvnmf" + + +@pytest.fixture +def adata(): + counts = pd.read_csv(f"{PATH_TEST_DATA}/counts.csv", index_col=0) + adata = AnnData(counts.T) + return adata + + +@pytest.fixture(params=[1, 2]) +def n_signatures(request): + return request.param + + +@pytest.fixture +def W_init(n_signatures): + return np.load(f"{PATH_TEST_DATA}/W_init_nsigs{n_signatures}.npy") + + +@pytest.fixture +def asignatures_init(W_init, adata): + asignatures = AnnData(W_init.T) + asignatures.var_names = adata.var_names + return asignatures + + +@pytest.fixture +def H_init(n_signatures): + return np.load(f"{PATH_TEST_DATA}/H_init_nsigs{n_signatures}.npy") + + +@pytest.fixture +def model_init(adata, asignatures_init, H_init): + n_signatures = asignatures_init.n_obs + model = mvnmf.MvNMF(n_signatures=n_signatures) + model.adata = adata + model.asignatures = asignatures_init + model.adata.obsm["exposures"] = H_init.T + model._gamma = 1.0 + return model + + +@pytest.fixture +def objective_init(n_signatures): + return np.load(f"{PATH_TEST_DATA}/objective_init_nsigs{n_signatures}.npy") + + +def test_objective_function(model_init, objective_init): + assert np.allclose(model_init.objective_function(), objective_init) + + +class TestUpdatesMVNMF: + @pytest.fixture + def W_updated(self, n_signatures): + return np.load(f"{PATH_TEST_DATA}/W_updated_nsigs{n_signatures}.npy") + + @pytest.fixture + def H_updated(self, n_signatures): + return np.load(f"{PATH_TEST_DATA}/H_updated_nsigs{n_signatures}.npy") + + def test_update_W(self, model_init, W_updated): + model_init._update_W(n_given_signatures=0) + assert np.allclose(model_init.asignatures.X, W_updated.T) + + def test_update_H(self, model_init, H_updated): + model_init._update_H() + assert np.allclose(model_init.adata.obsm["exposures"], H_updated.T) + + def test_given_signatures(self, n_signatures, adata): + for n_given_signatures in range(1, n_signatures + 1): + given_asignatures = adata[:n_given_signatures, :].copy() + given_asignatures.X = given_asignatures.X / np.sum( + given_asignatures.X, axis=1, keepdims=True + ) + model = mvnmf.MvNMF( + n_signatures=n_signatures, min_iterations=3, max_iterations=3 + ) + model.fit(adata, given_parameters={"asignatures": given_asignatures}) + assert np.allclose( + given_asignatures.X, model.asignatures.X[:n_given_signatures, :] + ) From e38791d5e57f6093d9b817f5d298a3d9c0840a14 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 17:55:36 -0400 Subject: [PATCH 032/103] fix line length --- src/salamander/nmf_framework/initialization.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/salamander/nmf_framework/initialization.py b/src/salamander/nmf_framework/initialization.py index dfc6517..df96feb 100644 --- a/src/salamander/nmf_framework/initialization.py +++ b/src/salamander/nmf_framework/initialization.py @@ -176,8 +176,9 @@ def initialize( W, H = init_flat(X, n_signatures) elif init_method in ["nndsvd", "nndsvda", "nndsvdar"]: - # mypy does not recognize that init_method is compatible with Literal["nndsvd", "nndsvda", "nndsvdar"] - W, H = init_nndsvd(X, n_signatures, init=init_method, **kwargs) # type: ignore[arg-type] + # mypy does not recognize that init_method is compatible + # with Literal["nndsvd", "nndsvda", "nndsvdar"] + W, H = init_nndsvd(X, n_signatures, init=init_method, **kwargs) # type: ignore[arg-type] # noqa: E501 elif init_method == "random": W, H = init_random(X, n_signatures, **kwargs) From 3f45e251a19c947490895c3376c02ca2affb2d3d Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 18:02:49 -0400 Subject: [PATCH 033/103] fix imports --- src/salamander/nmf_framework/mvnmf.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/salamander/nmf_framework/mvnmf.py b/src/salamander/nmf_framework/mvnmf.py index 2c27a72..fda6644 100644 --- a/src/salamander/nmf_framework/mvnmf.py +++ b/src/salamander/nmf_framework/mvnmf.py @@ -3,16 +3,15 @@ from typing import TYPE_CHECKING import numpy as np -import pandas as pd from numba import njit from ..utils import normalize_WH -from ._utils_klnmf import kl_divergence, poisson_llh, samplewise_kl_divergence, update_H -from .initialization import EPSILON, _Init_methods +from ._utils_klnmf import kl_divergence, samplewise_kl_divergence, update_H +from .initialization import EPSILON from .standard_nmf import StandardNMF if TYPE_CHECKING: - from typing import Literal + from typing import Any, Literal from .initialization import _Init_methods @@ -137,14 +136,14 @@ def __init__( ) self.lam = lam self.delta = delta - self._gamma = None + self._gamma = 1.0 def compute_reconstruction_errors(self) -> None: """ Add the samplewise Kullback-Leibler divergences as observation annotations to the AnnData count data. """ - errors = _utils_klnmf.samplewise_kl_divergence( + errors = samplewise_kl_divergence( self.adata.X.T, self.asignatures.X.T, self.adata.obsm["exposures"].T ) self.adata.obs["reconstruction_error"] = errors From 8346e15b3c0687b1f17c812fc0966634f2cea408 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 18:06:38 -0400 Subject: [PATCH 034/103] remove f-strings --- src/salamander/tools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/salamander/tools.py b/src/salamander/tools.py index e2e6912..ede81ef 100644 --- a/src/salamander/tools.py +++ b/src/salamander/tools.py @@ -27,7 +27,7 @@ def pca(adata: AnnData, basis: str, **kwargs) -> None: observation annotations named 'basis'. """ data = _get_basis_obsm(adata, basis) - adata.obsm[f"X_pca"] = _pca(data, **kwargs) + adata.obsm["X_pca"] = _pca(data, **kwargs) def _tsne( @@ -51,7 +51,7 @@ def tsne(adata: AnnData, basis: str, **kwargs) -> None: observation annotations named 'basis'. """ data = _get_basis_obsm(adata, basis) - adata.obsm[f"X_tsne"] = _tsne(data, **kwargs) + adata.obsm["X_tsne"] = _tsne(data, **kwargs) def _umap( @@ -77,7 +77,7 @@ def umap(adata: AnnData, basis: str, **kwargs) -> None: observation annotations named 'basis'. """ data = _get_basis_obsm(adata, basis) - adata.obsm[f"X_umap"] = _umap(data, **kwargs) + adata.obsm["X_umap"] = _umap(data, **kwargs) def _reduce_dimension( @@ -175,4 +175,4 @@ def correlation(adata: AnnData, basis: str, **kwargs) -> None: observation annotations named 'basis'. """ data = _get_basis_obsm(adata, basis) - adata.obsp[f"X_correlation"] = _correlation(data, **kwargs) + adata.obsp["X_correlation"] = _correlation(data, **kwargs) From 910a0a9cb6d7d82cb92f5be6711536630129d318 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 18:10:52 -0400 Subject: [PATCH 035/103] fix imports --- src/salamander/nmf_framework/standard_nmf.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/nmf_framework/standard_nmf.py index 320a61b..c941c86 100644 --- a/src/salamander/nmf_framework/standard_nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -1,20 +1,17 @@ from __future__ import annotations import warnings -from abc import abstractmethod from typing import TYPE_CHECKING import anndata as ad -import numpy as np -import pandas as pd from .. import tools as tl -from ..utils import _get_basis_obsm, match_signatures_pair, type_checker +from ..utils import match_signatures_pair, type_checker from .initialization import initialize -from .signature_nmf import SignatureNMF, _Dim_reduction_methods +from .signature_nmf import SignatureNMF if TYPE_CHECKING: - from typing import Any, Literal + from typing import Any from .signature_nmf import _Dim_reduction_methods @@ -65,7 +62,8 @@ def _initialize( for parameter in given_parameters: if parameter not in _PARAMETERS_NAMES: raise ValueError( - f"The given parameters include parameters outside of {_PARAMETERS_NAMES}." + "The given parameters include parameters outside " + f"of {_PARAMETERS_NAMES}." ) given_asignatures = given_parameters["asignatures"] From d9f41dd8ea731f40137de791680365fd88f2272b Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 18:19:15 -0400 Subject: [PATCH 036/103] fix import --- src/salamander/nmf_framework/klnmf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/salamander/nmf_framework/klnmf.py b/src/salamander/nmf_framework/klnmf.py index 60e1d2f..2180b16 100644 --- a/src/salamander/nmf_framework/klnmf.py +++ b/src/salamander/nmf_framework/klnmf.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import numpy as np -import pandas as pd from ..utils import shape_checker, type_checker from . import _utils_klnmf @@ -135,7 +134,8 @@ def _setup_fitting_parameters( for kwarg in fitting_kwargs: if kwarg not in _FITTING_KWARGS: raise ValueError( - f"The given fitting keyword arguments include parameters outside of {_FITTING_KWARGS}." + "The given fitting keyword arguments include parameters " + f"outside of {_FITTING_KWARGS}." ) for name, weights in fitting_kwargs.items(): From ba09d03e3aa9e6772edba28080d820d658236617 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 18:43:11 -0400 Subject: [PATCH 037/103] fix reconstruction --- src/salamander/nmf_framework/signature_nmf.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/salamander/nmf_framework/signature_nmf.py b/src/salamander/nmf_framework/signature_nmf.py index 8214b00..9254dc3 100644 --- a/src/salamander/nmf_framework/signature_nmf.py +++ b/src/salamander/nmf_framework/signature_nmf.py @@ -226,7 +226,7 @@ def compute_reconstruction(self) -> None: @property def data_reconstructed(self) -> pd.DataFrame: - if not "X_reconstructed" in self.adata.obsm: + if "X_reconstructed" not in self.adata.obsm: self.compute_reconstruction() return pd.DataFrame( @@ -248,7 +248,7 @@ def reconstruction_error(self) -> float: The total reconstruction error between the data and the reconstructed data. """ - if not "reconstruction_error" in self.adata.obs: + if "reconstruction_error" not in self.adata.obs: self.compute_reconstruction_errors() return np.sum(self.adata.obs["reconstruction_error"]) @@ -512,11 +512,10 @@ def compute_correlation( respective anndata object. """ value_checker("data", data, ["samples", "signatures"]) - - assert ( - "exposures" in self.adata.obsm - ), "Computing the sample or signature correlation requires fitting the NMF model." - + assert "exposures" in self.adata.obsm, ( + "Computing the sample or signature correlation " + "requires fitting the NMF model." + ) values = self.adata.obsm["exposures"] if data == "signatures": From 4fe0a58ad607f29fd1c74030255e4651bd678c44 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 18:52:35 -0400 Subject: [PATCH 038/103] improve import --- src/salamander/plot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/salamander/plot.py b/src/salamander/plot.py index 1fb861c..547ee52 100644 --- a/src/salamander/plot.py +++ b/src/salamander/plot.py @@ -1,6 +1,5 @@ from __future__ import annotations -import warnings from typing import TYPE_CHECKING, Iterable import fastcluster @@ -15,7 +14,7 @@ from scipy.spatial.distance import pdist from .consts import COLORS_INDEL83, COLORS_SBS96, INDEL_TYPES_83, SBS_TYPES_96 -from .utils import _get_basis_obsm, _get_basis_obsp, match_to_catalog, value_checker +from .utils import _get_basis_obsm, _get_basis_obsp, match_to_catalog if TYPE_CHECKING: from typing import Any From d3050acf0251eea03e5dba4f0b5da37933251103 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 18:52:50 -0400 Subject: [PATCH 039/103] fix pylint --- src/salamander/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/salamander/tools.py b/src/salamander/tools.py index ede81ef..cfc5a8f 100644 --- a/src/salamander/tools.py +++ b/src/salamander/tools.py @@ -61,7 +61,7 @@ def _umap( min_dist: float = 0.1, **kwargs, ) -> np.ndarray: - import umap + import umap # pylint: disable=redefined-outer-name n_neighbors = min(n_neighbors, len(data) - 1.0) data_reduced_dim = umap.UMAP( From d4fbf853fd81f0448d46ade02d8d2eb17327ce1f Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 18:57:54 -0400 Subject: [PATCH 040/103] improve function names --- src/salamander/nmf_framework/signature_nmf.py | 2 +- src/salamander/tools.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/salamander/nmf_framework/signature_nmf.py b/src/salamander/nmf_framework/signature_nmf.py index 9254dc3..e301818 100644 --- a/src/salamander/nmf_framework/signature_nmf.py +++ b/src/salamander/nmf_framework/signature_nmf.py @@ -521,7 +521,7 @@ def compute_correlation( if data == "signatures": values = values.T - correlation = tl._correlation(values, **kwargs) + correlation = tl.correlation_numpy(values, **kwargs) if data == "samples": self.adata.obsp["X_correlation"] = correlation diff --git a/src/salamander/tools.py b/src/salamander/tools.py index cfc5a8f..f5a53f3 100644 --- a/src/salamander/tools.py +++ b/src/salamander/tools.py @@ -80,7 +80,7 @@ def umap(adata: AnnData, basis: str, **kwargs) -> None: adata.obsm["X_umap"] = _umap(data, **kwargs) -def _reduce_dimension( +def reduce_dimension_numpy( data: np.ndarray, method: str = "umap", n_components: int = 2, @@ -130,7 +130,7 @@ def reduce_dimension( ) return - adata.obsm[f"X_{method}"] = _reduce_dimension( + adata.obsm[f"X_{method}"] = reduce_dimension_numpy( data, method=method, n_components=n_components, **kwargs ) @@ -153,7 +153,7 @@ def reduce_dimension_multiple( ) return - data_reduced_dim = _reduce_dimension(data, method=method, **kwargs) + data_reduced_dim = reduce_dimension_numpy(data, method=method, **kwargs) sum_n_obs = 0 for adata in adatas: @@ -162,7 +162,7 @@ def reduce_dimension_multiple( sum_n_obs += n_obs -def _correlation(data: np.ndarray, **kwargs) -> np.ndarray: +def correlation_numpy(data: np.ndarray, **kwargs) -> np.ndarray: """ Compute the correlation of the rows of the data. """ @@ -175,4 +175,4 @@ def correlation(adata: AnnData, basis: str, **kwargs) -> None: observation annotations named 'basis'. """ data = _get_basis_obsm(adata, basis) - adata.obsp["X_correlation"] = _correlation(data, **kwargs) + adata.obsp["X_correlation"] = correlation_numpy(data, **kwargs) From 6e0078bd039013e45d980b853d1c8c014f4b3af2 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 12 Apr 2024 19:03:39 -0400 Subject: [PATCH 041/103] improve consistency --- tests/test_klnmf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_klnmf.py b/tests/test_klnmf.py index f831695..2cba82c 100644 --- a/tests/test_klnmf.py +++ b/tests/test_klnmf.py @@ -72,8 +72,8 @@ def WH_updated(self, n_signatures): def test_update_parameters(self, model_init, WH_updated): model_init._update_parameters(given_parameters=_DEFAULT_GIVEN_PARAMETERS) W_updated, H_updated = WH_updated - assert np.allclose(model_init.asignatures.X.T, W_updated) - assert np.allclose(model_init.adata.obsm["exposures"].T, H_updated) + assert np.allclose(model_init.asignatures.X, W_updated.T) + assert np.allclose(model_init.adata.obsm["exposures"], H_updated.T) def test_given_signatures(self, n_signatures, adata): for n_given_signatures in range(1, n_signatures + 1): From 6b759fa14f3d2154138d0d7b4898705acf82822d Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 16 Apr 2024 17:27:57 -0400 Subject: [PATCH 042/103] update lock file --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 781512d..2e87120 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1265,13 +1265,13 @@ stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] [[package]] name = "setuptools" -version = "69.4.0" +version = "69.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.4.0-py3-none-any.whl", hash = "sha256:b6df12d754b505e4ca283c61582d5578db83ae2f56a979b3bc9a8754705ae3bf"}, - {file = "setuptools-69.4.tar.gz", hash = "sha256:659e902e587e77fab8212358f5b03977b5f0d18d4724310d4a093929fee4ca1a"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [package.extras] From a80046cf91288d7ac33e4a2b8e352db6e49cdaf2 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 16 Apr 2024 17:29:08 -0400 Subject: [PATCH 043/103] update parameter check --- src/salamander/nmf_framework/_utils_klnmf.py | 38 +++++++++++++++++++ src/salamander/nmf_framework/signature_nmf.py | 26 ------------- src/salamander/nmf_framework/standard_nmf.py | 3 +- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/salamander/nmf_framework/_utils_klnmf.py b/src/salamander/nmf_framework/_utils_klnmf.py index 2722d41..d5c8d75 100644 --- a/src/salamander/nmf_framework/_utils_klnmf.py +++ b/src/salamander/nmf_framework/_utils_klnmf.py @@ -1,9 +1,12 @@ from __future__ import annotations import numpy as np +from anndata import AnnData from numba import njit from scipy.special import gammaln +from ..utils import type_checker + EPSILON = np.finfo(np.float32).eps @@ -160,6 +163,41 @@ def poisson_llh(X: np.ndarray, W: np.ndarray, H: np.ndarray) -> float: return result +def check_given_asignatures( + adata: AnnData, given_asignatures: AnnData, n_signatures_model: int +) -> None: + """ + Check if the given signatures are compatible with + the input data and the NMF model. + The number of given signatures can be less or equal to the number of + signatures specified by the model. + + Inputs + ------ + given_adata: AnnData + Data with mutation types annotations. + + given_asignatures: AnnData + Known signatures that should be fixed by the algorithm. + + n_signatures_model: int + The number of signatures of the NMF model. + """ + type_checker("given_asignatures", given_asignatures, AnnData) + mutation_types = adata.var_names.to_numpy(dtype=str) + given_mutation_types = given_asignatures.var_names.to_numpy(dtype=str) + compatible = ( + np.array_equal(mutation_types, given_mutation_types) + and given_asignatures.n_obs <= n_signatures_model + ) + if not compatible: + raise ValueError( + "The given signatures are not compatible with the NMF model or the data. " + f"You have to provide at most {n_signatures_model} signatures with " + "mutation types matching to your data." + ) + + @njit def update_W( X: np.ndarray, diff --git a/src/salamander/nmf_framework/signature_nmf.py b/src/salamander/nmf_framework/signature_nmf.py index e301818..fd7e9d8 100644 --- a/src/salamander/nmf_framework/signature_nmf.py +++ b/src/salamander/nmf_framework/signature_nmf.py @@ -281,32 +281,6 @@ def _setup_adata(self, adata: AnnData) -> None: self.adata = adata self.adata.X = self.adata.X.clip(EPSILON) - def _check_given_asignatures(self, given_asignatures: AnnData) -> None: - """ - Check if the given signatures are compatible with the - number of signatures of the algorithm and the - mutation types of the input data. - Should be called by implementations of _initialize. - - Inputs - ------ - given_asignatures: AnnData - Known signatures that should be fixed by the algorithm. - The number of known signatures can be less or equal to the - number of signatures specified by the algorithm. - """ - type_checker("given_asignatures", given_asignatures, AnnData) - given_mutation_types = given_asignatures.var_names.to_numpy(dtype=str) - compatible = ( - np.array_equal(given_mutation_types, self.mutation_types) - and given_asignatures.n_obs <= self.n_signatures - ) - if not compatible: - raise ValueError( - f"You have to provide at most {self.n_signatures} signatures with " - "mutation types matching to your data." - ) - @abstractmethod def _initialize( self, diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/nmf_framework/standard_nmf.py index c941c86..b6f0147 100644 --- a/src/salamander/nmf_framework/standard_nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -7,6 +7,7 @@ from .. import tools as tl from ..utils import match_signatures_pair, type_checker +from ._utils_klnmf import check_given_asignatures from .initialization import initialize from .signature_nmf import SignatureNMF @@ -69,7 +70,7 @@ def _initialize( given_asignatures = given_parameters["asignatures"] if given_asignatures is not None: - self._check_given_asignatures(given_asignatures) + check_given_asignatures(self.adata, given_asignatures, self.n_signatures) given_signatures = given_asignatures.to_df().T init_kwargs = {} if init_kwargs is None else init_kwargs.copy() From c45efaa3b4820a0c27de192fad00b5d53e9454d8 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 16 Apr 2024 17:55:42 -0400 Subject: [PATCH 044/103] improve parameter check --- src/salamander/nmf_framework/_utils_klnmf.py | 13 ++++++------- src/salamander/nmf_framework/standard_nmf.py | 4 +++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/salamander/nmf_framework/_utils_klnmf.py b/src/salamander/nmf_framework/_utils_klnmf.py index d5c8d75..feccfcd 100644 --- a/src/salamander/nmf_framework/_utils_klnmf.py +++ b/src/salamander/nmf_framework/_utils_klnmf.py @@ -164,7 +164,7 @@ def poisson_llh(X: np.ndarray, W: np.ndarray, H: np.ndarray) -> float: def check_given_asignatures( - adata: AnnData, given_asignatures: AnnData, n_signatures_model: int + given_asignatures: AnnData, mutation_types_data: np.ndarray, n_signatures_model: int ) -> None: """ Check if the given signatures are compatible with @@ -174,25 +174,24 @@ def check_given_asignatures( Inputs ------ - given_adata: AnnData - Data with mutation types annotations. - given_asignatures: AnnData Known signatures that should be fixed by the algorithm. + mutation_types_data: np.ndarray + The expected mutation types. + n_signatures_model: int The number of signatures of the NMF model. """ type_checker("given_asignatures", given_asignatures, AnnData) - mutation_types = adata.var_names.to_numpy(dtype=str) given_mutation_types = given_asignatures.var_names.to_numpy(dtype=str) compatible = ( - np.array_equal(mutation_types, given_mutation_types) + np.array_equal(mutation_types_data, given_mutation_types) and given_asignatures.n_obs <= n_signatures_model ) if not compatible: raise ValueError( - "The given signatures are not compatible with the NMF model or the data. " + "The given signatures are incompatible with the data or the model. " f"You have to provide at most {n_signatures_model} signatures with " "mutation types matching to your data." ) diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/nmf_framework/standard_nmf.py index b6f0147..12080bc 100644 --- a/src/salamander/nmf_framework/standard_nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -70,7 +70,9 @@ def _initialize( given_asignatures = given_parameters["asignatures"] if given_asignatures is not None: - check_given_asignatures(self.adata, given_asignatures, self.n_signatures) + check_given_asignatures( + given_asignatures, self.mutation_types, self.n_signatures + ) given_signatures = given_asignatures.to_df().T init_kwargs = {} if init_kwargs is None else init_kwargs.copy() From ee3ee38920ae5726a94659a3b9666de5c8331653 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 16 Apr 2024 23:48:17 -0400 Subject: [PATCH 045/103] improve reorder With NMF models built around AnnData, 'reorder' is identical for KL-NMF and correlated NMF. --- src/salamander/nmf_framework/signature_nmf.py | 14 ++++++++++++-- src/salamander/nmf_framework/standard_nmf.py | 13 ------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/salamander/nmf_framework/signature_nmf.py b/src/salamander/nmf_framework/signature_nmf.py index fd7e9d8..7cb886c 100644 --- a/src/salamander/nmf_framework/signature_nmf.py +++ b/src/salamander/nmf_framework/signature_nmf.py @@ -384,12 +384,22 @@ def fit( return self - @abstractmethod - def reorder(self, asignatures_other: AnnData) -> None: + def reorder( + self, + asignatures_other: ad.AnnData, + metric: str = "cosine", + ) -> None: """ Reorder the model parameters to match the order of another collection of signatures. """ + reordered_indices = match_signatures_pair( + asignatures_other.to_df().T, self.asignatures.to_df().T, metric=metric + ) + self.asignatures = self.asignatures[reordered_indices, :].copy() + self.adata.obsm["exposures"] = self.adata.obsm["exposures"][ + :, reordered_indices + ] def plot_history(self, outfile: str | None = None, **kwargs) -> Axes: """ diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/nmf_framework/standard_nmf.py index 12080bc..d814b74 100644 --- a/src/salamander/nmf_framework/standard_nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -100,19 +100,6 @@ def _initialize( self.adata.obsm["exposures"] = H.T return given_parameters - def reorder( - self, - asignatures_other: ad.AnnData, - metric: str = "cosine", - ) -> None: - reordered_indices = match_signatures_pair( - asignatures_other.to_df().T, self.asignatures.to_df().T, metric=metric - ) - self.asignatures = self.asignatures[reordered_indices, :].copy() - self.adata.obsm["exposures"] = self.adata.obsm["exposures"][ - :, reordered_indices - ] - def reduce_dimension_embeddings( self, method: _Dim_reduction_methods = "umap", n_components: int = 2, **kwargs ) -> None: From e642d7c9167237d3272af78359ca4c77a792b8a7 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 17 Apr 2024 18:25:30 -0400 Subject: [PATCH 046/103] add test data --- .../nmf_framework/corrnmf/counts.csv | 97 ++++++++++++++++++ .../corrnmf/objective_init_nsigs1_dim1.npy | Bin 0 -> 136 bytes .../corrnmf/objective_init_nsigs2_dim2.npy | Bin 0 -> 136 bytes .../nmf_framework/corrnmf/p_nsigs1_dim1.npy | Bin 0 -> 7808 bytes .../nmf_framework/corrnmf/p_nsigs2_dim2.npy | Bin 0 -> 15488 bytes .../sample_embeddings_init_nsigs1_dim1.npy | Bin 0 -> 208 bytes .../sample_embeddings_init_nsigs2_dim2.npy | Bin 0 -> 288 bytes .../sample_embeddings_updated_nsigs1_dim1.npy | Bin 0 -> 208 bytes .../sample_embeddings_updated_nsigs2_dim2.npy | Bin 0 -> 288 bytes .../sample_scalings_init_nsigs1_dim1.npy | Bin 0 -> 208 bytes .../sample_scalings_init_nsigs2_dim2.npy | Bin 0 -> 208 bytes .../sample_scalings_updated_nsigs1_dim1.npy | Bin 0 -> 208 bytes .../sample_scalings_updated_nsigs2_dim2.npy | Bin 0 -> 208 bytes .../signature_embeddings_init_nsigs1_dim1.npy | Bin 0 -> 136 bytes .../signature_embeddings_init_nsigs2_dim2.npy | Bin 0 -> 160 bytes ...gnature_embeddings_updated_nsigs1_dim1.npy | Bin 0 -> 136 bytes ...gnature_embeddings_updated_nsigs2_dim2.npy | Bin 0 -> 160 bytes .../signature_scalings_init_nsigs1_dim1.npy | Bin 0 -> 136 bytes .../signature_scalings_init_nsigs2_dim2.npy | Bin 0 -> 144 bytes ...signature_scalings_updated_nsigs1_dim1.npy | Bin 0 -> 136 bytes ...signature_scalings_updated_nsigs2_dim2.npy | Bin 0 -> 144 bytes .../signatures_mat_init_nsigs1_dim1.npy | Bin 0 -> 896 bytes .../signatures_mat_init_nsigs2_dim2.npy | Bin 0 -> 1664 bytes .../signatures_mat_updated_nsigs1_dim1.npy | Bin 0 -> 896 bytes .../signatures_mat_updated_nsigs2_dim2.npy | Bin 0 -> 1664 bytes .../corrnmf/variance_init_nsigs1_dim1.npy | Bin 0 -> 136 bytes .../corrnmf/variance_init_nsigs2_dim2.npy | Bin 0 -> 136 bytes .../corrnmf/variance_updated_nsigs1_dim1.npy | Bin 0 -> 136 bytes .../corrnmf/variance_updated_nsigs2_dim2.npy | Bin 0 -> 136 bytes 29 files changed, 97 insertions(+) create mode 100644 tests/test_data/nmf_framework/corrnmf/counts.csv create mode 100644 tests/test_data/nmf_framework/corrnmf/objective_init_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/objective_init_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/p_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/p_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/sample_embeddings_init_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/sample_embeddings_init_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/sample_embeddings_updated_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/sample_embeddings_updated_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/sample_scalings_init_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/sample_scalings_init_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/sample_scalings_updated_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/sample_scalings_updated_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signature_embeddings_init_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signature_embeddings_init_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signature_embeddings_updated_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signature_embeddings_updated_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signature_scalings_init_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signature_scalings_init_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signature_scalings_updated_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signature_scalings_updated_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signatures_mat_init_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signatures_mat_init_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signatures_mat_updated_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/signatures_mat_updated_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/variance_init_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/variance_init_nsigs2_dim2.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/variance_updated_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/variance_updated_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/counts.csv b/tests/test_data/nmf_framework/corrnmf/counts.csv new file mode 100644 index 0000000..05310ed --- /dev/null +++ b/tests/test_data/nmf_framework/corrnmf/counts.csv @@ -0,0 +1,97 @@ +Type,SP9251,SP6730,SP10084,SP5381,SP10635,SP2714,SP11235,SP8085,SP4593,SP4820 +A[C>A]A,94,54,239,28,35,180,78,103,32,112 +A[C>A]C,60,63,199,25,23,129,60,58,24,131 +A[C>A]G,10,10,28,4,8,17,10,7,5,22 +A[C>A]T,72,42,222,17,24,148,43,60,20,106 +C[C>A]A,57,47,163,19,24,159,78,65,17,119 +C[C>A]C,73,35,161,18,13,143,41,33,10,121 +C[C>A]G,13,9,16,6,9,15,10,10,3,20 +C[C>A]T,68,57,189,7,20,173,62,65,14,116 +G[C>A]A,75,69,122,23,22,89,100,95,20,65 +G[C>A]C,45,45,84,14,16,83,27,50,15,92 +G[C>A]G,8,6,13,5,8,16,10,11,5,16 +G[C>A]T,49,66,101,10,13,109,72,63,13,68 +T[C>A]A,65,87,171,19,33,204,99,135,30,84 +T[C>A]C,86,61,132,18,24,202,60,82,33,84 +T[C>A]G,6,12,24,6,11,10,11,16,4,13 +T[C>A]T,91,134,250,24,28,265,151,182,44,100 +A[C>G]A,95,24,95,8,17,150,46,26,10,105 +A[C>G]C,40,19,61,8,7,70,36,19,14,60 +A[C>G]G,11,4,25,0,0,35,3,5,5,23 +A[C>G]T,96,18,118,7,9,145,45,35,16,112 +C[C>G]A,66,15,47,2,10,151,12,17,13,60 +C[C>G]C,52,10,36,3,9,92,23,15,11,77 +C[C>G]G,22,11,14,3,3,39,6,11,2,42 +C[C>G]T,79,19,50,7,7,203,28,31,15,106 +G[C>G]A,36,6,28,3,4,78,18,6,8,68 +G[C>G]C,30,9,43,3,8,64,22,8,7,61 +G[C>G]G,8,1,6,0,1,14,8,3,1,19 +G[C>G]T,51,8,34,6,10,107,23,18,9,93 +T[C>G]A,119,61,131,8,20,687,68,116,24,93 +T[C>G]C,85,32,80,9,11,448,52,56,13,110 +T[C>G]G,15,11,13,1,1,45,11,14,1,16 +T[C>G]T,239,90,236,14,28,1125,136,214,35,207 +A[C>T]A,126,91,238,24,46,187,80,70,54,140 +A[C>T]C,61,56,103,17,26,83,43,47,35,79 +A[C>T]G,149,272,257,92,119,185,147,168,134,145 +A[C>T]T,92,51,181,18,32,157,33,57,43,119 +C[C>T]A,75,76,112,21,46,140,53,66,40,100 +C[C>T]C,69,67,89,28,39,97,59,59,54,77 +C[C>T]G,93,163,139,45,72,108,110,108,85,88 +C[C>T]T,107,94,185,27,49,220,68,75,56,162 +G[C>T]A,68,75,86,13,37,99,74,58,38,123 +G[C>T]C,46,61,95,22,44,103,80,45,44,79 +G[C>T]G,90,230,176,71,81,155,124,118,102,116 +G[C>T]T,74,55,129,10,22,110,35,49,38,93 +T[C>T]A,139,178,198,28,63,520,136,224,97,106 +T[C>T]C,126,97,155,24,40,341,98,95,79,98 +T[C>T]G,80,128,117,35,68,101,79,103,53,87 +T[C>T]T,152,128,244,26,72,382,109,147,116,137 +A[T>A]A,43,66,115,17,16,86,44,26,13,57 +A[T>A]C,25,19,75,20,24,46,31,21,27,48 +A[T>A]G,37,30,99,10,17,76,22,29,16,64 +A[T>A]T,63,61,168,21,32,120,49,38,18,117 +C[T>A]A,31,32,85,4,9,61,15,16,3,63 +C[T>A]C,32,17,65,2,16,71,19,22,7,101 +C[T>A]G,55,24,105,9,14,108,34,22,9,68 +C[T>A]T,63,39,182,6,9,117,24,23,17,90 +G[T>A]A,22,17,42,5,4,47,10,9,7,40 +G[T>A]C,20,14,39,3,8,38,14,12,10,38 +G[T>A]G,23,16,33,5,14,48,18,15,5,39 +G[T>A]T,41,16,99,2,9,102,18,17,10,71 +T[T>A]A,31,63,124,16,29,122,51,60,18,76 +T[T>A]C,30,21,95,3,10,59,25,21,12,44 +T[T>A]G,19,15,57,2,5,43,9,9,7,39 +T[T>A]T,76,38,240,9,13,146,41,41,13,116 +A[T>C]A,90,101,150,29,49,189,79,57,52,157 +A[T>C]C,42,29,68,10,23,87,36,19,18,85 +A[T>C]G,58,47,85,10,24,108,55,31,26,131 +A[T>C]T,99,95,118,19,46,200,95,58,40,167 +C[T>C]A,39,50,57,7,15,69,44,14,20,73 +C[T>C]C,55,27,92,11,15,139,28,20,13,109 +C[T>C]G,43,37,42,10,14,59,28,25,15,88 +C[T>C]T,59,42,68,5,13,105,56,37,22,113 +G[T>C]A,40,63,79,11,32,101,50,22,12,81 +G[T>C]C,29,27,35,12,11,62,37,18,14,47 +G[T>C]G,26,41,32,11,9,58,39,17,15,64 +G[T>C]T,57,49,70,14,36,103,55,28,20,73 +T[T>C]A,56,83,106,7,23,93,41,28,23,76 +T[T>C]C,47,52,73,14,16,91,50,24,9,73 +T[T>C]G,25,29,57,4,13,61,18,21,13,54 +T[T>C]T,54,75,92,17,39,133,55,46,27,99 +A[T>G]A,29,25,49,2,13,61,29,18,13,49 +A[T>G]C,12,12,15,4,5,26,11,4,7,35 +A[T>G]G,43,16,40,6,6,74,17,19,6,44 +A[T>G]T,37,24,57,13,13,56,24,15,9,46 +C[T>G]A,18,7,12,5,3,30,10,9,3,29 +C[T>G]C,23,13,21,8,4,42,6,10,3,27 +C[T>G]G,43,11,46,1,6,100,21,25,7,62 +C[T>G]T,20,14,49,8,9,70,14,25,7,64 +G[T>G]A,7,10,17,0,1,27,7,7,5,26 +G[T>G]C,9,6,12,3,6,19,6,3,4,18 +G[T>G]G,25,15,37,11,4,76,16,12,8,57 +G[T>G]T,27,6,24,6,5,63,20,11,5,41 +T[T>G]A,39,25,39,2,9,66,21,8,9,52 +T[T>G]C,19,9,30,3,4,37,15,6,8,31 +T[T>G]G,39,18,73,4,10,86,21,18,4,45 +T[T>G]T,58,38,81,10,20,110,48,38,16,109 diff --git a/tests/test_data/nmf_framework/corrnmf/objective_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/objective_init_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..a525012b9a0947caef14bf467c36acec0aceb975 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alXGwO{x9F5G?q06zyHi~s-t literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/objective_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/objective_init_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..1f8c79825cf35784379e43c307716a1a5a1de0b1 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46ag;gizfbF(wh$e05gXkZU6uP literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/p_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/p_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..1ca7a583c5d704174cd39dabc313778b2f433591 GIT binary patch literal 7808 zcmeIyu?oUK41nQt^(nGN;Raob7NMJ)#l^u%j`oU!*m5T>(x>Q)`Ut)D5!{471VS=~ z3m(7xA|!8V-++PJv2MDqD>oK!98znsQolQx<~YB9`CO6sbB3gE8CQtwUu6e ziKUt2TTrJBum|pdJKzs+00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zDX G*MSiMrHnKH literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/p_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/p_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..5fe2958383ed94463965b9fccbfa45eeb995fa4d GIT binary patch literal 15488 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 literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/sample_embeddings_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/sample_embeddings_init_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..158189821ecaae915bdd848df86bd4152171156c GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqpcnmP)#3giN=nVdguIigP6mr0vxH!r(wKe>b949B|1`*$iXVN^fy z#D2p|Lr;G1hxVDWr*aUnhm;rl^&r_4S*b7k8v`e~1sXiq F4*=$!Ll*!5 literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/sample_embeddings_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/sample_embeddings_init_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..da10265c5fa88f220a5f5189b05a76d6969805f8 GIT binary patch literal 288 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Wf%nItsN4WCJdPcP6F34*a*DU7YB3I9R}e|4mH8?8jUVD~|E+Hce-6 z;C`W%rL>5{f$!T7dpU+z_6avm>mE@Ma)^(6rS16TwSDT|OZU@M1RUZ_-SfhHgdC*i zEZKj@{eyk5f0yHxNk8qyO8EBGO0YUas^8dh#DvY^=V$8&rRV?KuetGxT~nLQf%{L? iK7D5{hi?A*=lyED4hwp}JMuneaZrB9Hp@1e*#Q8NAYb+X literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/sample_embeddings_updated_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/sample_embeddings_updated_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..aa11556311b9791a06cf66a2e7fa2295ed26c5bb GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqpcnmP)#3giN=k21I3MP!|}?{)uw^4ZSY_6}=IFMr(fc)za9`^5i$ zp4jvBe6D?;`p}-AAxEpP`rrOwXK78==EwUpkH2?VFz4d_XNyaiB+b99coc`Z_N&WMO=RE}+p43j*DzuTyAvw#dZ;C5} z!}r)4St7}wVwjLKMFc< z8->qaYX8B$GH$_IfzF@y_1g?}wtQ!CD1O0wO<$VL;p^=kEh(G-+wW(Zy6?FRn}bE~ jq~xeuoDMxMS5(vQ@HnhB>220O#^NBWX}kZIE3*RtbG>Fz literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/sample_scalings_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/sample_scalings_init_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..4ec304fa0fd29f088041b82238818e3c8aae353e GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41FqtC+Lx}1C^~#DTb5h3TET%i(kJ+3g_1+DXPXE67iot% z#*s^}aLGBWxEivdWva4+R&wI(51$nr7XP^Tz2lUE!_D;Chue?HJGhz~TyYjqasU93 Cm_J7V literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/sample_scalings_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/sample_scalings_init_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..9bf62371a876a4ed66b60e0aec43eb4d301808de GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41FlW(-kCq%NI7hmedPanx`KnuO_hjPb7hBI6E21}9R&vu zo2U0fc_bYeRM#we&Zg+VA?9?yOij)qV!4{a9u`@LIgi)*iWW#Y*oyp=(wwj00065U BH?9Bx literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/sample_scalings_updated_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/sample_scalings_updated_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..3c0755146817f59a5e4d235e3ed029df9b487459 GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL411@3N|Dk_H6de?LDzk2^R&da=oL#f5Ldk)(JKIj|hqS}H zj#(-XdE^|v9J{mZ^%P}?tv-P*$)6P*G;+EkJx(b&JbCfX#QT7}!{wS|7Z?SU8~{bV BJ~99R literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/sample_scalings_updated_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/sample_scalings_updated_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..064d254febfba1fdfaf41887c6e6a3690e948d8d GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41Fm-$t_Eqmm2&9cvSAYA3^B>u3w`~ZIT=;eW42uRc literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/signature_embeddings_updated_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/signature_embeddings_updated_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..a60643c2280523f52649ce2ddfb8950095bb9e40 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= fXCxM+0{I$-ItqrGItsN4WCN}%+@8gJNhj?AK-wOF literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/signature_embeddings_updated_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/signature_embeddings_updated_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..7db8c45a471f746cf4363854c66f5540ac26fdf1 GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Pzeb3bhL41Fi_~l*d-zuk3GL;}myq{v&&{idURd7ku3xYP{_ft<8 literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/signature_scalings_updated_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/signature_scalings_updated_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..0256105f0b2f6f8359791585d6c4c783fc3669e9 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= eXCxM+0{I$-I+{8PwF(pfE{2BF(myrq_X7YuM;_q- literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/signature_scalings_updated_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/signature_scalings_updated_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..8410c6edde0b8b7cac43d4c4dc759d1ec574671f GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= mXCxM+0{I$7I+{8PwF(pfE(V68@Ye?=>=_tzm75=#*#iJFULaEd literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/signatures_mat_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/signatures_mat_init_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..dd4d1fbe56f6fe8b16da9769aa22ef1102f1763e GIT binary patch literal 896 zcmbVK`%?=591qPRd6itLvr9E&c@#?0#|e4#Ad)z=*483h+M2aTrafw1lO}BvqRbJ8 zGOwic^2*LTo!BuQJ-pJoE^pn=AK>%L_r9MWzTfZX`}ypThz?&JJH&=>BO)g-xb#$V zAc-8r3?NfUWF{w-mr7%A<)kJs{_@Lc$y~-Cp39=8F#hyY=g*^(W>csn3F&_^GLMp0 z%u#FLK6+bp`A;>90{BX2V>}uk2H}7A)eHMSpM!Xg{B18J=PG^oBnV9`cT!tqu4CiHye3fstg1ky-Iy8PRA9m zCjX#Yi71Nf6F;IVP|p+2sCd9dowW|VVsp`CiGtJmVs>7;2$Lh+XyRCUJ>OVql7u< zho#d`1Esn*ljog*jwch%HBB-|o_ZH=a1di<gsfCvuWoSM1y}#-_A3ddv zT5YfZ(rC4z?}!W^&u2Ufe=JAC<^lh{oqMeJ@IkCgENHLE#_&P^`Ka_Yh4*YqfPIRw z=-cQtET&|6E1vVAH%!}Rdw`BjQ$Bx)W3upzj@Ym^kk%o<@k@f**Y-z4~W5LSFO2;es8yBxm(O~i(V literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/signatures_mat_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/signatures_mat_init_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..b79c09a88c2d5b19acb1f7d2c66ec59265d4ca4a GIT binary patch literal 1664 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 literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/signatures_mat_updated_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/signatures_mat_updated_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..0d8f2f67053ebe3a9546431ebb82938dc9377623 GIT binary patch literal 1664 zcmbV}dsGty8^$M)=;R`KLqvt-YviziIDkg)8(@5fV0eT;<-^v12qt42AT$@ZT^Kh9 zgIxsW=A3bvCOSyK1rZlDLMASgo2Z54Wq1ImL&^Gs{{No;-se2O^FHT1;yq!(A^VpQ zQVDEQbPS8iB<*k@`NVpYTpUQTbY?P>l6Z*DjE?!+51`OkF|U1A93?5{_3gaf)5XF4 z9Tx|V!~ZLyiyOi9XKg92sH%?dUUwYMUhq9=e^Z3>S2+pEV#tDalyYx26uJ8-6;kKU0WN4cS9`UgUsc`l~+Ad@k}MqK5~!mw}M1d(V13 z1zWSiy(UiNgVljs!qf6xteXj3*%Mg>Hy;VVP4Rl&Kb&r|VTTIHDIaHyTNWe#E@3v? zsuKn&a++21zyFo+&hgQPatwGy@t6`T;Y<6sIT@RUXuiO6Pqyy?t@P+GBX4m~qm-X4 zdnf)rzF zn`bhzJq8X`IIbiYQNAdoyHH-VDJ+KnxtSZJU z#9+;fu}U~BZdtqX+A+)`)LKe5l>QIIb(Pc#Zcp8gc-in!$5t<2+Q*0Vo$vOQf67_RSL&v0Y0NBa@L#^|aFrOk z2BMy%^Q2IUHy?;@90!|=*M-Di0@ zI3Ie)**3cgc)Z#J%Hdp0T=&3BHzJ1{YbH;GUS!~eYg1+8Y(0p(CT6YIGqBUFZTAM^ zagfy+*{5|(oTLZZG3x4}?~lWQx+|GzJ!~4hZC@KK{rcRM=dD6)GdjDO{Vs=&sW|h1 zDK_3O8&Gq)MbNq1RADi}z}da+nE{#Q(8wydcgb)J8DreA5Dyt_oZND?rneLy&rFi^ zE9B7lvW@Y9Yc{^2N@Eq7Rzo?lH~8pkA)fWsJ&_-kL3Pj*iy(R_nm-Gb?KGZ-++yu; z;=@d|d=%ifYN!a}kIIMR-sj`kh`p~~+X0s6*Ja=1WMD;JD`SXV47)s+5kpmc{5hEF zlq*!i>Xh+<+;|@TbV*FsERn&c7xP;mrlsRtfnfJ@P=IYkxMhVG8xQFW%|5G3Aw!bM zy|t8&@(av(S#K4DiP{FuC<0W)UQlNz2;t8}(%pix9JKXoj<|182{(+q1$}EehK*CL z)#E2Y;y}}O_vRyc+)-Y$tO^Rfx?E+O)3J$lF-}ZWz^)g4O8sxKC{mrJ{*Wn$%hDp_ zrfV#G<&bJgh{!+%^;h3YMLKSrNl_&_$>8gVholdTF<7*%m-FpO4P2)Um()-bu*Q47 zW5Ko?YWk(Tv7sdVb0C>z`c#56pVaF*S{nX%@xNV$PBmolgGV%VU!d646g$GzU_*D? zH@74T&A!r=dEBWlra)pfo>KbfBG$dyC5KiJhIWDSRL4jJW`7MZc;C>y+>p Dcy#DC literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/variance_init_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/variance_init_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..8269ea423951502f72555638b0e149b55b10f048 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC!@qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= XXCxM+0{I%6ItsN46ag+q1_%HED6|_x literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/variance_init_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/variance_init_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..8269ea423951502f72555638b0e149b55b10f048 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC!@qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= XXCxM+0{I%6ItsN46ag+q1_%HED6|_x literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/variance_updated_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/variance_updated_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..a2346a97cbb51a391265e553dfd3f4799f00efca GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alU$vNs;YD?POb06d%@od5s; literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/variance_updated_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/variance_updated_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..7cbc02e77dfd28adfd49d96eb0a3245f7ce21ed4 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= bXCxM+0{I%6ItsN46alWyY(9zh|79EiH8vhU literal 0 HcmV?d00001 From 9bfe5ff5f085f4c80950749186bb228bc1932f04 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 17 Apr 2024 18:25:52 -0400 Subject: [PATCH 047/103] fix import --- src/salamander/nmf_framework/signature_nmf.py | 4 ++-- src/salamander/nmf_framework/standard_nmf.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/salamander/nmf_framework/signature_nmf.py b/src/salamander/nmf_framework/signature_nmf.py index 7cb886c..50891c7 100644 --- a/src/salamander/nmf_framework/signature_nmf.py +++ b/src/salamander/nmf_framework/signature_nmf.py @@ -10,7 +10,7 @@ from .. import plot as pl from .. import tools as tl -from ..utils import type_checker, value_checker +from ..utils import match_signatures_pair, type_checker, value_checker from .initialization import _INIT_METHODS, EPSILON if TYPE_CHECKING: @@ -386,7 +386,7 @@ def fit( def reorder( self, - asignatures_other: ad.AnnData, + asignatures_other: AnnData, metric: str = "cosine", ) -> None: """ diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/nmf_framework/standard_nmf.py index d814b74..fa4a8ca 100644 --- a/src/salamander/nmf_framework/standard_nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -6,7 +6,7 @@ import anndata as ad from .. import tools as tl -from ..utils import match_signatures_pair, type_checker +from ..utils import type_checker from ._utils_klnmf import check_given_asignatures from .initialization import initialize from .signature_nmf import SignatureNMF From c84694023d172f07f3cabcd12b1c3f4e8a812a71 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 17 Apr 2024 18:29:34 -0400 Subject: [PATCH 048/103] update lock file --- poetry.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2e87120..4219545 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1361,24 +1361,24 @@ telegram = ["requests"] [[package]] name = "types-pytz" -version = "2024.1.0.20240203" +version = "2024.1.0.20240417" description = "Typing stubs for pytz" optional = false python-versions = ">=3.8" files = [ - {file = "types-pytz-2024.1.0.20240203.tar.gz", hash = "sha256:c93751ee20dfc6e054a0148f8f5227b9a00b79c90a4d3c9f464711a73179c89e"}, - {file = "types_pytz-2024.1.0.20240203-py3-none-any.whl", hash = "sha256:9679eef0365db3af91ef7722c199dbb75ee5c1b67e3c4dd7bfbeb1b8a71c21a3"}, + {file = "types-pytz-2024.1.0.20240417.tar.gz", hash = "sha256:6810c8a1f68f21fdf0f4f374a432487c77645a0ac0b31de4bf4690cf21ad3981"}, + {file = "types_pytz-2024.1.0.20240417-py3-none-any.whl", hash = "sha256:8335d443310e2db7b74e007414e74c4f53b67452c0cb0d228ca359ccfba59659"}, ] [[package]] name = "types-seaborn" -version = "0.13.2.20240311" +version = "0.13.2.20240417" description = "Typing stubs for seaborn" optional = false python-versions = ">=3.9" files = [ - {file = "types-seaborn-0.13.2.20240311.tar.gz", hash = "sha256:75b6d94214b8e94591c58e09581f65f46a673c0ee2dab162a76205c46c95057c"}, - {file = "types_seaborn-0.13.2.20240311-py3-none-any.whl", hash = "sha256:96879a786dafa026aa376854d498dd20c345bd8764de8a2a1590b7b423c0a69d"}, + {file = "types-seaborn-0.13.2.20240417.tar.gz", hash = "sha256:130989a40d92a1256292c9693b62a7420f1747b25e0d74b6c156764a56ed10ec"}, + {file = "types_seaborn-0.13.2.20240417-py3-none-any.whl", hash = "sha256:5a0e9acfde4d4814fa4d926e11c2b874a19f754987665afe5423a4fb322f46eb"}, ] [package.dependencies] @@ -1423,13 +1423,13 @@ tbb = ["tbb (>=2019.0)"] [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.25.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.25.3-py3-none-any.whl", hash = "sha256:8aac4332f2ea6ef519c648d0bc48a5b1d324994753519919bddbb1aff25a104e"}, + {file = "virtualenv-20.25.3.tar.gz", hash = "sha256:7bb554bbdfeaacc3349fa614ea5bff6ac300fc7c335e9facf3a3bcfc703f45be"}, ] [package.dependencies] @@ -1438,7 +1438,7 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] From cf9f79672dff77ac1fc6d424041a58f660935e8a Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 17 Apr 2024 18:35:14 -0400 Subject: [PATCH 049/103] fix whitespace --- src/salamander/__init__.py | 1 + src/salamander/nmf_framework/initialization.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/salamander/__init__.py b/src/salamander/__init__.py index db76571..4aa8daf 100644 --- a/src/salamander/__init__.py +++ b/src/salamander/__init__.py @@ -2,6 +2,7 @@ Salamander: a non-negative matrix factorization framework for signature analysis ================================================================================ """ + # from .nmf_framework.klnmf import KLNMF from .plot import set_salamander_style diff --git a/src/salamander/nmf_framework/initialization.py b/src/salamander/nmf_framework/initialization.py index df96feb..241dd19 100644 --- a/src/salamander/nmf_framework/initialization.py +++ b/src/salamander/nmf_framework/initialization.py @@ -1,6 +1,7 @@ """ Initialization methods for non-negative matrix factorization (NMF) """ + from __future__ import annotations from typing import Literal, get_args From 93713d147dc0a4c85b71a1f15c475aae93cd6582 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 17 Apr 2024 18:51:47 -0400 Subject: [PATCH 050/103] update pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf8a5f5..07f9f31 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: poetry-check - id: poetry-lock - repo: https://github.com/psf/black - rev: 23.9.0 + rev: 24.3.0 hooks: - id: black - repo: https://github.com/PyCQA/isort From 8320a3a0b901dca95d8591b483dca02771978018 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 17 Apr 2024 18:59:10 -0400 Subject: [PATCH 051/103] update format --- src/salamander/plot.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/salamander/plot.py b/src/salamander/plot.py index 547ee52..00ba51c 100644 --- a/src/salamander/plot.py +++ b/src/salamander/plot.py @@ -218,11 +218,7 @@ def _basisobsm2name(basis: str) -> str: name = ( "PC" if basis == "pca" - else "tSNE" - if basis == "tsne" - else "UMAP" - if basis == "umap" - else basis + else "tSNE" if basis == "tsne" else "UMAP" if basis == "umap" else basis ) return name From 4f9bc9fbb3aa0466b5e92dd9fdbf436d2f93ce75 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 17 Apr 2024 19:00:06 -0400 Subject: [PATCH 052/103] add corrnmf A correlated NMF implementation built around AnnData --- .../nmf_framework/_utils_corrnmf.py | 468 ++++++++++++++++++ src/salamander/nmf_framework/corrnmf.py | 276 +++++++++++ src/salamander/nmf_framework/corrnmf_det.py | 272 ++++++++++ tests/test_corrnmf.py | 254 ++++++++++ 4 files changed, 1270 insertions(+) create mode 100644 src/salamander/nmf_framework/_utils_corrnmf.py create mode 100644 src/salamander/nmf_framework/corrnmf.py create mode 100644 src/salamander/nmf_framework/corrnmf_det.py create mode 100644 tests/test_corrnmf.py diff --git a/src/salamander/nmf_framework/_utils_corrnmf.py b/src/salamander/nmf_framework/_utils_corrnmf.py new file mode 100644 index 0000000..16036cb --- /dev/null +++ b/src/salamander/nmf_framework/_utils_corrnmf.py @@ -0,0 +1,468 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np +from numba import njit + +from ..utils import shape_checker, type_checker +from ._utils_klnmf import check_given_asignatures, poisson_llh + +if TYPE_CHECKING: + from typing import Any + +_PARAMETERS_NAMES = [ + "asignatures", + "signature_scalings", + "sample_scalings", + "signature_embeddings", + "sample_embeddings", + "variance", +] +_DEFAULT_GIVEN_PARAMETERS = {parameter: None for parameter in _PARAMETERS_NAMES} + + +@njit +def compute_exposures( + signature_scalings: np.ndarray, + sample_scalings: np.ndarray, + signature_embeddings: np.ndarray, + sample_embeddings: np.ndarray, +) -> np.ndarray: + """ + Get the exposure matrix of shape (n_samples, n_signatures). + """ + return np.exp( + signature_scalings[:, np.newaxis] + + sample_scalings + + signature_embeddings @ sample_embeddings.T + ).T + + +def elbo_corrnmf( + data_mat: np.ndarray, + signatures_mat: np.ndarray, + exposures_mat: np.ndarray, + signature_embeddings: np.ndarray, + sample_embeddings: np.ndarray, + variance: float, + penalize_sample_embeddings: bool = True, +) -> float: + """ + The evidence lower bound (ELBO) of correlated NMF. + + Inputs + ------ + data_mat : np.ndarray + shape (n_samples, n_features) + + signatures_mat : np.ndarray + shape (n_signatures, n_features) + + exposures_mat : np.ndarray + shape (n_samples, n_signatures) + + signature_embeddings : np.ndarray + shape (n_signatures, dim_embeddings) + + sample_embeddings : np.ndarray + shape (n_samples, dim_embeddings) + + variance : float + + penalize_sample_embeddings : bool, default=True + If False, the sample embedding penalty is not added. + This is useful for the implementation of multimodal correlated NMF. + """ + n_signatures, dim_embeddings = signature_embeddings.shape + n_samples = sample_embeddings.shape[0] + elbo = poisson_llh(data_mat.T, signatures_mat.T, exposures_mat.T) + elbo -= 0.5 * dim_embeddings * n_signatures * np.log(2 * np.pi * variance) + elbo -= np.sum(signature_embeddings**2) / (2 * variance) + + if penalize_sample_embeddings: + elbo -= 0.5 * dim_embeddings * n_samples * np.log(2 * np.pi * variance) + elbo -= np.sum(sample_embeddings**2) / (2 * variance) + + return elbo + + +def check_given_scalings( + given_scalings: np.ndarray, n_scalings_expected: int, name: str +) -> None: + type_checker(name, given_scalings, np.ndarray) + shape_checker(name, given_scalings, (n_scalings_expected,)) + + +def check_given_embeddings( + given_embeddings: np.ndarray, + n_embeddings_expected: int, + dim_embeddings_expected: int, + name: str, +) -> None: + type_checker(name, given_embeddings, np.ndarray) + shape_checker( + name, given_embeddings, (n_embeddings_expected, dim_embeddings_expected) + ) + + +def check_given_parameters( + given_parameters: dict[str, Any] | None, + mutation_types_data: np.ndarray, + n_samples_data: int, + n_signatures_model: int, + dim_embeddings_model: int, +) -> dict[str, Any]: + given_parameters = {} if given_parameters is None else given_parameters.copy() + type_checker("given_parameters", given_parameters, dict) + + for parameter in given_parameters: + if parameter not in _PARAMETERS_NAMES: + raise ValueError( + "The given parameters include parameters outside " + f"of {_PARAMETERS_NAMES}." + ) + + if "asignatures" in given_parameters: + check_given_asignatures( + given_parameters["asignatures"], mutation_types_data, n_signatures_model + ) + if "signature_scalings" in given_parameters: + check_given_scalings( + given_parameters["signature_scalings"], + n_signatures_model, + "given_signature_scalings", + ) + if "sample_scalings" in given_parameters: + check_given_scalings( + given_parameters["sample_scalings"], n_samples_data, "given_sample_scalings" + ) + if "signature_embeddings" in given_parameters: + check_given_embeddings( + given_parameters["signature_embeddings"], + n_signatures_model, + dim_embeddings_model, + "given_signature_embeddings", + ) + if "sample_embeddings" in given_parameters: + check_given_embeddings( + given_parameters["sample_embeddings"], + n_samples_data, + dim_embeddings_model, + "given_sample_embeddings", + ) + if "variance" in given_parameters: + given_variance = given_parameters["variance"] + type_checker("given_variance", given_variance, [float, int]) + if given_variance <= 0.0: + raise ValueError("The variance has to be a positive real number.") + + return given_parameters + + +@njit +def update_signature_scalings( + data_mat: np.ndarray, + p: np.ndarray, + sample_scalings: np.ndarray, + signature_embeddings: np.ndarray, + sample_embeddings: np.ndarray, +) -> np.ndarray: + """ + Compute the new signature scalings according to the update rule of CorrNMF. + + Inputs + ------ + data_mat : np.ndarray + shape (n_samples, n_features) + + p : np.ndarray + auxiliary parameters of shape (n_featurues, n_signatures, n_samples) + + sample_scalings : np.ndarray + shape (n_samples,) + + signature_embeddings : np.ndarray + shape (n_signatures, dim_embeddings) + + sample_embeddings : np.ndarray + shape (n_samples, dim_embeddings) + + Returns + ------- + signature_scalings : np.ndarray + shape (n_signatures,) + """ + n_features, n_signatures, n_samples = p.shape + first_sum = np.zeros(n_signatures) + + for k in range(n_signatures): + for v in range(n_features): + for d in range(n_samples): + first_sum[k] += data_mat[d, v] * p[v, k, d] + + second_sum = np.sum( + np.exp(sample_scalings + signature_embeddings @ sample_embeddings.T), axis=1 + ) + signature_scalings = np.log(first_sum) - np.log(second_sum) + return signature_scalings + + +@njit +def update_sample_scalings( + data_mat: np.ndarray, + signature_scalings: np.ndarray, + signature_embeddings: np.ndarray, + sample_embeddings: np.ndarray, +) -> np.ndarray: + """ + Compute the new sample scalings according to the update rule of CorrNMF. + + Parameters + ---------- + data_mat : np.ndarray + shape (n_features, n_samples) + + signature_scalings : np.ndarray + shape (n_signatures,) + + signature_embeddings : np.ndarray + shape (n_signatures, dim_embeddings) + + sample_embeddings : np.ndarray + shape (n_samples, dim_embeddings) + + Returns + ------- + sample_scalings : np.ndarray + shape (n_samples,) + """ + first_sum = np.sum(data_mat, axis=1) + second_sum = np.sum( + np.exp( + signature_scalings[:, np.newaxis] + + signature_embeddings @ sample_embeddings.T + ), + axis=0, + ) + sample_scalings = np.log(first_sum) - np.log(second_sum) + return sample_scalings + + +@njit +def update_p_unnormalized( + signatures_mat: np.ndarray, exposures_mat: np.ndarray +) -> np.ndarray: + """ + Compute the auxiliary parameters p according to the update rule of CorrNMF. + The normalization per mutation type and sample is not performed yet. + + Faster than np.einsum(). + + Inputs + ------ + signatures_mat : np.ndarray + shape (n_signatures, n_features) + + exposures_mat : np.ndarray + shape (n_samples, n_signatures) + + Returns + ------- + aux : np.ndarray + shape (n_features, n_signatures, n_samples) + """ + n_signatures, n_features = signatures_mat.shape + n_samples = exposures_mat.shape[0] + p_unnormalized = np.zeros((n_features, n_signatures, n_samples)) + + for v in range(n_features): + for k in range(n_signatures): + for d in range(n_samples): + p_unnormalized[v, k, d] = signatures_mat[k, v] * exposures_mat[d, k] + + return p_unnormalized + + +@njit +def objective_function_embedding( + embedding: np.ndarray, + embeddings_other: np.ndarray, + scaling: float, + scalings_other: np.ndarray, + variance: float, + aux_vector: np.ndarray, + add_penalty: bool = True, +) -> float: + r""" + The negative objective function of a signature or sample embedding in CorrNMF. + + Parameters + ---------- + embedding : np.ndarray + shape (dim_embeddings,) + + embeddings_other : np.ndarray + shape (n_samples | n_signatures, dim_embeddings) + If 'embedding' is a signature embedding, 'embeddings_other' are + all sample embeddings. If 'embedding' is a sample embedding, + 'embeddings_other' are all signature embeddings. + + scaling : float + The scaling of the signature or sample corresponding to the + embedding. + + scalings_other : np.ndarray + shape (n_samples | n_signatures,) + The scalings of all samples or all signatures. + If 'embedding' is a signature embeddings, 'scalings_other' + are all sample scalingss. If 'embedding' is a sample embedding, + 'scalings_other' are all sample scalings. + + variance : float + + aux_vector : np.ndarray of + shape (n_signatures | n_samples,) + A row or column of aux[k, d] = \sum_v X_dv * p_vkd, + where X is the data matrix and p are the auxiliary parameters of CorrNMF. + If 'embedding' is a signature embedding, the corresponding row is provided. + If 'embedding' is a sample embedding, the corresponding column is provided. + + add_penalty : bool, default=True + If True, the norm of the embedding will be penalized. + This argument is useful for the implementation of multimodal CorrNMF. + """ + n_embeddings_other = embeddings_other.shape[0] + of_value = 0.0 + scalar_products = embeddings_other.dot(embedding) + + # aux_vector not necessarily contiguous: + # np.dot(scalar_products, aux_vec) doesn't work + for i in range(n_embeddings_other): + of_value += scalar_products[i] * aux_vector[i] + + of_value -= np.sum(np.exp(scaling + scalings_other + scalar_products)) + + if add_penalty: + of_value -= np.dot(embedding, embedding) / (2 * variance) + + return -of_value + + +@njit +def gradient_embedding( + embedding: np.ndarray, + embeddings_other: np.ndarray, + scaling: float, + scalings_other: np.ndarray, + variance: float, + summand_grad, + add_penalty: bool = True, +) -> np.ndarray: + r""" + The negative gradient of the objective function w.r.t. a signature or + sample embedding in CorrNMF. + + Inputs + ------ + embedding : np.ndarray + shape (dim_embeddings,) + + embeddings_other : np.ndarray + shape (n_samples | n_signatures, dim_embeddings) + If 'embedding' is a signature embedding, 'embeddings_other' are + all sample embeddings. If 'embedding' is a sample embedding, + 'embeddings_other' are all signature embeddings. + + scaling : float + The scaling of the signature or sample corresponding to the + embedding. + + scalings_other : np.ndarray + shape (n_samples | n_signatures,) + The scalings of all samples or all signatures. + If 'embedding' is a signature embeddings, 'scalings_other' + are all sample scalingss. If 'embedding' is a sample embedding, + 'scalings_other' are all sample scalings. + + variance : float + + summand_grad : np.ndarray + shape (dim_embeddings,). A signature/sample-independent summand. + + add_penalty : bool, default=True + If True, the norm of the embedding will be penalized. + This argument is useful for the implementation of multimodal CorrNMF. + """ + scalar_products = embeddings_other.dot(embedding) + gradient = -np.sum( + np.exp(scaling + scalings_other + scalar_products)[:, np.newaxis] + * embeddings_other, + axis=0, + ) + gradient += summand_grad + + if add_penalty: + gradient -= embedding / variance + + return -gradient + + +@njit +def hessian_embedding( + embedding: np.ndarray, + embeddings_other: np.ndarray, + scaling: float, + scalings_other: np.ndarray, + variance: float, + outer_prods_embeddings_other: np.ndarray, + add_penalty: bool = True, +) -> np.ndarray: + r""" + The negative Hessian of the objective function w.r.t. a signature or + sample embedding in CorrNMF. + + Inputs + ------ + embedding : np.ndarray + shape (dim_embeddings,) + + embeddings_other : np.ndarray + shape (n_samples | n_signatures, dim_embeddings) + If 'embedding' is a signature embedding, 'embeddings_other' are + all sample embeddings. If 'embedding' is a sample embedding, + 'embeddings_other' are all signature embeddings. + + scaling : float + The scaling of the signature or sample corresponding to the + embedding. + + scalings_other : np.ndarray + shape (n_samples | n_signatures,) + The scalings of all samples or all signatures. + If 'embedding' is a signature embeddings, 'scalings_other' + are all sample scalingss. If 'embedding' is a sample embedding, + 'scalings_other' are all sample scalings. + + variance : float + + outer_prods_embeddings_other : np.ndarray + shape (n_samples | n_signatures, dim_embeddings, dim_embeddings) + + add_penalty : bool, default=True + Set to True, the norm of the embedding will be penalized. + This argument is useful for the implementation of multimodal CorrNMF. + """ + n_embeddings_other, dim_embeddings = embeddings_other.shape + scalar_products = embeddings_other.dot(embedding) + scalings = np.exp(scaling + scalings_other + scalar_products) + hessian = np.zeros((dim_embeddings, dim_embeddings)) + + for m1 in range(dim_embeddings): + for m2 in range(dim_embeddings): + for i in range(n_embeddings_other): + hessian[m1, m2] -= scalings[i] * outer_prods_embeddings_other[i, m1, m2] + if add_penalty and m1 == m2: + hessian[m1, m2] -= 1 / variance + + return -hessian diff --git a/src/salamander/nmf_framework/corrnmf.py b/src/salamander/nmf_framework/corrnmf.py new file mode 100644 index 0000000..6d809ca --- /dev/null +++ b/src/salamander/nmf_framework/corrnmf.py @@ -0,0 +1,276 @@ +from __future__ import annotations + +import warnings +from typing import TYPE_CHECKING + +import anndata as ad +import numpy as np +from scipy.spatial.distance import squareform + +from ..tools import reduce_dimension_multiple +from ..utils import value_checker +from . import _utils_corrnmf +from ._utils_klnmf import samplewise_kl_divergence +from .initialization import initialize +from .signature_nmf import SignatureNMF + +if TYPE_CHECKING: + from typing import Any, Literal + + from .initialization import _Init_methods + from .signature_nmf import _Dim_reduction_methods + + +class CorrNMF(SignatureNMF): + """ + The abstract class CorrNMF unifies the structure of NMF algorithms + with a signature matrix and an exposure matrix refactored into + signature and sample scalings and embeddings. + """ + + def __init__( + self, + n_signatures: int = 1, + init_method: _Init_methods = "nndsvd", + dim_embeddings: int | None = None, + min_iterations: int = 500, + max_iterations: int = 10000, + conv_test_freq: int = 10, + tol: float = 1e-7, + ): + """ + Input: + ------ + dim_embeddings: int + The assumed dimension of the signature and sample embeddings. + Should be smaller or equal to the number of signatures as a dimension + equal to the number of signatures covers the case of independent + signatures. The smaller the embedding dimension, the stronger the + enforced correlation structure on both signatures and samples. + """ + super().__init__( + n_signatures, + init_method, + min_iterations, + max_iterations, + conv_test_freq, + tol, + ) + if dim_embeddings is None: + dim_embeddings = n_signatures + + self.dim_embeddings = dim_embeddings + self.variance = 1.0 + + def compute_exposures(self) -> None: + """ + In contrast to the classical NMF framework, the exposure matrix is + restructured and determined by the signature & sample biases and + embeddings. + """ + self.adata.obsm["exposures"] = _utils_corrnmf.compute_exposures( + self.asignatures.obs["scalings"].values, + self.adata.obs["scalings"].values, + self.asignatures.obsm["embeddings"], + self.adata.obsm["embeddings"], + ) + + def compute_reconstruction_errors(self): + self.compute_exposures() + errors = samplewise_kl_divergence( + self.adata.X.T, self.asignatures.X.T, self.adata.obsm["exposures"].T + ) + self.adata.obs["reconstruction_error"] = errors + + def objective_function(self, penalize_sample_embeddings: bool = True) -> float: + """ + The evidence lower bound (ELBO) + """ + return _utils_corrnmf.elbo_corrnmf( + self.adata.X, + self.asignatures.X, + self.adata.obsm["exposures"], + self.asignatures.obsm["embeddings"], + self.adata.obsm["embeddings"], + self.variance, + penalize_sample_embeddings=penalize_sample_embeddings, + ) + + @property + def objective(self) -> Literal["minimize", "maximize"]: + return "maximize" + + def _initialize( + self, + given_parameters: dict[str, Any] | None = None, + init_kwargs: dict[str, Any] | None = None, + ) -> dict[str, Any]: + """ + Initialize the signature matrix, the signature and sample scalings, + the signature and sample embeddings, and the variance. + + Parameters + ---------- + given_parameters: dict, default=None + A priori known parameters / parameters to fix during model training. + Allowed keys: 'asignatures', 'signature_scalings', 'sample_scalings', + 'signature_embeddings', 'sample_embeddings'. The values have to + have the appropriate shape. If 'asignatures' is not None, it is + expected to be an AnnData object. + + init_kwargs : dict + Any further keyword arguments to pass to the initialization method + of the signatures. This includes, for example, a possible 'seed' + keyword argument for all stochastic initialization methods. + """ + given_parameters = _utils_corrnmf.check_given_parameters( + given_parameters=given_parameters, + mutation_types_data=self.mutation_types, + n_samples_data=self.adata.n_obs, + n_signatures_model=self.n_signatures, + dim_embeddings_model=self.dim_embeddings, + ) + init_kwargs = {} if init_kwargs is None else init_kwargs.copy() + + if "asignatures" in given_parameters: + given_asignatures = given_parameters["asignatures"] + given_signatures = given_asignatures.to_df().T + else: + given_signatures = None + + W, _, signature_names = initialize( + self.adata.X.T, + self.n_signatures, + self.init_method, + given_signatures, + **init_kwargs, + ) + self.asignatures = ad.AnnData(W.T) + self.asignatures.obs_names = signature_names + self.asignatures.var_names = self.mutation_types + + # keep signature annotations + if "asignatures" in given_parameters: + n_given_signatures = given_asignatures.n_obs + asignatures_new = self.asignatures[n_given_signatures:, :] + self.asignatures = ad.concat( + [given_asignatures, asignatures_new], join="outer" + ) + + if "signature_scalings" in given_parameters: + self.asignatures.obs["scalings"] = given_parameters["signature_scalings"] + else: + self.asignatures.obs["scalings"] = np.zeros(self.n_signatures) + + if "sample_scalings" in given_parameters: + self.adata.obs["scalings"] = given_parameters["sample_scalings"] + else: + self.adata.obs["scalings"] = np.zeros(self.adata.n_obs) + + if "signature_embeddings" in given_parameters: + self.asignatures.obsm["embeddings"] = given_parameters[ + "signature_embeddings" + ] + else: + self.asignatures.obsm["embeddings"] = np.random.multivariate_normal( + np.zeros(self.dim_embeddings), + np.identity(self.dim_embeddings), + size=self.n_signatures, + ) + + if "sample_embeddings" in given_parameters: + self.adata.obsm["embeddings"] = given_parameters["sample_embeddings"] + else: + self.adata.obsm["embeddings"] = np.random.multivariate_normal( + np.zeros(self.dim_embeddings), + np.identity(self.dim_embeddings), + size=self.adata.n_obs, + ) + + if "variance" in given_parameters: + self.variance = float(given_parameters["variance"]) + else: + self.variance = 1.0 + + self.compute_exposures() + return given_parameters + + def _setup_fitting_parameters( + self, fitting_kwargs: dict[str, Any] | None = None + ) -> None: + """ + No additional fitting parameters implemented so far. + """ + return + + def compute_correlation_scaled( + self, data: Literal["samples", "signatures"] = "signatures" + ) -> None: + """ + Compute the signature or sample correlation based on the + scaled exposures and store it in the respective anndata object. + """ + value_checker("data", data, ["samples", "signatures"]) + assert "embeddings" in self.adata.obsm, ( + "Computing the sample or signature correlation " + "requires fitting the CorrNMF model." + ) + + if data == "samples": + vectors = self.adata.obsm["embeddings"] + else: + vectors = self.asignatures.obsm["embeddings"] + + norms = np.sqrt(np.sum(vectors**2, axis=1)) + n_vectors = len(norms) + corr_vector = np.array( + [ + np.dot(v1, v2) / (norms[i1] * norms[i1 + i2 + 1]) + for i1, v1 in enumerate(vectors) + for i2, v2 in enumerate(vectors[(i1 + 1) :, :]) + ] + ) + correlation = squareform(corr_vector) + np.identity(n_vectors) + + if data == "samples": + self.adata.obsp["X_correlation"] = correlation + else: + self.asignatures.obsp["correlation"] = correlation + + def reduce_dimension_embeddings( + self, method: _Dim_reduction_methods = "umap", n_components: int = 2, **kwargs + ) -> None: + reduce_dimension_multiple( + adatas=[self.asignatures, self.adata], + basis="embeddings", + method=method, + n_components=n_components, + **kwargs, + ) + + def _get_embedding_plot_adata( + self, method: _Dim_reduction_methods = "umap" + ) -> tuple[ad.AnnData, str]: + """ + Plot the exposures directly if the number of signatures is at most 2. + """ + plot_adata = ad.concat([self.asignatures, self.adata]) + + if self.dim_embeddings <= 2: + warnings.warn( + f"The embedding dimension is {self.dim_embeddings}. " + "The embeddings are plotted without an additional " + "dimensionality reduction.", + UserWarning, + ) + basis = "embeddings" + else: + basis = method + + return plot_adata, basis + + def _get_default_embedding_plot_annotations(self) -> np.ndarray: + """ + The embedding plot defaults to annotating the signature embeddings. + """ + return self.signature_names diff --git a/src/salamander/nmf_framework/corrnmf_det.py b/src/salamander/nmf_framework/corrnmf_det.py new file mode 100644 index 0000000..d828762 --- /dev/null +++ b/src/salamander/nmf_framework/corrnmf_det.py @@ -0,0 +1,272 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np +from scipy import optimize + +from . import _utils_corrnmf +from ._utils_klnmf import update_W +from .corrnmf import CorrNMF +from .initialization import EPSILON + +if TYPE_CHECKING: + from typing import Any + + +class CorrNMFDet(CorrNMF): + """ + An implementation of a variant of the deterministic batch version of + correlated NMF. + + "Bayesian Nonnegative Matrix Factorization with Stochastic Variational + Inference" by Paisley et al. + + Reference + --------- + JW Paisley, DM Blei, MI Jordan: Bayesian Nonnegative Matrix Factorization + with Stochastic Variational Inference, 2014 + """ + + def update_sample_scalings( + self, given_parameters: dict[str, Any] | None = None + ) -> None: + if given_parameters is None: + given_parameters = {} + + if "sample_scalings" not in given_parameters: + self.adata.obs["scalings"] = _utils_corrnmf.update_sample_scalings( + self.adata.X, + self.asignatures.obs["scalings"].values, + self.asignatures.obsm["embeddings"], + self.adata.obsm["embeddings"], + ) + + def update_signature_scalings( + self, p: np.ndarray, given_parameters: dict[str, Any] | None = None + ) -> None: + if given_parameters is None: + given_parameters = {} + + if "signature_scalings" not in given_parameters: + self.asignatures.obs["scalings"] = _utils_corrnmf.update_signature_scalings( + self.adata.X, + p, + self.adata.obs["scalings"].values, + self.asignatures.obsm["embeddings"], + self.adata.obsm["embeddings"], + ) + + def update_variance(self, given_parameters: dict[str, Any] | None = None) -> None: + if given_parameters is None: + given_parameters = {} + + if "variance" not in given_parameters: + embeddings = np.concatenate( + [self.asignatures.obsm["embeddings"], self.adata.obsm["embeddings"]] + ) + variance = np.mean(embeddings**2) + self.variance = np.clip(variance, EPSILON, None) + + def update_signatures(self, given_parameters: dict[str, Any] | None = None) -> None: + if given_parameters is None: + given_parameters = {} + + if "asignatures" in given_parameters: + n_given_signatures = given_parameters["asignatures"].n_obs + else: + n_given_signatures = 0 + + W = update_W( + self.adata.X.T, + self.asignatures.X.T, + self.adata.obsm["exposures"].T, + n_given_signatures=n_given_signatures, + ) + self.asignatures.X = W.T + + def update_p(self) -> np.ndarray: + p = _utils_corrnmf.update_p_unnormalized( + self.asignatures.X, self.adata.obsm["exposures"] + ) + p /= np.sum(p, axis=1, keepdims=True) + p = p.clip(EPSILON) + return p + + def update_signature_embedding( + self, index: int, aux_row: np.ndarray, outer_prods_sample_embeddings: np.ndarray + ) -> None: + scaling = self.asignatures.obs["scalings"][index] + + def objective_fun(embedding): + return _utils_corrnmf.objective_function_embedding( + embedding, + self.adata.obsm["embeddings"], + scaling, + self.adata.obs["scalings"].values, + self.variance, + aux_row, + ) + + summand_grad = np.sum( + aux_row[:, np.newaxis] * self.adata.obsm["embeddings"], axis=0 + ) + + def gradient(embedding): + return _utils_corrnmf.gradient_embedding( + embedding, + self.adata.obsm["embeddings"], + scaling, + self.adata.obs["scalings"].values, + self.variance, + summand_grad, + ) + + def hessian(embedding): + return _utils_corrnmf.hessian_embedding( + embedding, + self.adata.obsm["embeddings"], + scaling, + self.adata.obs["scalings"].values, + self.variance, + outer_prods_sample_embeddings, + ) + + embedding = optimize.minimize( + fun=objective_fun, + x0=self.asignatures.obsm["embeddings"][index, :], + method="Newton-CG", + jac=gradient, + hess=hessian, + ).x + embedding[(0 < embedding) & (embedding < EPSILON)] = EPSILON + embedding[(-EPSILON < embedding) & (embedding < 0)] = -EPSILON + self.asignatures.obsm["embeddings"][index, :] = embedding + + def update_signature_embeddings( + self, aux: np.ndarray, outer_prods_sample_embeddings: np.ndarray | None = None + ) -> None: + r""" + Update all signature embeddings by optimizing + the surrogate objective function using scipy.optimize.minimize + with the 'Newton-CG' method. + + aux: np.ndarray + aux_kd = \sum_v X_vd * p_vkd + is used for updating the signatures and the sample embeddidngs. + """ + if outer_prods_sample_embeddings is None: + outer_prods_sample_embeddings = np.einsum( + "Dm,Dn->Dmn", + self.adata.obsm["embeddings"], + self.adata.obsm["embeddings"], + ) + + for k, aux_row in enumerate(aux): + self.update_signature_embedding(k, aux_row, outer_prods_sample_embeddings) + + def update_sample_embedding( + self, + index: int, + aux_col: np.ndarray, + outer_prods_signature_embeddings: np.ndarray, + ) -> None: + scaling = self.adata.obs["scalings"][index] + + def objective_fun(embedding): + return _utils_corrnmf.objective_function_embedding( + embedding, + self.asignatures.obsm["embeddings"], + scaling, + self.asignatures.obs["scalings"].values, + self.variance, + aux_col, + ) + + summand_grad = np.sum( + aux_col[:, np.newaxis] * self.asignatures.obsm["embeddings"], axis=0 + ) + + def gradient(embedding): + return _utils_corrnmf.gradient_embedding( + embedding, + self.asignatures.obsm["embeddings"], + scaling, + self.asignatures.obs["scalings"].values, + self.variance, + summand_grad, + ) + + def hessian(embedding): + return _utils_corrnmf.hessian_embedding( + embedding, + self.asignatures.obsm["embeddings"], + scaling, + self.asignatures.obs["scalings"].values, + self.variance, + outer_prods_signature_embeddings, + ) + + embedding = optimize.minimize( + fun=objective_fun, + x0=self.adata.obsm["embeddings"][index, :], + method="Newton-CG", + jac=gradient, + hess=hessian, + options={"maxiter": 3}, + ).x + embedding[(0 < embedding) & (embedding < EPSILON)] = EPSILON + embedding[(-EPSILON < embedding) & (embedding < 0)] = -EPSILON + self.adata.obsm["embeddings"][index, :] = embedding + + def update_sample_embeddings(self, aux: np.ndarray) -> None: + r""" + Update all sample embeddings by optimizing + the surrogate objective function using scipy.optimize.minimize + with the 'Newton-CG' method (strictly convex for each embedding). + + aux: np.ndarray + aux_kd = \sum_v X_vd * p_vkd + is used for updating the signatures and the sample embeddidngs. + """ + outer_prods_signature_embeddings = np.einsum( + "Km,Kn->Kmn", + self.asignatures.obsm["embeddings"], + self.asignatures.obsm["embeddings"], + ) + + for d, aux_col in enumerate(aux.T): + self.update_sample_embedding(d, aux_col, outer_prods_signature_embeddings) + + def update_embeddings( + self, + p: np.ndarray, + given_parameters: dict[str, Any] | None = None, + ) -> None: + if given_parameters is None: + given_parameters = {} + + aux = np.einsum("dv,vkd->kd", self.adata.X, p) + + if "signature_embeddings" not in given_parameters: + self.update_signature_embeddings(aux) + + if "sample_embeddings" not in given_parameters: + self.update_sample_embeddings(aux) + + def _update_parameters(self, given_parameters: dict[str, Any]) -> None: + self.update_sample_scalings(given_parameters) + p = self.update_p() + self.update_signature_scalings(p, given_parameters) + self.update_embeddings(p, given_parameters) + self.update_variance(given_parameters) + + if "asignatures" in given_parameters: + n_given_signatures = given_parameters["asignatures"].n_obs + else: + n_given_signatures = 0 + + if n_given_signatures < self.n_signatures: + self.update_signatures(given_parameters) + + self.compute_exposures() diff --git a/tests/test_corrnmf.py b/tests/test_corrnmf.py new file mode 100644 index 0000000..a592440 --- /dev/null +++ b/tests/test_corrnmf.py @@ -0,0 +1,254 @@ +import numpy as np +import pandas as pd +import pytest +from anndata import AnnData + +from salamander.nmf_framework import corrnmf_det + +PATH = "tests/test_data" +PATH_TEST_DATA = f"{PATH}/nmf_framework/corrnmf" + + +@pytest.fixture +def counts(): + return pd.read_csv(f"{PATH_TEST_DATA}/counts.csv", index_col=0).T + + +@pytest.fixture(params=[1, 2]) +def n_signatures(request): + return request.param + + +@pytest.fixture +def dim_embeddings(n_signatures): + return n_signatures + + +@pytest.fixture +def path_suffix(n_signatures, dim_embeddings): + return f"nsigs{n_signatures}_dim{dim_embeddings}.npy" + + +@pytest.fixture +def sample_scalings_init(path_suffix): + return np.load(f"{PATH_TEST_DATA}/sample_scalings_init_{path_suffix}") + + +@pytest.fixture +def sample_embeddings_init(path_suffix): + return np.load(f"{PATH_TEST_DATA}/sample_embeddings_init_{path_suffix}").T + + +@pytest.fixture +def adata(counts, sample_scalings_init, sample_embeddings_init): + adata = AnnData(counts) + adata.obs["scalings"] = sample_scalings_init + adata.obsm["embeddings"] = sample_embeddings_init + return adata + + +@pytest.fixture +def signatures_mat_init(path_suffix): + return np.load(f"{PATH_TEST_DATA}/signatures_mat_init_{path_suffix}").T + + +@pytest.fixture +def signature_scalings_init(path_suffix): + return np.load(f"{PATH_TEST_DATA}/signature_scalings_init_{path_suffix}") + + +@pytest.fixture +def signature_embeddings_init(path_suffix): + return np.load(f"{PATH_TEST_DATA}/signature_embeddings_init_{path_suffix}").T + + +@pytest.fixture +def asignatures_init( + adata, signatures_mat_init, signature_scalings_init, signature_embeddings_init +): + asignatures = AnnData(signatures_mat_init) + asignatures.var_names = adata.var_names + asignatures.obs["scalings"] = signature_scalings_init + asignatures.obsm["embeddings"] = signature_embeddings_init + return asignatures + + +@pytest.fixture +def _p(path_suffix): + return np.load(f"{PATH_TEST_DATA}/p_{path_suffix}") + + +@pytest.fixture +def _aux(adata, _p): + return np.einsum("vd,vkd->kd", adata.X.T, _p) + + +@pytest.fixture +def variance_init(path_suffix): + return np.load(f"{PATH_TEST_DATA}/variance_init_{path_suffix}") + + +@pytest.fixture +def model_init( + adata, + asignatures_init, + variance_init, +): + n_signatures, dim_embeddings = asignatures_init.obsm["embeddings"].shape + model = corrnmf_det.CorrNMFDet( + n_signatures=n_signatures, dim_embeddings=dim_embeddings + ) + model.adata = adata + model.asignatures = asignatures_init + model.compute_exposures() + model.variance = variance_init + return model + + +@pytest.fixture +def objective_init(path_suffix): + return np.load(f"{PATH_TEST_DATA}/objective_init_{path_suffix}") + + +def test_objective_function(model_init, objective_init): + assert np.allclose(model_init.objective_function(), objective_init) + + +@pytest.fixture +def signatures_mat_updated(path_suffix): + return np.load(f"{PATH_TEST_DATA}/signatures_mat_updated_{path_suffix}").T + + +@pytest.fixture +def signature_scalings_updated(path_suffix): + return np.load(f"{PATH_TEST_DATA}/signature_scalings_updated_{path_suffix}") + + +@pytest.fixture +def sample_scalings_updated(path_suffix): + return np.load(f"{PATH_TEST_DATA}/sample_scalings_updated_{path_suffix}") + + +@pytest.fixture +def signature_embeddings_updated(path_suffix): + return np.load(f"{PATH_TEST_DATA}/signature_embeddings_updated_{path_suffix}").T + + +@pytest.fixture +def sample_embeddings_updated(path_suffix): + return np.load(f"{PATH_TEST_DATA}/sample_embeddings_updated_{path_suffix}").T + + +@pytest.fixture +def variance_updated(path_suffix): + return np.load(f"{PATH_TEST_DATA}/variance_updated_{path_suffix}") + + +class TestUpdatesCorrNMFDet: + def test_update_signatures(self, model_init, signatures_mat_updated): + model_init.update_signatures() + assert np.allclose(model_init.asignatures.X, signatures_mat_updated) + + def test_update_signature_scalings( + self, model_init, _p, signature_scalings_updated + ): + model_init.update_signature_scalings(_p) + assert np.allclose( + model_init.asignatures.obs["scalings"].values, signature_scalings_updated + ) + + def test_update_sample_scalings(self, model_init, sample_scalings_updated): + model_init.update_sample_scalings() + assert np.allclose( + model_init.adata.obs["scalings"].values, sample_scalings_updated + ) + + def test_p(self, model_init, _p): + p_computed = model_init.update_p() + assert np.allclose(p_computed, _p) + + def test_update_signature_embeddings( + self, model_init, _aux, signature_embeddings_updated + ): + model_init.update_signature_embeddings(_aux) + assert np.allclose( + model_init.asignatures.obsm["embeddings"], signature_embeddings_updated + ) + + def test_update_sample_embeddings( + self, model_init, _aux, sample_embeddings_updated + ): + model_init.update_sample_embeddings(_aux) + assert np.allclose( + model_init.adata.obsm["embeddings"], sample_embeddings_updated + ) + + def test_update_variance(self, model_init, variance_updated): + model_init.update_variance() + assert np.allclose(model_init.variance, variance_updated) + + +@pytest.mark.parametrize("n_signatures,dim_embeddings", [(1, 1), (2, 1), (2, 2)]) +class TestGivenParametersCorrNMFDet: + @pytest.fixture + def model(self, n_signatures, dim_embeddings): + return corrnmf_det.CorrNMFDet( + n_signatures=n_signatures, + dim_embeddings=dim_embeddings, + min_iterations=3, + max_iterations=3, + ) + + @pytest.fixture + def adata(self, counts): + return AnnData(counts) + + def test_given_signatures(self, model, adata): + for n_given_signatures in range(1, model.n_signatures + 1): + given_asignatures = adata[:n_given_signatures, :].copy() + given_asignatures.X = given_asignatures.X / np.sum( + given_asignatures.X, axis=1, keepdims=True + ) + model.fit(adata, given_parameters={"asignatures": given_asignatures}) + assert np.allclose( + given_asignatures.X, model.asignatures.X[:n_given_signatures, :] + ) + + def test_given_signature_scalings(self, model, adata): + given_signature_scalings = np.random.uniform(size=model.n_signatures) + model.fit( + adata, given_parameters={"signature_scalings": given_signature_scalings} + ) + assert np.allclose( + given_signature_scalings, model.asignatures.obs["scalings"].values + ) + + def test_given_sample_scalings(self, model, adata): + given_sample_scalings = np.random.uniform(size=adata.n_obs) + model.fit(adata, given_parameters={"sample_scalings": given_sample_scalings}) + assert np.allclose(given_sample_scalings, model.adata.obs["scalings"].values) + + def test_given_signature_embeddings(self, model, adata): + given_signature_embeddings = np.random.uniform( + size=(model.n_signatures, model.dim_embeddings) + ) + model.fit( + adata, given_parameters={"signature_embeddings": given_signature_embeddings} + ) + assert np.allclose( + given_signature_embeddings, model.asignatures.obsm["embeddings"] + ) + + def test_given_sample_embeddings(self, model, adata): + given_sample_embeddings = np.random.uniform( + size=(adata.n_obs, model.dim_embeddings) + ) + model.fit( + adata, given_parameters={"sample_embeddings": given_sample_embeddings} + ) + assert np.allclose(given_sample_embeddings, model.adata.obsm["embeddings"]) + + def test_given_variance(self, model, adata): + given_variance = 3 + model.fit(adata, given_parameters={"variance": given_variance}) + assert np.allclose(given_variance, model.variance) From 5c11ac7c00f1e959d60df2b17b1e64feb2d95608 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 17 Apr 2024 22:43:13 -0400 Subject: [PATCH 053/103] unify embedding updates Also removed the computation of the auxiliary parameters p. All parameter updates can be made without storing p explicitly. (Forgot to have a separate commit for outsourcing the embedding updates to _utils_corrnmf) --- .../nmf_framework/_utils_corrnmf.py | 79 ++++++-- src/salamander/nmf_framework/corrnmf_det.py | 174 +++++------------- tests/test_corrnmf.py | 8 +- 3 files changed, 106 insertions(+), 155 deletions(-) diff --git a/src/salamander/nmf_framework/_utils_corrnmf.py b/src/salamander/nmf_framework/_utils_corrnmf.py index 16036cb..9b070c2 100644 --- a/src/salamander/nmf_framework/_utils_corrnmf.py +++ b/src/salamander/nmf_framework/_utils_corrnmf.py @@ -4,9 +4,11 @@ import numpy as np from numba import njit +from scipy import optimize from ..utils import shape_checker, type_checker from ._utils_klnmf import check_given_asignatures, poisson_llh +from .initialization import EPSILON if TYPE_CHECKING: from typing import Any @@ -162,22 +164,19 @@ def check_given_parameters( @njit def update_signature_scalings( - data_mat: np.ndarray, - p: np.ndarray, + aux: np.ndarray, sample_scalings: np.ndarray, signature_embeddings: np.ndarray, sample_embeddings: np.ndarray, ) -> np.ndarray: - """ + r""" Compute the new signature scalings according to the update rule of CorrNMF. Inputs ------ - data_mat : np.ndarray - shape (n_samples, n_features) - - p : np.ndarray - auxiliary parameters of shape (n_featurues, n_signatures, n_samples) + aux : np.ndarray + auxiliary parameters of shape (n_signatures, n_samples) with + aux[k,d] = \sum_v x_vd p_vkd sample_scalings : np.ndarray shape (n_samples,) @@ -193,14 +192,7 @@ def update_signature_scalings( signature_scalings : np.ndarray shape (n_signatures,) """ - n_features, n_signatures, n_samples = p.shape - first_sum = np.zeros(n_signatures) - - for k in range(n_signatures): - for v in range(n_features): - for d in range(n_samples): - first_sum[k] += data_mat[d, v] * p[v, k, d] - + first_sum = np.sum(aux, axis=1) second_sum = np.sum( np.exp(sample_scalings + signature_embeddings @ sample_embeddings.T), axis=1 ) @@ -466,3 +458,58 @@ def hessian_embedding( hessian[m1, m2] -= 1 / variance return -hessian + + +def update_embedding( + embedding_init: np.ndarray, + embeddings_other: np.ndarray, + scaling: float, + scalings_other: np.ndarray, + variance: float, + aux_vec: np.ndarray, + outer_prods_embeddings_other: np.ndarray, + **kwargs, +) -> np.ndarray: + def objective_fun(embedding): + return objective_function_embedding( + embedding, + embeddings_other, + scaling, + scalings_other, + variance, + aux_vec, + ) + + summand_grad = np.sum(aux_vec[:, np.newaxis] * embeddings_other, axis=0) + + def gradient(embedding): + return gradient_embedding( + embedding, + embeddings_other, + scaling, + scalings_other, + variance, + summand_grad, + ) + + def hessian(embedding): + return hessian_embedding( + embedding, + embeddings_other, + scaling, + scalings_other, + variance, + outer_prods_embeddings_other, + ) + + embedding = optimize.minimize( + fun=objective_fun, + x0=embedding_init, + method="Newton-CG", + jac=gradient, + hess=hessian, + **kwargs, + ).x + embedding[(0 < embedding) & (embedding < EPSILON)] = EPSILON + embedding[(-EPSILON < embedding) & (embedding < 0)] = -EPSILON + return embedding diff --git a/src/salamander/nmf_framework/corrnmf_det.py b/src/salamander/nmf_framework/corrnmf_det.py index d828762..3e59c2b 100644 --- a/src/salamander/nmf_framework/corrnmf_det.py +++ b/src/salamander/nmf_framework/corrnmf_det.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import numpy as np -from scipy import optimize from . import _utils_corrnmf from ._utils_klnmf import update_W @@ -28,6 +27,20 @@ class CorrNMFDet(CorrNMF): with Stochastic Variational Inference, 2014 """ + def _compute_aux(self) -> np.ndarray: + r""" + 'aux' is the numpy array of shape (n_signatures, n_samples) given by + aux_kd = \sum_v x_vd * p_vkd + It can be computed without explicitly storing the parameters p. + Notice that 'aux' is sufficient to update all model parameters. + There is no need to compute and store p. + """ + error_ratios = self.adata.X / ( + self.adata.obsm["exposures"] @ self.asignatures.X + ) + aux = self.adata.obsm["exposures"].T * (self.asignatures.X @ error_ratios.T) + return aux + def update_sample_scalings( self, given_parameters: dict[str, Any] | None = None ) -> None: @@ -43,15 +56,14 @@ def update_sample_scalings( ) def update_signature_scalings( - self, p: np.ndarray, given_parameters: dict[str, Any] | None = None + self, aux: np.ndarray, given_parameters: dict[str, Any] | None = None ) -> None: if given_parameters is None: given_parameters = {} if "signature_scalings" not in given_parameters: self.asignatures.obs["scalings"] = _utils_corrnmf.update_signature_scalings( - self.adata.X, - p, + aux, self.adata.obs["scalings"].values, self.asignatures.obsm["embeddings"], self.adata.obsm["embeddings"], @@ -85,64 +97,6 @@ def update_signatures(self, given_parameters: dict[str, Any] | None = None) -> N ) self.asignatures.X = W.T - def update_p(self) -> np.ndarray: - p = _utils_corrnmf.update_p_unnormalized( - self.asignatures.X, self.adata.obsm["exposures"] - ) - p /= np.sum(p, axis=1, keepdims=True) - p = p.clip(EPSILON) - return p - - def update_signature_embedding( - self, index: int, aux_row: np.ndarray, outer_prods_sample_embeddings: np.ndarray - ) -> None: - scaling = self.asignatures.obs["scalings"][index] - - def objective_fun(embedding): - return _utils_corrnmf.objective_function_embedding( - embedding, - self.adata.obsm["embeddings"], - scaling, - self.adata.obs["scalings"].values, - self.variance, - aux_row, - ) - - summand_grad = np.sum( - aux_row[:, np.newaxis] * self.adata.obsm["embeddings"], axis=0 - ) - - def gradient(embedding): - return _utils_corrnmf.gradient_embedding( - embedding, - self.adata.obsm["embeddings"], - scaling, - self.adata.obs["scalings"].values, - self.variance, - summand_grad, - ) - - def hessian(embedding): - return _utils_corrnmf.hessian_embedding( - embedding, - self.adata.obsm["embeddings"], - scaling, - self.adata.obs["scalings"].values, - self.variance, - outer_prods_sample_embeddings, - ) - - embedding = optimize.minimize( - fun=objective_fun, - x0=self.asignatures.obsm["embeddings"][index, :], - method="Newton-CG", - jac=gradient, - hess=hessian, - ).x - embedding[(0 < embedding) & (embedding < EPSILON)] = EPSILON - embedding[(-EPSILON < embedding) & (embedding < 0)] = -EPSILON - self.asignatures.obsm["embeddings"][index, :] = embedding - def update_signature_embeddings( self, aux: np.ndarray, outer_prods_sample_embeddings: np.ndarray | None = None ) -> None: @@ -163,62 +117,17 @@ def update_signature_embeddings( ) for k, aux_row in enumerate(aux): - self.update_signature_embedding(k, aux_row, outer_prods_sample_embeddings) - - def update_sample_embedding( - self, - index: int, - aux_col: np.ndarray, - outer_prods_signature_embeddings: np.ndarray, - ) -> None: - scaling = self.adata.obs["scalings"][index] - - def objective_fun(embedding): - return _utils_corrnmf.objective_function_embedding( - embedding, - self.asignatures.obsm["embeddings"], - scaling, - self.asignatures.obs["scalings"].values, - self.variance, - aux_col, - ) - - summand_grad = np.sum( - aux_col[:, np.newaxis] * self.asignatures.obsm["embeddings"], axis=0 - ) - - def gradient(embedding): - return _utils_corrnmf.gradient_embedding( - embedding, - self.asignatures.obsm["embeddings"], - scaling, - self.asignatures.obs["scalings"].values, - self.variance, - summand_grad, - ) - - def hessian(embedding): - return _utils_corrnmf.hessian_embedding( - embedding, - self.asignatures.obsm["embeddings"], - scaling, - self.asignatures.obs["scalings"].values, + embedding_init = self.asignatures.obsm["embeddings"][k, :] + self.asignatures.obsm["embeddings"][k, :] = _utils_corrnmf.update_embedding( + embedding_init, + self.adata.obsm["embeddings"], + self.asignatures.obs["scalings"][k], + self.adata.obs["scalings"].values, self.variance, - outer_prods_signature_embeddings, + aux_row, + outer_prods_sample_embeddings, ) - embedding = optimize.minimize( - fun=objective_fun, - x0=self.adata.obsm["embeddings"][index, :], - method="Newton-CG", - jac=gradient, - hess=hessian, - options={"maxiter": 3}, - ).x - embedding[(0 < embedding) & (embedding < EPSILON)] = EPSILON - embedding[(-EPSILON < embedding) & (embedding < 0)] = -EPSILON - self.adata.obsm["embeddings"][index, :] = embedding - def update_sample_embeddings(self, aux: np.ndarray) -> None: r""" Update all sample embeddings by optimizing @@ -234,20 +143,27 @@ def update_sample_embeddings(self, aux: np.ndarray) -> None: self.asignatures.obsm["embeddings"], self.asignatures.obsm["embeddings"], ) - for d, aux_col in enumerate(aux.T): - self.update_sample_embedding(d, aux_col, outer_prods_signature_embeddings) + embedding_init = self.adata.obsm["embeddings"][d, :] + self.adata.obsm["embeddings"][d, :] = _utils_corrnmf.update_embedding( + embedding_init, + self.asignatures.obsm["embeddings"], + self.adata.obs["scalings"][d], + self.asignatures.obs["scalings"].values, + self.variance, + aux_col, + outer_prods_signature_embeddings, + options={"maxiter": 3}, + ) def update_embeddings( self, - p: np.ndarray, + aux: np.ndarray, given_parameters: dict[str, Any] | None = None, ) -> None: if given_parameters is None: given_parameters = {} - aux = np.einsum("dv,vkd->kd", self.adata.X, p) - if "signature_embeddings" not in given_parameters: self.update_signature_embeddings(aux) @@ -256,17 +172,9 @@ def update_embeddings( def _update_parameters(self, given_parameters: dict[str, Any]) -> None: self.update_sample_scalings(given_parameters) - p = self.update_p() - self.update_signature_scalings(p, given_parameters) - self.update_embeddings(p, given_parameters) - self.update_variance(given_parameters) - - if "asignatures" in given_parameters: - n_given_signatures = given_parameters["asignatures"].n_obs - else: - n_given_signatures = 0 - - if n_given_signatures < self.n_signatures: - self.update_signatures(given_parameters) - self.compute_exposures() + aux = self._compute_aux() + self.update_signature_scalings(aux, given_parameters) + self.update_embeddings(aux, given_parameters) + self.update_variance(given_parameters) + self.update_signatures(given_parameters) diff --git a/tests/test_corrnmf.py b/tests/test_corrnmf.py index a592440..9f7f3bb 100644 --- a/tests/test_corrnmf.py +++ b/tests/test_corrnmf.py @@ -150,9 +150,9 @@ def test_update_signatures(self, model_init, signatures_mat_updated): assert np.allclose(model_init.asignatures.X, signatures_mat_updated) def test_update_signature_scalings( - self, model_init, _p, signature_scalings_updated + self, model_init, _aux, signature_scalings_updated ): - model_init.update_signature_scalings(_p) + model_init.update_signature_scalings(_aux) assert np.allclose( model_init.asignatures.obs["scalings"].values, signature_scalings_updated ) @@ -163,10 +163,6 @@ def test_update_sample_scalings(self, model_init, sample_scalings_updated): model_init.adata.obs["scalings"].values, sample_scalings_updated ) - def test_p(self, model_init, _p): - p_computed = model_init.update_p() - assert np.allclose(p_computed, _p) - def test_update_signature_embeddings( self, model_init, _aux, signature_embeddings_updated ): From 6d40e8607e81b18dac8044108838f72e19723cc7 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 17 Apr 2024 22:53:54 -0400 Subject: [PATCH 054/103] improve tests Replace 'p' by 'aux' in the CorrNMF tests. The new auxiliary parameter is more memory-efficient and now used in all parameter updates. --- tests/test_corrnmf.py | 9 ++------- .../nmf_framework/corrnmf/aux_nsigs1_dim1.npy | Bin 0 -> 208 bytes .../nmf_framework/corrnmf/aux_nsigs2_dim2.npy | Bin 0 -> 288 bytes .../nmf_framework/corrnmf/p_nsigs1_dim1.npy | Bin 7808 -> 0 bytes .../nmf_framework/corrnmf/p_nsigs2_dim2.npy | Bin 15488 -> 0 bytes 5 files changed, 2 insertions(+), 7 deletions(-) create mode 100644 tests/test_data/nmf_framework/corrnmf/aux_nsigs1_dim1.npy create mode 100644 tests/test_data/nmf_framework/corrnmf/aux_nsigs2_dim2.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/p_nsigs1_dim1.npy delete mode 100644 tests/test_data/nmf_framework/corrnmf/p_nsigs2_dim2.npy diff --git a/tests/test_corrnmf.py b/tests/test_corrnmf.py index 9f7f3bb..4f344b2 100644 --- a/tests/test_corrnmf.py +++ b/tests/test_corrnmf.py @@ -74,13 +74,8 @@ def asignatures_init( @pytest.fixture -def _p(path_suffix): - return np.load(f"{PATH_TEST_DATA}/p_{path_suffix}") - - -@pytest.fixture -def _aux(adata, _p): - return np.einsum("vd,vkd->kd", adata.X.T, _p) +def _aux(path_suffix): + return np.load(f"{PATH_TEST_DATA}/aux_{path_suffix}") @pytest.fixture diff --git a/tests/test_data/nmf_framework/corrnmf/aux_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/aux_nsigs1_dim1.npy new file mode 100644 index 0000000000000000000000000000000000000000..39dbe04f8564123a2b911905fb4e31e3fa4f7952 GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqpcnmP)#3giMV1`xQr#Q{w3-3X!g9fZ&!lOgn#`4IZ&aR@zk1B6b1 M(jp5X{Jy;o0AfEX@&Et; literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/aux_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/aux_nsigs2_dim2.npy new file mode 100644 index 0000000000000000000000000000000000000000..bb04a843b8f77c73e76b71783a61076029153cd8 GIT binary patch literal 288 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$7ItqpcnmP)#3giMVHZ9&=)k&Ki)UGi>l#?N?J)D%Cpx03C>8)&Kwi literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/corrnmf/p_nsigs1_dim1.npy b/tests/test_data/nmf_framework/corrnmf/p_nsigs1_dim1.npy deleted file mode 100644 index 1ca7a583c5d704174cd39dabc313778b2f433591..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7808 zcmeIyu?oUK41nQt^(nGN;Raob7NMJ)#l^u%j`oU!*m5T>(x>Q)`Ut)D5!{471VS=~ z3m(7xA|!8V-++PJv2MDqD>oK!98znsQolQx<~YB9`CO6sbB3gE8CQtwUu6e ziKUt2TTrJBum|pdJKzs+00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zDX G*MSiMrHnKH diff --git a/tests/test_data/nmf_framework/corrnmf/p_nsigs2_dim2.npy b/tests/test_data/nmf_framework/corrnmf/p_nsigs2_dim2.npy deleted file mode 100644 index 5fe2958383ed94463965b9fccbfa45eeb995fa4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15488 zcmbVzWmHy8)HWcBsDMRCC<-D-iXb4mM`;WYQNkbvF;S3ER6t1;Nl6Lm?(R;J?(PO@ z1%&quYrW6+f=bInKD1(!`B9ad$g#t~C(!MC&7?Ahdf z!{rbK+K0_)a4Iser|4W6$md)-wiXo*rLlH?)A!TiBI^Jj?l=5|+D)v3>g@?ej6b9g z{lwT^Z5n~`26ObMKLj3r;OUC>d~a_T!MxLz)tB+-vb1gv*nY6@@aBp`ILJ>pkwunEju-)^&TrWSo}4y6AV;!!Ac?@C*TB+G4GdeB@n17eUxQ)6IjoVC7!yS3K4I&#HRO` zg64-`r8vI~Z*J6MT}f(JDzX0xX2yM3pSJi3Ev#>WyEOV-X>boUlzlnayWex9RKUpJU0$87w zY2gIc1u8BlG5!k<+TBi|r-`ub70VOdV zBgW1G5PM}|D{NQ?G&hrzaGmp-+@r*Kiqxq+g#86|aI)a|Q4EN@!}?l1MjrPOxN%Lk z0LN+CpsyYC*mhDC;pg>vhrFyfZNUy9onakRb|AA1~syh`AS8zOh`=l2ZH|HQhMHTRvwpU?5ODq?%Q z=$q)G-&vrxW2Pf-!y5+4*Xw^c2K_g`2#HDm@Oa}0Z|iv4a=9B|l78Udr+d+`XwPoT z6OaXCLib5uY50M@1<7%LvKSaq9Gbv&Zc*D>hvWB+*NzcmLb$aW_UqVXFo^vIpb{);;Uma>nW zu9@?IpdI?MMjbz3<%^Ew2=zEDDRiahnH7VIfPAOMLIXH`KHdM6Cj~^=(5Z+i@ivr=fSJF1fxQfv z7aGT@hwV~_wJFa2oq^0x0pFGSe!>8s!_5JvCU|?Zw}DeC2~6K#W6FJz2g`=`d-t^M zKu@F68>>xvp*rK|y$YWhc;&b&t0}M!rc*LQhMpVSBi9vFm~Vh*UX8tZ>=R={-KQa)RG04ZCnLi&iS+YECsgk3V2km?% z=ldZ4rf-yh`Yd=33m0*ns|S0*a~+mjeLz7@vIEbvHnyQZunyt9A3xzbw{SYSiQ{%P zOGOUjKZP$VodkyNF&xIDC$D0@kq_%F)vRQ!1gcmK6&ws-F_uGzhL0xdOXsh#?O&;*U z@l}oEwXmp6!HDOr8%fzJSO=Nep;{cb!Ch@uIKM1!_)cLIR6LVAL*TG|{Clj2T;m@< z=CNj+;lsSJ2pOH)z08$|1wvA(GMQ_ z)h7Ou9E8mN?!wP|a^Wb<+wZwl1LIF9z78|Tfoy3*v0zRvNWAZ>sUi9eH;H?O=i8-g zt9S7}pnD+UBKCJLgm-)1y?rmU(u8QAY}9`}LLg@yjnPj6A5%=9$8l_vyw*5O;NoEK zrTvU+aAYava2R_BNHOe7*&W&sy?iI?Yv> z?Nf$7<$qztfp`AYr!FWcP>2R-iW?>Oas?pbmsa8JYKd8GlT;Pocfse6xZSE;g%81 zlW}~17ms(g`png70(m7pN2O&Kz$C$yL)WhXwD#p)S666*j8_T4J89A&z)h9qz)}(P z-6mmo6Wom^ix0n*a32I)IU5sNss#v;do@EQ-vn2$^zh5>9)i&!Gubej4s>zW%hgbR z3~|^utA5iTLAv|v1kx&|(T#?pGl6}R$cF1Viw8v&a`npVZXXyx(LBXzUoUka?pMqa zKaxk$3+aPg@reV7idyO(ulW$NXRGgKeb#`|=T&aB2)3Z%xAw19inCB|_8-xY)}<)W zB=iqs{sii+w~pFo)4; zlyq8%?8nY&^yU7KJX2C){V+GQy{0;gj8YFs8f)~Rt9t(`ZZJ%u+MUOAeu#~tx-j;j z&fsZe*CD;ZvNnOdbrzjeGn-JwH;R_*noMLGtuedWSb#n&G+*Ux2t;+JyQ4%tN27|9 zW*X%;22ji6J^i@>73kqok$B#hCiJPHbMy2>HX8F8r*@#PM5=#cPo5|8B300?UQeLk zvj0pWfmDY)3i19b- z*D05^uH-PF4|_F&4mGU;hpphwGmcx3Gdj}I_P!f-{7Niky*du-wL|ikGz8Gx?)n|- zCn%As*N$HP-z=Q@k^dkt=$7? zm!e;(`Sl@G5~8bW%J)bq+pjVOe!(NtNc8?CAxaeG%hgm$EjnEJL)q0OWrInFGi-soE% z3I)%ig1FeHBzZHarc3pB(#3X!BKVgZRK}6+`3DoxokQqIu66et>nZg9+P_w1!Etm@ zp|*?Mq8%-8KVtXvFGM>2I3J(py z!DQp#wZ5ZZMApz#_q3GQ-+wA7d(;4R_cx|z%;*tm%rS$LL3JiOv{IwEI`irK8mbOVqJB0xRgZnPkO+)#EU>7^v8IdK%cKw9c=`Poa25UutDr! z6drb{6K!v!V1_(`r%_2?5Jj}d-&YjhCNN0(*1ivh?C9j2XID?c4pg00AkIv)2ZgNF z6jFQ{gV#zaqD+UD;LsI?aO!>HsL_vuuBr4e(lKdmH{oMPr*+4v&b#bH*=r~EN66Bm z(N~O|`yz)>xoce0M9mah_P1|Hikd_*w{#!-`Ol)6`a=W$X)}m^-a~WF^ulDTKte^NI~y;t0c*0 z8&OM+T1;(zIkI1Ex?mmHjh1gyTblSaA}02G^&*;+$o0pS>rAWt=)i9Sk*?E2=wuqn zJH3RyLMv|T_pF^M#Mb2v&H<9G%>I0 zj%f>xm{g$$=bN4+K|hK#zwAc0s~ZJ+QoMWoYaB&HDpR>U8bM#a?)o#fkcMuP+^Z8H zYe8AbwagY7RY>aM5i#f8eTbp?m`P4_Gvc%xOSTK1K%$@3IsHHMp;*bj^rejf^dW3u zuS)q(lo+oz(#O++`1!U-LN3psmMtakvG*hBru$FP<8Bj3Q`=ZK)1n_O(Y}xljvhhp z=XPgI8h0ZfQLS@2KI4dSmLaTqa1_zsUVGnvcLuTW>DW2tPazYNzpO^-wMccU=#gj@;ODMZW3|AqfYI&^_e?$d|>E!_2o9)v*0adwjkTNviVh z-e65bPO@gBRXdANNN|yvuG=_LI?!!I(b%tpiQ5#;GEo1&7FhK$oDUyyY* zqkn$aRjl7vq1co`5ytLbH0CRMfAAwQ&j}P(L?uq32YMqZ%n5y{L6-V^$H77Lp@Kr+ zmAn?w{PU-{cdG?CQF(EFlbb=ZVV^4AyNw`HpQq89K@*5OoO*b{wI4Yp>o!p5k02dm z)-QPfl$FfEjrXC$I+sWBzUnpa-9p^%(zbBNC~TP5h4%q>$A!g!1x^MVQom+@drs+kXdNXTOob!G?pf>&U*Sfnwu$e8 z|9{WTIt1&=xjVzN>!l~~drjCKekL4;hs+Z1u@1eXl!n+}V^8C5i~?gHd8Y{cE4G&u z$1UEvRvh!FjQ&pJ=WTj=$JPl{tdg&ueN_kFbmMRDxgHPQwMEQxRH;x%^73r=Q8#EG zPLd8C34k!6I)=B6!(hC6Rv>(}0P-qj{#Na;gLNxELm}@(2&0{Mw9zUArjda_>?g_T zh8Wh@GS3!{{r!2d*Bt9oQQ>F62$PlOc>k$G8gL2gyIOs3`+Y67Ncf+6E~Jx*RWe=)0P-YB60_kr z_?1;)i2E%U_7B85hBY5=zgHHK@C|M28twXpQONsZH_qSQ+d)FO9Xp@(3iIX_o_XSS zC2Jr*#>k0HJQNy9{8;9G8l3Gude>L{@8a31X4N|LeCjLjpZjk(B&yU zqLms6N5X6*V;_8iJ(=yJO#6JH{OHt9{QgxqGpMxf&mg)S=T}zwfhG3$TtA&10O@Z(D_IasPFe$+;JyauZ1cd|DOM1%UOd1h>N5>Q#6 z_A)u-0=XPwf@dH3!73F)B-QId@R<-8xNce(Xke8*XJV< z7m;l{9gS>(UJl#}u_Rm>c)Q9NlxeEwuXKFcKx|( zp7@OQogRC_k3Tm|r+SU`i72;F~}zSCt&;=JIK!!7Crg8JoZ9w7(E|5JaWZRa)LeaMF8M2_1g>K@SaJ#@;t zJOp&VzJf2TwqPYj6%x_m0aV|mM)3UNe`wbg>~D@jA{W=6ndzQ)*k2}3Dl0~*XO;rE z{uDm->0+HH{Rh`Euj!yHE9UL1IDQ@5uSHZk&piGKuQ>S212kiRW!$!KjwKnkk{Qk< zX}CanEY+ho6MjJUHOI}WZV+lHJ>qr@=bugX{{;Sx#>TJfac~PlE9O1(pse{k z&mZSuL`i+W`nu>edK?nbgZQRU2Af=iUFj@h(XEP7T$(`(?AoUJ109H+f$wS8!Z;du zC7NU&F^mp{aLM+LO(CJYJM53@$B~)JrwE$DkyJebO1k%0+qW$f;@cT=@Y@;vV9>MQQb*rv5Cld!%CS`nLx?VZS|+ATx<% z>MuAbSd5|!?maF=1=A?*=372tu1TabGTNge)P$11dJk4EWgxPQbWP!ke5B-NqP!6m zfJ$aR)l>UMp$j$5k|H|>km~c8$CpOR(JM{6IUUL-Bo!QQxk#FguBK7TaNn&!&Rg%< zaeiX9-fjP$rEmAl0O!-UrML%UY*fJZy4;@s?=YVKCIfZ1$HS*8gVUI|Hs<~TKZhJ& zZm)yF1r!wfGvh#xMV{6w=nLFm;6AIk;P!v&FR|taIc01K#NGEJ`*Jn_c;Na&V|8Ml z=F%kHb=MhwJlfy>Yn~V%;5uTI$!ErXH90@L#X9yEc;4+J zF!+LME}kzBXL2=RUa7xb0e)W6uipdPl~{C~xFg%a|1Vi3D$D@x#?qV0FA8Atc*6w| zgYV#Lx${krNF>mcm&{vsAh7R)fk=212%Ybo+sSJWN6RUXaumCRQdaM~z#pBk z%VE$#>t!l*SB6Lz^prrGsQCdMu`t-$>-dgqAr*c;i@l2d?2%mW!G5L{XpvOUGPTq*S#$Ll`r{_j`^-K+3Bm`D3nr5N*Ucau6{`?yVmoI*n}DHq-G&B6^-&FG(-PPBuZJ@akLV=2JW8O5*vtr+Y`mPg>)I^8ydBp`%lJloj%mB^&tDwx zZ>KFUVBXUqg9n($t$VW*+j%Y?_gE@xg77m-$5!{IfL4K(%!PAVU=p=R+g{-baEi`l z=t&5uU8c2C;++ICtEt*!45hG1+Y(J`-vk$me1|i;Q^9sq**M*^6jolW>0mv*%NLJh zJ>p;3I&eHVV;*epUs7nEf5rA*>FVcLPp-i_7yf)!-r_mt9W^K0-gl2YdVCby&BfHr zdB$^LfKgoPa=RZC?6`iR;#n{-F`PNq5^e?BP4b7{KKKGRq9k8EIne|IOOmhTwc?f3-!ac*Vb?{wHxM(34# zFCXO2?@K#t=m){3M`+!2BVf7ZNV|;P9I#zK$nsCH5+*Y?4~W^cfh^fPW#KX6{Fl6O z!my?ixB}HY@w&k6G2nvhNl?}FIL?!^hv#7Y1M4chlF>CPRt! zAuk@SKk)2UObl;&BSinPxJ$`D2y%74beEr%0NwJ_x70jM5SMLah<|5ED$fkT>s7cy z{blS&e!$}=*0m+8jBtDua*iHKAh57V&>a5`qbyojk9iCIM}Onv%K%$`qK2lt;VVsz%FZpr*Sv8*hSdad(WC4unwI8?l0ZzLYCvkqed8MN8 zc~STo16v%gy*I44&$YT3YGU#8mzD$`Vf&!#NlKO<4KP?IQ2*9$Z&19@^$x5csj`VgUYK;mKd27|i2% z{ksv*i`67*n|OSe^T^yC|N93v*Bs#hO*`1@7O#c2_W^lq$GUZSF4zg~y^0(vp>Okm zGQY!qbQ`@tSFAk&9!^(2>P7znwuUsSV|O~il}>mof^-tD$htS-e#N4nCu1Fg(>fsLSrAXIP@o*y7i~Ler7C2n?H$J6IiUPOP?}+$xLIH(s$B~W+ zkS^K%%hbOHngv(PD=u}x@xXfr8i;;1Xrhzxc#N1}--FMc`69Zv_s9RNdN1JdGGO7D z_mRNOP3BWL&+xZ29P_SN4hG=3%W@35;B)Gn2E#-pvsDm@ksgTgZG}YYWS;b?9{6kg zxtx(Q2a2-yZrJXt0L4U#AO8Dk5M?A8=}`L^v~p{I^ygWH=Q@u`#f#g){8?59i{UuD zT4RdEc@nN&JBsyLibcxc{GMw~F~V^>Fyc0k z)!XYXiOa}TsIeR0 z8*oU3L%Qz+HwV+e@FCnuEO&>Rck5dm-9aFb$`fFjH43MXv|WsSSpV_(FGI-QTG`sW_L836Z5#`mb@WyeoO587w*A3YW;Y6(Ote|815P?Q_ z6y0#X&mN;N#=K*OKHK-VF2yjvz;-I8Adl7iEzsv}ma^eNtW!dNOUxk$XifM0p78gE zU1T1gLpMT!&ZD;0RDK!)RYX0D)62kh!ndH92)F+7;s*S zvKNbS9<91|x@vrmIDTxJ z4}~#qxs1-@^Zj<{2jyT*Ev|hw)Hs{}V13WnWnZdNUR$NLnL7v;Ii- zKf^>Q+J9l}QK}Ox?C@ebtn32?qNIjL4h+E3*7@TxXS1PphuC)`(<=D2);&%x6ayR` z6uCL=+3+~RrX1&G%W!i0T#eKz>NfV5=asa5o)E!beHZIvnoT>6Pz~!>`o__Q zdBN92w(n0^qj*_tAHA%qGc!^OWW&@X>c>N2J$9J?VOTV%mWA*4J>&p=#uxTx+j&5@ zc@3LKbQfs1UM?%SK-{C4%81qrDTP**N#W1?!Xf_g+>WosX^>~(sEo(!-D`Pb`1|i= zStG%9&cbrI59>T=a{myV34GtE#~dZ3hBYm2cMv5!*#(dWV?Gt ztq%g|PWsad=0mk~>u36n5?Ep8=F6uJ1}VnAc+Tp0c-bA$J5jI%JBBy<&UV#-VPm2B z@#S97O>AO%=~xIJA9Awim56oj@ON9>FLkTRGXDNO9$eQjZk)>0#c@+quwTadHtGzV z@c3Q&ERclt%y|0*VcrW9+YJwl5*nRx@;fK1PZ0UaYTH-8~G?zj?o1m9dWR@u8ELmvDO84L@I( z$9fLivn}pc$>=qLlv}1QWG2Db$%kT2uQI@F4(+r%^9}xusr6bM2!?21!SJVd#^Dm$ zcQG@m7&7fEg40hofveE{q9r=n9!b2#BnHK_1(gLg5?U-F;eGO>0^Dq zHN|UKU%VXJ#PL&MYA?q+a%U2%F;AMO%wZ}}Wp56#VFYkWHrfm3EC zd#7L;I4ui9C5Jmu_UOtqDF?xGR;7$W(ot{`4|59~E(CTu2lk-H1aGp_f7CGv#P^GP z?o}*;zn|nA@H*9~JlK!dkJUZOvUq(f35k!z_4tU&@D?5iDV#A`wggUQ20P&Ot!q-@ zK_h{3Uq3Y9_3hL7UrRW?T5KQB-=$v#q1iiX;?N2!Pxeo$zwCh*UIPQ4e6wLhN~2mb zz8roUHVJ=orAEfFw6gcj#^7<5+o$&06$qJoF|>BC9eR9B%FgT^2PawK-*}!4*GzZE zaqxOGSB-T&A*dEO{ z`r_NMX7F1qC%dkb0%j&s+%}O}z|Qxwk&)jErVCC~s?!#uj7eU6xS;>H01Y$@hS!TT!HNg4o>;D)?fpD! zpWO@0)7p7)0e>&rd*lpz__e-zY~GTsSP91@ANEBdxOv zVa;4OL|QBW${XHaQY(#u_FdQC=-rW zZ`8okkz3pMtmRc>hOwXce(@o!FZ7(iL7ZRK=?{;wzPpbs+OWR9xvrO3hs~ac5?G&| zSNHaQXNulM4%?+SIfYe-d)7DIGo-^hGr=cO{@s4#Lb&Dnk^66!KX8pA&+&-VF?>}kvGec*k+$}k+~heJEJ>rIx6dMExKgXC{w@bmli2Q+aVR&UOo+Tt#S zw<|6Y=y?ciK3V3xof!oc<#DDDF4}|mS(*$+;tKZuXsN2_shyxEUN&5BnF_U)V|Oa1 zO2C(|AxTp%3@%@>ty>~V18?JackDNN#iA6)PvYy3Ozii`SC{ScvNQaQ+xKD3R02%8 z347z>0xynJ*1^a5m}lDOXM%Z#SJ%m~UE@^)Lr8lkoQUDR!rGk$fh49yd@E%?R|IX1I}rzvuymsnxSfgRr`7<12(p$~Hhly)w3fklRR6A$MUSYH;&HQG@N?c}e$4g`0DhLX2Y z2hp#=4!H~*59<20G(0Y!GpmT7ZG=SHA^$^-(H?A1_cd^U#T{f z=vMTL;a1NfpuG0>vI^xQM7`v7{HW3lO|xpmrhgcEvRE}ankUiO52R!M9<%6W^*}@I z$yqdcBq_MdnRp**lKkl}LFlkys2$)RL8cXZjN#-AYNGa9SR0r`L2AEtMjFqe!)ErW zqlagZGsC-&`U$0|qkGKxczQURP#YRJ5fXD_9z3{=p{=8xCW?6i6+oJY2fmhJt1bI<(t z|5em|`LluJSX&7G%>*tn+Q#DiPsu7YV4jt_eH^aGi+2y0W4meOWv*_WemL|?=;j;O z0{F&i>=!v)3UQMqCkFe2L5BU4k2pgjWX<>lY9%d$DF2b_C z@7QYoGs6(t?Gzw%mIrao1!lU3??g_jA0F=9LyO)7aTR`evjVqgXOFG&?Lv#U zmjB5T{eCEZoq+wYU2s$_BT)NRa{(R)H+<<0@V@kQ;oBYtLLa!tS_TvCz~WTdD+JmE z@|)HXm~0X)7(`&ThxYBuB=qRLRKZb6&J~#Soz)h<^$(J|#8p+TI)QOAL2uk^6m}c@ zIm>Cphu)hBbRE{BK(0)&4=FR~kVExFfpMun@cfp$&h14CluW};V`x8!_)k*xnu<>$ z{z&T9EaILaYv-5vw+6GQOzvo6iq;Ium6+x;9%w;%jUrs^8e@oVqRdV&Xb=g+ed%lO zm_*hZS#(L~#*lx1#=!CUP9*cB#-9CP36jz*i(sIwME~v!(hHkMq2??qOP#e;6x{1* zAVV{P?C!Y4Wk@!lCf-*ILIs_OxcF-Q-Mf^ym+RE%@792heE_;j;(Nam*>J$|x-N8y z7r*Bwo>cgY>ndo?;{={}g5_OSo)8#tzt}RS2lk{6E-*3AfO_kNqc4blKTeyg_Yi1vP5wk9fl9xzyF^b@(HZ-zH1

(4Cmqc>>C4q|+8+$tmd|yvSpGIK9vXGwW#sO3W`Hqsx^KdyY zPrYk)10SV=2E*AxD$FtW2_GXzzS~;A-&!M8UahgLG{H zaxMF>qHHoGFh#8yM)dn2FN0^2K#@o1nQ?qFbW~pBxLI=P_Tl#uL0Tp1{RB46Y%Mep z*vaB*8BSoT-)RQ?KJv6;q`sa&+V!*27b16|{qMg^$Tf{XTTG>A+0Zn+)?+gXXRHRe zM;a$~xfO0Q2%{qR7*fot*C7zzT zM>l~g>7yB5{+dCW!CPNfeovvdox3D1P1m52Gt`D`M-$OoZ(f5cDu z?uiehu)4x9ljJGH6`J?K?%g`kWb{L zrcCrSKCdXzpx=Y%JHey^?t(=iNmC_HiWwFHHn^_7WIXx@Ug@fp5!OB5-{^ z_hKu>^}CgF?CQ-IBq)pV$*Zq1ec(iO@yGb`FboWzv$yv!as2mbj0SP*lD!^FnT2?G|Ms%bhN2+IYhtn;=_J;JqTq_x{mAc z=5eJXSl=0%=QQ3kE19q-WX!|WNe7Q)qF-8@gLm=oE$pA17G>G*T` zQ%g`G{ZJ9djpi1^I*wQNW$JjWi+e=vC*EH@5_z_W*WW6ir*(LJF+n@G&yyYrIND&| zG;!Yo^C+Xe6>%MW(HG73PV9#qtBQnxLxnK8&iJ=nwhV6VP>g$dJ_L5#r%QQ0NQ7N) pm{Q~!Rv=c0&U$&d9%e+m>P#*A!AkRn+?Zbx)X&bw{L*TG{{j8WH>Cgo From 3ec43dbe6a3d59e6c46f5cd992662e878a4fcca8 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 17 Apr 2024 23:01:17 -0400 Subject: [PATCH 055/103] remove aux parameter The auxiliary parameter 'p' is no longer used in the parameter updates. --- .../nmf_framework/_utils_corrnmf.py | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/src/salamander/nmf_framework/_utils_corrnmf.py b/src/salamander/nmf_framework/_utils_corrnmf.py index 9b070c2..546574d 100644 --- a/src/salamander/nmf_framework/_utils_corrnmf.py +++ b/src/salamander/nmf_framework/_utils_corrnmf.py @@ -241,41 +241,6 @@ def update_sample_scalings( return sample_scalings -@njit -def update_p_unnormalized( - signatures_mat: np.ndarray, exposures_mat: np.ndarray -) -> np.ndarray: - """ - Compute the auxiliary parameters p according to the update rule of CorrNMF. - The normalization per mutation type and sample is not performed yet. - - Faster than np.einsum(). - - Inputs - ------ - signatures_mat : np.ndarray - shape (n_signatures, n_features) - - exposures_mat : np.ndarray - shape (n_samples, n_signatures) - - Returns - ------- - aux : np.ndarray - shape (n_features, n_signatures, n_samples) - """ - n_signatures, n_features = signatures_mat.shape - n_samples = exposures_mat.shape[0] - p_unnormalized = np.zeros((n_features, n_signatures, n_samples)) - - for v in range(n_features): - for k in range(n_signatures): - for d in range(n_samples): - p_unnormalized[v, k, d] = signatures_mat[k, v] * exposures_mat[d, k] - - return p_unnormalized - - @njit def objective_function_embedding( embedding: np.ndarray, From 6430dbee5bdf941c8b5a7f72d30b7b1c89883cd0 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 18 Apr 2024 16:21:47 -0400 Subject: [PATCH 056/103] unify parameter check --- .../nmf_framework/_utils_corrnmf.py | 14 ++----- src/salamander/nmf_framework/_utils_klnmf.py | 24 +++++++++++- src/salamander/nmf_framework/klnmf.py | 11 ++++-- src/salamander/nmf_framework/mvnmf.py | 25 +++++++----- src/salamander/nmf_framework/standard_nmf.py | 39 ++++++------------- src/salamander/utils.py | 23 +++++++++++ tests/test_klnmf.py | 3 +- tests/test_mvnmf.py | 2 +- 8 files changed, 85 insertions(+), 56 deletions(-) diff --git a/src/salamander/nmf_framework/_utils_corrnmf.py b/src/salamander/nmf_framework/_utils_corrnmf.py index 546574d..cc5fcc5 100644 --- a/src/salamander/nmf_framework/_utils_corrnmf.py +++ b/src/salamander/nmf_framework/_utils_corrnmf.py @@ -6,14 +6,14 @@ from numba import njit from scipy import optimize -from ..utils import shape_checker, type_checker +from ..utils import dict_checker, shape_checker, type_checker from ._utils_klnmf import check_given_asignatures, poisson_llh from .initialization import EPSILON if TYPE_CHECKING: from typing import Any -_PARAMETERS_NAMES = [ +GIVEN_PARAMETERS_ALLOWED = [ "asignatures", "signature_scalings", "sample_scalings", @@ -21,7 +21,6 @@ "sample_embeddings", "variance", ] -_DEFAULT_GIVEN_PARAMETERS = {parameter: None for parameter in _PARAMETERS_NAMES} @njit @@ -116,14 +115,7 @@ def check_given_parameters( dim_embeddings_model: int, ) -> dict[str, Any]: given_parameters = {} if given_parameters is None else given_parameters.copy() - type_checker("given_parameters", given_parameters, dict) - - for parameter in given_parameters: - if parameter not in _PARAMETERS_NAMES: - raise ValueError( - "The given parameters include parameters outside " - f"of {_PARAMETERS_NAMES}." - ) + dict_checker("given_parameters", given_parameters, GIVEN_PARAMETERS_ALLOWED) if "asignatures" in given_parameters: check_given_asignatures( diff --git a/src/salamander/nmf_framework/_utils_klnmf.py b/src/salamander/nmf_framework/_utils_klnmf.py index feccfcd..2ab1e4a 100644 --- a/src/salamander/nmf_framework/_utils_klnmf.py +++ b/src/salamander/nmf_framework/_utils_klnmf.py @@ -1,13 +1,19 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import numpy as np from anndata import AnnData from numba import njit from scipy.special import gammaln -from ..utils import type_checker +from ..utils import dict_checker, type_checker + +if TYPE_CHECKING: + from typing import Any EPSILON = np.finfo(np.float32).eps +GIVEN_PARAMETERS_ALLOWED = ["asignatures"] @njit(fastmath=True) @@ -197,6 +203,22 @@ def check_given_asignatures( ) +def check_given_parameters( + given_parameters: dict[str, Any] | None, + mutation_types_data: np.ndarray, + n_signatures_model: int, +) -> dict[str, Any]: + given_parameters = {} if given_parameters is None else given_parameters.copy() + dict_checker("given_parameters", given_parameters, GIVEN_PARAMETERS_ALLOWED) + + if "asignatures" in given_parameters: + check_given_asignatures( + given_parameters["asignatures"], mutation_types_data, n_signatures_model + ) + + return given_parameters + + @njit def update_W( X: np.ndarray, diff --git a/src/salamander/nmf_framework/klnmf.py b/src/salamander/nmf_framework/klnmf.py index 2180b16..c384dfd 100644 --- a/src/salamander/nmf_framework/klnmf.py +++ b/src/salamander/nmf_framework/klnmf.py @@ -85,11 +85,14 @@ def objective_function(self) -> float: def objective(self) -> Literal["minimize", "maximize"]: return "minimize" - def _update_parameters(self, given_parameters: dict[str, Any]) -> None: - given_asignatures = given_parameters["asignatures"] + def _update_parameters( + self, given_parameters: dict[str, Any] | None = None + ) -> None: + if given_parameters is None: + given_parameters = {} - if given_asignatures is not None: - n_given_signatures = given_asignatures.n_obs + if "asignatures" in given_parameters: + n_given_signatures = given_parameters["asignatures"].n_obs else: n_given_signatures = 0 diff --git a/src/salamander/nmf_framework/mvnmf.py b/src/salamander/nmf_framework/mvnmf.py index fda6644..b807e03 100644 --- a/src/salamander/nmf_framework/mvnmf.py +++ b/src/salamander/nmf_framework/mvnmf.py @@ -166,7 +166,7 @@ def _update_H(self): self.adata.X.T, self.asignatures.X.T, self.adata.obsm["exposures"].T ).T - def _update_W_unconstrained(self, n_given_signatures: int) -> np.ndarray: + def _update_W_unconstrained(self, n_given_signatures: int = 0) -> np.ndarray: return update_W_unconstrained( self.adata.X.T, self.asignatures.X.T, @@ -189,22 +189,27 @@ def _line_search(self, W_unconstrained: np.ndarray) -> None: self.asignatures.X = W.T self.adata.obsm["exposures"] = H.T - def _update_W(self, n_given_signatures: int) -> None: + def _update_W(self, n_given_signatures: int = 0) -> None: + if n_given_signatures == self.n_signatures: + return + W_unconstrained = self._update_W_unconstrained(n_given_signatures) self._line_search(W_unconstrained) - def _update_parameters(self, given_parameters: dict[str, Any]) -> None: - given_asignatures = given_parameters["asignatures"] + def _update_parameters( + self, given_parameters: dict[str, Any] | None = None + ) -> None: + if given_parameters is None: + given_parameters = {} + + self._update_H() - if given_asignatures is not None: - n_given_signatures = given_asignatures.n_obs + if "asignatures" in given_parameters: + n_given_signatures = given_parameters["asignatures"].n_obs else: n_given_signatures = 0 - self._update_H() - - if n_given_signatures < self.n_signatures: - self._update_W(n_given_signatures) + self._update_W(n_given_signatures) def _setup_fitting_parameters( self, fitting_kwargs: dict[str, Any] | None = None diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/nmf_framework/standard_nmf.py index fa4a8ca..f5cc3de 100644 --- a/src/salamander/nmf_framework/standard_nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -6,8 +6,7 @@ import anndata as ad from .. import tools as tl -from ..utils import type_checker -from ._utils_klnmf import check_given_asignatures +from ._utils_klnmf import check_given_parameters from .initialization import initialize from .signature_nmf import SignatureNMF @@ -16,9 +15,6 @@ from .signature_nmf import _Dim_reduction_methods -_PARAMETERS_NAMES = ["asignatures", "exposures"] -_DEFAULT_GIVEN_PARAMETERS = {parameter: None for parameter in _PARAMETERS_NAMES} - class StandardNMF(SignatureNMF): """ @@ -52,30 +48,19 @@ def _initialize( Any further keywords arguments to be passed to the initialization method. This includes, for example, an optional 'seed' for all stochastic methods. """ - if given_parameters is None: - given_parameters = _DEFAULT_GIVEN_PARAMETERS - given_signatures = None - else: - given_parameters = given_parameters.copy() - - type_checker("given_parameters", given_parameters, dict) - - for parameter in given_parameters: - if parameter not in _PARAMETERS_NAMES: - raise ValueError( - "The given parameters include parameters outside " - f"of {_PARAMETERS_NAMES}." - ) - - given_asignatures = given_parameters["asignatures"] + given_parameters = check_given_parameters( + given_parameters=given_parameters, + mutation_types_data=self.mutation_types, + n_signatures_model=self.n_signatures, + ) + init_kwargs = {} if init_kwargs is None else init_kwargs.copy() - if given_asignatures is not None: - check_given_asignatures( - given_asignatures, self.mutation_types, self.n_signatures - ) + if "asignatures" in given_parameters: + given_asignatures = given_parameters["asignatures"] given_signatures = given_asignatures.to_df().T + else: + given_signatures = None - init_kwargs = {} if init_kwargs is None else init_kwargs.copy() # initialize takes counts X of shape (n_features, n_samples), # and given_signatures of shape (n_features, n_given_signatures) W, H, signature_names = initialize( @@ -90,7 +75,7 @@ def _initialize( self.asignatures.var_names = self.mutation_types # keep signature annotations - if given_asignatures is not None: + if "asignatures" in given_parameters: n_given_signatures = given_asignatures.n_obs asignatures_new = self.asignatures[n_given_signatures:, :] self.asignatures = ad.concat( diff --git a/src/salamander/utils.py b/src/salamander/utils.py index 175e292..1329999 100644 --- a/src/salamander/utils.py +++ b/src/salamander/utils.py @@ -16,6 +16,29 @@ EPSILON = np.finfo(np.float32).eps +def dict_checker( + dict_name: str, dictionary: dict[Any, Any], valid_keys: list[Any] +) -> None: + """ + A helper function to test the keys of a dictionary. + + Input: + ------ + dict_name: str + The name of the dictionary + + dictionary: dict[Any, Any] + + valid_keys: list[Any] + The allowed keys of 'dictionary' + """ + type_checker(dict_name, dictionary, dict) + + for key in dictionary.keys(): + if key not in valid_keys: + raise ValueError(f"'{dict_name}' includes keys outside of {valid_keys}.") + + def shape_checker( arg_name: str, arg: np.ndarray | pd.DataFrame, allowed_shape: tuple[int, ...] ) -> None: diff --git a/tests/test_klnmf.py b/tests/test_klnmf.py index 2cba82c..820cc8e 100644 --- a/tests/test_klnmf.py +++ b/tests/test_klnmf.py @@ -6,7 +6,6 @@ from anndata import AnnData from salamander.nmf_framework import klnmf -from salamander.nmf_framework.standard_nmf import _DEFAULT_GIVEN_PARAMETERS PATH = "tests/test_data" PATH_TEST_DATA = f"{PATH}/nmf_framework/klnmf" @@ -70,7 +69,7 @@ def WH_updated(self, n_signatures): return WH_updated def test_update_parameters(self, model_init, WH_updated): - model_init._update_parameters(given_parameters=_DEFAULT_GIVEN_PARAMETERS) + model_init._update_parameters() W_updated, H_updated = WH_updated assert np.allclose(model_init.asignatures.X, W_updated.T) assert np.allclose(model_init.adata.obsm["exposures"], H_updated.T) diff --git a/tests/test_mvnmf.py b/tests/test_mvnmf.py index f07aa71..9d390e3 100644 --- a/tests/test_mvnmf.py +++ b/tests/test_mvnmf.py @@ -68,7 +68,7 @@ def H_updated(self, n_signatures): return np.load(f"{PATH_TEST_DATA}/H_updated_nsigs{n_signatures}.npy") def test_update_W(self, model_init, W_updated): - model_init._update_W(n_given_signatures=0) + model_init._update_W() assert np.allclose(model_init.asignatures.X, W_updated.T) def test_update_H(self, model_init, H_updated): From 74358a8c82bf0f655bdb7ee483ec14cfb98e4528 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 18 Apr 2024 16:24:05 -0400 Subject: [PATCH 057/103] add type --- src/salamander/nmf_framework/_utils_corrnmf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/salamander/nmf_framework/_utils_corrnmf.py b/src/salamander/nmf_framework/_utils_corrnmf.py index cc5fcc5..7b2bde4 100644 --- a/src/salamander/nmf_framework/_utils_corrnmf.py +++ b/src/salamander/nmf_framework/_utils_corrnmf.py @@ -305,7 +305,7 @@ def gradient_embedding( scaling: float, scalings_other: np.ndarray, variance: float, - summand_grad, + summand_grad: np.ndarray, add_penalty: bool = True, ) -> np.ndarray: r""" From 1b0aefbebc7f806b0c2b0c304564c409114d3079 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 18 Apr 2024 16:51:21 -0400 Subject: [PATCH 058/103] move computation Move a function to the utils module to add jit compilation. --- .../nmf_framework/_utils_corrnmf.py | 27 +++++++++++++++++++ src/salamander/nmf_framework/corrnmf_det.py | 13 ++------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/salamander/nmf_framework/_utils_corrnmf.py b/src/salamander/nmf_framework/_utils_corrnmf.py index 7b2bde4..376b0c5 100644 --- a/src/salamander/nmf_framework/_utils_corrnmf.py +++ b/src/salamander/nmf_framework/_utils_corrnmf.py @@ -40,6 +40,33 @@ def compute_exposures( ).T +@njit +def compute_aux( + data_mat: np.ndarray, signatures_mat: np.ndarray, exposures_mat: np.ndarray +) -> np.ndarray: + r""" + 'aux' is the numpy array of shape (n_signatures, n_samples) given by + aux_kd = \sum_v x_vd * p_vkd + It can be computed without explicitly storing the parameters p. + Notice that 'aux' is sufficient to update all model parameters. + There is no need to compute and store p. + + Inputs + ------ + data_mat : np.ndarray + shape (n_samples, n_features) + + signatures_mat : np.ndarray + shape (n_signatures, n_features) + + exposures_mat : np.ndarray + shape (n_samples, n_signatures) + """ + error_ratios = data_mat / (exposures_mat @ signatures_mat) + aux = exposures_mat.T * (signatures_mat @ error_ratios.T) + return aux + + def elbo_corrnmf( data_mat: np.ndarray, signatures_mat: np.ndarray, diff --git a/src/salamander/nmf_framework/corrnmf_det.py b/src/salamander/nmf_framework/corrnmf_det.py index 3e59c2b..7826b72 100644 --- a/src/salamander/nmf_framework/corrnmf_det.py +++ b/src/salamander/nmf_framework/corrnmf_det.py @@ -28,18 +28,9 @@ class CorrNMFDet(CorrNMF): """ def _compute_aux(self) -> np.ndarray: - r""" - 'aux' is the numpy array of shape (n_signatures, n_samples) given by - aux_kd = \sum_v x_vd * p_vkd - It can be computed without explicitly storing the parameters p. - Notice that 'aux' is sufficient to update all model parameters. - There is no need to compute and store p. - """ - error_ratios = self.adata.X / ( - self.adata.obsm["exposures"] @ self.asignatures.X + return _utils_corrnmf.compute_aux( + self.adata.X, self.asignatures.X, self.adata.obsm["exposures"] ) - aux = self.adata.obsm["exposures"].T * (self.asignatures.X @ error_ratios.T) - return aux def update_sample_scalings( self, given_parameters: dict[str, Any] | None = None From d824f61b3181bd953ed4a99d528f61e5296cda07 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 19 Apr 2024 16:52:33 -0400 Subject: [PATCH 059/103] update lock file --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4219545..5a52178 100644 --- a/poetry.lock +++ b/poetry.lock @@ -195,13 +195,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] From e7687b1478a968f50df34cef31ef5e817ac68b83 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 19 Apr 2024 17:15:18 -0400 Subject: [PATCH 060/103] improve initialization Rewrite initialization module around AnnData --- src/salamander/nmf_framework/corrnmf.py | 20 +- .../nmf_framework/initialization.py | 238 ++++++++++++------ src/salamander/nmf_framework/standard_nmf.py | 25 +- 3 files changed, 169 insertions(+), 114 deletions(-) diff --git a/src/salamander/nmf_framework/corrnmf.py b/src/salamander/nmf_framework/corrnmf.py index 6d809ca..f292832 100644 --- a/src/salamander/nmf_framework/corrnmf.py +++ b/src/salamander/nmf_framework/corrnmf.py @@ -134,28 +134,16 @@ def _initialize( if "asignatures" in given_parameters: given_asignatures = given_parameters["asignatures"] - given_signatures = given_asignatures.to_df().T else: - given_signatures = None + given_asignatures = None - W, _, signature_names = initialize( - self.adata.X.T, + self.asignatures, _ = initialize( + self.adata, self.n_signatures, self.init_method, - given_signatures, + given_asignatures, **init_kwargs, ) - self.asignatures = ad.AnnData(W.T) - self.asignatures.obs_names = signature_names - self.asignatures.var_names = self.mutation_types - - # keep signature annotations - if "asignatures" in given_parameters: - n_given_signatures = given_asignatures.n_obs - asignatures_new = self.asignatures[n_given_signatures:, :] - self.asignatures = ad.concat( - [given_asignatures, asignatures_new], join="outer" - ) if "signature_scalings" in given_parameters: self.asignatures.obs["scalings"] = given_parameters["signature_scalings"] diff --git a/src/salamander/nmf_framework/initialization.py b/src/salamander/nmf_framework/initialization.py index 241dd19..1e2e1d9 100644 --- a/src/salamander/nmf_framework/initialization.py +++ b/src/salamander/nmf_framework/initialization.py @@ -6,8 +6,8 @@ from typing import Literal, get_args +import anndata as ad import numpy as np -import pandas as pd from sklearn.decomposition import _nmf as sknmf from ..utils import normalize_WH, shape_checker, type_checker, value_checker @@ -16,7 +16,6 @@ _Init_methods = Literal[ "custom", "flat", - "hierarchical_cluster", "nndsvd", "nndsvda", "nndsvdar", @@ -27,57 +26,69 @@ def init_custom( - X: np.ndarray, n_signatures: int, W_custom: np.ndarray, H_custom: np.ndarray + data_mat: np.ndarray, + n_signatures: int, + signatures_mat: np.ndarray, + exposures_mat: np.ndarray, ) -> tuple[np.ndarray, np.ndarray]: """ Perform type and shape checks on custom signature and exposure matrix initializations. + + Inputs: + ------- + data_mat: np.ndarray + shape (n_samples, n_features) + + n_signatures: int + + signatures_mat: np.ndarray + shape (n_signatures, n_features) + + exposures_mat: np.ndarray + shape (n_samples, n_signatures) """ - type_checker("W_custom", W_custom, np.ndarray) - type_checker("H_custom", H_custom, np.ndarray) - n_features, n_samples = X.shape - shape_checker("W_custom", W_custom, (n_features, n_signatures)) - shape_checker("H_custom", H_custom, (n_signatures, n_samples)) - return W_custom, H_custom + type_checker("signatures_mat", signatures_mat, np.ndarray) + type_checker("exposures_mat", exposures_mat, np.ndarray) + n_samples, n_features = data_mat.shape + shape_checker("signatures_mat", signatures_mat, (n_signatures, n_features)) + shape_checker("exposures_mat", exposures_mat, (n_samples, n_signatures)) + return signatures_mat, exposures_mat -def init_flat(X: np.ndarray, n_signatures: int) -> tuple[np.ndarray, np.ndarray]: +def init_flat(data_mat: np.ndarray, n_signatures: int) -> tuple[np.ndarray, np.ndarray]: """ Initialize the signature and exposure matrices with one float, respectively. """ - n_features, n_samples = X.shape - scaling = np.mean(np.sum(X, axis=0)) - W = np.full((n_features, n_signatures), 1 / n_features) - H = np.full((n_signatures, n_samples), scaling / n_signatures) - return W, H + n_samples, n_features = data_mat.shape + scaling = np.mean(np.sum(data_mat, axis=1)) + signatures_mat = np.full((n_signatures, n_features), 1 / n_features) + exposures_mat = np.full((n_samples, n_signatures), scaling / n_signatures) + return signatures_mat, exposures_mat def init_nndsvd( - X: np.ndarray, + data_mat: np.ndarray, n_signatures: int, - init: Literal["nndsvd", "nndsvda", "nndsvdar"], + init: Literal["nndsvd", "nndsvda", "nndsvdar"] = "nndsvd", seed: int | None = None, ) -> tuple[np.ndarray, np.ndarray]: """ A wrapper around the non-negative double singular value decomposition (NNDSVD) initialization methods "nndsvd", "nndsvda" and "nndsvdar" from scikit-learn. - - Inputs: - ------ - init: str - One of "nndsvd", "nndsvda" and "nndsvdar" """ if seed is not None: np.random.seed(seed) # pylint: disable-next=W0212 - W, H = sknmf._initialize_nmf(X, n_signatures, init=init) - - return W, H + exposures_mat, signatures_mat = sknmf._initialize_nmf( + data_mat, n_signatures, init=init + ) + return signatures_mat, exposures_mat def init_random( - X: np.ndarray, n_signatures: int, seed: int | None = None + data_mat: np.ndarray, n_signatures: int, seed: int | None = None ) -> tuple[np.ndarray, np.ndarray]: """ Initialize each signature by drawing from the uniform @@ -90,15 +101,17 @@ def init_random( if seed is not None: np.random.seed(seed) - n_features, n_samples = X.shape - W = np.random.dirichlet(np.ones(n_features), size=n_signatures).T - scaling = np.sum(X, axis=0) - H = scaling * np.random.dirichlet(np.ones(n_signatures), size=n_samples).T - return W, H + n_samples, n_features = data_mat.shape + signatures_mat = np.random.dirichlet(np.ones(n_features), size=n_signatures) + scaling = np.sum(data_mat, axis=1) + exposures_mat = scaling[:, np.newaxis] * np.random.dirichlet( + np.ones(n_signatures), size=n_samples + ) + return signatures_mat, exposures_mat def init_separableNMF( - X: np.ndarray, n_signatures: int, seed: int | None = None + data_mat: np.ndarray, n_signatures: int, seed: int | None = None ) -> tuple[np.ndarray, np.ndarray]: r""" This code is following Algorithm 1 from "Fast and Robust Recursive @@ -108,46 +121,46 @@ def init_separableNMF( Assumption 2 from the paper. """ signature_indices = np.empty(n_signatures, dtype=int) - R = X / np.sum(X, axis=0) + R = data_mat.T / np.sum(data_mat.T, axis=0) for k in range(n_signatures): column_norms = np.sum(R**2, axis=0) kstar = np.argmax(column_norms) u = R[:, kstar] - R = (np.identity(X.shape[0]) - np.outer(u, u) / column_norms[kstar]) @ R + R = (np.identity(R.shape[0]) - np.outer(u, u) / column_norms[kstar]) @ R signature_indices[k] = kstar - W = X[:, signature_indices].astype(float) - _, H = init_random(X, n_signatures, seed=seed) - return W, H + signatures_mat = data_mat[signature_indices, :].astype(float) + signatures_mat /= signatures_mat.sum(axis=1)[:, np.newaxis] + _, exposures_mat = init_random(data_mat, n_signatures, seed=seed) + return signatures_mat, exposures_mat -def initialize( - X: np.ndarray, +def initialize_mat( + data_mat: np.ndarray, n_signatures: int, init_method: _Init_methods = "nndsvd", - given_signatures: pd.DataFrame | None = None, + given_signatures_mat: np.ndarray | None = None, **kwargs, -) -> tuple[np.ndarray, np.ndarray, np.ndarray]: +) -> [np.ndarray, np.ndarray]: """ Initialize the signature and exposure matrices. Inputs ------ - X : np.ndarray - count matrix + data_mat : np.ndarray + shape (n_samples, n_features) n_signatures : int - number of signatures init_method : str - initialization method. One of 'custom', 'flat', 'hierarchical_cluster', + initialization method. One of 'custom', 'flat', 'nndsvd', 'nndsvda', 'nndsvdar', 'random', 'separableNMF'. - given_signatures : pd.Dataframe, optional + given_signatures_mat : np.ndarray, optional At most 'n_signatures' many signatures can be provided to overwrite some of the initialized signatures. This does not - change the initialized exposurses. + change the initialized exposures. kwargs : dict Any keyword arguments to be passed to the initialization method. @@ -156,47 +169,116 @@ def initialize( Returns ------- - W : np.ndarray - signature matrix - - H : np.ndarray - exposure matrix + signatures_mat : np.ndarray + shape (n_signatures, n_features) - signature_names : np.ndarray - The signature names. By default, the signatures are named - 'Sigk', where 'k' is one plus the index of the signature. - If 'given_signatures' are provided, the names are adjusted - accordingly. + exposures_mat : np.ndarray + shape (n_samples, n_signatures) """ value_checker("init_method", init_method, _INIT_METHODS) if init_method == "custom": - W, H = init_custom(X, n_signatures, **kwargs) - + matrices = init_custom(data_mat, n_signatures, **kwargs) elif init_method == "flat": - W, H = init_flat(X, n_signatures) - + matrices = init_flat(data_mat, n_signatures) elif init_method in ["nndsvd", "nndsvda", "nndsvdar"]: # mypy does not recognize that init_method is compatible # with Literal["nndsvd", "nndsvda", "nndsvdar"] - W, H = init_nndsvd(X, n_signatures, init=init_method, **kwargs) # type: ignore[arg-type] # noqa: E501 - + matrices = init_nndsvd( + data_mat, n_signatures, init=init_method, **kwargs # type: ignore[arg-type] # noqa: E501 + ) elif init_method == "random": - W, H = init_random(X, n_signatures, **kwargs) - + matrices = init_random(data_mat, n_signatures, **kwargs) else: - W, H = init_separableNMF(X, n_signatures, **kwargs) - - if given_signatures is not None: - n_given_signatures = len(given_signatures.columns) - W[:, :n_given_signatures] = given_signatures.copy().values - given_signatures_names = given_signatures.columns.to_numpy(dtype=str) - n_new_signatures = n_signatures - n_given_signatures - new_signatures_names = np.array([f"Sig{k+1}" for k in range(n_new_signatures)]) - signature_names = np.concatenate([given_signatures_names, new_signatures_names]) - else: - signature_names = np.array([f"Sig{k+1}" for k in range(n_signatures)]) + matrices = init_separableNMF(data_mat, n_signatures, **kwargs) + + signatures_mat, exposures_mat = matrices + + if given_signatures_mat is not None: + type_checker("given_signatures_mat", given_signatures_mat, np.ndarray) + given_n_signatures, given_n_features = given_signatures_mat.shape - W, H = normalize_WH(W, H) + if given_n_features != data_mat.shape[1]: + raise ValueError( + "The given signature matrix has a different number of features " + "than the data." + ) + if given_n_signatures > n_signatures: + raise ValueError("The given signature matrix contains too many signatures.") + + signatures_mat[:given_n_signatures, :] = given_signatures_mat.copy() + + W, H = normalize_WH(signatures_mat.T, exposures_mat.T) W, H = W.clip(EPSILON), H.clip(EPSILON) - return W, H, signature_names + signatures_mat, exposures_mat = W.T, H.T + return signatures_mat, exposures_mat + + +def initialize( + adata: ad.AnnData, + n_signatures: int, + init_method: _Init_methods = "nndsvd", + given_asignatures: ad.AnnData | None = None, + **kwargs, +) -> tuple[ad.AnnData, np.ndarray]: + """ + Initialize the signature ann data object and the exposure matrix. + + Inputs + ------ + adata : ad.AnnData + + n_signatures : int + + init_method : str + initialization method. One of 'custom', 'flat', + 'nndsvd', 'nndsvda', 'nndsvdar', 'random', 'separableNMF'. + + given_asignatures : ad.AnnData, optional + At most 'n_signatures' many signatures can be provided to + overwrite some of the initialized signatures. This does not + change the initialized exposures. + + kwargs : dict + Any keyword arguments to be passed to the initialization method. + This includes, for example, a possible 'seed' keyword argument + for all stochastic methods. + + Returns + ------- + asignatures : ad.AnnData + Annotated signature matrix of shape (n_signatures, n_features) + + exposures_mat : np.ndarray + shape (n_samples, n_signatures) + """ + if given_asignatures is not None: + if given_asignatures.n_vars != adata.n_vars: + raise ValueError( + "The given signatures have a different number of features " + "than the data." + ) + if not all(given_asignatures.var_names == adata.var_names): + raise ValueError( + "The features of the given signatures and the data are not identical." + ) + given_signatures_mat = given_asignatures.X + else: + given_signatures_mat = None + + signatures_mat, exposures_mat = initialize_mat( + adata.X, n_signatures, init_method, given_signatures_mat, **kwargs + ) + asignatures = ad.AnnData(signatures_mat) + asignatures.var_names = adata.var_names + asignatures.obs_names = [f"Sig{k+1}" for k in range(n_signatures)] + + # keep signature annotations + if given_asignatures is not None: + n_given_signatures = given_asignatures.n_obs + asignatures.obs_names = np.roll(asignatures.obs_names, n_given_signatures) + asignatures = ad.concat( + [given_asignatures, asignatures[n_given_signatures:, :]], join="outer" + ) + + return asignatures, exposures_mat diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/nmf_framework/standard_nmf.py index f5cc3de..6d82e43 100644 --- a/src/salamander/nmf_framework/standard_nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -57,32 +57,17 @@ def _initialize( if "asignatures" in given_parameters: given_asignatures = given_parameters["asignatures"] - given_signatures = given_asignatures.to_df().T else: - given_signatures = None + given_asignatures = None - # initialize takes counts X of shape (n_features, n_samples), - # and given_signatures of shape (n_features, n_given_signatures) - W, H, signature_names = initialize( - self.adata.X.T, + self.asignatures, exposures_mat = initialize( + self.adata, self.n_signatures, self.init_method, - given_signatures, + given_asignatures, **init_kwargs, ) - self.asignatures = ad.AnnData(W.T) - self.asignatures.obs_names = signature_names - self.asignatures.var_names = self.mutation_types - - # keep signature annotations - if "asignatures" in given_parameters: - n_given_signatures = given_asignatures.n_obs - asignatures_new = self.asignatures[n_given_signatures:, :] - self.asignatures = ad.concat( - [given_asignatures, asignatures_new], join="outer" - ) - - self.adata.obsm["exposures"] = H.T + self.adata.obsm["exposures"] = exposures_mat return given_parameters def reduce_dimension_embeddings( From ae49cc83cc7e4e2ae3a6a78e579cc9d03eeda0f7 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 19 Apr 2024 17:18:00 -0400 Subject: [PATCH 061/103] fix type --- src/salamander/nmf_framework/initialization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/salamander/nmf_framework/initialization.py b/src/salamander/nmf_framework/initialization.py index 1e2e1d9..474f410 100644 --- a/src/salamander/nmf_framework/initialization.py +++ b/src/salamander/nmf_framework/initialization.py @@ -142,7 +142,7 @@ def initialize_mat( init_method: _Init_methods = "nndsvd", given_signatures_mat: np.ndarray | None = None, **kwargs, -) -> [np.ndarray, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray]: """ Initialize the signature and exposure matrices. From 5e370600797a52f4c8ed0f699f7241c776b1d925 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 19 Apr 2024 17:44:56 -0400 Subject: [PATCH 062/103] update pre-commit add mypy as a pre-commit hook --- .pre-commit-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07f9f31..ab35636 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,3 +22,7 @@ repos: hooks: - id: isort args: ["--profile", "black"] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.9.0 + hooks: + - id: mypy From 71abdb893b2daf5bd06075a75d38f2999e4b85fe Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 19 Apr 2024 17:50:07 -0400 Subject: [PATCH 063/103] update tox The new implementation of correlated NMF does not use ambiguous variable names --- tox.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/tox.ini b/tox.ini index 51cdaae..6b6cf36 100644 --- a/tox.ini +++ b/tox.ini @@ -19,9 +19,6 @@ commands = extend-ignore = # see https://github.com/psf/black/issues/315 E203 -per-file-ignores = - # ignore ambiguous variable name 'l' - src/salamander/nmf_framework/corrnmf_det.py: E741 [testenv:pylint] deps = From 04eae8cceb7a1de560abc01c293f72eebff272d6 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Sat, 20 Apr 2024 16:07:24 -0400 Subject: [PATCH 064/103] improve init sample-specific 'flat' initialization --- src/salamander/nmf_framework/initialization.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/salamander/nmf_framework/initialization.py b/src/salamander/nmf_framework/initialization.py index 474f410..6d73cbc 100644 --- a/src/salamander/nmf_framework/initialization.py +++ b/src/salamander/nmf_framework/initialization.py @@ -60,10 +60,10 @@ def init_flat(data_mat: np.ndarray, n_signatures: int) -> tuple[np.ndarray, np.n """ Initialize the signature and exposure matrices with one float, respectively. """ - n_samples, n_features = data_mat.shape - scaling = np.mean(np.sum(data_mat, axis=1)) + n_features = data_mat.shape[1] signatures_mat = np.full((n_signatures, n_features), 1 / n_features) - exposures_mat = np.full((n_samples, n_signatures), scaling / n_signatures) + exposures = np.sum(data_mat, axis=1) / n_signatures + exposures_mat = np.tile(exposures, (n_signatures, 1)).T return signatures_mat, exposures_mat From 481ad1cc097d45af6d2303aa01aba29e65e770b5 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Sat, 20 Apr 2024 16:17:40 -0400 Subject: [PATCH 065/103] improve init simplified variable names --- .../nmf_framework/initialization.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/salamander/nmf_framework/initialization.py b/src/salamander/nmf_framework/initialization.py index 6d73cbc..c54c3ac 100644 --- a/src/salamander/nmf_framework/initialization.py +++ b/src/salamander/nmf_framework/initialization.py @@ -70,7 +70,7 @@ def init_flat(data_mat: np.ndarray, n_signatures: int) -> tuple[np.ndarray, np.n def init_nndsvd( data_mat: np.ndarray, n_signatures: int, - init: Literal["nndsvd", "nndsvda", "nndsvdar"] = "nndsvd", + method: Literal["nndsvd", "nndsvda", "nndsvdar"] = "nndsvd", seed: int | None = None, ) -> tuple[np.ndarray, np.ndarray]: """ @@ -82,7 +82,7 @@ def init_nndsvd( # pylint: disable-next=W0212 exposures_mat, signatures_mat = sknmf._initialize_nmf( - data_mat, n_signatures, init=init + data_mat, n_signatures, init=method ) return signatures_mat, exposures_mat @@ -139,7 +139,7 @@ def init_separableNMF( def initialize_mat( data_mat: np.ndarray, n_signatures: int, - init_method: _Init_methods = "nndsvd", + method: _Init_methods = "nndsvd", given_signatures_mat: np.ndarray | None = None, **kwargs, ) -> tuple[np.ndarray, np.ndarray]: @@ -153,7 +153,7 @@ def initialize_mat( n_signatures : int - init_method : str + method : str initialization method. One of 'custom', 'flat', 'nndsvd', 'nndsvda', 'nndsvdar', 'random', 'separableNMF'. @@ -175,19 +175,19 @@ def initialize_mat( exposures_mat : np.ndarray shape (n_samples, n_signatures) """ - value_checker("init_method", init_method, _INIT_METHODS) + value_checker("method", method, _INIT_METHODS) - if init_method == "custom": + if method == "custom": matrices = init_custom(data_mat, n_signatures, **kwargs) - elif init_method == "flat": + elif method == "flat": matrices = init_flat(data_mat, n_signatures) - elif init_method in ["nndsvd", "nndsvda", "nndsvdar"]: - # mypy does not recognize that init_method is compatible + elif method in ["nndsvd", "nndsvda", "nndsvdar"]: + # mypy does not recognize that 'method' is compatible # with Literal["nndsvd", "nndsvda", "nndsvdar"] matrices = init_nndsvd( - data_mat, n_signatures, init=init_method, **kwargs # type: ignore[arg-type] # noqa: E501 + data_mat, n_signatures, method=method, **kwargs # type: ignore[arg-type] # noqa: E501 ) - elif init_method == "random": + elif method == "random": matrices = init_random(data_mat, n_signatures, **kwargs) else: matrices = init_separableNMF(data_mat, n_signatures, **kwargs) @@ -217,7 +217,7 @@ def initialize_mat( def initialize( adata: ad.AnnData, n_signatures: int, - init_method: _Init_methods = "nndsvd", + method: _Init_methods = "nndsvd", given_asignatures: ad.AnnData | None = None, **kwargs, ) -> tuple[ad.AnnData, np.ndarray]: @@ -230,7 +230,7 @@ def initialize( n_signatures : int - init_method : str + method : str initialization method. One of 'custom', 'flat', 'nndsvd', 'nndsvda', 'nndsvdar', 'random', 'separableNMF'. @@ -267,7 +267,7 @@ def initialize( given_signatures_mat = None signatures_mat, exposures_mat = initialize_mat( - adata.X, n_signatures, init_method, given_signatures_mat, **kwargs + adata.X, n_signatures, method, given_signatures_mat, **kwargs ) asignatures = ad.AnnData(signatures_mat) asignatures.var_names = adata.var_names From 6b4dc4ce97deb1c34cb244ff40c9c13dc9118fdc Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Sun, 21 Apr 2024 23:18:06 -0400 Subject: [PATCH 066/103] add tests --- .../nmf_framework/initialization/data_mat.npy | Bin 0 -> 224 bytes .../initialization/exposures_mat_flat.npy | Bin 0 -> 192 bytes .../exposures_mat_nndsvd_seed1.npy | Bin 0 -> 192 bytes .../exposures_mat_nndsvda_seed1.npy | Bin 0 -> 192 bytes .../exposures_mat_nndsvdar_seed1.npy | Bin 0 -> 192 bytes .../exposures_mat_random_seed1.npy | Bin 0 -> 192 bytes .../exposures_mat_separableNMF_seed1.npy | Bin 0 -> 192 bytes .../initialization/signatures_mat_flat.npy | Bin 0 -> 176 bytes .../signatures_mat_nndsvd_seed1.npy | Bin 0 -> 176 bytes .../signatures_mat_nndsvda_seed1.npy | Bin 0 -> 176 bytes .../signatures_mat_nndsvdar_seed1.npy | Bin 0 -> 176 bytes .../signatures_mat_random_seed1.npy | Bin 0 -> 176 bytes .../signatures_mat_separableNMF_seed1.npy | Bin 0 -> 176 bytes tests/test_initialization.py | 67 ++++++++++++++++++ 14 files changed, 67 insertions(+) create mode 100644 tests/test_data/nmf_framework/initialization/data_mat.npy create mode 100644 tests/test_data/nmf_framework/initialization/exposures_mat_flat.npy create mode 100644 tests/test_data/nmf_framework/initialization/exposures_mat_nndsvd_seed1.npy create mode 100644 tests/test_data/nmf_framework/initialization/exposures_mat_nndsvda_seed1.npy create mode 100644 tests/test_data/nmf_framework/initialization/exposures_mat_nndsvdar_seed1.npy create mode 100644 tests/test_data/nmf_framework/initialization/exposures_mat_random_seed1.npy create mode 100644 tests/test_data/nmf_framework/initialization/exposures_mat_separableNMF_seed1.npy create mode 100644 tests/test_data/nmf_framework/initialization/signatures_mat_flat.npy create mode 100644 tests/test_data/nmf_framework/initialization/signatures_mat_nndsvd_seed1.npy create mode 100644 tests/test_data/nmf_framework/initialization/signatures_mat_nndsvda_seed1.npy create mode 100644 tests/test_data/nmf_framework/initialization/signatures_mat_nndsvdar_seed1.npy create mode 100644 tests/test_data/nmf_framework/initialization/signatures_mat_random_seed1.npy create mode 100644 tests/test_data/nmf_framework/initialization/signatures_mat_separableNMF_seed1.npy create mode 100644 tests/test_initialization.py diff --git a/tests/test_data/nmf_framework/initialization/data_mat.npy b/tests/test_data/nmf_framework/initialization/data_mat.npy new file mode 100644 index 0000000000000000000000000000000000000000..e146e1f6b1c0ff2d6e9431dec86497c06d3c6fcd GIT binary patch literal 224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC!@qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yIts>`ItsN4WCJcn1_)q+(#%kr1xm9*X*MX$4y8GuG$)kig3{bjng>b) E0I{zd!vFvP literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_flat.npy b/tests/test_data/nmf_framework/initialization/exposures_mat_flat.npy new file mode 100644 index 0000000000000000000000000000000000000000..f8b9e22b53ee7e03e00b82bf99348626fbc81d4d GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u nBo?Fsxf&)q3Pzeb3bhL411<(I;BbJ@a!^_WLN^#d`MBi)h72D< literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_nndsvd_seed1.npy b/tests/test_data/nmf_framework/initialization/exposures_mat_nndsvd_seed1.npy new file mode 100644 index 0000000000000000000000000000000000000000..360593ed7afd3bfb0bce9ab199d4d84d0c743b69 GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItoUbItsN4WCJdjJ+u67?2>TUK1Jw+H|sNdwMed5PXj#%bID}Jti#vr i#a1PIPG+=lxH(Zk>-gd6_Gg3@KQ)v(I52=ggB<`|axy^x literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_nndsvda_seed1.npy b/tests/test_data/nmf_framework/initialization/exposures_mat_nndsvda_seed1.npy new file mode 100644 index 0000000000000000000000000000000000000000..f2323c488892c2b372a9dd54b0f4b42e0e1e72cf GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItoUbItsN4WCJdjJ+u67?2>TUzAxZ{`({-KwMed5PXj#%r!6x-EzA>h m5L=b(IhoPI!F98or%U#A`!m9dpBhRX92z*bS}uDS8b9?6&XYLQ&Ao(6gjR}{_Fi$2}7 m7h9F=IhoPI;UeSyfK_6%?av4+erhOnaL`y;xAEWdP4)n^={HdT literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_random_seed1.npy b/tests/test_data/nmf_framework/initialization/exposures_mat_random_seed1.npy new file mode 100644 index 0000000000000000000000000000000000000000..9139788a0a9bed27ef7084e1685a58f1ae31f52c GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItoUbItsN4WCJd@1GR~+>HqED_b*tkrzYUA#>T41e2cilnum|vo~SB0 moOu$dmbgjI;cQ#xw)9{Fhvk?1{g*%Bbm(E_6MKEw)&T(L0XO0R literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_separableNMF_seed1.npy b/tests/test_data/nmf_framework/initialization/exposures_mat_separableNMF_seed1.npy new file mode 100644 index 0000000000000000000000000000000000000000..eef6dabc1d0f8566746f6f0639f8eeb30186f9de GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItoUbItsN4WCJdj1GR~+>HqED_b*tkrzYUA+QzELe2cilnum|vo~SB0 moO%+embgjI;cQ#xw)9{FhozVM{g*%Bbm(E_6MKEw)&T(J@i*H5 literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_flat.npy b/tests/test_data/nmf_framework/initialization/signatures_mat_flat.npy new file mode 100644 index 0000000000000000000000000000000000000000..bcb3ae13807391c05de6902ffed4090cb3827eaa GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u dBo?Fsxf(_~3dWi`3bhL41FldoxN1)<4FD)%EwKOq literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_nndsvd_seed1.npy b/tests/test_data/nmf_framework/initialization/signatures_mat_nndsvd_seed1.npy new file mode 100644 index 0000000000000000000000000000000000000000..22a71873c65f1536c7bba24fabadeb8c3e4bf315 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3dWi`3bhL41FoJ=-77`*T(V~Xg9f{xP!PBZ=PT}zW1RNxI+Xvx9suBL BDI@>@ literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_nndsvda_seed1.npy b/tests/test_data/nmf_framework/initialization/signatures_mat_nndsvda_seed1.npy new file mode 100644 index 0000000000000000000000000000000000000000..0aaf6e38293fcf14326d4843eeea22b7902e7ea4 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3dWi`3bhL41FoJ=-77`*T(aL^QS_?d$6foNP!PBZ=PT}zW1RNxx;?v9 K&YB9F4fX)$vNNLq literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_nndsvdar_seed1.npy b/tests/test_data/nmf_framework/initialization/signatures_mat_nndsvdar_seed1.npy new file mode 100644 index 0000000000000000000000000000000000000000..840aced8e7b8d5dcd943329abb5df3a78a74c0de GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3dWi`3bhL41FoJ=-77`*T(aNFc+g2ovfn-^6a=o?tH1YZ&}!Rlued{w SaoW4<_9F9MyIm7{V-Em#5iqj= literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_random_seed1.npy b/tests/test_data/nmf_framework/initialization/signatures_mat_random_seed1.npy new file mode 100644 index 0000000000000000000000000000000000000000..a155c13aec83a419673d3f491e9a65d492a4e23b GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3dWi`3bhL41FjYq>ko2Vm+iBMz)H UuhHdx+Ad&Ue*eQOX4ND10EJI6i~s-t literal 0 HcmV?d00001 diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_separableNMF_seed1.npy b/tests/test_data/nmf_framework/initialization/signatures_mat_separableNMF_seed1.npy new file mode 100644 index 0000000000000000000000000000000000000000..9aa9e4954b40a8b32cba59de976535a6229b505e GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3dWi`3bhL41FldoIBK6d+1Ko5%4K^9|0 Date: Tue, 23 Apr 2024 16:30:08 -0400 Subject: [PATCH 067/103] update lock file --- poetry.lock | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5a52178..24f7585 100644 --- a/poetry.lock +++ b/poetry.lock @@ -383,13 +383,13 @@ numpy = ">=1.17.3" [[package]] name = "identify" -version = "2.5.35" +version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [package.extras] @@ -955,28 +955,29 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1423,13 +1424,13 @@ tbb = ["tbb (>=2019.0)"] [[package]] name = "virtualenv" -version = "20.25.3" +version = "20.26.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.3-py3-none-any.whl", hash = "sha256:8aac4332f2ea6ef519c648d0bc48a5b1d324994753519919bddbb1aff25a104e"}, - {file = "virtualenv-20.25.3.tar.gz", hash = "sha256:7bb554bbdfeaacc3349fa614ea5bff6ac300fc7c335e9facf3a3bcfc703f45be"}, + {file = "virtualenv-20.26.0-py3-none-any.whl", hash = "sha256:0846377ea76e818daaa3e00a4365c018bc3ac9760cbb3544de542885aad61fb3"}, + {file = "virtualenv-20.26.0.tar.gz", hash = "sha256:ec25a9671a5102c8d2657f62792a27b48f016664c6873f6beed3800008577210"}, ] [package.dependencies] From ec58668ce30c3c30015e57d73a92a51dcbc820a9 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 23 Apr 2024 16:31:01 -0400 Subject: [PATCH 068/103] restructure initialize --- src/salamander/initialization/__init__.py | 1 + .../initialize.py} | 137 ++---------------- src/salamander/initialization/methods.py | 135 +++++++++++++++++ .../nmf_framework/_utils_corrnmf.py | 2 +- src/salamander/nmf_framework/corrnmf.py | 4 +- src/salamander/nmf_framework/corrnmf_det.py | 2 +- src/salamander/nmf_framework/klnmf.py | 2 +- src/salamander/nmf_framework/mvnmf.py | 4 +- src/salamander/nmf_framework/signature_nmf.py | 5 +- src/salamander/nmf_framework/standard_nmf.py | 2 +- tests/test_initialization.py | 6 +- 11 files changed, 161 insertions(+), 139 deletions(-) create mode 100644 src/salamander/initialization/__init__.py rename src/salamander/{nmf_framework/initialization.py => initialization/initialize.py} (55%) create mode 100644 src/salamander/initialization/methods.py diff --git a/src/salamander/initialization/__init__.py b/src/salamander/initialization/__init__.py new file mode 100644 index 0000000..e16c76d --- /dev/null +++ b/src/salamander/initialization/__init__.py @@ -0,0 +1 @@ +"" diff --git a/src/salamander/nmf_framework/initialization.py b/src/salamander/initialization/initialize.py similarity index 55% rename from src/salamander/nmf_framework/initialization.py rename to src/salamander/initialization/initialize.py index c54c3ac..27e0ec7 100644 --- a/src/salamander/nmf_framework/initialization.py +++ b/src/salamander/initialization/initialize.py @@ -1,139 +1,24 @@ """ -Initialization methods for non-negative matrix factorization (NMF) +Initialization methods for non-negative matrix factorization (NMF) models. """ from __future__ import annotations -from typing import Literal, get_args - import anndata as ad import numpy as np -from sklearn.decomposition import _nmf as sknmf -from ..utils import normalize_WH, shape_checker, type_checker, value_checker +from ..utils import normalize_WH, type_checker, value_checker +from .methods import ( + _INIT_METHODS, + _Init_methods, + init_custom, + init_flat, + init_nndsvd, + init_random, + init_separableNMF, +) EPSILON = np.finfo(np.float32).eps -_Init_methods = Literal[ - "custom", - "flat", - "nndsvd", - "nndsvda", - "nndsvdar", - "random", - "separableNMF", -] -_INIT_METHODS = get_args(_Init_methods) - - -def init_custom( - data_mat: np.ndarray, - n_signatures: int, - signatures_mat: np.ndarray, - exposures_mat: np.ndarray, -) -> tuple[np.ndarray, np.ndarray]: - """ - Perform type and shape checks on custom signature and - exposure matrix initializations. - - Inputs: - ------- - data_mat: np.ndarray - shape (n_samples, n_features) - - n_signatures: int - - signatures_mat: np.ndarray - shape (n_signatures, n_features) - - exposures_mat: np.ndarray - shape (n_samples, n_signatures) - """ - type_checker("signatures_mat", signatures_mat, np.ndarray) - type_checker("exposures_mat", exposures_mat, np.ndarray) - n_samples, n_features = data_mat.shape - shape_checker("signatures_mat", signatures_mat, (n_signatures, n_features)) - shape_checker("exposures_mat", exposures_mat, (n_samples, n_signatures)) - return signatures_mat, exposures_mat - - -def init_flat(data_mat: np.ndarray, n_signatures: int) -> tuple[np.ndarray, np.ndarray]: - """ - Initialize the signature and exposure matrices with one float, respectively. - """ - n_features = data_mat.shape[1] - signatures_mat = np.full((n_signatures, n_features), 1 / n_features) - exposures = np.sum(data_mat, axis=1) / n_signatures - exposures_mat = np.tile(exposures, (n_signatures, 1)).T - return signatures_mat, exposures_mat - - -def init_nndsvd( - data_mat: np.ndarray, - n_signatures: int, - method: Literal["nndsvd", "nndsvda", "nndsvdar"] = "nndsvd", - seed: int | None = None, -) -> tuple[np.ndarray, np.ndarray]: - """ - A wrapper around the non-negative double singular value decomposition (NNDSVD) - initialization methods "nndsvd", "nndsvda" and "nndsvdar" from scikit-learn. - """ - if seed is not None: - np.random.seed(seed) - - # pylint: disable-next=W0212 - exposures_mat, signatures_mat = sknmf._initialize_nmf( - data_mat, n_signatures, init=method - ) - return signatures_mat, exposures_mat - - -def init_random( - data_mat: np.ndarray, n_signatures: int, seed: int | None = None -) -> tuple[np.ndarray, np.ndarray]: - """ - Initialize each signature by drawing from the uniform - distribution on the simplex. - Initialize the exposures of each sample as a scaled sample - from the uniform distribution on a simplex. - The scaling is chosen such that the expected total exposure is equal to - the column sum of that sample in the count matrix X. - """ - if seed is not None: - np.random.seed(seed) - - n_samples, n_features = data_mat.shape - signatures_mat = np.random.dirichlet(np.ones(n_features), size=n_signatures) - scaling = np.sum(data_mat, axis=1) - exposures_mat = scaling[:, np.newaxis] * np.random.dirichlet( - np.ones(n_signatures), size=n_samples - ) - return signatures_mat, exposures_mat - - -def init_separableNMF( - data_mat: np.ndarray, n_signatures: int, seed: int | None = None -) -> tuple[np.ndarray, np.ndarray]: - r""" - This code is following Algorithm 1 from "Fast and Robust Recursive - Algorithms for Separable Nonnegative Matrix Factorization" - (Gillis and Vavasis, 2013), with the canonical choice of - f(x) = \| x \|_2^2 as the strongly convex function f satisfying - Assumption 2 from the paper. - """ - signature_indices = np.empty(n_signatures, dtype=int) - R = data_mat.T / np.sum(data_mat.T, axis=0) - - for k in range(n_signatures): - column_norms = np.sum(R**2, axis=0) - kstar = np.argmax(column_norms) - u = R[:, kstar] - R = (np.identity(R.shape[0]) - np.outer(u, u) / column_norms[kstar]) @ R - signature_indices[k] = kstar - - signatures_mat = data_mat[signature_indices, :].astype(float) - signatures_mat /= signatures_mat.sum(axis=1)[:, np.newaxis] - _, exposures_mat = init_random(data_mat, n_signatures, seed=seed) - return signatures_mat, exposures_mat def initialize_mat( diff --git a/src/salamander/initialization/methods.py b/src/salamander/initialization/methods.py new file mode 100644 index 0000000..612caaf --- /dev/null +++ b/src/salamander/initialization/methods.py @@ -0,0 +1,135 @@ +""" +Initialization methods for non-negative matrix factorization (NMF) +""" + +from __future__ import annotations + +from typing import Literal, get_args + +import numpy as np +from sklearn.decomposition import _nmf as sknmf + +from ..utils import shape_checker, type_checker + +EPSILON = np.finfo(np.float32).eps +_Init_methods = Literal[ + "custom", + "flat", + "nndsvd", + "nndsvda", + "nndsvdar", + "random", + "separableNMF", +] +_INIT_METHODS = get_args(_Init_methods) + + +def init_custom( + data_mat: np.ndarray, + n_signatures: int, + signatures_mat: np.ndarray, + exposures_mat: np.ndarray, +) -> tuple[np.ndarray, np.ndarray]: + """ + Perform type and shape checks on custom signature and + exposure matrix initializations. + + Inputs: + ------- + data_mat: np.ndarray + shape (n_samples, n_features) + + n_signatures: int + + signatures_mat: np.ndarray + shape (n_signatures, n_features) + + exposures_mat: np.ndarray + shape (n_samples, n_signatures) + """ + type_checker("signatures_mat", signatures_mat, np.ndarray) + type_checker("exposures_mat", exposures_mat, np.ndarray) + n_samples, n_features = data_mat.shape + shape_checker("signatures_mat", signatures_mat, (n_signatures, n_features)) + shape_checker("exposures_mat", exposures_mat, (n_samples, n_signatures)) + return signatures_mat, exposures_mat + + +def init_flat(data_mat: np.ndarray, n_signatures: int) -> tuple[np.ndarray, np.ndarray]: + """ + Initialize the signature and exposure matrices with one float, respectively. + """ + n_features = data_mat.shape[1] + signatures_mat = np.full((n_signatures, n_features), 1 / n_features) + exposures = np.sum(data_mat, axis=1) / n_signatures + exposures_mat = np.tile(exposures, (n_signatures, 1)).T + return signatures_mat, exposures_mat + + +def init_nndsvd( + data_mat: np.ndarray, + n_signatures: int, + method: Literal["nndsvd", "nndsvda", "nndsvdar"] = "nndsvd", + seed: int | None = None, +) -> tuple[np.ndarray, np.ndarray]: + """ + A wrapper around the non-negative double singular value decomposition (NNDSVD) + initialization methods "nndsvd", "nndsvda" and "nndsvdar" from scikit-learn. + """ + if seed is not None: + np.random.seed(seed) + + # pylint: disable-next=W0212 + exposures_mat, signatures_mat = sknmf._initialize_nmf( + data_mat, n_signatures, init=method + ) + return signatures_mat, exposures_mat + + +def init_random( + data_mat: np.ndarray, n_signatures: int, seed: int | None = None +) -> tuple[np.ndarray, np.ndarray]: + """ + Initialize each signature by drawing from the uniform + distribution on the simplex. + Initialize the exposures of each sample as a scaled sample + from the uniform distribution on a simplex. + The scaling is chosen such that the expected total exposure is equal to + the column sum of that sample in the count matrix X. + """ + if seed is not None: + np.random.seed(seed) + + n_samples, n_features = data_mat.shape + signatures_mat = np.random.dirichlet(np.ones(n_features), size=n_signatures) + scaling = np.sum(data_mat, axis=1) + exposures_mat = scaling[:, np.newaxis] * np.random.dirichlet( + np.ones(n_signatures), size=n_samples + ) + return signatures_mat, exposures_mat + + +def init_separableNMF( + data_mat: np.ndarray, n_signatures: int, seed: int | None = None +) -> tuple[np.ndarray, np.ndarray]: + r""" + This code is following Algorithm 1 from "Fast and Robust Recursive + Algorithms for Separable Nonnegative Matrix Factorization" + (Gillis and Vavasis, 2013), with the canonical choice of + f(x) = \| x \|_2^2 as the strongly convex function f satisfying + Assumption 2 from the paper. + """ + signature_indices = np.empty(n_signatures, dtype=int) + R = data_mat.T / np.sum(data_mat.T, axis=0) + + for k in range(n_signatures): + column_norms = np.sum(R**2, axis=0) + kstar = np.argmax(column_norms) + u = R[:, kstar] + R = (np.identity(R.shape[0]) - np.outer(u, u) / column_norms[kstar]) @ R + signature_indices[k] = kstar + + signatures_mat = data_mat[signature_indices, :].astype(float) + signatures_mat /= signatures_mat.sum(axis=1)[:, np.newaxis] + _, exposures_mat = init_random(data_mat, n_signatures, seed=seed) + return signatures_mat, exposures_mat diff --git a/src/salamander/nmf_framework/_utils_corrnmf.py b/src/salamander/nmf_framework/_utils_corrnmf.py index 376b0c5..c700431 100644 --- a/src/salamander/nmf_framework/_utils_corrnmf.py +++ b/src/salamander/nmf_framework/_utils_corrnmf.py @@ -6,9 +6,9 @@ from numba import njit from scipy import optimize +from ..initialization.initialize import EPSILON from ..utils import dict_checker, shape_checker, type_checker from ._utils_klnmf import check_given_asignatures, poisson_llh -from .initialization import EPSILON if TYPE_CHECKING: from typing import Any diff --git a/src/salamander/nmf_framework/corrnmf.py b/src/salamander/nmf_framework/corrnmf.py index f292832..91b1736 100644 --- a/src/salamander/nmf_framework/corrnmf.py +++ b/src/salamander/nmf_framework/corrnmf.py @@ -7,17 +7,17 @@ import numpy as np from scipy.spatial.distance import squareform +from ..initialization.initialize import initialize from ..tools import reduce_dimension_multiple from ..utils import value_checker from . import _utils_corrnmf from ._utils_klnmf import samplewise_kl_divergence -from .initialization import initialize from .signature_nmf import SignatureNMF if TYPE_CHECKING: from typing import Any, Literal - from .initialization import _Init_methods + from ..initialization.methods import _Init_methods from .signature_nmf import _Dim_reduction_methods diff --git a/src/salamander/nmf_framework/corrnmf_det.py b/src/salamander/nmf_framework/corrnmf_det.py index 7826b72..b321067 100644 --- a/src/salamander/nmf_framework/corrnmf_det.py +++ b/src/salamander/nmf_framework/corrnmf_det.py @@ -4,10 +4,10 @@ import numpy as np +from ..initialization.initialize import EPSILON from . import _utils_corrnmf from ._utils_klnmf import update_W from .corrnmf import CorrNMF -from .initialization import EPSILON if TYPE_CHECKING: from typing import Any diff --git a/src/salamander/nmf_framework/klnmf.py b/src/salamander/nmf_framework/klnmf.py index c384dfd..32d9d68 100644 --- a/src/salamander/nmf_framework/klnmf.py +++ b/src/salamander/nmf_framework/klnmf.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing import Any, Literal - from .initialization import _Init_methods + from ..initialization.methods import _Init_methods _FITTING_KWARGS = ["weights_kl", "weights_lhalf"] _DEFAULT_FITTING_KWARGS = {kwarg: None for kwarg in _FITTING_KWARGS} diff --git a/src/salamander/nmf_framework/mvnmf.py b/src/salamander/nmf_framework/mvnmf.py index b807e03..c983207 100644 --- a/src/salamander/nmf_framework/mvnmf.py +++ b/src/salamander/nmf_framework/mvnmf.py @@ -5,15 +5,15 @@ import numpy as np from numba import njit +from ..initialization.initialize import EPSILON from ..utils import normalize_WH from ._utils_klnmf import kl_divergence, samplewise_kl_divergence, update_H -from .initialization import EPSILON from .standard_nmf import StandardNMF if TYPE_CHECKING: from typing import Any, Literal - from .initialization import _Init_methods + from ..initialization.methods import _Init_methods _DEFAULT_FITTING_KWARGS = None diff --git a/src/salamander/nmf_framework/signature_nmf.py b/src/salamander/nmf_framework/signature_nmf.py index 50891c7..756a8e2 100644 --- a/src/salamander/nmf_framework/signature_nmf.py +++ b/src/salamander/nmf_framework/signature_nmf.py @@ -10,8 +10,9 @@ from .. import plot as pl from .. import tools as tl +from ..initialization.initialize import EPSILON +from ..initialization.methods import _INIT_METHODS from ..utils import match_signatures_pair, type_checker, value_checker -from .initialization import _INIT_METHODS, EPSILON if TYPE_CHECKING: from typing import Any @@ -19,7 +20,7 @@ from matplotlib.axes import Axes from seaborn.matrix import ClusterGrid - from .initialization import _Init_methods + from ..initialization.methods import _Init_methods _Dim_reduction_methods = Literal[ "pca", diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/nmf_framework/standard_nmf.py index 6d82e43..fc84066 100644 --- a/src/salamander/nmf_framework/standard_nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -6,8 +6,8 @@ import anndata as ad from .. import tools as tl +from ..initialization.initialize import initialize from ._utils_klnmf import check_given_parameters -from .initialization import initialize from .signature_nmf import SignatureNMF if TYPE_CHECKING: diff --git a/tests/test_initialization.py b/tests/test_initialization.py index 774e616..e1c1d1c 100644 --- a/tests/test_initialization.py +++ b/tests/test_initialization.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from salamander.nmf_framework import initialization +from salamander.initialization import initialize PATH = "tests/test_data" PATH_TEST_DATA = f"{PATH}/nmf_framework/initialization" @@ -46,7 +46,7 @@ def test_initialize_mat( self, data_mat, method, signatures_mat_expected, exposures_mat_expected ): kwargs = {"seed": SEED} if method in METHODS_STOCH else {} - signatures_mat, exposures_mat = initialization.initialize_mat( + signatures_mat, exposures_mat = initialize.initialize_mat( data_mat, N_SIGNATURES, method, **kwargs ) assert np.allclose(signatures_mat, signatures_mat_expected) @@ -56,7 +56,7 @@ def test_initialize_mat( def test_initialize_custom(data_mat): signatures_mat_custom = np.array([[0.1, 0.2, 0.7], [0.6, 0.1, 0.3]]) exposures_mat_custom = np.arange(1, 9).reshape((4, 2)) # initialization clips zeros - signatures_mat, exposures_mat = initialization.initialize_mat( + signatures_mat, exposures_mat = initialize.initialize_mat( data_mat, N_SIGNATURES, "custom", From 06225864e86863968643222e9857d712534f0c62 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 23 Apr 2024 18:37:27 -0400 Subject: [PATCH 069/103] restructure initialize Separating the model initializations and given parameter checks of correlated NMF into a different module allows to reuse the code for multimodal correlated NMF. For consistency, I fully separated the model initializations and the algorithms of all NMF models. --- src/salamander/initialization/initialize.py | 230 +++++++++++++++++- .../nmf_framework/_utils_corrnmf.py | 83 +------ src/salamander/nmf_framework/_utils_klnmf.py | 58 ----- src/salamander/nmf_framework/corrnmf.py | 59 +---- src/salamander/nmf_framework/corrnmf_det.py | 7 +- src/salamander/nmf_framework/signature_nmf.py | 11 +- src/salamander/nmf_framework/standard_nmf.py | 22 +- 7 files changed, 240 insertions(+), 230 deletions(-) diff --git a/src/salamander/initialization/initialize.py b/src/salamander/initialization/initialize.py index 27e0ec7..e804025 100644 --- a/src/salamander/initialization/initialize.py +++ b/src/salamander/initialization/initialize.py @@ -4,10 +4,18 @@ from __future__ import annotations +from typing import Any + import anndata as ad import numpy as np -from ..utils import normalize_WH, type_checker, value_checker +from ..utils import ( + dict_checker, + normalize_WH, + shape_checker, + type_checker, + value_checker, +) from .methods import ( _INIT_METHODS, _Init_methods, @@ -20,6 +28,17 @@ EPSILON = np.finfo(np.float32).eps +# allowed given parameters +GIVEN_PARAMETERS_STANDARD_NMF = ["asignatures"] +GIVEN_PARAMETERS_CORRNMF = [ + "asignatures", + "signature_scalings", + "sample_scalings", + "signature_embeddings", + "sample_embeddings", + "variance", +] + def initialize_mat( data_mat: np.ndarray, @@ -99,7 +118,43 @@ def initialize_mat( return signatures_mat, exposures_mat -def initialize( +def check_given_asignatures( + given_asignatures: ad.AnnData, adata: ad.AnnData, n_signatures: int +) -> None: + """ + Check if the given signatures are compatible with + the input data and the number of signatures to be initialized. + The number of given signatures can be less or equal to the number of + signatures specified. + + Inputs + ------ + given_asignatures: AnnData + Known signatures that should be fixed by the algorithm. + + adata: ad.AnnData + Input data. + + n_signatures: int + The number of signatures to initialize. + """ + type_checker("given_asignatures", given_asignatures, ad.AnnData) + if given_asignatures.n_vars != adata.n_vars: + raise ValueError( + "The given signatures have a different number of features than the data." + ) + if not all(given_asignatures.var_names == adata.var_names): + raise ValueError( + "The features of the given signatures and the data are not identical." + ) + if given_asignatures.n_obs > n_signatures: + raise ValueError( + "The number of given signatures exceeds " + "the number of signatures to initialize." + ) + + +def initialize_base( adata: ad.AnnData, n_signatures: int, method: _Init_methods = "nndsvd", @@ -107,7 +162,8 @@ def initialize( **kwargs, ) -> tuple[ad.AnnData, np.ndarray]: """ - Initialize the signature ann data object and the exposure matrix. + Initialize the signature anndata object and the exposure matrix. + The anndata object is unchanged and the exposure matrix is returned. Inputs ------ @@ -138,15 +194,7 @@ def initialize( shape (n_samples, n_signatures) """ if given_asignatures is not None: - if given_asignatures.n_vars != adata.n_vars: - raise ValueError( - "The given signatures have a different number of features " - "than the data." - ) - if not all(given_asignatures.var_names == adata.var_names): - raise ValueError( - "The features of the given signatures and the data are not identical." - ) + check_given_asignatures(given_asignatures, adata, n_signatures) given_signatures_mat = given_asignatures.X else: given_signatures_mat = None @@ -167,3 +215,161 @@ def initialize( ) return asignatures, exposures_mat + + +def check_given_parameters_standard_nmf( + adata: ad.AnnData, + n_signatures: int, + given_parameters: dict[str, Any], +) -> None: + dict_checker("given_parameters", given_parameters, GIVEN_PARAMETERS_STANDARD_NMF) + + if "asignatures" in given_parameters: + check_given_asignatures(given_parameters["asignatures"], adata, n_signatures) + + +def initialize_standard_nmf( + adata: ad.AnnData, + n_signatures: int, + method: _Init_methods = "nndsvd", + given_parameters: dict[str, Any] | None = None, + **kwargs, +) -> ad.AnnData: + given_parameters = {} if given_parameters is None else given_parameters.copy() + check_given_parameters_standard_nmf(adata, n_signatures, given_parameters) + + if "asignatures" in given_parameters: + given_asignatures = given_parameters["asignatures"] + else: + given_asignatures = None + + asignatures, exposures_mat = initialize_base( + adata, + n_signatures, + method, + given_asignatures, + **kwargs, + ) + adata.obsm["exposures"] = exposures_mat + return asignatures + + +def check_given_scalings_corrnmf( + given_scalings: np.ndarray, n_scalings_expected: int, name: str +) -> None: + type_checker(name, given_scalings, np.ndarray) + shape_checker(name, given_scalings, (n_scalings_expected,)) + + +def check_given_embeddings_corrnmf( + given_embeddings: np.ndarray, + n_embeddings_expected: int, + dim_embeddings_expected: int, + name: str, +) -> None: + type_checker(name, given_embeddings, np.ndarray) + shape_checker( + name, given_embeddings, (n_embeddings_expected, dim_embeddings_expected) + ) + + +def check_given_parameters_corrnmf( + adata: ad.AnnData, + n_signatures: int, + dim_embeddings: int, + given_parameters: dict[str, Any], +) -> None: + dict_checker("given_parameters", given_parameters, GIVEN_PARAMETERS_CORRNMF) + + if "asignatures" in given_parameters: + check_given_asignatures(given_parameters["asignatures"], adata, n_signatures) + + if "signature_scalings" in given_parameters: + check_given_scalings_corrnmf( + given_parameters["signature_scalings"], + n_signatures, + "given_signature_scalings", + ) + if "sample_scalings" in given_parameters: + check_given_scalings_corrnmf( + given_parameters["sample_scalings"], adata.n_obs, "given_sample_scalings" + ) + if "signature_embeddings" in given_parameters: + check_given_embeddings_corrnmf( + given_parameters["signature_embeddings"], + n_signatures, + dim_embeddings, + "given_signature_embeddings", + ) + if "sample_embeddings" in given_parameters: + check_given_embeddings_corrnmf( + given_parameters["sample_embeddings"], + adata.n_obs, + dim_embeddings, + "given_sample_embeddings", + ) + if "variance" in given_parameters: + given_variance = given_parameters["variance"] + type_checker("given_variance", given_variance, [float, int]) + if given_variance <= 0.0: + raise ValueError("The variance has to be a positive real number.") + + +def initialize_corrnmf( + adata: ad.AnnData, + n_signatures: int, + dim_embeddings: int, + method: _Init_methods = "nndsvd", + given_parameters: dict[str, Any] | None = None, + **kwargs, +) -> tuple[ad.AnnData, float]: + given_parameters = {} if given_parameters is None else given_parameters.copy() + check_given_parameters_corrnmf( + adata, n_signatures, dim_embeddings, given_parameters + ) + + if "asignatures" in given_parameters: + given_asignatures = given_parameters["asignatures"] + else: + given_asignatures = None + + asignatures, _ = initialize_base( + adata, + n_signatures, + method, + given_asignatures, + **kwargs, + ) + + if "signature_scalings" in given_parameters: + asignatures.obs["scalings"] = given_parameters["signature_scalings"] + else: + asignatures.obs["scalings"] = np.zeros(n_signatures) + + if "sample_scalings" in given_parameters: + adata.obs["scalings"] = given_parameters["sample_scalings"] + else: + adata.obs["scalings"] = np.zeros(adata.n_obs) + + if "signature_embeddings" in given_parameters: + asignatures.obsm["embeddings"] = given_parameters["signature_embeddings"] + else: + asignatures.obsm["embeddings"] = np.random.multivariate_normal( + np.zeros(dim_embeddings), np.identity(dim_embeddings), size=n_signatures + ) + + if "sample_embeddings" in given_parameters: + adata.obsm["embeddings"] = given_parameters["sample_embeddings"] + else: + adata.obsm["embeddings"] = np.random.multivariate_normal( + np.zeros(dim_embeddings), + np.identity(dim_embeddings), + size=adata.n_obs, + ) + + if "variance" in given_parameters: + variance = float(given_parameters["variance"]) + else: + variance = 1.0 + + return asignatures, variance diff --git a/src/salamander/nmf_framework/_utils_corrnmf.py b/src/salamander/nmf_framework/_utils_corrnmf.py index c700431..d05e4c7 100644 --- a/src/salamander/nmf_framework/_utils_corrnmf.py +++ b/src/salamander/nmf_framework/_utils_corrnmf.py @@ -1,26 +1,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING - import numpy as np from numba import njit from scipy import optimize from ..initialization.initialize import EPSILON -from ..utils import dict_checker, shape_checker, type_checker -from ._utils_klnmf import check_given_asignatures, poisson_llh - -if TYPE_CHECKING: - from typing import Any - -GIVEN_PARAMETERS_ALLOWED = [ - "asignatures", - "signature_scalings", - "sample_scalings", - "signature_embeddings", - "sample_embeddings", - "variance", -] +from ._utils_klnmf import poisson_llh @njit @@ -115,72 +100,6 @@ def elbo_corrnmf( return elbo -def check_given_scalings( - given_scalings: np.ndarray, n_scalings_expected: int, name: str -) -> None: - type_checker(name, given_scalings, np.ndarray) - shape_checker(name, given_scalings, (n_scalings_expected,)) - - -def check_given_embeddings( - given_embeddings: np.ndarray, - n_embeddings_expected: int, - dim_embeddings_expected: int, - name: str, -) -> None: - type_checker(name, given_embeddings, np.ndarray) - shape_checker( - name, given_embeddings, (n_embeddings_expected, dim_embeddings_expected) - ) - - -def check_given_parameters( - given_parameters: dict[str, Any] | None, - mutation_types_data: np.ndarray, - n_samples_data: int, - n_signatures_model: int, - dim_embeddings_model: int, -) -> dict[str, Any]: - given_parameters = {} if given_parameters is None else given_parameters.copy() - dict_checker("given_parameters", given_parameters, GIVEN_PARAMETERS_ALLOWED) - - if "asignatures" in given_parameters: - check_given_asignatures( - given_parameters["asignatures"], mutation_types_data, n_signatures_model - ) - if "signature_scalings" in given_parameters: - check_given_scalings( - given_parameters["signature_scalings"], - n_signatures_model, - "given_signature_scalings", - ) - if "sample_scalings" in given_parameters: - check_given_scalings( - given_parameters["sample_scalings"], n_samples_data, "given_sample_scalings" - ) - if "signature_embeddings" in given_parameters: - check_given_embeddings( - given_parameters["signature_embeddings"], - n_signatures_model, - dim_embeddings_model, - "given_signature_embeddings", - ) - if "sample_embeddings" in given_parameters: - check_given_embeddings( - given_parameters["sample_embeddings"], - n_samples_data, - dim_embeddings_model, - "given_sample_embeddings", - ) - if "variance" in given_parameters: - given_variance = given_parameters["variance"] - type_checker("given_variance", given_variance, [float, int]) - if given_variance <= 0.0: - raise ValueError("The variance has to be a positive real number.") - - return given_parameters - - @njit def update_signature_scalings( aux: np.ndarray, diff --git a/src/salamander/nmf_framework/_utils_klnmf.py b/src/salamander/nmf_framework/_utils_klnmf.py index 2ab1e4a..1720c37 100644 --- a/src/salamander/nmf_framework/_utils_klnmf.py +++ b/src/salamander/nmf_framework/_utils_klnmf.py @@ -1,17 +1,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING - import numpy as np -from anndata import AnnData from numba import njit from scipy.special import gammaln -from ..utils import dict_checker, type_checker - -if TYPE_CHECKING: - from typing import Any - EPSILON = np.finfo(np.float32).eps GIVEN_PARAMETERS_ALLOWED = ["asignatures"] @@ -169,56 +161,6 @@ def poisson_llh(X: np.ndarray, W: np.ndarray, H: np.ndarray) -> float: return result -def check_given_asignatures( - given_asignatures: AnnData, mutation_types_data: np.ndarray, n_signatures_model: int -) -> None: - """ - Check if the given signatures are compatible with - the input data and the NMF model. - The number of given signatures can be less or equal to the number of - signatures specified by the model. - - Inputs - ------ - given_asignatures: AnnData - Known signatures that should be fixed by the algorithm. - - mutation_types_data: np.ndarray - The expected mutation types. - - n_signatures_model: int - The number of signatures of the NMF model. - """ - type_checker("given_asignatures", given_asignatures, AnnData) - given_mutation_types = given_asignatures.var_names.to_numpy(dtype=str) - compatible = ( - np.array_equal(mutation_types_data, given_mutation_types) - and given_asignatures.n_obs <= n_signatures_model - ) - if not compatible: - raise ValueError( - "The given signatures are incompatible with the data or the model. " - f"You have to provide at most {n_signatures_model} signatures with " - "mutation types matching to your data." - ) - - -def check_given_parameters( - given_parameters: dict[str, Any] | None, - mutation_types_data: np.ndarray, - n_signatures_model: int, -) -> dict[str, Any]: - given_parameters = {} if given_parameters is None else given_parameters.copy() - dict_checker("given_parameters", given_parameters, GIVEN_PARAMETERS_ALLOWED) - - if "asignatures" in given_parameters: - check_given_asignatures( - given_parameters["asignatures"], mutation_types_data, n_signatures_model - ) - - return given_parameters - - @njit def update_W( X: np.ndarray, diff --git a/src/salamander/nmf_framework/corrnmf.py b/src/salamander/nmf_framework/corrnmf.py index 91b1736..613ad8f 100644 --- a/src/salamander/nmf_framework/corrnmf.py +++ b/src/salamander/nmf_framework/corrnmf.py @@ -7,7 +7,7 @@ import numpy as np from scipy.spatial.distance import squareform -from ..initialization.initialize import initialize +from ..initialization.initialize import initialize_corrnmf from ..tools import reduce_dimension_multiple from ..utils import value_checker from . import _utils_corrnmf @@ -104,7 +104,7 @@ def _initialize( self, given_parameters: dict[str, Any] | None = None, init_kwargs: dict[str, Any] | None = None, - ) -> dict[str, Any]: + ) -> None: """ Initialize the signature matrix, the signature and sample scalings, the signature and sample embeddings, and the variance. @@ -123,65 +123,16 @@ def _initialize( of the signatures. This includes, for example, a possible 'seed' keyword argument for all stochastic initialization methods. """ - given_parameters = _utils_corrnmf.check_given_parameters( - given_parameters=given_parameters, - mutation_types_data=self.mutation_types, - n_samples_data=self.adata.n_obs, - n_signatures_model=self.n_signatures, - dim_embeddings_model=self.dim_embeddings, - ) init_kwargs = {} if init_kwargs is None else init_kwargs.copy() - - if "asignatures" in given_parameters: - given_asignatures = given_parameters["asignatures"] - else: - given_asignatures = None - - self.asignatures, _ = initialize( + self.asignatures, self.variance = initialize_corrnmf( self.adata, self.n_signatures, + self.dim_embeddings, self.init_method, - given_asignatures, + given_parameters, **init_kwargs, ) - - if "signature_scalings" in given_parameters: - self.asignatures.obs["scalings"] = given_parameters["signature_scalings"] - else: - self.asignatures.obs["scalings"] = np.zeros(self.n_signatures) - - if "sample_scalings" in given_parameters: - self.adata.obs["scalings"] = given_parameters["sample_scalings"] - else: - self.adata.obs["scalings"] = np.zeros(self.adata.n_obs) - - if "signature_embeddings" in given_parameters: - self.asignatures.obsm["embeddings"] = given_parameters[ - "signature_embeddings" - ] - else: - self.asignatures.obsm["embeddings"] = np.random.multivariate_normal( - np.zeros(self.dim_embeddings), - np.identity(self.dim_embeddings), - size=self.n_signatures, - ) - - if "sample_embeddings" in given_parameters: - self.adata.obsm["embeddings"] = given_parameters["sample_embeddings"] - else: - self.adata.obsm["embeddings"] = np.random.multivariate_normal( - np.zeros(self.dim_embeddings), - np.identity(self.dim_embeddings), - size=self.adata.n_obs, - ) - - if "variance" in given_parameters: - self.variance = float(given_parameters["variance"]) - else: - self.variance = 1.0 - self.compute_exposures() - return given_parameters def _setup_fitting_parameters( self, fitting_kwargs: dict[str, Any] | None = None diff --git a/src/salamander/nmf_framework/corrnmf_det.py b/src/salamander/nmf_framework/corrnmf_det.py index b321067..33d90c4 100644 --- a/src/salamander/nmf_framework/corrnmf_det.py +++ b/src/salamander/nmf_framework/corrnmf_det.py @@ -161,7 +161,12 @@ def update_embeddings( if "sample_embeddings" not in given_parameters: self.update_sample_embeddings(aux) - def _update_parameters(self, given_parameters: dict[str, Any]) -> None: + def _update_parameters( + self, given_parameters: dict[str, Any] | None = None + ) -> None: + if given_parameters is None: + given_parameters = {} + self.update_sample_scalings(given_parameters) self.compute_exposures() aux = self._compute_aux() diff --git a/src/salamander/nmf_framework/signature_nmf.py b/src/salamander/nmf_framework/signature_nmf.py index 756a8e2..6147ebb 100644 --- a/src/salamander/nmf_framework/signature_nmf.py +++ b/src/salamander/nmf_framework/signature_nmf.py @@ -287,10 +287,9 @@ def _initialize( self, given_parameters: dict[str, Any] | None = None, init_kwargs: dict[str, Any] | None = None, - ) -> dict[str, Any]: + ) -> None: """ - Initialize the NMF model parameters and return the potentially - updated 'given_parameters'. + Initialize the NMF model parameters. Example: Before running the Lee & Seung NMF multiplicative update rules to @@ -308,7 +307,9 @@ def _setup_fitting_parameters( """ @abstractmethod - def _update_parameters(self, given_parameters: dict[str, Any]) -> None: + def _update_parameters( + self, given_parameters: dict[str, Any] | None = None + ) -> None: """ Update all model parameters. """ @@ -357,7 +358,7 @@ def fit( iterations are printed. Only applies if 'verbose' is set to 1. """ self._setup_adata(adata) - given_parameters = self._initialize(given_parameters, init_kwargs) + self._initialize(given_parameters, init_kwargs) self._setup_fitting_parameters(fitting_kwargs) of_values = [self.objective_function()] n_iteration = 0 diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/nmf_framework/standard_nmf.py index fc84066..bd7f9da 100644 --- a/src/salamander/nmf_framework/standard_nmf.py +++ b/src/salamander/nmf_framework/standard_nmf.py @@ -6,8 +6,7 @@ import anndata as ad from .. import tools as tl -from ..initialization.initialize import initialize -from ._utils_klnmf import check_given_parameters +from ..initialization.initialize import initialize_standard_nmf from .signature_nmf import SignatureNMF if TYPE_CHECKING: @@ -33,7 +32,7 @@ def _initialize( self, given_parameters: dict[str, Any] | None = None, init_kwargs: dict[str, Any] | None = None, - ): + ) -> None: """ Initialize the signatures and exposures. A subset of the signatures can be given by the user. They will @@ -48,27 +47,14 @@ def _initialize( Any further keywords arguments to be passed to the initialization method. This includes, for example, an optional 'seed' for all stochastic methods. """ - given_parameters = check_given_parameters( - given_parameters=given_parameters, - mutation_types_data=self.mutation_types, - n_signatures_model=self.n_signatures, - ) init_kwargs = {} if init_kwargs is None else init_kwargs.copy() - - if "asignatures" in given_parameters: - given_asignatures = given_parameters["asignatures"] - else: - given_asignatures = None - - self.asignatures, exposures_mat = initialize( + self.asignatures = initialize_standard_nmf( self.adata, self.n_signatures, self.init_method, - given_asignatures, + given_parameters, **init_kwargs, ) - self.adata.obsm["exposures"] = exposures_mat - return given_parameters def reduce_dimension_embeddings( self, method: _Dim_reduction_methods = "umap", n_components: int = 2, **kwargs From b6c57815e694feb861ece2a5dd7e13580c99485f Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 24 Apr 2024 14:45:20 -0400 Subject: [PATCH 070/103] improve initialization Not initializing the sample embeddings in correlated NMF is necessary for the multimodal implementation. --- src/salamander/initialization/initialize.py | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/salamander/initialization/initialize.py b/src/salamander/initialization/initialize.py index e804025..4ada538 100644 --- a/src/salamander/initialization/initialize.py +++ b/src/salamander/initialization/initialize.py @@ -321,8 +321,15 @@ def initialize_corrnmf( dim_embeddings: int, method: _Init_methods = "nndsvd", given_parameters: dict[str, Any] | None = None, + initialize_sample_embeddings: bool = True, **kwargs, ) -> tuple[ad.AnnData, float]: + if method == "custom": + raise ValueError( + "Custom parameter initializations are currently not supported " + "for (multimodal) correlated NMF." + ) + given_parameters = {} if given_parameters is None else given_parameters.copy() check_given_parameters_corrnmf( adata, n_signatures, dim_embeddings, given_parameters @@ -358,6 +365,22 @@ def initialize_corrnmf( np.zeros(dim_embeddings), np.identity(dim_embeddings), size=n_signatures ) + if initialize_sample_embeddings: + if "sample_embeddings" in given_parameters: + adata.obsm["embeddings"] = given_parameters["sample_embeddings"] + else: + adata.obsm["embeddings"] = np.random.multivariate_normal( + np.zeros(dim_embeddings), + np.identity(dim_embeddings), + size=adata.n_obs, + ) + + if "variance" in given_parameters: + variance = float(given_parameters["variance"]) + else: + variance = 1.0 + + return asignatures, variance if "sample_embeddings" in given_parameters: adata.obsm["embeddings"] = given_parameters["sample_embeddings"] else: From f2a498bbffa217ce486f2a06fac6c031045f627f Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 24 Apr 2024 14:49:46 -0400 Subject: [PATCH 071/103] add dependency mudata is used for multimodal NMF models --- poetry.lock | 79 ++++++++++++++++++++++++++++++++------------------ pyproject.toml | 1 + 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/poetry.lock b/poetry.lock index 24f7585..1dc5149 100644 --- a/poetry.lock +++ b/poetry.lock @@ -627,40 +627,61 @@ pillow = ">=8" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" +[[package]] +name = "mudata" +version = "0.2.3" +description = "Multimodal omics analysis framework" +optional = false +python-versions = ">= 3.8" +files = [ + {file = "mudata-0.2.3-py3-none-any.whl", hash = "sha256:39f234b5943d800d30622009e448189fbc635b6f9b51b985f9e6926b313ea7a0"}, + {file = "mudata-0.2.3.tar.gz", hash = "sha256:45288ac150bfc598d68acb7c2c1c43c38c5c39522107e04f7efbf3360c7f2035"}, +] + +[package.dependencies] +anndata = ">=0.8" +h5py = "*" +numpy = "*" +pandas = "*" + +[package.extras] +docs = ["nbsphinx", "pydata-sphinx-theme (==0.8.1)", "readthedocs-sphinx-search", "sphinx (>=4.0)", "sphinx-book-theme (==0.3.3)", "sphinx_automodapi"] +test = ["zarr"] + [[package]] name = "mypy" -version = "1.9.0" +version = "1.10.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, - {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, - {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, - {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, - {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, - {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, - {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, - {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, - {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, - {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, - {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, - {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, - {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, - {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, - {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, - {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, - {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, - {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, - {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, + {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, + {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, + {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, + {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, + {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, + {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, + {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, + {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, + {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, + {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, + {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, + {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, + {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, + {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, + {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, + {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, + {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, ] [package.dependencies] @@ -1460,4 +1481,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "6a4bed5f74ce07f45874658f9bf8b4399500a88af934e34c4323de900d0e3074" +content-hash = "ecf51758d95b6b6ca0424519ee90bc6ee79040637652d0871f4d6d56d7a3feb3" diff --git a/pyproject.toml b/pyproject.toml index 0eb0549..adaba4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ adjustText = "^0.8" anndata = "^0.10.5" fastcluster = "^1.2.6" matplotlib = "^3.7.1" +mudata = "^0.2.3" numba = "^0.59" numpy = "^1.24.3" pandas = "^1.5.3" From 51f22f80fc27af54e0c46b958b6160b044ee81b4 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 25 Apr 2024 02:47:45 -0400 Subject: [PATCH 072/103] improve doc --- src/salamander/nmf_framework/corrnmf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/salamander/nmf_framework/corrnmf.py b/src/salamander/nmf_framework/corrnmf.py index 613ad8f..c1ab83a 100644 --- a/src/salamander/nmf_framework/corrnmf.py +++ b/src/salamander/nmf_framework/corrnmf.py @@ -191,7 +191,7 @@ def _get_embedding_plot_adata( self, method: _Dim_reduction_methods = "umap" ) -> tuple[ad.AnnData, str]: """ - Plot the exposures directly if the number of signatures is at most 2. + Plot the embeddings directly if the embedding dimension is at most 2. """ plot_adata = ad.concat([self.asignatures, self.adata]) From 7b49b0004dc544906fd48c8c42bb4e914f46f768 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 25 Apr 2024 02:48:17 -0400 Subject: [PATCH 073/103] add data HRDetect training data and labels --- data/hrdetect_counts_training.csv | 97 ++++++++++ data/hrdetect_labels_training.csv | 312 ++++++++++++++++++++++++++++++ 2 files changed, 409 insertions(+) create mode 100755 data/hrdetect_counts_training.csv create mode 100644 data/hrdetect_labels_training.csv diff --git a/data/hrdetect_counts_training.csv b/data/hrdetect_counts_training.csv new file mode 100755 index 0000000..5b91b12 --- /dev/null +++ b/data/hrdetect_counts_training.csv @@ -0,0 +1,97 @@ +Type,PD10014a,PD11327a,PD11742a,PD11748a,PD11750a,PD11751a,PD11755a,PD13296a,PD13297a,PD13299a,PD13311a,PD13627a,PD13771a,PD18259a,PD22355a,PD22358a,PD23558a,PD23562a,PD23574a,PD23578a,PD24186a,PD24191a,PD24202a,PD24337a,PD3890a,PD3904a,PD3905a,PD3945a,PD4005a,PD4006a,PD4107a,PD4115a,PD4116a,PD4604a,PD4872a,PD4874a,PD4876a,PD4951a,PD4952a,PD4953a,PD4954a,PD4955a,PD4956a,PD4957a,PD4958a,PD4959a,PD5930a,PD5935a,PD5945a,PD5948a,PD5960a,PD6042a,PD6406a,PD6409a,PD6411a,PD6413a,PD6415a,PD6684a,PD6731a2,PD7211a,PD7215a,PD7217a,PD7250a,PD8610a,PD8611a,PD8621a,PD8652a2,PD8832a,PD8969a,PD8978a,PD8980a,PD8984a,PD9000a,PD9004a,PD9585a,PD9592a,PD9702a,PD10010a,PD11336a,PD11337a,PD11338a,PD11339a,PD11340a,PD11341a,PD11342a,PD11343a,PD11344a,PD11345a,PD11346a,PD11347a,PD11348a,PD11349a,PD11355a,PD11357a,PD11359a,PD11360a,PD11361a,PD11364a,PD11366a,PD11367a,PD11368a,PD11369a,PD11374a,PD11375a,PD11376a,PD11380a,PD11381a,PD11383a,PD11384a,PD11385a,PD11386a,PD11388a,PD11393a,PD11394a,PD11395a,PD11396a,PD11397a,PD11398a,PD11399a,PD11402a,PD11462a,PD11465a,PD11740a,PD11741a,PD11744a,PD11745a,PD11752a,PD11756a,PD11760a,PD11761a,PD11762a,PD11766a,PD11769a,PD11816a,PD13164a,PD13166a,PD13167a,PD13168a,PD13302a,PD13306a,PD13307a,PD13419a,PD13420a,PD13422a,PD13424a,PD13425a,PD13427a,PD13428a,PD13603a,PD13605a,PD13606a,PD13607a,PD13608a,PD13609a,PD13619a,PD13620a,PD13622a,PD13625a,PD13626a,PD13629a,PD13630a,PD13631a,PD13752a,PD13754a,PD13757a,PD13758a,PD13763a,PD13764a,PD13766a,PD13767a,PD13768a,PD13770a,PD14432a,PD14433a,PD14435a,PD14437a,PD14439a,PD14441a,PD14450a,PD14453a,PD14454a,PD14456a,PD14457a,PD14459a,PD14460a,PD14461a,PD14462a,PD14465a,PD14467a,PD14468a,PD14471a,PD14472a,PD14473a,PD17973a,PD17981a,PD17991a,PD17994a,PD18022a,PD18045a,PD18046a,PD18047a,PD18049a,PD18050a,PD18100a,PD18101a,PD18116a,PD18149a,PD18188a,PD18189a,PD18251a,PD18257a,PD18258a,PD18264a,PD18269a,PD18730a,PD18733a,PD18734a,PD18748a,PD18751a,PD18754a,PD18756a,PD18768a,PD18771a,PD18775a,PD18776a,PD3851a,PD4069a,PD4085a,PD4199a,PD4225a,PD4248a,PD4255a,PD4261a,PD4266a,PD4315a,PD4605a,PD4606a,PD4613a,PD4962a,PD4965a,PD4967a,PD4968a,PD4969a,PD4970a,PD4971a,PD4976a,PD4981a,PD4985a,PD5936a,PD5946a,PD5951a,PD5959a,PD5961a,PD5964a,PD6041a,PD6045a,PD6417a,PD6422a,PD6466b,PD6711a2,PD6719a,PD6720a,PD6721a,PD7069a,PD7199a,PD7201a,PD7206a,PD7207a,PD7210a,PD7214a,PD7218a,PD7219a,PD7220a,PD7221a,PD7238a,PD7243a,PD7322a,PD8609a,PD8614a,PD8615a,PD8617a,PD8618a,PD8619a,PD8622a,PD8623a,PD8828a,PD8965a,PD8973a,PD8977a,PD8979a,PD8981a,PD9001a,PD9063a,PD9065a,PD9067a,PD9193a,PD9539a,PD9541a,PD9577a,PD9578a,PD9582a,PD9591a,PD9593a,PD9597a,PD9599a,PD9605a,PD9752a,PD9754a,PD9755a,PD9756a,PD9759a +A[C>A]A,235,281,117,232,105,264,48,317,136,117,81,136,240,118,188,90,197,120,208,166,119,123,133,109,116,127,113,295,81,189,220,245,141,189,80,150,100,62,211,152,118,134,184,67,133,109,74,126,162,218,56,180,98,259,136,86,157,176,81,193,114,151,149,84,86,122,269,420,93,154,333,148,234,155,195,193,135,37,88,58,37,28,68,25,31,62,26,93,34,17,86,106,41,41,43,39,31,35,40,149,93,72,37,38,44,43,30,57,23,43,28,76,94,33,73,47,94,32,53,33,42,229,36,57,41,42,159,33,86,53,29,11,22,59,46,31,29,30,74,31,59,12,21,38,74,220,23,44,32,19,121,38,42,27,31,82,114,201,19,17,49,33,56,39,16,28,46,100,31,34,14,55,28,37,69,74,23,91,20,130,51,31,104,49,29,20,32,62,27,22,61,39,29,57,120,14,13,29,133,53,50,38,46,32,12,22,48,32,72,49,58,25,24,28,9,53,90,39,27,26,30,43,25,24,29,52,51,65,40,46,45,125,31,35,58,38,25,26,113,65,43,85,14,52,71,119,41,33,49,73,46,33,48,41,27,32,22,86,21,21,130,30,37,57,28,30,129,44,26,22,41,41,32,38,24,143,49,43,22,43,23,44,65,31,40,18,83,31,38,55,29,41,52,61,18,26,60,32,29,15,55,17,26,11,46,81,72,39,30,39,38 +A[C>A]C,241,229,100,178,92,207,41,291,125,96,62,118,233,126,178,101,183,108,195,145,121,109,113,100,91,114,82,195,68,165,177,179,162,191,72,123,74,43,189,119,96,111,193,52,148,84,56,140,166,184,75,122,80,169,119,64,108,156,85,152,108,136,118,81,79,98,218,467,81,75,324,139,179,146,192,137,87,25,63,47,40,38,56,31,36,48,28,60,18,20,68,101,34,49,36,41,20,22,46,149,82,79,24,22,54,36,34,32,22,29,50,91,66,38,55,38,71,37,31,38,37,172,31,34,28,45,99,16,53,41,29,24,18,42,31,14,20,29,64,28,51,14,29,29,63,190,14,41,29,30,102,37,31,33,28,59,91,172,15,22,40,41,33,38,14,30,41,65,31,28,17,33,26,26,50,48,17,80,10,96,31,21,54,29,18,19,29,40,29,23,56,30,15,27,103,14,15,19,95,36,26,43,37,22,12,19,28,34,61,41,28,16,31,17,11,51,68,36,35,19,17,37,18,19,24,45,20,45,28,29,31,62,29,23,46,46,13,17,94,49,43,72,12,44,37,75,27,29,55,37,50,27,37,24,26,19,27,96,24,25,103,25,28,41,26,16,94,27,19,20,25,25,25,21,22,100,26,40,19,30,26,20,36,14,26,19,70,33,53,48,36,29,47,27,16,24,59,26,25,11,40,16,27,13,33,46,51,33,18,24,25 +A[C>A]G,37,53,10,12,18,36,9,52,20,20,18,21,29,13,27,25,29,18,31,18,23,9,28,13,14,13,12,25,13,32,37,15,16,14,6,28,12,5,36,22,17,13,34,10,24,9,5,23,19,24,10,19,15,28,23,11,24,30,18,15,10,25,25,13,22,19,29,73,15,16,43,29,25,25,24,20,26,8,14,3,4,5,11,9,3,7,5,16,2,1,16,18,12,12,3,6,3,6,5,21,17,14,4,4,6,6,7,4,3,3,8,13,11,4,11,11,13,2,9,7,9,26,10,9,4,6,13,2,5,10,3,3,4,7,5,8,5,5,9,4,5,4,4,8,8,32,2,11,6,3,17,5,6,6,3,4,14,22,2,4,6,4,5,6,2,6,10,11,3,3,1,4,9,8,7,7,4,11,3,16,6,7,14,7,3,0,8,14,5,3,9,2,3,6,8,5,3,2,22,1,10,3,12,3,1,2,3,8,7,11,10,6,3,5,2,7,9,7,7,2,6,6,2,1,2,10,5,5,6,7,8,15,4,4,7,9,5,1,11,8,7,5,5,8,10,14,4,7,7,5,13,6,6,2,3,1,4,13,2,4,12,8,7,4,6,3,23,6,3,5,5,3,4,3,3,16,4,10,4,7,11,3,10,1,4,3,13,10,10,9,3,10,5,6,5,4,5,3,2,3,7,3,3,3,7,11,9,5,2,6,5 +A[C>A]T,234,260,104,227,103,214,42,277,106,77,62,111,214,113,203,86,147,100,216,120,136,87,125,93,88,114,78,181,76,202,181,163,162,145,80,114,64,34,158,110,85,107,157,44,152,70,70,116,153,231,43,104,67,200,113,74,138,140,86,143,85,124,91,73,82,119,228,426,73,74,265,98,162,124,170,164,102,24,59,40,27,22,60,10,22,29,9,70,18,19,53,89,30,38,25,29,19,21,30,121,56,55,21,19,32,18,17,36,15,22,29,60,66,15,45,34,61,35,28,28,33,178,13,28,19,40,93,20,53,29,13,15,15,49,33,18,29,21,53,22,41,7,32,22,57,177,12,33,30,14,71,31,27,33,16,61,64,155,12,20,41,35,40,16,4,30,39,53,17,34,10,19,22,18,47,51,13,61,16,87,35,25,56,32,16,19,12,36,11,12,34,23,14,26,66,10,4,9,74,28,24,22,34,15,7,15,22,21,53,36,39,14,19,10,11,32,61,24,33,21,23,37,22,10,14,33,24,46,23,11,29,47,20,14,40,25,22,20,76,34,38,48,8,28,47,60,14,18,36,38,21,17,20,33,20,20,21,59,16,21,106,22,21,37,15,19,80,26,23,22,22,31,26,14,18,97,20,12,15,30,20,17,37,17,23,13,55,25,36,29,16,20,33,25,13,17,35,36,25,10,20,11,16,6,24,39,25,20,13,25,19 +C[C>A]A,244,284,113,230,129,254,50,318,125,116,86,119,204,134,187,95,206,122,204,148,142,108,119,116,115,115,93,238,69,180,183,202,184,190,68,135,61,57,200,135,108,104,175,61,203,87,48,161,165,244,67,141,85,199,128,69,130,202,84,185,82,133,110,81,93,139,235,475,105,88,260,134,165,166,204,125,146,34,97,48,26,18,92,17,25,36,21,108,24,12,80,75,38,41,30,37,28,16,31,150,79,65,28,25,38,15,19,30,13,20,25,73,101,27,66,33,96,29,43,25,38,167,23,44,32,52,142,19,96,27,20,16,22,40,40,36,24,26,58,22,36,13,23,27,50,261,22,35,41,20,114,45,27,25,22,68,124,210,10,16,43,39,38,28,15,17,30,67,24,41,16,35,16,27,39,78,15,99,13,99,37,27,107,39,19,12,19,32,13,16,45,31,18,49,118,12,11,12,128,33,29,25,41,17,15,19,24,34,63,59,42,13,41,19,9,31,50,28,27,24,22,33,17,18,20,29,33,32,34,11,40,107,16,18,77,16,14,20,66,48,36,53,16,24,46,116,33,28,41,64,29,18,25,30,22,22,13,85,14,11,116,10,24,44,17,19,73,21,15,21,28,33,36,14,27,139,32,24,20,29,14,19,34,28,35,17,55,21,49,47,22,20,79,53,15,25,38,32,23,12,42,13,28,4,43,54,42,28,20,21,16 +C[C>A]C,238,219,119,215,103,235,35,291,120,104,69,112,211,127,178,97,163,102,160,133,106,68,129,87,70,80,54,193,86,161,175,152,181,221,66,125,69,47,205,119,93,105,166,50,184,76,54,134,145,185,66,122,72,150,116,63,100,163,110,143,75,125,100,90,93,110,199,447,92,69,257,131,169,157,180,133,110,28,58,33,18,25,55,13,26,41,13,43,10,18,63,93,20,21,35,32,14,24,17,137,49,65,23,18,37,15,21,18,9,16,29,52,45,16,40,24,46,24,33,19,18,158,23,21,12,43,73,12,42,17,13,13,23,36,22,20,24,18,42,15,35,7,21,19,46,191,7,27,26,13,58,48,15,15,18,55,70,128,14,10,33,18,30,15,10,24,26,56,15,23,6,22,19,21,30,49,11,81,11,71,29,15,37,14,23,14,23,26,10,10,42,15,12,17,37,9,12,15,107,32,23,25,31,6,6,7,20,19,43,26,30,7,16,12,6,24,52,13,16,8,9,18,10,14,9,26,18,35,32,15,22,42,14,13,28,21,8,9,47,17,21,33,9,26,32,69,19,16,27,31,29,23,14,13,11,17,11,59,17,12,97,19,22,26,8,9,56,17,8,13,6,26,13,9,13,65,18,16,20,26,11,18,31,18,26,7,51,16,24,32,19,27,47,19,13,15,34,25,23,6,41,11,14,9,27,34,24,14,26,20,13 +C[C>A]G,35,46,20,32,20,32,5,65,23,22,20,12,25,18,22,14,26,16,27,12,14,10,16,17,18,10,14,23,7,34,27,17,33,21,8,33,11,4,40,18,18,14,33,7,28,9,7,24,18,35,9,18,17,29,16,8,23,35,22,28,8,33,15,13,16,17,36,70,17,9,37,31,19,33,23,17,19,8,20,10,5,6,24,6,6,3,4,18,3,5,14,27,4,14,5,5,3,7,8,16,11,15,11,5,11,3,13,3,4,1,5,4,23,3,7,5,12,3,10,6,7,34,13,4,4,6,27,6,13,5,5,2,5,4,5,11,9,3,12,5,5,4,1,3,12,53,4,14,6,4,25,2,2,2,7,7,18,39,6,8,3,7,3,7,1,4,9,8,4,8,4,2,3,2,4,10,4,11,3,21,6,4,22,7,4,3,7,8,4,4,5,5,6,7,21,2,2,2,23,10,4,6,15,4,1,5,3,6,11,7,4,0,4,4,1,10,11,4,12,5,9,2,2,1,3,5,4,7,6,3,5,29,3,0,10,6,1,3,12,4,2,9,3,3,8,21,2,3,5,10,13,4,2,4,3,3,3,22,3,3,9,4,3,8,2,2,8,2,5,1,4,5,4,3,3,29,6,1,5,3,4,3,14,4,11,4,6,3,10,10,6,14,7,6,1,4,2,5,4,0,10,3,3,0,8,7,8,4,2,8,5 +C[C>A]T,243,264,101,207,131,235,26,272,134,110,79,114,191,113,221,93,164,118,197,132,120,72,114,104,73,89,72,209,53,162,197,159,147,240,83,108,58,39,208,117,87,105,145,46,181,75,53,105,162,212,80,103,88,162,100,78,140,128,109,119,86,89,123,84,81,128,230,501,100,68,237,115,159,163,163,148,91,23,65,36,16,18,69,14,23,35,16,100,29,9,70,88,30,30,12,44,22,19,13,119,61,67,19,22,41,18,22,27,9,24,27,55,124,12,47,25,71,23,40,23,26,203,26,24,25,65,119,14,74,29,16,14,15,27,32,34,17,33,67,17,43,11,12,19,51,223,17,26,36,8,98,58,24,17,17,57,92,182,8,26,29,26,32,22,8,16,23,53,16,27,6,19,17,17,26,64,4,64,7,72,29,25,71,30,13,13,17,35,11,13,39,10,21,30,79,10,8,20,109,36,17,20,29,16,11,17,26,28,51,50,42,10,17,13,7,33,48,23,18,17,11,23,14,7,14,28,27,27,22,11,30,76,17,19,45,23,8,14,86,25,24,60,8,34,34,110,14,18,22,48,25,9,25,17,28,20,11,98,13,15,90,18,25,33,7,13,80,19,23,11,19,19,18,12,14,108,23,17,14,30,10,19,43,24,30,8,59,21,45,38,17,17,50,30,10,25,30,17,18,9,37,7,17,11,22,44,36,11,17,21,15 +G[C>A]A,146,199,82,155,68,133,37,175,90,68,53,79,137,75,139,59,115,68,123,95,85,60,77,75,63,60,55,141,46,134,173,138,90,103,47,79,47,26,134,117,58,77,98,43,84,51,54,91,90,156,52,79,57,188,67,62,81,106,64,92,58,85,82,50,56,106,176,344,74,55,189,88,140,74,123,98,89,31,83,45,15,35,69,24,31,35,18,101,21,11,95,64,52,43,29,38,30,37,47,95,85,70,20,33,62,29,32,38,25,27,20,80,158,39,69,41,112,20,46,32,34,174,30,51,29,42,161,26,140,27,18,25,19,58,50,28,23,49,69,17,42,14,27,47,61,218,18,49,43,15,98,31,16,8,17,63,151,159,11,26,48,38,52,28,12,33,31,66,29,39,13,48,17,19,53,41,22,65,11,85,32,43,140,70,33,16,23,59,14,19,36,31,16,95,173,11,10,22,111,32,39,40,50,38,5,16,34,24,74,29,57,19,26,18,8,54,90,33,43,25,29,22,19,8,17,47,50,45,36,29,28,124,19,16,47,30,11,22,62,53,32,42,21,30,30,116,35,33,40,74,43,25,48,34,26,38,15,81,16,13,71,22,28,34,23,20,65,26,17,29,44,37,23,14,22,154,64,16,26,28,25,24,42,28,21,20,50,21,45,44,26,49,33,87,23,50,48,40,10,23,38,21,23,13,51,78,37,33,24,27,35 +G[C>A]C,126,144,66,108,51,142,23,183,95,67,51,85,147,76,122,73,146,60,128,83,61,62,86,56,61,84,44,115,45,98,101,137,113,123,36,57,47,20,110,87,68,70,112,25,106,41,34,67,91,125,48,81,36,96,77,40,65,92,71,83,68,105,73,63,41,75,149,283,64,49,194,85,81,124,110,102,61,22,48,35,30,19,37,18,16,22,15,38,15,13,54,53,15,27,17,25,18,15,35,98,54,50,14,18,22,27,16,26,9,18,23,52,38,15,37,14,52,25,29,31,23,106,23,18,12,24,77,7,36,17,9,17,12,26,19,14,18,24,35,13,27,4,15,14,36,129,4,25,25,3,70,23,13,14,15,33,38,99,10,21,28,31,31,30,9,17,22,32,15,22,14,21,18,22,29,24,20,51,8,46,17,18,44,20,16,10,18,26,11,9,30,17,11,23,78,9,14,12,67,22,21,31,22,20,13,14,30,16,40,28,21,4,21,10,4,23,39,18,14,9,6,31,15,15,12,19,21,27,22,9,25,47,14,9,21,26,12,13,50,24,13,32,8,15,35,47,20,17,27,40,28,14,20,21,18,12,12,44,12,14,66,10,21,21,16,14,50,18,5,8,13,18,20,8,21,62,25,23,13,16,15,16,35,15,19,7,44,20,25,32,16,30,32,12,11,21,36,23,14,15,21,11,15,5,28,33,32,24,15,18,25 +G[C>A]G,24,34,17,21,7,18,6,37,9,18,17,12,22,5,22,9,12,6,15,18,11,11,9,10,9,10,3,17,4,16,18,20,14,20,6,23,9,8,20,16,13,11,21,1,16,7,5,12,11,24,12,10,8,8,11,6,6,15,6,12,6,16,13,19,14,12,23,50,11,8,24,14,16,15,22,12,12,11,20,6,3,6,11,2,5,6,6,14,5,3,19,12,6,10,10,6,4,3,19,15,21,5,9,12,12,6,9,7,2,2,5,8,16,6,9,4,7,10,9,4,11,28,4,5,4,3,17,3,13,4,5,4,1,10,4,5,7,7,8,0,10,6,6,3,6,32,3,9,6,3,21,8,4,7,3,9,17,27,1,4,7,7,5,2,5,6,9,7,3,6,1,3,6,5,8,11,3,7,1,11,3,8,19,6,3,7,9,4,5,3,14,7,4,12,11,3,2,2,11,4,8,9,11,2,6,3,9,3,12,6,8,4,5,1,4,7,17,5,11,5,4,5,3,5,3,4,12,7,7,10,7,18,2,4,5,6,4,3,11,8,6,16,2,9,9,12,6,8,13,16,9,6,4,6,5,3,10,21,1,6,8,4,5,10,5,6,11,5,3,1,5,7,10,5,8,18,9,5,2,6,9,8,12,4,4,3,6,4,13,9,3,10,18,12,4,5,7,3,2,4,6,2,4,3,8,10,7,2,3,5,4 +G[C>A]T,160,165,68,137,80,141,28,181,83,81,51,73,129,70,144,55,110,64,127,89,75,59,81,67,61,52,57,123,44,113,129,143,101,102,56,80,50,30,124,83,42,70,106,36,97,56,34,83,91,141,45,68,42,113,77,47,77,109,63,103,51,94,65,50,40,76,163,336,72,47,172,82,117,75,133,106,69,22,66,46,22,22,64,8,23,24,18,81,12,6,54,65,33,28,32,35,15,25,28,86,54,41,15,24,30,20,21,21,14,12,23,50,121,22,54,22,81,20,41,20,27,111,30,39,17,38,126,9,107,17,12,9,11,30,29,21,19,32,51,14,33,4,14,17,50,187,12,30,30,16,71,29,14,10,12,48,96,134,5,17,40,37,22,29,13,15,17,48,11,28,5,38,16,21,34,39,25,47,15,59,32,28,87,38,13,11,12,37,11,18,26,21,12,35,120,6,8,9,73,23,19,16,38,14,5,13,21,17,63,24,36,18,23,13,3,33,38,13,30,24,19,21,11,9,10,22,25,24,31,16,23,81,13,7,28,21,12,11,57,28,19,34,14,13,33,87,18,16,27,47,23,16,29,22,21,25,14,54,18,13,42,14,18,17,9,19,57,13,14,13,31,14,20,7,14,106,40,24,14,25,14,14,30,13,18,9,25,14,36,31,14,21,27,53,19,18,30,24,14,9,28,8,12,9,27,50,30,10,9,21,24 +T[C>A]A,202,250,147,184,139,296,36,283,145,225,74,89,224,82,122,120,167,121,194,113,94,73,121,77,99,120,89,280,184,144,169,162,197,138,66,250,80,41,206,166,94,104,175,55,188,78,62,133,150,175,79,214,85,204,122,81,197,247,174,150,124,143,93,113,93,110,260,356,150,92,324,127,187,179,199,149,131,40,104,85,44,43,112,18,55,62,305,263,45,39,114,148,52,45,41,56,27,36,47,120,150,138,25,37,68,43,24,39,28,18,58,105,136,29,66,45,106,36,72,36,54,558,38,64,26,88,144,30,99,46,39,20,35,74,47,42,37,40,93,31,124,11,15,39,69,1318,14,46,71,21,191,69,67,104,21,87,122,257,14,15,96,54,46,37,24,34,35,76,55,38,20,39,27,53,62,87,35,94,19,180,33,36,129,52,77,14,48,55,23,30,49,36,19,47,173,17,17,28,528,56,37,59,58,44,14,20,36,26,115,58,70,22,146,16,40,62,105,43,30,49,28,51,31,21,23,48,40,68,163,32,58,162,25,49,82,32,13,50,314,51,89,89,18,30,48,147,43,25,46,66,57,24,57,25,37,44,22,173,25,19,85,26,35,128,21,25,113,38,27,21,30,465,41,24,36,175,69,103,15,77,19,27,84,34,40,28,104,127,57,58,34,51,1426,72,26,28,56,38,24,9,54,20,25,54,78,62,50,36,22,81,31 +T[C>A]C,191,178,128,176,130,296,34,272,121,166,66,106,232,113,126,118,179,108,214,113,107,81,112,81,94,103,72,181,126,154,131,167,149,151,80,175,67,44,181,145,115,114,225,40,156,67,70,141,125,157,85,153,86,150,121,70,158,228,104,115,88,126,105,77,81,105,211,351,133,91,304,156,178,149,179,121,120,40,94,52,35,39,83,22,47,66,144,145,27,31,98,115,43,56,54,40,22,25,75,154,122,92,29,46,51,56,29,51,23,26,47,87,79,24,71,27,99,28,51,42,52,263,32,32,29,67,143,26,69,47,28,25,24,58,25,46,34,35,64,42,67,13,24,39,66,563,15,47,50,26,103,53,48,45,26,75,94,170,22,24,62,45,33,41,13,34,31,70,46,44,21,45,43,38,52,49,20,102,17,103,51,30,94,39,62,21,46,37,16,22,41,48,18,37,121,19,15,13,272,52,38,49,56,29,8,16,48,24,67,49,45,18,83,20,27,54,79,52,26,45,23,51,19,20,27,37,32,44,77,33,28,98,23,21,53,54,16,25,136,55,59,64,13,48,41,120,27,27,52,77,53,28,34,22,35,28,18,92,27,26,81,24,36,67,21,29,106,47,25,23,31,206,38,23,21,148,41,53,23,55,29,27,58,31,32,17,67,75,50,48,27,55,582,45,23,35,52,39,22,14,54,20,32,33,53,55,67,25,13,59,33 +T[C>A]G,17,23,16,18,17,31,1,29,17,26,7,8,29,9,18,14,17,13,16,10,10,10,19,17,10,12,8,19,10,14,26,19,10,20,5,19,8,2,22,25,9,12,26,7,20,3,5,22,11,16,11,20,14,13,20,14,18,18,9,11,11,17,14,10,15,13,29,53,16,10,37,10,24,19,21,12,14,12,12,8,2,4,15,10,10,8,15,34,9,5,14,11,3,15,11,16,9,10,15,17,26,13,6,12,11,9,10,2,3,3,11,12,26,8,12,5,15,7,11,9,7,49,9,9,10,4,24,5,21,11,12,8,4,13,10,8,6,6,16,6,17,6,1,4,8,112,5,7,11,3,28,9,8,11,4,10,27,26,5,6,10,7,6,6,6,5,6,15,8,12,3,9,8,8,11,7,7,11,2,20,8,6,28,5,11,3,8,11,7,4,11,4,6,10,23,4,10,4,45,16,4,10,10,4,3,4,8,1,12,15,11,1,23,1,6,6,16,11,6,7,6,7,7,7,9,5,4,8,15,9,4,19,4,6,9,8,2,9,19,12,8,11,5,4,5,22,8,5,9,16,7,5,9,5,5,9,6,18,5,4,2,6,1,9,6,5,14,6,4,6,6,41,7,8,5,24,11,11,5,7,9,8,16,5,8,4,8,12,13,12,7,13,86,10,3,3,18,14,1,3,5,4,6,7,6,9,9,7,3,9,6 +T[C>A]T,253,307,152,249,173,297,49,329,151,200,81,100,315,130,187,107,207,114,254,133,120,120,187,103,109,132,99,335,170,175,241,278,179,194,100,207,99,40,268,206,103,178,207,66,230,103,84,168,163,240,59,178,101,200,152,88,162,226,155,166,101,156,122,123,90,131,322,507,163,105,388,159,237,221,191,147,148,48,164,90,57,47,156,36,69,67,194,297,50,34,174,159,82,66,67,96,35,56,71,156,171,154,54,49,88,66,45,67,39,33,63,132,225,32,93,66,174,61,112,43,67,415,58,76,25,121,277,22,223,60,40,33,28,87,84,80,44,68,133,39,92,15,26,50,94,937,24,57,102,30,253,86,54,95,27,162,209,319,8,30,94,108,86,66,34,44,49,112,44,71,24,78,41,52,73,169,37,112,37,174,54,51,185,87,74,28,33,80,40,34,56,47,23,116,232,25,23,37,341,57,49,57,40,44,20,15,49,39,178,74,111,25,136,16,24,89,156,65,36,83,44,53,31,20,38,43,88,71,104,38,52,247,29,29,93,57,18,41,245,69,76,105,25,36,57,219,45,39,71,112,61,34,68,45,55,56,26,200,41,20,104,38,56,106,30,41,116,40,25,28,36,216,43,28,31,224,84,51,27,74,33,35,71,29,40,32,117,135,70,75,47,37,710,117,27,60,66,41,30,26,56,19,44,35,77,114,70,43,24,87,37 +A[C>G]A,158,199,77,171,135,250,51,272,181,98,76,144,166,73,118,118,141,114,205,104,111,107,136,88,93,58,62,145,61,172,125,129,113,108,38,83,79,26,161,88,80,51,182,34,94,53,55,159,157,303,45,87,91,142,121,59,115,208,74,135,50,118,167,61,126,137,246,466,83,74,161,175,123,139,123,191,141,35,44,18,17,15,38,11,18,32,39,36,11,12,34,54,13,24,23,28,8,6,17,91,29,29,21,10,24,19,12,21,8,8,28,33,31,9,27,14,28,29,21,17,22,150,13,5,12,18,79,10,21,26,9,20,15,20,18,16,13,12,26,6,18,11,13,6,30,238,3,24,17,9,44,22,15,23,15,34,33,65,5,9,33,12,24,21,10,21,21,51,10,19,4,9,22,9,18,47,10,50,7,76,24,20,29,8,15,12,10,24,13,8,24,15,15,10,27,8,7,15,111,20,16,15,22,9,5,14,25,22,21,40,28,10,22,11,9,21,22,19,12,6,11,20,13,8,12,15,9,26,22,12,19,31,18,17,20,21,9,14,35,15,32,26,9,15,28,62,12,17,19,29,13,18,15,16,11,14,12,48,15,13,82,17,18,33,6,7,51,11,10,12,3,44,21,18,11,53,14,21,12,50,9,9,37,10,10,19,29,21,24,33,9,10,95,9,14,24,16,29,12,8,22,6,9,7,18,17,18,13,13,12,14 +A[C>G]C,71,133,53,102,102,138,26,153,101,61,55,70,111,35,74,54,78,78,95,67,57,54,64,60,41,44,39,89,37,100,85,76,76,84,40,54,39,22,68,73,50,46,116,14,68,45,42,91,77,157,28,42,51,64,48,28,48,131,32,58,44,61,98,50,63,89,139,254,49,66,101,71,79,87,68,90,62,5,28,11,10,13,29,6,19,24,11,31,7,9,31,43,12,16,15,11,6,14,9,70,23,29,15,7,16,22,10,17,9,14,11,39,14,11,22,11,27,18,19,18,26,92,7,9,4,20,50,8,12,20,10,9,10,13,17,7,15,7,27,6,11,4,11,11,23,112,6,15,8,5,48,20,18,16,9,25,18,39,5,9,19,15,12,24,10,14,8,33,9,12,5,9,16,12,13,30,8,28,5,41,18,5,19,10,10,5,11,16,11,9,11,12,4,9,25,8,8,7,79,14,10,17,14,7,3,10,12,8,21,26,19,6,8,6,4,19,15,7,6,4,7,17,7,2,9,14,4,24,17,8,16,29,14,11,14,14,8,9,24,7,22,16,4,18,10,39,10,11,14,21,14,6,10,7,8,6,7,30,9,11,43,16,22,23,5,3,40,15,5,5,4,19,10,11,10,29,10,14,10,27,7,11,26,17,21,9,23,13,23,14,10,20,34,11,4,24,17,15,12,5,18,6,11,4,15,16,20,3,7,12,13 +A[C>G]G,26,92,24,38,39,69,10,100,49,39,31,33,32,12,36,22,39,46,34,25,31,24,32,24,20,17,12,23,15,60,58,22,18,22,9,51,8,5,38,19,12,15,81,10,22,13,12,55,43,52,7,22,41,36,38,10,22,45,16,22,14,61,23,10,28,37,59,128,18,17,49,34,20,44,24,30,46,16,11,4,6,3,5,3,1,4,3,18,4,3,6,6,3,3,3,7,1,3,3,18,4,6,5,1,5,2,6,1,8,9,9,4,7,3,3,5,6,6,6,1,4,59,2,4,4,8,16,7,5,7,0,5,2,2,4,5,6,6,6,4,5,3,5,0,5,46,0,9,9,4,9,4,2,11,3,4,14,13,4,3,10,0,1,4,0,6,2,10,3,2,1,5,1,7,2,11,3,6,2,23,5,7,8,3,4,4,1,6,2,2,5,5,4,3,3,2,2,5,15,2,1,7,8,3,1,1,3,2,13,5,4,3,2,1,2,4,2,4,4,4,3,3,2,3,1,8,3,7,3,1,5,25,4,1,9,6,1,0,7,4,7,1,3,6,11,14,2,6,1,9,3,1,3,4,2,1,4,8,2,5,8,0,5,3,2,2,4,2,2,3,1,6,2,3,3,8,3,1,3,11,4,1,13,1,2,2,5,3,4,8,2,4,7,2,3,1,3,2,1,2,4,1,0,3,8,9,3,5,4,3,4 +A[C>G]T,180,220,84,177,157,257,40,287,186,124,72,182,149,75,110,98,158,138,179,103,100,106,112,81,98,66,73,128,81,170,119,138,114,108,54,114,94,32,151,107,89,61,209,32,113,50,64,159,171,312,39,104,97,171,128,66,110,250,62,122,51,105,183,59,100,133,263,479,85,98,175,167,120,164,111,193,134,25,37,23,8,13,37,10,25,22,41,41,15,14,46,60,7,17,13,24,12,16,16,98,40,30,20,9,14,16,15,18,13,14,23,35,28,12,28,14,26,22,25,18,25,165,14,11,12,33,69,10,12,20,13,14,9,28,27,9,33,14,31,13,18,13,14,11,34,275,4,21,19,19,39,23,12,24,22,47,39,64,8,10,19,21,22,25,12,24,11,43,18,12,6,16,5,20,18,37,3,52,10,87,18,18,17,12,22,19,14,30,9,8,37,16,8,10,32,8,9,13,121,18,20,24,25,7,3,9,16,21,32,42,19,10,16,12,12,19,38,16,12,12,9,25,13,6,5,12,12,29,25,8,29,51,12,16,27,16,11,5,49,16,18,18,9,20,24,58,12,13,18,24,25,23,7,13,13,15,5,46,9,17,105,13,12,31,5,6,50,16,6,10,10,33,19,8,14,47,11,17,10,39,17,8,31,13,11,13,46,31,26,36,9,20,113,3,8,19,23,20,12,12,25,7,15,15,14,22,16,9,14,20,18 +C[C>G]A,107,119,62,134,125,199,28,215,138,107,56,96,112,61,111,71,103,91,120,53,95,76,85,53,57,58,41,117,53,110,88,99,85,110,35,86,58,25,119,87,72,51,157,31,89,28,55,114,105,200,36,56,69,90,67,40,83,177,63,73,47,93,105,43,99,88,183,301,66,61,147,132,73,113,105,103,92,15,28,16,15,12,20,5,16,19,38,23,14,12,22,53,10,10,10,15,4,8,14,64,25,28,9,7,13,5,9,14,13,10,16,27,14,4,19,13,20,14,13,11,20,110,17,9,12,11,40,6,11,14,5,7,3,22,19,11,15,12,9,10,19,7,4,6,16,245,5,22,13,8,23,11,13,16,9,15,15,49,2,3,16,17,11,13,6,9,17,33,8,10,5,9,5,5,20,28,5,44,5,54,23,7,15,8,12,6,13,16,7,1,14,8,4,3,21,4,2,7,97,11,8,19,19,14,2,6,7,15,16,27,14,8,21,9,8,15,19,5,9,7,10,18,4,6,6,12,3,13,20,4,8,40,8,10,21,9,6,12,35,18,21,12,5,16,20,37,2,7,11,20,13,9,4,3,10,7,13,19,6,10,48,8,8,16,11,6,35,3,7,7,5,37,9,8,7,38,5,14,6,33,5,4,31,13,12,12,18,24,15,25,8,10,116,5,4,5,13,11,5,1,13,7,7,7,8,12,17,3,14,13,9 +C[C>G]C,105,102,60,140,76,159,28,173,136,69,54,103,107,48,94,54,89,67,130,67,71,60,73,54,48,43,32,97,53,86,79,93,79,105,43,52,60,25,98,69,49,56,145,28,91,37,35,99,71,155,31,49,44,65,53,40,61,133,53,63,49,99,101,41,74,83,174,272,65,58,128,96,88,99,81,71,83,19,24,13,11,16,24,14,13,22,8,19,7,10,23,34,7,17,18,13,8,15,11,71,21,23,15,9,13,10,9,9,6,6,14,18,15,9,22,10,26,12,12,19,15,84,9,10,10,17,29,7,16,11,6,10,8,14,20,9,10,3,23,11,15,3,8,9,14,125,4,17,14,5,32,18,16,16,12,19,24,53,7,5,18,11,12,12,5,13,18,28,9,13,11,9,7,9,20,18,5,36,8,35,14,9,19,4,11,7,8,16,11,11,18,5,9,7,15,5,12,8,63,25,17,15,16,2,7,6,16,10,24,29,10,10,13,5,2,8,8,8,7,10,7,15,9,3,4,9,6,22,15,4,14,17,8,14,17,17,5,8,27,16,8,15,5,14,9,26,5,13,8,19,19,13,4,11,7,4,9,16,9,17,49,9,7,10,8,6,19,11,5,5,1,15,13,4,10,30,7,11,9,20,15,10,29,7,12,8,25,11,14,18,7,14,29,5,8,9,11,13,10,1,12,8,7,5,11,12,10,10,6,14,7 +C[C>G]G,40,78,21,55,36,60,14,73,33,27,22,30,27,12,32,18,27,37,49,25,20,25,42,20,11,20,11,18,18,46,38,26,23,17,10,37,11,5,30,16,20,14,64,4,30,9,16,46,32,44,17,14,26,47,18,12,25,47,15,31,8,46,35,11,39,34,69,119,21,6,44,42,26,44,24,32,33,20,17,5,6,3,11,4,6,9,5,21,12,7,8,22,2,6,4,4,4,6,7,17,8,12,8,6,4,9,5,4,2,4,6,7,6,6,3,6,5,6,8,9,8,56,2,5,7,5,28,8,11,9,6,4,3,5,8,3,9,3,9,4,4,3,3,8,6,59,2,11,17,2,10,4,5,13,6,4,17,18,2,4,7,5,6,0,2,11,6,15,2,7,1,4,6,5,4,7,3,14,3,24,9,9,13,4,6,4,4,14,2,5,9,2,3,7,8,2,0,6,39,6,7,12,9,0,2,2,5,4,13,6,3,3,3,3,6,10,9,4,2,6,4,6,3,3,5,4,5,4,5,7,6,24,5,5,5,4,4,5,4,5,1,11,3,7,2,15,4,5,4,7,10,2,3,2,5,3,3,8,5,3,11,3,0,10,5,7,3,7,1,2,3,9,7,2,5,16,3,5,3,10,5,3,21,3,4,2,11,5,3,11,3,1,8,2,5,5,9,6,6,5,7,8,8,3,7,5,4,7,6,6,4 +C[C>G]T,144,178,82,190,166,279,49,316,174,141,69,150,199,72,140,94,151,126,237,75,113,104,100,94,92,86,49,150,65,140,120,157,96,134,46,104,100,29,173,108,63,73,220,34,106,51,62,155,162,231,50,74,82,106,127,64,104,233,82,130,44,129,142,76,118,136,265,424,94,89,201,202,140,164,136,147,126,26,33,28,15,17,35,10,18,16,44,34,12,12,29,55,11,12,20,24,7,13,18,109,30,32,13,6,13,23,13,15,11,18,17,42,22,12,30,14,30,34,18,19,12,147,13,13,16,23,55,11,17,24,8,11,13,25,33,15,28,16,27,17,20,6,22,17,24,308,12,27,17,10,35,16,26,22,9,19,36,74,8,12,28,15,20,19,15,15,24,44,11,19,8,12,7,15,15,40,12,50,8,58,19,15,21,10,10,9,10,29,15,8,21,14,13,9,34,3,6,10,128,25,10,24,22,8,5,11,28,19,29,45,16,9,25,10,10,14,25,11,15,6,9,16,15,7,9,9,4,16,36,10,15,25,13,14,28,18,7,4,51,14,21,27,13,18,25,50,5,14,15,25,22,15,7,13,10,8,12,37,6,18,87,15,20,26,6,12,39,13,4,10,8,41,14,11,8,38,19,27,14,37,11,3,33,20,18,13,34,23,20,30,17,17,136,9,7,18,23,12,13,14,36,11,12,12,13,25,25,14,9,23,14 +G[C>G]A,63,82,31,76,69,117,16,129,75,46,29,64,72,23,56,49,62,49,95,41,63,38,48,28,49,35,27,75,27,68,68,53,63,77,23,40,37,16,64,55,40,30,97,21,51,38,28,69,55,170,17,31,38,83,55,28,54,92,41,56,32,51,76,29,50,65,131,187,34,50,90,77,61,81,47,79,62,6,17,16,12,8,13,4,13,10,6,13,6,4,15,24,3,12,5,9,7,7,7,50,4,17,6,4,6,7,7,5,5,4,10,19,15,4,5,4,11,13,12,10,10,60,5,9,8,14,29,9,8,7,6,6,4,18,6,6,11,4,11,11,13,1,8,6,8,102,3,11,6,3,17,7,6,5,5,7,18,20,4,3,8,6,13,10,3,8,8,19,8,11,2,8,8,8,16,11,3,22,4,30,8,7,9,3,6,7,5,10,7,5,6,6,6,1,13,6,1,4,57,10,4,8,12,5,4,8,14,4,10,20,10,4,6,5,5,10,13,2,4,3,3,8,10,4,3,7,4,9,10,5,10,19,9,7,14,9,5,7,13,12,16,13,1,8,8,35,3,3,6,9,10,7,4,5,2,5,9,18,6,7,35,7,6,12,3,2,23,4,1,5,1,17,4,1,5,20,2,13,2,12,7,3,17,13,9,4,15,7,16,15,2,9,30,6,5,16,7,8,5,5,12,1,2,4,6,14,7,4,7,7,11 +G[C>G]C,70,100,41,92,56,123,18,127,67,46,29,59,74,27,47,30,65,47,56,44,47,50,43,43,30,34,25,73,33,51,61,64,81,66,12,27,39,16,71,55,34,37,80,21,58,29,21,75,53,139,18,36,33,49,51,22,50,87,30,31,32,50,69,41,60,54,109,183,39,54,67,66,56,80,61,67,50,12,21,8,8,5,19,4,15,20,5,12,7,5,17,27,5,18,9,19,9,4,9,46,10,29,16,8,6,5,8,17,9,6,21,15,9,10,12,4,17,16,17,10,10,56,10,5,3,17,27,4,12,10,13,5,6,21,10,9,12,12,13,5,11,6,11,7,20,100,2,13,19,4,34,7,11,7,8,38,20,32,1,2,11,7,11,10,5,12,7,19,4,6,6,6,7,7,10,17,8,27,3,42,8,11,13,6,5,8,7,11,5,3,13,5,6,3,11,3,6,6,52,14,9,11,8,5,2,7,15,5,16,19,4,1,7,11,4,10,6,3,8,9,1,13,8,5,6,10,11,15,13,2,11,24,2,7,7,8,2,8,27,15,17,13,2,10,7,24,7,12,11,12,5,10,14,4,8,6,11,28,3,10,37,12,11,8,4,3,28,3,3,8,2,16,3,10,4,22,11,12,9,23,6,9,18,5,3,6,18,8,15,11,5,5,21,9,11,7,6,5,7,6,11,4,8,3,9,11,10,9,7,9,4 +G[C>G]G,25,29,10,20,15,25,4,54,16,18,12,16,24,8,18,7,17,23,19,8,11,8,10,7,15,2,9,17,8,26,20,14,11,13,3,12,2,4,16,10,2,5,29,3,9,1,2,27,18,32,3,4,13,19,25,6,13,20,5,12,3,18,18,4,25,17,39,55,15,3,36,14,14,18,6,20,21,9,3,2,11,2,3,0,1,3,1,7,1,2,4,5,2,6,2,2,1,5,4,8,7,2,5,1,2,0,4,2,1,1,3,2,6,1,5,1,1,1,0,4,2,16,2,0,5,5,5,2,1,4,0,1,2,1,5,2,2,0,2,3,1,2,1,5,5,23,0,2,6,1,6,2,4,3,2,3,5,8,1,3,6,1,2,2,2,0,1,3,2,1,2,1,1,1,3,3,2,6,2,2,4,3,4,1,2,3,2,2,0,2,3,3,1,0,0,0,2,1,11,1,1,3,3,1,1,2,4,1,9,2,2,0,2,3,1,1,2,4,1,0,0,3,2,0,1,2,2,0,1,0,3,8,2,1,2,3,4,1,4,2,0,0,0,1,4,7,1,0,3,3,3,4,4,1,2,0,4,6,3,1,3,3,2,1,2,1,4,2,0,3,5,2,2,2,3,9,1,1,2,2,3,1,4,1,1,0,3,1,2,1,2,4,3,0,1,2,1,3,1,0,3,1,2,1,4,5,2,2,1,4,3 +G[C>G]T,120,123,64,109,93,212,32,212,123,67,60,109,135,43,106,71,104,94,152,77,89,76,72,79,65,46,45,98,51,117,95,99,87,83,38,74,52,23,79,79,67,53,148,24,62,35,51,141,100,205,21,40,61,110,76,44,84,181,50,92,40,66,132,45,112,82,200,340,57,73,118,107,112,112,80,143,91,14,18,15,12,7,16,2,6,17,26,27,4,6,18,40,3,13,6,11,5,9,10,72,20,17,6,8,9,11,12,9,7,11,13,25,11,8,16,6,20,14,10,4,13,89,14,5,7,15,27,3,9,10,5,10,8,13,14,8,16,5,19,5,16,2,11,7,25,174,8,15,9,4,26,10,14,15,8,18,20,38,6,8,12,5,10,12,4,8,14,24,7,14,6,6,4,7,18,24,7,37,3,54,8,7,13,5,7,5,7,12,13,7,18,13,4,2,19,8,2,4,69,11,14,10,16,2,3,3,9,6,14,22,11,2,14,5,10,11,18,9,4,6,5,8,5,7,4,12,6,15,14,4,15,18,12,10,14,14,3,6,29,10,18,14,4,6,20,34,5,7,11,23,14,10,11,8,5,6,8,18,2,7,67,6,8,17,2,8,24,9,8,5,2,22,7,3,6,28,6,7,8,23,4,4,22,8,6,7,21,19,18,23,5,8,67,9,3,6,10,4,7,1,15,7,5,9,11,13,14,5,5,9,5 +T[C>G]A,198,295,313,238,841,626,59,628,284,1167,93,247,476,156,174,310,263,231,642,140,160,104,288,117,227,162,181,218,501,153,147,168,335,224,244,683,141,68,316,133,159,119,399,98,278,56,137,392,210,290,66,429,113,396,180,212,496,762,964,180,410,438,145,170,285,212,539,534,242,151,447,336,272,426,343,213,348,54,55,75,11,19,82,43,120,47,1719,837,71,48,50,269,9,21,26,26,8,40,13,110,213,173,28,3,21,29,10,23,33,18,78,82,43,15,29,29,86,29,41,23,37,2500,14,43,9,58,78,20,19,31,22,51,20,207,55,89,45,48,30,13,253,13,15,12,64,5314,6,47,95,16,55,95,383,635,12,48,25,111,10,29,227,43,26,28,67,14,18,129,277,33,7,19,11,137,89,107,5,81,11,724,47,10,22,15,318,15,93,29,23,13,26,49,27,11,146,8,35,27,2831,46,26,85,88,9,8,27,30,16,108,55,143,7,596,6,178,29,37,55,19,111,13,25,15,13,8,15,8,26,807,15,47,64,18,223,152,47,12,177,913,27,299,68,10,26,38,126,9,17,32,58,35,36,26,27,6,73,25,84,12,23,76,32,27,390,8,6,60,11,5,16,6,1924,81,74,27,117,14,400,7,448,12,16,75,22,24,108,130,682,27,57,24,21,6344,14,32,51,53,37,14,6,55,9,11,263,113,27,93,14,11,323,11 +T[C>G]C,159,176,145,192,371,438,37,354,222,388,65,149,242,97,128,131,141,156,322,107,103,82,132,85,143,90,100,184,159,127,127,149,143,158,87,200,94,40,173,111,92,81,253,41,123,67,70,209,106,201,42,155,96,175,137,92,183,428,258,116,137,192,122,99,162,158,316,375,168,112,231,208,192,202,228,131,200,37,47,34,17,24,48,14,41,26,326,181,23,19,33,117,9,15,24,22,11,23,26,79,56,85,20,6,24,19,14,20,11,11,24,58,27,11,21,12,46,19,21,15,14,539,6,21,10,35,73,10,13,14,8,20,12,53,26,29,23,13,20,15,82,9,14,11,33,1185,8,24,42,10,42,38,79,118,13,29,36,76,5,15,55,20,14,20,12,19,15,60,72,24,8,11,17,24,36,40,5,56,11,184,26,11,24,6,68,11,28,25,16,15,21,20,13,8,42,6,11,28,630,21,26,32,41,15,5,10,14,25,55,35,40,6,102,6,41,20,21,17,15,30,8,14,13,10,4,18,14,27,216,8,17,41,12,47,43,27,9,38,227,25,95,30,3,22,23,67,10,19,15,32,28,22,17,12,9,17,10,51,11,7,62,12,15,91,6,12,61,16,9,7,9,351,28,19,20,64,10,93,9,107,10,16,38,17,21,43,64,109,22,37,18,17,1316,11,17,24,27,12,9,8,44,10,15,56,44,29,38,13,12,73,13 +T[C>G]G,33,42,15,39,46,47,6,56,20,64,9,29,31,9,27,12,25,24,42,10,15,13,22,14,19,7,14,10,26,40,18,15,30,14,9,43,7,5,32,7,19,12,35,6,16,6,9,38,24,36,3,21,18,43,14,10,23,43,38,22,20,27,28,13,26,22,46,60,14,10,39,24,18,42,23,32,32,12,4,8,2,1,12,4,11,5,50,52,9,4,8,13,3,1,3,7,2,4,3,4,17,8,7,2,1,6,2,3,3,3,8,7,8,6,5,7,8,3,8,9,5,131,1,6,1,6,18,4,7,5,5,7,2,15,4,3,3,7,8,3,17,3,3,7,7,183,0,8,16,3,8,5,16,24,2,3,8,22,1,2,7,4,2,2,3,2,3,5,26,4,0,4,3,8,4,6,6,9,4,55,4,2,12,3,20,3,6,9,3,1,5,5,5,3,14,1,5,4,115,8,6,7,7,3,2,3,4,5,9,4,9,3,16,5,4,3,2,6,2,7,3,5,2,1,8,3,1,3,22,2,5,8,2,2,9,3,1,4,40,3,8,10,3,8,2,4,2,6,3,4,6,2,2,3,5,5,4,6,5,3,8,2,4,22,3,6,6,6,1,3,0,90,2,3,2,9,3,17,2,18,2,1,11,1,1,8,12,25,6,7,3,3,214,1,3,2,3,1,2,1,5,2,3,10,8,3,6,3,0,22,1 +T[C>G]T,325,461,419,404,1125,1032,116,1042,537,1415,132,383,727,284,314,385,389,345,1053,205,221,180,454,220,389,266,264,432,736,320,261,308,461,385,312,874,254,132,500,274,279,234,744,145,383,116,256,586,330,459,121,553,210,552,407,293,659,1147,984,302,498,507,325,244,447,345,842,906,444,302,700,595,436,636,586,403,565,85,124,134,16,44,159,45,169,69,2158,961,115,78,101,410,26,42,54,38,28,58,35,220,287,255,51,11,41,46,24,42,60,27,105,131,76,30,45,36,168,44,67,37,65,2598,29,56,27,97,126,23,27,61,34,55,31,304,69,122,103,90,63,34,471,16,34,25,108,6306,21,69,141,36,125,132,410,696,30,130,55,231,25,55,335,64,42,53,78,30,38,184,359,53,17,31,28,202,112,151,10,141,26,1023,101,28,47,30,409,19,109,77,44,16,71,61,20,15,180,11,33,62,3026,47,50,123,138,33,19,34,51,44,135,85,154,17,751,19,206,58,69,121,30,136,26,39,36,21,28,35,16,68,1005,37,69,106,27,284,229,102,17,192,1137,41,378,147,13,70,76,236,13,37,45,101,66,59,33,38,30,113,39,148,19,30,178,52,50,590,15,22,121,29,6,31,14,2337,109,93,34,182,17,420,23,526,20,32,153,39,38,137,206,939,68,124,59,35,8403,27,57,67,81,38,22,15,94,13,29,350,148,69,168,26,26,458,39 +A[C>T]A,229,205,101,213,89,249,53,284,662,122,60,115,230,106,198,105,182,135,204,124,160,105,129,112,101,141,77,230,65,180,194,197,142,151,63,155,74,49,260,158,120,124,191,54,152,92,68,145,166,224,57,117,74,165,118,82,160,181,88,159,121,123,114,98,98,140,242,420,93,89,225,162,136,153,168,152,110,49,98,38,57,54,82,21,56,62,68,116,54,39,94,130,34,61,44,54,25,44,61,154,92,104,58,29,57,48,47,60,46,30,83,102,86,46,53,50,63,36,58,50,52,260,51,34,54,49,181,36,64,52,38,31,34,62,52,43,46,51,100,43,59,23,43,45,74,296,23,95,56,34,102,54,62,46,47,83,104,192,20,41,68,44,60,39,21,42,55,72,45,60,26,37,40,58,67,85,27,83,28,132,43,57,88,39,64,29,48,56,36,29,63,45,34,46,115,28,22,37,182,66,43,55,38,22,14,38,50,37,68,48,48,17,57,38,31,53,83,54,42,39,32,63,53,21,42,48,35,87,48,44,58,118,33,36,49,61,24,30,86,73,58,91,20,65,65,124,44,55,50,72,56,32,31,25,41,40,30,110,23,54,113,47,45,73,26,34,106,88,34,35,25,88,52,46,46,125,32,71,23,54,36,35,70,42,53,43,77,53,73,91,32,49,287,30,23,31,45,47,39,27,40,22,37,34,53,65,62,27,40,37,50 +A[C>T]C,89,102,60,117,46,148,29,148,553,55,34,57,149,61,84,55,95,50,107,64,57,61,55,56,60,53,39,104,60,96,68,105,80,80,45,63,48,27,128,70,56,57,80,31,99,51,32,74,84,100,34,78,37,77,75,35,58,102,35,78,63,60,69,50,66,73,127,219,70,44,141,76,86,86,75,66,70,31,55,26,28,38,81,14,22,37,27,63,25,34,63,69,18,42,35,36,20,30,39,72,68,47,48,24,50,43,27,38,17,25,37,56,44,19,60,38,49,35,35,31,42,145,33,17,21,30,105,23,46,30,29,30,23,37,29,19,32,29,52,29,37,8,29,16,44,136,20,63,27,20,59,34,32,17,26,58,42,84,6,28,43,26,34,23,10,38,38,58,29,38,16,20,39,25,48,32,13,55,19,55,40,23,59,27,31,22,33,33,9,21,36,27,27,23,64,14,14,19,97,31,30,29,53,18,7,19,20,30,64,28,31,16,36,22,16,31,35,35,20,29,26,32,23,18,22,21,19,44,38,22,25,57,17,25,23,36,25,23,42,41,30,62,13,40,46,77,24,17,50,46,37,34,40,12,24,24,25,90,17,27,60,27,35,46,17,23,86,42,20,27,13,35,23,25,39,75,25,30,19,35,29,20,45,24,33,17,49,27,52,55,17,43,96,19,19,16,31,29,30,18,23,10,23,22,52,47,48,26,33,24,28 +A[C>T]G,178,150,137,227,118,261,56,327,208,123,76,147,165,205,261,92,189,162,185,99,109,105,157,109,106,87,100,206,102,166,150,181,93,190,77,125,77,75,298,141,109,114,168,88,149,126,76,199,124,216,99,141,93,161,119,96,164,172,123,180,118,174,144,63,112,163,288,353,132,184,253,189,149,144,233,161,128,100,274,140,134,214,184,81,126,146,71,169,86,99,308,241,91,226,160,187,131,136,136,259,260,335,125,135,201,148,91,122,89,102,152,311,322,221,191,122,268,126,214,172,195,451,136,89,110,104,441,104,279,75,78,69,115,121,119,102,156,152,271,108,159,57,119,101,237,335,72,146,139,72,298,176,134,93,120,285,244,423,64,97,147,107,117,130,61,91,160,160,79,126,103,121,92,128,216,207,68,110,58,188,119,129,243,128,133,103,84,184,108,109,243,124,110,114,446,71,81,102,472,176,147,169,258,82,58,89,105,125,151,133,135,56,127,81,70,127,201,170,130,110,123,132,114,83,102,88,132,188,109,175,91,202,102,82,114,195,72,62,103,159,127,270,52,148,193,378,151,111,154,229,252,124,129,47,118,81,144,170,98,123,150,105,156,168,89,110,207,170,88,91,101,97,140,125,132,239,124,112,59,124,87,93,167,86,96,106,95,169,344,198,112,123,204,119,86,84,366,232,71,50,196,82,86,75,218,157,206,130,91,89,133 +A[C>T]T,186,197,86,169,115,192,37,284,223,99,43,113,232,89,174,88,143,101,190,109,118,104,123,102,83,101,67,221,84,185,183,161,112,166,67,122,78,41,196,125,81,100,186,38,110,74,67,143,135,168,66,118,55,194,130,64,144,156,71,129,61,96,129,61,84,104,200,421,95,118,239,102,167,150,156,155,101,42,67,37,45,38,59,15,40,31,39,90,29,25,68,97,23,52,33,50,17,26,44,124,53,78,37,28,35,30,27,30,26,25,63,62,43,25,41,41,53,34,45,37,49,166,38,15,24,28,123,24,47,38,27,19,27,30,28,15,32,32,57,25,45,9,22,22,58,166,18,68,34,10,111,37,36,35,33,35,75,139,20,28,51,31,33,28,16,41,38,46,25,36,22,31,29,29,48,62,17,57,19,52,40,28,61,19,31,19,42,40,24,17,38,30,22,33,61,16,17,20,105,39,32,34,36,26,17,23,36,27,51,33,18,15,26,21,13,39,40,35,34,23,18,22,30,12,25,35,37,40,33,25,29,85,32,30,39,43,18,18,66,46,28,74,9,37,51,84,24,41,51,47,45,29,23,15,17,24,22,89,17,23,86,21,29,42,9,24,60,43,27,32,18,47,26,26,21,94,28,47,23,36,25,28,46,22,40,25,54,46,35,65,15,47,111,23,28,13,48,34,25,21,29,18,21,17,47,37,42,24,33,29,30 +C[C>T]A,136,122,90,141,70,270,38,229,706,112,62,83,187,78,97,68,150,85,159,79,84,48,87,63,80,104,74,176,86,105,139,153,130,119,60,94,68,30,190,123,92,98,137,52,187,63,32,100,87,126,65,122,55,123,85,51,120,176,103,119,74,86,84,66,77,115,168,304,106,73,223,114,133,139,141,117,100,48,110,34,52,66,100,27,68,50,98,124,33,33,93,125,35,50,41,68,24,39,69,136,105,121,53,39,59,64,49,53,43,37,60,98,58,41,67,44,87,30,67,51,53,258,41,26,38,40,121,28,64,59,33,32,43,58,52,32,39,46,78,21,67,19,37,24,66,338,22,80,49,22,89,62,67,52,39,85,93,162,16,49,55,44,49,36,20,49,42,71,57,63,30,40,37,64,52,65,26,74,27,119,41,44,110,39,60,29,51,57,27,29,42,43,32,35,98,20,15,29,171,45,39,43,60,34,9,26,48,41,74,63,45,19,80,31,30,49,57,50,33,45,28,42,42,31,28,48,47,66,95,40,50,84,34,38,68,55,16,30,117,54,60,99,23,51,39,96,47,36,44,74,59,51,51,28,35,37,30,133,30,40,82,44,42,76,16,29,91,83,22,42,34,125,56,33,47,125,31,72,29,61,38,30,53,40,58,36,63,67,57,78,38,60,587,41,18,22,47,37,30,20,47,24,32,36,65,56,62,42,48,63,46 +C[C>T]C,124,97,78,103,63,153,27,192,649,63,49,72,209,80,107,39,117,68,87,65,57,46,55,52,48,84,64,144,46,77,92,116,89,271,54,80,52,36,141,104,68,79,110,23,115,56,26,78,80,107,73,102,40,76,62,38,92,115,68,86,78,68,67,47,73,75,115,267,84,77,253,75,118,243,105,72,72,37,81,35,55,33,88,18,43,41,46,70,33,49,66,82,23,45,37,45,32,36,64,106,66,82,42,28,76,60,38,37,25,45,48,80,70,47,68,45,78,31,70,49,40,138,49,18,38,48,90,26,65,39,27,39,26,66,49,27,40,43,75,42,47,19,23,42,52,150,21,80,40,28,83,39,43,25,47,77,74,145,17,32,53,39,64,32,17,53,42,59,42,47,21,28,34,42,59,59,23,66,21,74,33,41,87,47,53,20,35,66,35,34,59,45,31,28,71,18,10,15,119,47,50,44,61,26,17,20,47,52,61,54,40,19,42,31,17,46,59,50,38,37,35,41,30,39,39,26,49,72,51,42,45,58,40,28,43,54,31,36,61,42,42,104,10,49,48,86,46,37,56,62,51,53,56,21,39,29,20,111,40,39,65,33,36,53,28,33,88,53,30,40,21,47,37,35,43,104,24,48,33,31,46,37,37,36,45,29,62,41,74,86,26,61,150,30,22,28,45,33,33,13,38,27,37,32,54,67,44,28,32,36,49 +C[C>T]G,144,111,77,147,89,193,66,222,176,90,51,98,142,116,167,54,124,117,111,65,64,66,98,74,64,67,53,93,75,102,106,124,86,120,46,104,41,35,181,98,88,70,127,48,86,69,69,127,69,146,71,90,65,105,78,46,94,135,94,107,73,116,100,57,75,84,212,255,92,117,165,138,87,100,122,111,80,55,158,85,102,111,137,67,81,93,62,141,72,73,176,140,61,131,84,126,73,79,100,152,160,194,80,83,126,103,69,81,65,80,89,145,173,150,109,94,148,84,160,95,83,301,88,56,65,64,293,75,175,56,65,38,73,70,80,65,105,106,180,61,120,36,68,70,148,243,37,118,82,43,182,106,93,52,68,152,173,302,44,77,94,80,92,85,41,66,100,110,63,67,51,82,47,100,135,118,48,82,36,118,72,84,126,81,107,56,88,123,73,63,128,62,73,65,267,62,59,46,283,98,93,106,144,60,31,41,73,84,97,74,61,51,90,36,43,63,110,105,83,75,72,90,57,51,64,72,82,110,92,101,52,130,48,54,80,113,42,44,82,114,83,166,38,101,103,224,82,57,96,175,155,78,84,61,68,42,86,152,60,72,96,73,99,105,60,61,117,116,56,63,63,85,103,80,85,173,87,103,52,70,58,72,103,44,80,70,81,95,222,105,58,86,240,72,57,54,198,147,59,38,109,49,53,50,112,114,136,76,58,94,76 +C[C>T]T,197,215,113,234,127,266,49,297,348,117,72,134,312,127,202,72,184,116,177,114,120,96,131,103,109,117,98,228,98,175,194,164,170,256,86,139,76,55,219,147,98,118,176,47,171,70,66,151,174,179,89,129,78,171,135,82,135,207,104,119,104,129,127,96,85,137,221,504,119,90,327,114,166,260,237,156,114,43,107,52,43,42,118,26,65,65,71,131,38,34,94,120,36,64,41,49,30,49,68,151,108,89,53,36,67,52,55,55,28,50,66,79,75,44,58,53,83,34,68,59,50,221,40,31,39,52,129,37,73,62,35,31,38,84,52,36,48,43,111,35,68,20,45,29,74,280,19,74,60,21,102,55,48,44,49,60,97,144,22,50,61,59,70,38,21,55,39,73,57,49,29,35,46,53,64,72,32,78,45,91,51,48,91,31,58,32,65,50,25,38,54,40,26,38,103,27,20,30,151,44,52,41,61,27,14,32,52,46,62,56,43,29,70,38,20,52,71,49,40,45,33,61,38,27,37,46,43,65,68,47,46,99,43,34,59,55,22,21,90,60,49,104,20,55,59,94,46,51,70,72,52,65,49,31,37,37,25,124,38,37,96,50,49,65,24,37,95,75,29,41,32,92,60,39,56,129,34,58,31,54,43,32,70,42,47,26,76,58,67,74,29,59,282,38,25,32,76,54,36,21,47,25,25,35,77,57,62,32,49,56,61 +G[C>T]A,141,133,92,123,74,236,35,191,494,74,38,73,164,77,115,76,131,103,134,93,75,101,99,60,72,86,64,159,59,110,112,142,108,92,48,96,52,39,159,119,75,88,164,48,90,81,50,99,91,145,53,123,55,96,88,60,87,140,61,127,96,90,121,65,67,96,149,322,88,76,156,156,106,113,127,131,74,40,113,37,51,52,82,21,57,61,73,92,27,34,72,125,28,70,44,55,28,32,52,135,88,97,49,38,54,71,29,50,28,26,56,126,64,34,69,44,87,33,50,66,52,217,37,23,42,34,115,19,61,50,28,28,27,59,42,31,36,38,69,40,54,16,34,29,70,255,20,56,43,19,87,43,47,43,39,73,89,146,16,31,52,28,49,33,19,50,34,54,46,53,22,36,40,52,52,57,32,73,31,68,41,24,84,44,52,18,37,48,22,27,37,34,31,27,84,24,18,19,139,50,46,65,40,20,19,19,52,38,61,35,43,16,72,25,23,60,74,48,40,30,28,44,31,20,16,31,46,59,60,44,47,74,31,34,51,42,21,41,88,45,59,78,16,48,34,91,29,31,67,55,54,33,46,22,34,32,37,102,39,41,83,29,48,63,21,33,81,61,20,37,37,71,58,21,49,105,36,58,22,36,32,28,36,33,41,21,43,59,57,49,31,63,283,26,18,32,48,30,29,21,36,25,32,30,43,34,63,33,31,38,33 +G[C>T]C,99,95,77,115,56,145,35,173,503,75,27,64,142,73,84,67,127,73,87,51,58,65,74,61,57,74,48,139,61,83,92,109,77,114,56,73,65,46,134,86,73,73,97,34,83,68,40,82,60,141,72,76,38,78,80,39,80,101,52,103,84,80,80,50,60,74,123,226,78,74,147,88,99,92,105,80,71,32,77,33,36,48,87,22,53,42,34,72,45,30,78,90,35,72,42,55,33,37,59,110,76,76,49,31,62,63,48,50,21,45,50,88,62,26,52,41,61,35,56,53,38,156,45,23,24,38,125,31,50,31,22,18,28,60,43,25,30,35,68,33,49,16,27,36,57,168,12,56,40,12,84,48,42,28,33,65,75,99,17,31,59,42,47,46,9,40,36,63,29,49,29,28,37,47,63,54,21,60,23,69,30,20,72,35,44,16,30,46,27,33,52,39,22,39,85,29,18,22,115,40,46,45,39,23,12,24,43,33,59,44,44,21,45,26,12,52,76,35,31,29,36,32,32,31,31,24,39,56,49,36,34,76,28,21,41,59,18,24,64,46,31,66,9,51,29,112,43,46,47,57,51,47,54,17,42,35,20,109,25,40,70,37,21,40,20,30,68,57,32,52,36,41,37,30,41,88,33,43,27,34,25,32,38,24,31,28,46,40,53,64,30,41,130,35,27,25,51,38,20,19,43,20,33,12,54,59,51,32,31,31,47 +G[C>T]G,180,138,110,183,89,207,49,256,160,98,58,91,111,160,187,61,151,97,127,84,77,82,107,77,72,63,92,163,73,121,128,140,85,143,69,71,50,50,187,106,93,101,136,63,116,96,52,137,109,203,110,73,53,103,75,56,124,153,102,128,90,116,100,74,74,123,224,292,120,162,215,127,107,114,145,104,96,82,194,109,119,136,154,58,107,117,56,142,81,65,285,178,68,198,116,157,92,111,124,227,222,227,109,131,164,112,63,98,73,63,132,168,213,164,154,114,179,83,176,93,142,341,89,90,82,85,363,83,210,76,62,46,90,107,108,87,106,124,211,66,133,55,69,82,183,259,56,116,110,45,219,124,139,63,115,222,221,301,50,87,109,95,101,101,46,59,114,138,73,92,75,82,66,101,152,140,66,94,69,185,81,115,159,92,112,65,114,142,91,63,181,89,68,77,294,61,58,49,348,124,132,122,205,65,45,58,90,87,128,101,73,65,101,56,47,89,166,114,105,84,91,82,74,67,76,70,139,110,90,133,67,154,70,51,98,155,40,62,84,117,102,226,37,108,157,336,112,67,111,188,193,84,129,55,84,56,94,169,80,74,126,82,109,114,69,75,135,155,60,56,82,77,124,81,87,177,103,104,43,88,82,70,114,55,89,66,97,118,277,109,58,122,177,63,50,46,249,178,59,44,138,62,63,61,155,135,165,104,92,70,94 +G[C>T]T,163,105,64,134,66,142,33,194,192,90,35,91,165,70,107,53,106,82,136,56,81,80,86,64,72,75,62,150,57,109,117,129,111,106,48,103,52,35,149,93,72,76,106,46,88,51,50,101,96,121,58,69,34,128,86,41,92,107,62,101,69,86,102,55,72,95,144,268,75,55,134,118,111,91,107,94,80,25,75,31,39,33,74,17,30,39,35,66,25,26,68,110,18,40,49,42,22,36,45,113,80,73,30,32,56,42,43,41,28,33,43,69,54,35,50,40,68,29,43,44,48,156,33,9,28,42,104,27,50,29,32,23,28,56,29,18,31,34,74,35,46,16,28,29,59,173,17,55,32,15,84,36,38,30,39,60,77,116,17,17,47,37,40,33,23,36,30,51,32,30,22,24,32,35,48,54,28,58,18,70,34,26,52,27,43,20,32,46,19,23,31,42,16,21,74,15,13,18,109,32,31,30,28,19,11,27,35,32,56,36,38,21,43,25,12,47,61,29,23,27,23,36,27,13,22,24,37,43,53,24,34,68,39,27,36,39,14,30,56,43,34,83,21,44,46,98,45,27,37,66,39,38,35,22,33,23,29,110,20,39,73,21,30,43,22,38,71,48,29,30,24,53,37,22,29,92,19,39,26,35,32,32,50,22,40,26,53,41,46,71,29,35,128,29,24,26,53,37,23,18,22,22,23,18,61,48,55,24,34,32,34 +T[C>T]A,188,202,331,220,321,538,53,464,864,902,59,137,505,297,136,160,321,157,463,115,102,95,241,95,214,295,209,334,656,134,442,210,342,271,307,628,179,77,444,215,173,186,248,135,413,94,104,249,151,155,113,541,72,271,165,224,453,424,786,153,635,483,115,190,180,152,370,424,290,143,588,226,325,343,275,154,161,67,311,155,39,59,233,115,404,105,2477,1923,193,187,134,558,45,83,106,86,60,64,108,169,787,428,58,52,110,120,66,47,90,50,270,160,109,50,107,72,195,46,159,89,76,2956,49,77,23,140,155,106,110,117,92,82,58,328,92,119,102,202,169,160,886,42,33,60,115,4503,16,93,159,27,168,166,421,714,62,124,105,366,22,176,352,107,77,51,164,51,87,190,768,162,33,76,47,544,141,127,40,111,38,1122,90,27,189,80,850,26,211,92,57,41,83,119,40,55,608,37,102,74,2623,91,74,234,211,64,17,57,61,68,288,103,180,21,1592,28,284,116,107,288,56,336,46,66,37,47,28,72,67,110,1487,83,111,123,35,637,278,81,21,374,1617,84,485,688,26,110,105,191,44,65,156,129,120,146,164,49,54,257,44,291,83,84,99,82,96,612,37,84,126,203,31,121,43,2783,213,185,166,333,34,1026,48,390,47,99,100,66,288,201,308,860,104,179,72,76,14895,141,98,44,94,145,35,31,108,44,40,604,214,94,195,50,70,675,86 +T[C>T]C,153,142,172,163,148,329,37,304,918,261,56,114,327,117,103,91,162,88,242,92,62,66,109,84,142,208,91,231,200,84,258,164,150,217,103,192,83,62,202,179,114,120,186,73,205,87,51,133,108,130,87,185,47,136,128,94,186,230,191,131,198,163,99,104,93,112,254,296,172,121,319,146,214,176,171,92,125,55,177,75,51,61,188,42,120,63,522,454,79,71,102,232,40,56,61,65,41,52,77,132,286,199,57,40,89,77,62,54,55,51,131,149,87,54,94,59,118,52,104,61,76,646,57,43,34,63,167,44,71,91,42,36,48,125,73,60,54,73,119,81,246,25,44,38,94,908,28,63,73,24,127,79,120,156,49,92,77,255,25,59,113,81,80,47,60,67,54,97,200,84,35,48,55,146,91,88,27,98,31,323,50,34,180,62,204,41,98,67,47,40,71,66,34,29,206,33,47,29,543,66,59,96,96,26,19,26,55,65,110,68,92,25,385,29,55,80,85,101,46,107,52,52,44,31,30,54,62,103,327,57,81,108,38,144,112,83,37,77,314,73,137,245,20,69,54,186,44,61,83,96,74,103,110,37,49,76,33,214,57,65,95,52,89,156,27,56,140,157,34,57,27,496,104,67,75,183,32,206,35,100,38,41,73,34,95,68,111,186,100,106,56,64,2808,53,50,32,88,77,41,23,81,29,50,143,118,91,93,41,59,170,67 +T[C>T]G,93,75,60,106,77,141,30,162,154,103,43,72,104,90,77,39,76,74,115,50,47,50,90,59,72,59,55,89,69,73,88,89,64,91,48,96,44,31,118,62,51,50,84,31,68,55,55,87,69,91,48,86,37,90,74,47,108,102,119,75,112,103,77,48,72,67,126,157,70,94,116,84,87,90,90,64,64,53,115,78,50,68,111,56,111,71,345,308,72,49,126,135,47,89,70,81,73,76,79,85,233,211,55,50,85,68,43,53,68,52,125,129,123,85,80,68,113,48,106,61,82,546,57,44,50,71,203,65,142,50,46,34,62,86,62,73,62,93,143,72,202,27,51,51,107,510,27,65,86,20,174,80,131,125,52,105,143,191,25,66,92,47,62,50,63,39,65,74,114,71,34,48,40,131,111,89,35,53,25,262,56,64,102,67,182,29,85,82,49,61,81,52,36,57,252,45,37,60,464,87,75,93,129,36,20,51,55,48,106,43,79,34,241,27,43,67,88,112,55,103,61,60,30,29,45,55,76,78,250,71,48,89,36,103,100,76,37,67,201,77,98,219,18,76,94,184,61,47,94,121,118,56,69,39,57,74,60,140,43,51,75,60,74,138,36,50,69,79,37,51,54,311,118,86,81,123,49,206,49,74,37,53,82,32,76,48,76,197,135,89,33,79,1587,64,61,42,160,93,45,31,61,36,42,127,108,91,92,49,51,157,63 +T[C>T]T,184,229,210,195,187,439,53,373,463,378,65,128,390,195,139,118,219,102,332,109,104,89,138,109,158,250,158,320,408,139,398,215,264,295,184,314,115,61,316,183,168,169,233,105,255,108,60,177,157,148,83,267,75,208,166,120,316,303,410,146,338,252,108,116,97,135,347,443,231,140,428,227,305,280,217,136,138,71,246,95,61,58,216,76,207,66,1249,1089,137,103,114,349,36,50,56,72,41,58,93,143,501,288,49,34,80,120,58,66,55,49,187,142,95,43,79,57,148,45,115,73,62,1414,63,56,35,93,168,93,87,115,70,49,55,182,78,76,67,135,139,104,497,32,41,44,91,1831,17,99,131,24,151,115,274,358,47,118,115,295,24,110,252,99,79,33,98,44,66,112,421,97,28,52,43,347,133,119,44,97,34,712,80,42,170,47,492,18,104,73,47,40,62,79,40,34,390,28,69,47,1150,59,61,158,143,54,19,49,48,61,162,77,128,22,814,19,122,89,91,212,40,226,51,65,43,38,28,67,69,113,792,57,73,94,43,344,214,91,34,190,681,82,240,486,33,65,76,159,42,62,109,99,102,103,138,49,55,169,48,257,57,55,97,46,88,326,39,51,131,175,34,106,33,1325,153,142,124,222,51,515,38,212,49,81,90,45,203,124,183,455,100,126,75,80,6976,112,67,48,76,100,41,12,77,33,31,310,139,96,133,36,50,372,72 +A[T>A]A,105,106,54,76,57,103,15,126,77,49,38,38,91,54,84,40,112,49,90,69,51,43,51,51,46,43,38,151,32,56,74,92,98,95,46,62,36,31,104,79,58,54,90,24,99,47,24,59,73,122,40,72,38,77,55,29,69,101,32,70,33,58,38,43,56,59,121,204,43,57,124,65,104,72,77,79,44,21,37,26,15,24,32,11,20,30,14,33,32,18,37,84,13,43,24,25,16,22,20,63,68,80,17,21,21,18,20,27,8,24,34,47,29,19,26,29,38,38,30,28,14,108,19,25,22,17,69,15,25,25,13,15,12,29,31,23,10,23,56,6,28,11,11,14,35,92,9,26,18,9,54,20,22,19,19,57,40,84,8,14,28,23,32,32,11,25,29,57,16,19,10,12,17,28,37,30,14,52,11,57,20,9,29,10,13,16,21,31,12,16,31,31,19,18,43,9,14,12,73,32,11,32,26,10,13,14,23,22,21,37,24,9,20,12,4,33,42,20,9,15,11,26,19,18,8,15,13,33,10,21,22,45,18,13,25,26,10,13,45,21,24,34,13,22,23,61,13,12,39,25,28,29,20,15,24,19,20,46,14,19,54,24,31,18,9,13,56,18,15,18,8,29,21,20,15,60,23,28,12,16,21,15,24,24,25,14,32,25,33,29,15,30,25,20,22,15,26,20,26,12,29,8,15,5,22,35,26,20,19,18,25 +A[T>A]C,90,103,62,74,40,94,19,96,38,37,26,52,96,60,55,55,87,41,75,64,48,36,53,27,37,33,26,95,38,64,62,79,66,95,43,54,41,26,78,72,53,47,57,20,75,44,24,50,74,81,41,51,32,69,46,23,39,61,50,59,58,48,56,43,44,52,114,173,46,45,121,39,77,59,66,58,47,15,36,27,23,31,29,14,20,24,9,38,17,20,25,47,15,29,26,16,20,30,26,72,32,43,23,23,30,27,27,29,13,24,24,45,48,16,29,16,37,30,40,27,21,85,23,23,18,37,44,21,36,24,11,24,14,37,33,24,18,24,50,20,21,11,17,13,37,74,13,29,22,9,44,29,26,20,22,33,26,76,16,21,27,21,20,15,8,34,32,37,21,19,12,17,14,27,27,30,16,46,13,41,26,10,34,24,17,12,26,34,17,16,31,19,14,15,51,20,9,17,58,33,23,32,34,14,13,14,36,29,36,23,24,16,16,16,5,40,54,18,21,13,12,24,16,17,31,24,9,32,24,25,21,28,21,9,25,29,20,8,38,27,12,44,10,40,29,34,15,24,38,26,31,31,13,10,16,20,8,43,19,19,48,17,29,27,16,17,38,15,16,20,15,18,13,17,22,59,20,18,11,13,21,12,19,23,18,13,25,25,34,35,17,34,32,17,6,14,43,30,12,9,12,10,16,6,30,29,34,29,13,24,14 +A[T>A]G,126,113,56,100,60,132,29,159,67,56,45,61,96,71,100,53,97,42,92,70,56,60,66,48,35,54,43,152,38,91,91,101,71,80,50,85,43,28,103,82,35,47,83,30,99,40,43,78,83,135,44,64,52,69,86,36,60,75,38,68,44,74,58,56,58,66,99,231,51,56,136,62,93,77,68,75,42,17,58,25,15,23,36,16,23,27,12,36,15,14,50,64,20,36,24,26,25,22,27,75,29,41,24,16,21,20,22,26,10,28,25,49,41,16,25,21,37,26,32,28,29,90,28,18,30,18,46,22,27,12,14,14,10,31,36,15,22,15,33,15,30,10,17,18,24,93,16,25,12,18,38,30,25,22,29,42,40,74,15,14,20,18,14,28,5,26,27,51,12,31,14,25,13,23,24,42,7,43,5,47,23,17,31,14,22,17,14,38,12,17,43,24,14,17,44,9,10,9,62,25,16,22,19,18,12,17,13,19,18,33,16,12,12,16,13,29,41,23,18,7,16,29,15,16,24,20,15,30,16,17,15,27,24,18,15,16,13,6,37,32,32,55,14,23,18,31,20,24,19,31,17,19,12,19,14,15,21,31,23,19,48,19,19,24,13,18,39,19,6,16,14,20,13,21,14,49,13,24,12,17,25,12,21,14,26,9,37,23,34,32,8,31,29,13,10,12,36,23,21,6,17,14,15,13,18,27,22,22,19,22,16 +A[T>A]T,174,189,101,136,94,176,31,239,95,81,63,126,163,103,117,59,146,78,129,91,84,88,91,72,46,66,57,194,34,125,120,141,106,142,71,104,53,35,176,94,56,91,126,39,127,85,48,112,111,174,45,86,64,124,82,46,85,112,54,110,86,90,79,63,63,72,178,390,84,70,171,85,118,93,112,100,82,30,64,51,31,47,63,27,33,41,18,72,35,25,58,84,32,43,33,51,22,32,47,114,90,60,48,29,64,23,22,39,27,28,32,87,89,27,62,37,61,47,51,34,59,171,42,30,25,56,100,18,63,34,29,34,23,45,50,45,33,20,83,32,42,21,34,26,58,154,13,36,49,12,89,53,28,26,28,57,67,163,20,31,43,39,36,43,24,24,39,79,26,39,19,33,43,35,48,89,10,62,24,94,37,31,53,21,26,15,36,41,19,29,42,28,29,34,66,27,18,26,87,39,44,42,35,18,10,25,36,30,68,43,37,20,25,24,11,50,71,33,23,14,16,27,23,19,25,30,38,47,24,34,39,87,25,16,44,25,7,10,81,28,29,66,23,29,44,104,29,27,38,46,33,33,20,24,29,35,19,91,36,23,71,30,38,45,19,13,89,31,18,20,15,37,28,22,29,120,30,27,19,25,33,20,47,31,34,18,49,36,63,48,22,41,48,25,15,32,39,38,26,23,28,24,17,10,41,39,33,29,16,31,24 +C[T>A]A,116,88,41,91,74,118,15,110,54,51,42,55,80,42,81,44,80,50,109,55,58,37,56,44,35,31,45,98,28,69,85,93,85,75,32,86,33,26,74,41,44,35,70,31,68,41,23,61,85,119,23,55,34,86,63,37,52,91,29,62,28,48,59,40,41,57,124,215,44,40,115,59,71,81,84,78,33,12,20,22,7,10,20,7,7,16,3,21,7,4,21,39,3,17,17,8,2,10,12,75,15,18,9,4,7,9,9,6,3,4,9,25,11,4,18,11,24,19,14,11,11,65,9,3,8,15,28,0,13,18,5,11,4,6,11,3,3,4,17,5,16,3,10,3,19,70,4,13,8,7,22,13,8,10,8,17,20,49,5,3,22,7,17,13,3,16,11,35,7,8,4,6,10,15,20,25,2,29,0,24,19,13,9,4,6,6,13,12,4,3,24,10,5,2,17,4,0,8,36,12,3,10,12,6,3,8,9,6,15,17,11,4,7,5,5,13,13,9,4,4,3,11,8,5,9,7,8,10,9,3,22,19,9,6,9,10,2,4,26,8,16,16,4,9,12,19,11,8,10,16,14,14,3,8,5,5,10,25,2,8,33,3,3,16,6,10,22,11,2,9,9,13,6,7,6,34,6,10,4,14,9,5,17,8,6,8,23,11,13,11,5,9,8,2,5,4,7,11,5,4,8,3,3,3,3,15,16,6,6,2,8 +C[T>A]C,139,153,55,142,79,167,32,224,69,63,47,89,121,87,124,63,111,59,105,84,74,57,82,54,56,57,51,138,44,119,107,108,90,91,55,98,47,26,144,71,50,53,100,29,101,62,27,77,100,104,52,63,38,93,82,48,95,107,59,95,43,77,69,60,59,69,128,281,58,54,195,82,102,105,123,95,59,7,34,17,11,14,30,10,16,11,8,28,6,11,27,50,13,20,23,27,10,18,11,80,33,23,12,10,14,8,10,16,8,14,12,29,21,18,31,14,37,19,16,8,13,105,12,8,10,18,42,7,21,20,7,6,8,23,16,10,11,10,33,8,18,8,12,13,40,93,5,16,13,10,38,14,23,12,11,25,30,103,7,10,23,14,18,14,6,13,24,30,13,14,3,11,7,10,22,21,8,51,7,44,19,17,25,10,10,6,18,20,11,5,20,7,10,9,30,6,3,11,44,18,9,31,13,13,5,12,18,14,32,22,12,5,11,14,5,22,29,15,12,8,9,12,8,11,11,15,11,29,22,14,17,36,11,9,16,9,5,5,39,10,16,28,9,13,22,30,6,13,14,23,18,14,7,11,6,15,12,13,7,9,63,9,16,21,8,10,37,11,6,4,2,13,11,11,13,37,3,16,8,15,15,2,15,13,10,3,35,11,18,24,14,19,20,2,11,10,18,12,13,8,18,14,7,9,7,16,19,15,4,14,11 +C[T>A]G,145,142,77,129,73,173,19,195,84,82,59,87,109,78,126,52,80,74,116,92,76,60,81,51,57,46,65,106,48,98,112,92,98,126,44,88,44,38,138,83,53,51,73,34,104,65,33,53,102,147,48,86,59,109,78,38,90,100,57,69,40,71,77,51,67,83,146,307,54,65,157,85,83,107,123,95,55,18,23,13,12,12,17,10,18,27,8,26,9,5,29,46,13,24,12,20,13,22,20,92,23,32,16,9,7,9,14,20,9,15,19,16,17,16,16,15,26,18,18,16,14,85,15,13,9,20,43,9,16,19,6,10,14,14,12,12,10,5,24,14,18,6,8,10,16,94,8,20,14,8,31,16,14,21,11,39,27,75,1,7,29,20,20,11,2,12,15,35,6,11,10,13,10,14,26,29,9,49,12,46,22,11,13,9,5,6,7,19,6,7,21,18,12,8,23,4,10,11,45,23,18,17,20,10,10,10,19,10,19,28,14,3,12,7,5,18,22,10,10,7,9,16,14,6,16,12,10,19,11,9,19,18,14,8,9,13,10,6,47,21,11,20,8,16,27,27,11,13,15,26,17,10,8,7,11,9,9,34,7,11,49,13,12,26,0,6,40,12,4,3,4,17,11,8,11,44,4,8,8,15,17,9,25,6,12,6,18,19,15,17,9,18,12,13,5,4,12,14,7,3,10,9,10,5,8,15,16,11,9,11,9 +C[T>A]T,217,227,91,188,141,214,27,277,104,102,46,120,162,112,192,82,109,83,173,140,134,71,115,64,62,62,61,147,49,184,166,165,104,168,67,154,63,28,151,88,58,88,109,37,126,74,48,102,144,242,55,72,63,147,85,68,121,142,100,122,56,100,104,45,70,144,206,568,59,56,194,105,110,115,154,141,85,16,46,12,14,11,39,10,15,19,4,32,11,10,38,49,10,21,14,9,9,19,14,141,21,26,18,5,12,12,13,13,6,18,18,50,20,13,29,26,36,20,15,9,18,148,10,9,10,23,59,8,16,16,5,11,8,17,15,17,9,10,30,16,25,8,10,7,33,129,3,23,14,9,33,8,18,8,12,28,29,146,2,9,29,11,26,17,8,20,33,48,10,15,9,12,9,10,30,35,3,62,6,50,16,15,25,8,14,14,13,27,11,5,25,8,17,5,25,9,5,7,59,22,10,25,24,9,3,11,13,12,24,29,15,10,13,8,10,15,22,16,10,6,5,20,9,7,4,22,7,27,10,7,16,24,8,14,20,14,9,4,49,16,15,36,7,23,20,34,5,9,15,18,16,13,15,14,9,12,6,31,8,5,62,10,14,24,4,8,41,12,9,7,6,6,8,11,5,53,2,14,6,30,13,5,24,12,13,9,48,20,23,33,6,21,18,13,8,10,19,12,9,6,27,5,8,9,8,21,24,9,5,13,12 +G[T>A]A,62,78,28,61,38,62,15,89,38,37,21,50,47,31,52,37,44,29,53,32,45,27,34,30,24,26,21,65,25,61,63,57,53,69,26,41,15,12,77,41,25,22,31,15,48,24,9,33,39,75,17,30,25,46,43,13,38,57,27,45,28,36,22,24,24,33,80,104,14,29,69,28,51,38,42,47,32,6,16,15,11,3,8,7,11,13,4,19,9,5,20,28,9,17,10,7,5,7,4,31,22,19,15,6,10,8,7,15,4,8,14,15,11,7,10,6,20,7,9,8,11,56,13,9,5,13,22,10,5,9,10,6,2,14,3,7,3,7,13,7,7,3,2,7,16,35,4,12,6,2,12,9,4,9,14,16,21,40,3,8,22,9,13,5,2,8,6,26,6,14,4,11,5,10,11,15,1,23,4,18,6,8,12,2,5,3,6,5,3,9,18,9,7,4,10,4,4,5,37,9,4,9,8,2,5,7,14,5,15,13,7,1,10,5,2,13,9,9,6,9,6,8,5,1,1,8,8,5,5,7,10,8,6,9,14,9,5,2,21,11,10,10,4,5,12,29,1,7,13,14,15,10,7,2,6,7,2,24,3,5,26,4,3,12,3,8,25,6,2,1,5,9,7,9,8,22,7,13,2,5,12,5,13,6,9,4,13,8,7,16,3,11,11,8,2,7,11,6,11,2,4,7,3,4,1,18,7,4,7,6,10 +G[T>A]C,66,59,35,58,25,73,13,96,34,41,25,49,39,39,42,28,45,24,70,30,41,24,29,30,28,25,22,68,20,47,44,63,50,58,24,47,19,15,42,31,31,23,39,19,40,14,14,29,52,62,15,49,18,41,34,18,45,45,25,41,23,42,31,16,29,22,76,140,22,26,98,29,42,46,32,39,36,6,26,9,11,8,16,7,7,8,4,20,4,6,16,17,7,16,10,10,11,11,19,36,16,9,9,7,12,15,8,15,6,9,8,16,18,10,16,5,21,10,12,8,10,49,15,5,10,16,30,7,8,14,8,5,14,10,14,11,7,11,20,6,8,9,16,3,20,49,6,12,14,3,18,6,14,8,6,13,16,46,1,10,15,11,6,13,5,13,8,18,9,13,4,9,8,6,12,14,1,25,7,18,8,9,13,9,8,5,6,19,9,8,21,10,5,14,28,4,3,5,29,7,8,19,18,3,5,6,9,14,11,14,10,6,11,3,11,12,22,4,12,5,5,8,1,7,5,9,12,12,10,9,7,12,7,5,12,13,8,8,26,14,7,20,7,12,14,15,8,10,11,13,9,11,8,8,12,12,3,14,4,4,29,7,7,10,4,15,26,8,3,8,3,6,4,5,13,25,10,10,6,11,7,10,8,7,11,5,17,8,10,17,5,5,14,7,6,9,17,15,10,4,4,12,6,4,11,11,12,17,5,14,18 +G[T>A]G,83,77,50,67,45,95,17,105,51,52,23,34,46,43,70,38,63,38,78,52,38,34,46,31,32,34,30,78,22,59,55,55,55,56,24,40,36,15,61,45,25,48,48,19,73,32,17,43,66,87,22,41,45,50,54,31,44,62,33,63,32,50,45,33,28,56,111,164,26,40,95,48,69,56,59,49,33,6,20,16,16,11,15,11,7,13,5,16,8,7,22,32,4,24,10,10,3,15,12,74,22,22,13,9,12,12,13,13,1,7,15,26,12,6,12,9,16,13,10,12,12,57,15,6,10,17,19,13,18,12,3,8,8,10,15,5,5,8,25,6,18,4,8,5,17,61,6,22,14,6,22,6,9,6,14,9,20,35,7,8,12,7,13,8,5,9,8,17,11,11,3,7,8,16,11,17,8,27,6,26,10,5,13,11,9,9,10,16,7,7,13,7,7,5,17,4,5,9,35,11,4,11,15,3,6,11,18,9,14,21,15,3,8,10,4,10,25,7,9,12,4,15,12,5,7,10,14,11,12,7,9,29,11,5,9,9,4,5,18,8,15,17,4,11,20,21,8,10,8,8,13,13,14,9,8,7,6,25,7,8,29,7,4,15,11,7,21,7,6,7,3,8,6,4,7,31,5,9,4,12,13,4,10,9,8,4,30,2,12,7,10,12,7,3,4,7,12,10,11,6,19,6,4,2,13,14,7,8,4,9,7 +G[T>A]T,126,134,54,116,80,109,17,199,65,50,35,72,111,73,102,35,88,54,105,83,70,51,75,47,38,39,49,91,33,85,104,106,83,100,48,65,39,22,101,59,44,51,71,38,83,39,32,48,109,129,35,61,38,80,50,49,70,72,61,82,46,70,56,42,42,75,115,273,43,43,142,56,93,64,104,77,60,13,22,8,7,5,31,9,12,19,9,24,12,5,37,41,8,15,21,16,9,13,15,79,18,19,12,9,11,11,8,5,5,9,8,27,26,14,27,11,34,14,13,9,13,99,12,9,5,25,29,14,14,6,10,9,6,22,17,10,13,7,17,18,15,7,9,7,22,75,2,12,9,13,20,11,8,5,12,17,17,95,1,11,26,6,16,12,4,12,15,33,5,19,4,5,12,16,19,24,10,47,6,46,15,12,23,13,10,7,11,29,4,9,19,6,9,4,27,1,4,11,41,12,6,10,19,9,5,8,15,14,22,18,13,6,9,5,4,14,20,11,13,2,10,10,5,13,7,17,6,14,10,10,14,26,10,5,15,7,10,9,23,21,14,21,3,11,20,29,17,10,13,8,7,15,8,14,8,16,3,27,6,9,39,7,8,16,9,5,19,5,5,8,2,11,4,4,10,36,11,12,7,11,15,8,16,10,6,3,30,14,16,15,11,9,14,9,9,11,17,11,11,6,24,6,5,2,14,17,25,11,13,9,8 +T[T>A]A,124,153,81,109,84,144,30,231,78,69,53,103,132,66,91,51,121,53,124,65,75,91,78,60,64,57,31,118,38,97,96,120,103,112,40,90,47,26,103,74,58,78,114,37,100,60,58,120,104,168,41,68,65,107,63,40,62,142,36,103,50,81,57,65,57,86,144,293,69,93,146,94,102,118,95,88,103,39,56,49,26,33,95,33,31,33,14,78,32,28,64,81,23,44,32,61,22,20,49,93,109,89,38,48,50,30,20,32,16,19,38,57,81,19,47,33,53,33,46,32,59,178,49,43,23,54,87,27,67,53,24,23,31,35,36,38,43,23,100,20,30,27,18,19,56,135,16,46,43,11,107,43,45,26,25,66,110,214,28,22,38,43,48,48,33,28,45,100,20,28,34,33,10,25,49,95,17,47,12,100,36,21,74,17,40,13,32,30,20,18,42,24,22,31,83,18,28,24,124,53,27,52,35,28,15,23,39,30,45,58,37,16,20,16,20,44,39,26,21,22,25,40,20,27,21,17,19,45,20,25,29,92,22,6,31,32,9,15,61,27,29,112,17,32,25,91,24,14,27,50,44,34,21,21,35,21,21,74,32,9,47,25,32,37,11,11,53,18,18,12,18,25,25,8,33,80,21,34,24,44,18,18,61,17,27,23,51,33,93,64,34,27,35,23,30,24,34,19,16,27,38,18,20,26,30,48,48,24,10,35,36 +T[T>A]C,89,118,40,76,63,115,18,127,66,50,37,53,82,55,57,48,75,49,75,61,44,53,58,40,30,36,39,103,33,79,67,77,66,65,28,69,35,11,72,44,42,45,83,22,78,42,20,36,73,77,38,53,38,87,61,38,51,80,49,73,43,52,41,38,39,58,107,183,36,43,128,52,63,73,66,59,48,13,21,16,17,20,33,10,20,20,6,22,4,6,20,46,11,15,17,11,14,5,20,56,23,25,14,15,11,12,8,10,6,16,21,31,21,13,19,11,25,18,13,14,9,79,14,10,7,19,37,10,16,15,9,6,4,21,9,5,7,10,24,4,14,6,10,8,20,87,5,22,16,14,23,17,9,16,14,22,34,65,3,3,19,18,14,10,4,13,18,33,5,13,2,11,12,13,21,29,4,34,11,34,16,8,16,10,10,6,9,9,10,13,17,13,3,6,19,9,7,9,49,20,19,15,23,9,5,5,22,14,12,16,10,4,11,10,2,22,20,5,8,8,7,12,4,8,9,13,7,18,17,6,8,30,12,6,10,13,10,5,30,17,9,22,7,9,22,31,9,20,15,19,19,14,10,7,13,15,5,15,6,11,45,10,16,14,6,12,37,10,9,8,7,16,7,3,7,27,10,15,11,13,14,5,20,13,14,7,27,14,19,22,7,20,25,8,9,5,15,18,6,2,11,9,6,4,14,13,17,13,10,13,10 +T[T>A]G,73,66,37,68,52,64,19,102,37,40,23,58,63,43,54,36,62,39,67,34,58,37,43,33,28,40,29,57,18,63,57,64,46,59,25,48,20,12,74,39,28,33,44,15,60,33,17,35,49,95,16,37,32,47,34,16,48,56,33,42,22,50,45,28,32,40,70,166,36,37,87,30,44,46,52,37,38,3,19,6,8,5,18,5,11,11,4,17,7,6,22,33,4,12,10,8,3,10,14,45,16,26,12,7,10,8,9,15,10,6,6,16,12,3,15,7,19,10,9,20,10,60,12,6,8,15,29,6,6,21,5,3,5,13,8,7,5,11,18,12,9,4,8,5,21,55,4,12,8,5,28,11,7,10,7,25,17,46,5,2,9,5,6,4,4,10,13,18,5,14,4,13,7,13,18,15,6,37,2,31,13,9,18,4,9,7,8,11,6,5,22,11,5,7,22,7,6,5,33,9,6,8,12,5,1,12,12,7,11,19,11,2,8,3,4,13,13,4,4,3,1,13,5,4,4,14,4,14,8,6,5,10,10,7,14,11,4,5,25,8,7,19,3,6,6,16,7,7,14,14,7,11,9,11,6,8,3,20,8,11,23,4,9,8,2,4,25,7,3,5,4,12,4,6,6,36,11,17,5,13,10,3,14,17,7,10,27,5,16,22,8,13,5,4,4,5,14,10,8,1,11,5,10,2,10,20,9,8,5,6,7 +T[T>A]T,221,237,77,179,124,226,43,284,112,115,66,135,201,116,207,85,143,116,195,142,131,84,112,81,62,79,64,181,52,207,188,145,126,154,73,131,62,41,158,104,71,94,137,51,122,89,51,107,193,248,59,101,78,194,112,70,109,153,63,145,95,107,110,71,78,132,211,510,78,93,212,105,144,150,163,150,108,35,74,31,20,32,52,26,29,31,7,54,22,15,52,75,18,48,31,42,20,33,34,125,61,61,28,27,33,17,20,49,20,20,34,53,51,19,64,22,50,28,36,30,47,155,27,42,28,36,75,15,41,35,20,21,20,37,32,22,21,19,61,21,21,12,18,29,57,159,14,37,33,12,84,34,25,32,28,57,47,151,20,16,49,25,29,26,11,29,39,63,19,28,15,20,18,20,33,43,12,66,12,80,43,13,38,17,24,19,17,33,17,12,47,23,20,17,45,20,12,20,80,27,22,26,27,19,11,23,27,23,49,48,31,19,30,19,7,43,48,28,23,20,16,31,12,11,19,24,25,37,11,15,32,72,14,15,29,20,10,10,68,22,24,41,13,20,42,76,14,16,22,42,24,26,19,16,13,19,9,69,16,22,86,19,31,40,9,16,59,18,15,13,13,25,26,21,17,91,17,30,18,28,28,13,42,23,18,11,71,35,45,45,12,26,26,16,18,11,37,29,21,13,34,16,20,16,27,38,32,21,18,16,32 +A[T>C]A,261,178,93,176,138,259,55,254,178,114,68,128,199,97,155,90,171,129,182,148,163,121,144,98,81,102,73,237,74,130,171,179,163,177,67,112,84,46,193,157,103,95,190,76,189,81,70,171,156,318,77,140,99,150,127,63,155,201,107,142,87,165,144,76,132,137,294,425,111,121,207,135,164,167,139,157,114,48,59,59,46,62,71,29,63,51,32,81,31,46,80,126,37,86,65,67,42,54,57,149,100,95,43,35,69,55,56,56,41,33,66,82,84,42,69,58,87,44,62,77,60,241,59,32,46,45,133,39,75,60,31,33,33,71,55,46,41,41,130,36,57,28,49,35,67,195,33,82,51,32,87,63,66,35,55,82,97,126,17,34,63,58,50,68,22,71,66,103,31,59,35,37,35,59,75,83,17,81,35,133,47,33,79,35,40,38,55,66,24,39,52,47,26,36,105,25,28,26,159,56,65,77,59,31,13,24,59,49,80,84,53,19,45,36,25,70,64,47,28,42,25,70,65,34,58,47,40,76,37,33,62,104,57,47,42,53,28,23,83,64,66,103,33,72,78,155,33,53,82,74,69,69,39,35,61,49,41,99,33,52,106,54,42,58,22,42,113,47,28,51,37,57,57,34,32,120,40,38,22,53,43,57,68,38,60,46,67,53,92,84,34,92,57,34,17,26,49,50,39,25,37,31,25,25,83,71,65,36,46,46,49 +A[T>C]C,122,124,40,113,70,123,23,130,89,54,28,80,82,53,78,37,50,52,108,53,71,62,62,36,42,41,37,73,32,103,98,90,71,72,28,56,46,26,78,46,44,42,80,17,68,33,22,80,72,181,32,62,51,79,57,43,86,111,46,70,41,60,68,41,65,85,155,228,48,56,93,60,92,84,94,72,63,20,31,13,14,22,30,7,14,23,19,25,11,11,30,49,12,25,18,29,10,19,27,52,35,34,13,17,26,18,17,19,10,14,18,31,25,14,25,18,27,17,22,20,18,99,11,7,16,14,67,9,23,14,19,12,7,25,20,9,18,13,30,17,16,8,17,18,21,92,8,31,19,12,38,27,10,15,18,37,32,48,7,13,16,22,15,24,6,26,16,32,12,15,14,8,11,13,27,33,15,26,10,51,14,6,33,16,15,12,19,24,10,14,17,17,10,12,36,12,8,13,57,18,12,17,20,11,10,10,23,26,21,30,13,7,16,16,16,33,28,14,7,15,5,11,12,7,11,23,17,25,19,13,24,53,19,12,15,19,11,9,26,25,17,43,10,14,21,39,12,16,33,26,22,22,9,9,23,9,16,40,11,13,53,14,22,26,4,13,41,19,15,12,15,9,18,11,12,36,15,20,8,23,17,29,30,23,12,11,36,24,28,28,13,30,15,15,7,8,29,10,15,7,19,9,12,10,27,26,23,11,17,11,9 +A[T>C]G,167,124,86,108,90,164,45,207,92,89,64,93,108,69,138,71,121,115,131,112,149,96,87,83,64,59,51,142,52,111,115,121,96,93,42,94,52,36,136,83,65,65,140,42,116,60,42,124,97,241,52,87,58,84,74,51,82,138,88,79,73,131,97,69,94,114,190,288,76,82,164,124,94,105,115,72,65,29,41,30,38,42,51,19,32,29,18,57,24,23,58,61,19,69,45,47,29,34,41,111,50,49,33,22,39,36,46,29,22,33,40,52,47,27,63,38,42,23,42,50,39,163,30,18,31,28,102,26,51,41,19,26,20,43,23,29,38,24,53,23,21,24,34,17,35,140,25,53,45,9,63,40,36,32,43,41,53,97,26,35,22,38,38,29,10,26,36,39,25,39,22,24,20,30,44,56,13,52,24,69,29,34,48,30,36,20,42,40,21,26,42,42,17,20,63,23,11,23,102,42,27,51,38,20,9,25,43,34,40,49,34,14,20,36,13,41,32,35,20,22,23,49,34,18,30,20,14,42,32,27,25,51,45,24,34,29,22,18,36,46,22,54,18,33,33,63,26,40,40,50,36,27,13,21,37,33,23,60,32,24,71,35,29,42,26,30,52,34,20,26,22,22,22,21,28,71,19,23,24,27,41,31,41,24,31,23,47,42,57,60,14,51,34,23,25,18,50,21,28,21,15,21,32,10,39,34,27,33,25,18,33 +A[T>C]T,211,204,110,218,148,261,55,244,113,108,74,142,172,102,200,101,154,156,201,132,180,155,124,112,105,75,79,222,85,159,178,204,139,182,66,132,74,61,194,133,99,94,180,60,152,87,70,188,136,386,84,131,76,155,109,62,144,195,126,128,87,173,140,68,128,136,308,428,109,125,235,146,167,143,181,130,123,44,92,47,48,54,73,27,60,50,25,75,26,38,89,103,41,93,56,60,27,64,64,165,86,88,47,34,63,54,41,53,39,38,48,68,58,46,70,56,80,43,60,50,44,254,57,24,54,42,175,40,56,33,37,40,35,85,45,40,45,39,88,35,57,33,47,29,43,203,26,80,48,36,86,62,55,39,61,77,95,148,20,32,52,57,50,63,25,45,41,93,46,38,31,34,40,47,73,56,29,76,37,112,40,35,63,34,31,37,51,51,36,27,71,47,28,40,100,33,37,25,161,75,46,80,69,29,27,36,54,56,62,58,45,18,59,44,28,49,69,50,31,27,36,64,46,29,43,46,34,67,36,30,38,92,47,37,49,70,32,23,73,73,54,90,25,78,59,119,33,43,68,75,58,58,35,25,55,30,38,106,33,41,130,40,48,65,18,43,92,53,36,51,29,43,37,36,43,91,39,50,36,60,42,63,59,39,57,40,89,53,78,75,25,63,55,35,20,24,77,43,31,30,29,30,43,21,79,63,50,36,38,52,49 +C[T>C]A,103,79,34,71,68,125,26,143,116,54,44,95,90,35,56,49,83,53,83,45,74,46,61,46,42,46,55,120,40,90,77,78,82,105,33,60,39,22,98,64,52,46,95,20,92,41,32,101,58,176,25,61,43,71,77,28,87,85,47,61,35,64,76,32,74,87,114,190,45,60,104,93,75,87,69,68,70,14,26,16,13,19,22,10,21,23,11,34,14,19,26,50,7,25,22,22,11,22,20,67,27,24,20,11,14,14,16,26,15,11,19,33,22,15,32,20,32,20,31,14,16,81,20,14,13,21,66,8,32,21,7,9,12,21,24,12,20,11,42,10,27,2,16,11,29,87,10,30,11,8,34,22,25,13,10,25,30,53,7,20,27,16,30,20,5,16,22,46,11,17,9,10,13,24,32,35,14,33,6,67,8,14,24,13,15,7,19,16,12,15,17,18,14,16,33,10,4,13,64,20,11,26,15,12,6,17,34,24,30,33,16,8,12,12,4,19,31,13,5,15,12,30,10,12,14,10,11,17,15,11,22,34,20,10,16,21,6,9,24,26,13,27,6,20,11,60,11,15,30,23,19,18,23,9,16,6,10,45,11,22,46,15,16,24,11,11,43,15,10,15,10,19,11,14,15,47,12,17,24,16,18,13,19,13,19,12,27,18,29,21,10,30,20,13,12,8,18,20,9,4,13,8,15,6,20,15,23,13,11,19,18 +C[T>C]C,144,156,45,172,138,163,30,168,123,69,60,122,118,59,107,57,65,81,125,64,89,65,72,51,53,50,66,134,42,92,170,125,90,133,42,71,42,20,101,83,68,45,118,29,65,49,25,100,95,243,38,68,51,109,99,34,122,154,75,75,52,69,81,41,66,93,165,308,42,67,134,60,110,117,116,136,80,17,31,13,11,9,35,15,10,15,10,30,7,8,31,38,10,19,24,11,12,14,24,71,25,30,16,10,22,15,10,19,11,4,19,21,21,14,26,15,31,23,26,10,14,93,13,8,19,18,71,8,14,19,8,7,9,6,13,9,9,10,30,14,17,5,11,8,19,94,4,16,17,10,30,14,17,15,9,29,39,102,8,14,19,12,10,16,7,16,21,38,12,10,7,8,10,12,11,27,7,41,5,43,12,8,15,12,7,6,16,25,9,9,26,13,13,8,26,5,3,13,50,16,12,14,18,9,3,10,23,5,23,21,10,7,12,11,2,28,23,13,13,7,9,17,19,9,8,21,14,19,21,10,15,35,6,12,23,18,9,8,26,33,22,30,3,19,20,43,5,23,23,18,15,9,19,13,7,10,14,48,5,16,52,19,11,18,9,15,47,20,13,12,9,18,9,7,8,37,8,10,8,16,12,22,20,12,19,2,36,12,26,21,10,24,19,11,9,6,19,10,12,10,9,16,7,9,35,24,16,15,10,13,6 +C[T>C]G,112,96,48,116,86,121,29,147,104,57,33,99,67,46,77,42,62,66,70,60,65,46,51,52,58,46,48,127,42,85,68,83,73,85,30,66,42,27,90,66,48,50,103,30,76,37,23,94,51,187,31,66,25,64,86,34,75,106,47,66,36,67,91,43,70,52,130,209,52,68,151,68,90,93,76,74,69,20,40,23,26,14,34,9,21,22,15,32,18,12,26,41,16,21,29,35,8,11,26,76,30,30,20,12,32,16,14,21,7,26,25,24,22,15,33,23,35,17,21,27,20,86,31,12,20,23,66,15,31,33,19,16,13,24,22,27,23,16,46,15,25,12,16,15,27,95,13,33,26,11,47,10,28,15,16,35,31,93,11,12,25,20,22,15,4,17,14,39,12,19,15,13,21,12,35,38,7,41,10,73,18,17,31,18,14,13,12,21,16,26,24,25,13,7,32,9,6,13,60,22,21,35,18,12,12,8,29,21,37,36,16,7,13,10,7,20,27,15,15,8,15,28,18,9,13,14,12,19,19,18,26,38,16,15,21,20,19,10,17,26,17,33,4,21,17,38,17,26,28,24,24,20,8,12,13,26,14,49,68,9,39,20,18,19,10,14,36,20,10,13,7,19,19,15,20,47,12,14,18,19,20,14,29,16,15,14,52,31,32,44,15,21,21,15,13,13,16,17,14,8,15,10,32,2,29,17,20,14,11,28,12 +C[T>C]T,129,123,51,120,99,164,28,233,129,72,58,92,122,56,120,59,88,93,115,73,118,70,79,61,89,60,49,150,47,80,129,154,94,143,36,110,65,21,109,80,72,60,122,27,95,46,33,109,96,264,46,90,56,97,93,38,103,140,68,88,42,100,101,40,104,94,162,338,49,86,184,87,137,125,101,122,81,30,79,21,23,18,43,12,22,30,13,41,13,10,40,45,16,21,29,21,9,24,17,91,50,37,25,14,17,24,11,21,17,17,19,35,31,17,35,23,34,23,12,21,17,129,21,12,21,22,84,15,28,23,14,13,10,28,20,21,22,16,45,12,18,13,18,12,34,117,6,40,23,14,31,25,28,22,17,67,48,296,19,8,19,18,34,20,10,17,23,56,15,18,15,28,21,14,38,44,7,41,13,99,22,17,37,19,18,19,23,27,13,10,28,18,14,14,27,9,10,9,82,22,12,20,28,8,7,10,25,28,30,43,17,8,20,6,7,25,26,21,13,16,9,20,21,12,10,16,12,19,17,18,14,39,15,16,21,32,7,6,39,16,22,37,9,26,34,58,10,18,28,21,27,14,17,7,13,13,19,40,8,16,58,22,20,18,8,12,42,18,10,12,13,16,13,16,22,53,19,20,13,30,21,14,25,14,25,10,44,24,30,37,11,20,23,9,7,8,24,17,19,10,21,9,9,9,32,27,24,14,14,21,20 +G[T>C]A,110,93,49,109,64,112,25,113,104,59,48,78,76,36,71,56,82,71,94,57,84,59,49,58,41,33,49,82,40,78,72,96,85,93,52,46,55,25,86,67,66,51,97,26,80,46,33,83,80,204,40,64,50,65,54,29,68,122,46,72,41,67,86,41,69,73,143,187,63,61,86,61,66,79,62,68,56,22,28,24,26,23,41,13,23,22,10,51,15,19,45,61,20,35,19,33,19,19,35,72,53,66,27,20,21,26,31,25,20,19,45,40,38,14,29,25,44,24,36,33,23,107,27,11,20,25,78,12,35,26,20,13,12,25,25,26,19,19,64,14,30,9,29,17,24,92,15,37,24,7,41,45,34,16,24,46,38,73,11,21,21,33,31,38,15,33,22,38,9,16,14,10,22,23,40,31,11,36,16,57,18,22,34,16,24,15,32,24,12,11,41,16,16,22,55,15,8,15,99,27,13,36,29,11,4,15,20,34,31,37,26,11,14,18,6,38,36,21,24,25,11,22,27,14,17,13,21,40,25,29,30,56,17,18,26,44,18,16,52,27,25,53,7,32,33,86,9,26,31,30,25,28,19,7,23,22,17,43,19,17,46,14,19,24,10,11,65,15,9,30,16,27,24,20,17,48,15,24,15,24,23,18,35,25,17,14,38,33,48,27,13,28,31,10,16,10,29,22,24,11,18,13,25,7,41,27,25,12,22,27,25 +G[T>C]C,81,77,23,54,71,78,14,83,67,26,28,59,61,34,42,32,37,47,61,32,54,41,36,40,38,32,35,55,31,62,69,66,48,65,30,36,20,23,71,45,38,23,61,20,41,28,18,47,44,158,26,30,23,63,36,15,46,89,31,46,33,49,45,22,40,67,97,159,26,37,53,40,41,54,67,43,28,9,23,16,22,14,28,10,12,18,10,28,10,14,33,42,12,28,14,23,3,11,20,35,33,40,11,20,15,14,14,14,8,14,18,29,15,10,17,19,32,13,22,23,11,74,17,7,12,13,58,12,23,11,8,9,9,25,15,14,7,12,32,15,15,7,16,12,20,57,10,23,14,7,38,19,16,10,12,27,31,44,2,12,10,9,9,25,9,17,16,28,6,16,12,8,13,18,16,23,6,19,15,42,15,12,17,6,16,11,13,16,10,7,24,10,8,12,46,9,10,8,44,19,12,28,25,7,6,10,22,10,31,32,13,7,11,8,9,16,16,14,8,12,12,17,12,4,18,7,15,16,14,13,24,30,12,10,5,17,8,12,27,19,13,23,6,19,13,54,10,9,23,8,18,10,15,6,12,11,12,26,13,6,35,7,18,18,6,15,34,19,7,16,9,15,16,12,13,30,15,20,6,18,20,11,12,16,14,10,19,20,23,24,12,17,15,8,9,11,15,13,12,10,11,9,7,6,36,18,12,12,5,10,13 +G[T>C]G,102,83,49,73,70,82,17,98,80,36,43,63,65,35,61,30,61,42,54,47,58,51,45,42,35,23,27,65,30,59,71,59,55,59,23,46,35,22,70,51,38,53,71,21,61,37,20,64,42,162,28,42,39,49,38,27,63,87,40,44,32,56,63,32,49,67,107,168,47,37,77,44,52,64,58,58,56,16,27,17,17,18,32,18,17,14,9,30,9,22,38,39,20,37,18,15,11,29,26,55,34,32,13,13,19,17,17,20,14,10,18,21,28,14,26,9,30,12,17,21,15,55,13,16,14,13,50,7,28,31,14,19,13,15,17,14,16,10,39,17,14,5,17,8,18,79,5,24,19,7,28,27,36,18,14,25,27,55,10,18,7,14,18,22,9,21,16,35,9,15,17,16,17,16,21,22,10,35,9,41,17,18,25,8,13,11,23,21,14,10,20,10,8,10,32,7,9,15,44,24,14,31,20,14,8,17,19,17,28,28,16,7,7,7,9,16,19,23,10,13,18,29,16,6,18,13,14,26,11,11,13,33,16,9,20,23,7,8,24,20,20,30,4,29,19,37,15,16,18,23,24,21,13,5,12,17,9,43,40,15,28,10,13,19,8,6,33,9,4,9,10,19,16,10,15,39,12,13,11,26,25,11,21,9,8,11,35,22,27,19,11,23,11,18,10,4,23,16,10,10,10,9,20,5,24,24,18,21,11,14,15 +G[T>C]T,135,112,59,128,99,116,25,163,86,61,47,80,105,51,101,48,86,83,97,54,79,68,68,57,46,36,44,117,38,77,72,106,78,89,25,58,45,27,128,62,52,49,101,25,72,47,32,80,72,227,58,60,40,71,51,35,86,125,64,64,46,109,100,50,85,96,154,226,61,60,117,61,87,82,89,61,65,24,29,17,29,27,29,15,18,25,14,43,10,24,49,81,16,42,33,37,12,17,42,89,61,62,25,21,30,29,32,28,19,27,27,45,42,21,32,28,38,24,35,24,23,143,30,11,26,32,95,15,45,19,24,20,20,39,21,18,19,25,71,25,41,10,27,19,26,93,16,55,21,16,45,40,31,21,20,44,33,90,10,23,30,28,31,45,16,29,23,39,26,24,13,23,27,29,48,40,12,59,21,51,20,25,42,34,34,17,22,30,13,20,38,31,19,29,71,20,6,25,85,23,18,43,37,16,9,19,28,35,47,43,26,14,19,13,13,43,37,28,18,18,9,30,27,18,18,28,16,48,26,18,42,66,24,29,33,36,13,17,33,39,27,38,10,31,22,75,14,36,42,45,24,33,22,15,24,30,12,40,18,18,49,30,25,37,13,16,81,31,22,25,13,29,25,17,29,48,19,36,26,36,25,25,30,16,23,16,51,35,40,23,13,46,33,24,17,11,24,35,16,11,19,19,16,18,49,33,30,10,11,25,20 +T[T>C]A,143,116,52,78,94,164,24,155,188,48,72,130,83,44,73,58,94,88,121,52,97,81,86,48,44,46,49,145,49,95,79,115,67,96,33,58,39,23,119,86,57,54,132,29,90,50,40,114,105,232,37,75,63,91,90,39,98,157,39,80,46,85,75,36,100,69,159,266,58,78,97,82,96,101,79,116,79,19,42,31,27,33,48,11,17,39,14,43,19,14,49,81,14,50,33,47,10,36,45,77,64,58,30,27,41,35,27,22,13,17,25,49,40,24,40,27,27,25,41,34,28,132,28,12,32,31,87,20,35,26,18,24,16,44,27,26,21,17,67,13,42,17,22,17,38,129,18,52,26,14,63,39,29,26,20,64,49,71,13,27,32,32,27,59,9,37,29,44,24,19,24,24,22,28,46,49,16,45,10,77,28,22,45,14,16,21,41,22,18,13,42,23,14,22,71,15,11,15,107,23,25,33,31,12,10,14,41,35,40,55,16,14,16,10,9,44,29,24,26,20,7,28,15,10,18,25,27,43,19,16,28,61,30,8,31,33,21,11,52,29,30,51,16,37,27,103,16,30,46,37,43,31,19,13,34,21,14,71,12,28,57,22,28,36,11,20,78,26,15,19,13,33,20,16,14,59,18,29,10,34,30,25,47,24,29,19,74,26,53,36,16,51,28,12,17,23,29,25,13,11,20,13,23,5,38,35,19,14,17,23,26 +T[T>C]C,118,102,47,110,135,138,27,136,93,45,55,104,83,47,69,50,50,61,110,53,88,59,66,48,56,34,54,100,43,97,107,64,66,85,32,47,28,21,84,58,41,41,102,25,62,35,40,89,87,230,26,55,48,109,67,32,94,141,38,66,45,69,50,41,56,92,140,232,56,73,110,46,95,99,108,117,68,13,26,24,20,15,42,12,18,32,11,40,14,15,51,47,17,28,17,36,14,11,21,50,44,49,17,11,34,22,19,22,17,10,15,32,41,12,23,23,21,19,33,20,24,116,24,15,17,18,74,9,31,31,13,20,16,26,25,29,18,17,36,11,21,8,7,17,38,80,13,22,17,9,41,26,28,23,19,42,58,56,13,21,19,24,26,21,5,23,19,51,13,13,9,12,16,22,28,37,10,36,12,57,17,11,31,9,14,14,18,26,11,10,17,9,15,18,63,15,14,10,57,22,15,26,20,13,7,17,28,15,45,32,19,9,18,15,11,36,21,20,15,13,6,19,14,12,13,13,20,30,29,19,17,60,11,10,21,27,11,6,38,26,32,46,8,19,21,68,5,25,27,34,26,29,23,10,20,13,13,41,21,11,46,15,15,28,9,10,58,21,9,12,13,27,15,11,18,38,16,21,11,38,18,16,22,25,24,14,47,21,55,32,15,37,23,16,13,12,30,25,8,7,19,13,13,5,33,29,30,13,9,27,9 +T[T>C]G,75,67,29,55,64,84,13,85,61,42,34,74,61,21,38,28,54,51,61,19,52,45,44,36,26,29,27,57,24,54,64,63,43,53,19,35,33,14,45,45,29,34,59,15,46,23,23,64,53,91,15,40,30,43,32,24,43,74,26,34,31,57,56,20,52,49,91,134,33,46,84,41,56,51,45,50,41,11,27,18,16,18,28,7,17,14,6,19,15,12,27,33,22,25,27,24,6,17,24,53,34,25,13,12,29,27,21,24,8,13,20,19,29,18,25,15,25,13,12,25,16,69,16,13,10,9,46,8,30,57,19,9,9,31,17,13,11,10,46,10,15,8,9,9,19,65,13,31,26,5,43,29,18,11,26,30,27,48,8,11,12,27,17,20,6,26,17,38,14,14,17,18,21,23,24,33,15,29,13,43,19,9,46,11,15,10,22,17,10,9,18,10,9,10,29,10,12,7,45,22,16,21,24,10,5,8,20,22,23,25,11,8,9,4,7,13,18,12,7,12,19,19,20,12,14,18,20,14,13,15,15,48,12,15,21,14,9,10,24,16,13,24,12,17,10,39,12,17,28,29,8,20,12,6,14,11,9,46,75,13,24,14,21,29,11,18,38,12,10,16,6,19,13,7,19,40,17,14,8,23,15,13,29,20,15,12,55,14,20,33,12,38,16,14,17,9,17,23,15,18,4,9,14,4,20,12,18,19,10,18,13 +T[T>C]T,148,98,54,136,128,182,27,188,113,80,65,133,124,57,104,72,97,101,115,63,111,79,87,57,59,61,67,129,54,99,119,135,78,107,45,62,48,33,128,82,59,65,122,38,105,63,39,110,115,285,40,80,60,110,75,48,106,159,65,78,59,105,93,51,88,98,192,335,72,98,147,84,87,122,112,118,105,25,48,38,24,30,60,15,35,45,14,50,26,19,50,84,15,44,30,37,29,32,36,94,74,59,39,17,33,47,35,44,14,27,44,57,51,21,52,27,36,25,52,46,45,146,33,17,28,31,93,25,68,32,22,29,22,40,23,30,22,26,92,22,38,14,27,29,46,121,21,53,40,20,70,42,31,24,34,67,66,96,19,26,42,32,41,60,11,32,34,67,18,34,23,31,27,36,49,61,19,53,26,107,25,27,50,19,24,30,20,45,20,24,37,41,17,32,87,14,22,13,121,55,25,47,31,22,18,28,37,23,43,58,27,15,25,23,18,64,38,38,23,12,25,56,28,18,23,24,23,57,28,24,43,79,26,13,51,32,17,22,57,42,44,51,16,36,39,104,23,32,43,40,28,41,31,16,30,29,21,97,22,31,67,38,37,37,17,15,94,30,24,27,17,30,32,23,28,61,23,36,28,45,21,18,50,28,34,24,77,36,50,54,21,50,43,25,16,14,36,35,38,15,22,17,23,10,49,43,37,33,20,21,22 +A[T>G]A,76,78,30,50,62,93,17,74,54,20,26,56,51,30,43,47,63,28,65,27,47,31,40,30,30,26,25,59,16,52,56,50,31,55,17,26,28,10,38,36,28,27,60,14,40,25,23,73,56,140,15,31,32,51,49,22,54,90,13,42,19,59,54,28,38,49,81,161,21,46,46,53,45,47,52,49,49,8,21,41,8,5,22,2,6,13,6,16,18,8,16,28,6,14,12,17,3,16,9,34,30,43,11,3,11,16,11,18,8,4,19,35,14,5,7,6,15,14,13,10,10,56,9,13,8,15,30,4,14,14,6,6,8,12,8,7,9,2,18,9,16,4,5,7,17,51,3,15,14,6,44,16,6,8,11,43,18,36,2,3,10,4,10,14,3,19,12,41,13,10,5,6,19,12,21,7,3,16,6,41,7,7,11,7,8,7,9,10,6,4,14,6,6,7,22,1,7,5,41,10,8,12,7,10,4,9,9,10,10,29,12,3,5,5,5,30,20,7,4,5,3,13,10,11,7,8,6,17,6,5,6,15,10,7,11,10,6,7,12,21,9,16,6,15,10,44,9,19,15,15,9,12,8,17,10,7,5,29,8,11,32,12,10,12,4,6,29,7,5,8,5,8,11,6,6,17,13,10,6,14,4,2,16,8,14,7,35,8,14,22,7,18,13,6,3,7,10,7,8,1,10,4,3,1,9,17,15,4,8,6,14 +A[T>G]C,27,25,12,37,24,38,9,33,25,14,8,18,33,5,19,11,25,12,31,9,14,11,11,13,16,10,7,31,5,23,23,19,21,29,11,18,9,8,41,16,11,7,41,8,16,4,7,21,11,54,8,14,16,23,18,3,22,33,6,14,9,24,28,9,12,14,35,73,20,29,34,23,30,25,21,26,24,6,6,9,6,5,12,3,7,7,1,11,3,3,15,26,3,7,8,6,6,4,11,24,16,20,5,10,6,5,5,8,3,4,8,16,4,3,11,4,8,12,9,6,9,26,5,3,3,7,11,6,3,5,2,3,3,8,9,2,4,4,12,3,3,5,5,1,8,30,2,10,11,2,15,5,6,4,9,11,6,25,3,2,13,6,6,13,3,13,8,16,2,7,1,4,5,4,9,11,4,16,4,15,6,4,9,4,6,5,8,9,3,1,7,6,4,4,17,4,1,2,25,8,7,6,7,3,1,3,14,7,4,6,5,1,5,1,7,15,5,7,4,2,5,7,2,1,7,3,3,7,6,5,5,14,3,1,9,8,3,7,9,7,5,7,4,8,8,22,10,2,8,4,5,9,4,7,6,3,9,13,2,3,11,6,6,8,3,6,16,10,6,5,3,5,5,1,8,16,8,7,2,3,6,3,6,10,8,5,10,6,9,12,3,12,3,4,9,2,10,4,1,6,5,8,6,2,6,12,3,2,8,3,3 +A[T>G]G,84,90,37,103,62,97,25,123,64,34,26,83,81,19,85,47,82,57,87,43,49,63,52,52,35,28,37,74,30,84,74,70,49,81,23,48,26,14,67,35,41,35,83,22,40,24,29,62,56,115,17,40,41,80,65,37,68,62,22,53,27,42,73,25,47,45,103,179,33,51,109,65,62,66,54,56,51,10,17,13,8,8,17,8,8,15,7,20,8,9,25,31,5,12,7,5,3,6,7,37,13,9,6,8,5,8,10,8,5,9,10,16,10,8,9,5,13,8,6,12,9,53,8,4,6,13,37,11,6,14,4,6,2,15,14,7,12,3,18,7,13,4,7,6,10,73,5,15,7,4,9,6,11,9,8,9,18,38,6,6,8,6,11,4,1,12,7,17,7,13,6,8,6,9,20,19,4,25,2,38,7,2,14,1,5,4,9,9,4,5,10,6,3,10,13,2,2,11,31,7,6,11,9,4,0,1,15,5,13,26,4,4,6,5,5,5,17,6,7,5,3,10,9,2,2,8,4,10,4,4,8,17,2,7,12,10,6,5,19,13,8,6,4,5,20,25,9,8,10,21,9,5,3,11,3,6,9,15,6,4,40,11,3,12,0,7,21,10,2,6,4,10,5,2,3,19,7,7,8,14,10,5,22,4,9,11,19,11,11,13,7,5,9,6,3,5,10,12,5,4,4,1,4,5,8,7,14,7,3,8,4 +A[T>G]T,59,95,26,58,57,105,11,114,97,31,28,55,66,29,49,40,70,46,78,48,59,36,41,34,38,24,37,62,27,49,46,47,44,52,23,52,33,12,73,44,42,25,78,17,36,30,25,51,52,114,21,29,35,65,52,31,47,93,22,67,22,51,63,24,39,69,96,179,34,45,63,66,56,56,48,73,64,23,56,14,10,15,31,11,8,5,8,10,8,7,13,41,9,18,17,13,13,9,18,43,25,27,11,11,11,12,10,7,6,10,9,20,20,7,30,15,18,23,12,12,15,79,12,8,8,14,34,6,12,15,5,9,8,13,20,7,17,10,21,7,18,4,14,13,25,88,5,21,20,8,27,14,19,16,14,21,19,101,9,10,14,14,18,16,5,13,10,30,10,11,8,11,10,13,16,31,7,26,5,47,8,14,13,8,11,7,13,11,9,9,20,12,12,6,21,5,6,11,37,16,15,25,19,7,5,9,20,14,24,30,9,9,8,8,12,9,9,12,6,9,8,10,12,8,11,11,7,11,2,5,22,30,2,8,18,21,2,4,19,21,9,18,3,12,14,32,8,16,18,18,20,13,7,12,6,5,16,26,7,10,39,11,10,19,4,9,32,7,3,11,5,11,13,10,9,43,8,17,6,17,8,11,18,7,20,9,32,12,14,34,6,15,20,5,5,6,18,10,9,9,15,10,3,4,14,11,14,8,3,12,12 +C[T>G]A,47,46,19,36,35,53,8,60,47,32,21,47,48,15,34,21,40,30,42,23,34,36,28,27,24,22,14,25,18,41,42,43,31,38,14,26,24,7,47,34,17,26,47,14,14,20,12,40,36,59,6,26,19,17,30,14,16,53,18,35,11,43,37,12,25,32,59,126,16,37,38,60,26,40,25,19,29,6,13,8,2,3,11,3,4,6,3,14,5,6,10,19,1,4,1,14,9,8,9,24,6,17,7,5,2,2,3,9,2,1,4,8,6,1,9,4,11,8,9,3,9,35,1,0,3,9,13,1,6,9,5,6,7,5,4,4,2,8,15,5,10,3,8,3,15,45,2,4,4,2,13,7,14,6,6,9,21,18,3,6,9,6,8,6,0,6,5,12,1,6,4,5,3,7,15,11,3,18,2,25,6,3,3,0,4,6,6,4,6,2,10,5,3,6,10,1,2,2,20,4,5,10,5,0,2,2,10,9,11,24,5,4,2,1,2,9,12,6,3,6,3,3,3,3,3,5,0,5,2,3,12,9,5,1,3,10,6,2,6,3,3,9,1,7,7,17,4,8,6,11,4,10,3,8,4,5,3,12,4,6,12,5,2,7,3,4,9,6,1,0,2,7,5,2,3,16,6,8,5,2,3,4,18,3,4,3,8,2,16,18,2,8,7,2,4,3,8,6,4,4,9,2,3,1,7,2,4,5,4,6,6 +C[T>G]C,54,57,30,66,36,74,18,103,61,30,21,34,41,34,51,21,51,30,43,34,45,30,26,25,27,28,16,54,17,46,38,53,27,37,18,21,20,10,45,35,18,23,56,13,29,27,15,61,38,83,10,34,19,26,35,18,35,69,24,39,16,29,51,25,26,35,71,141,19,43,57,43,49,40,30,42,32,8,10,8,8,8,14,5,6,9,5,7,4,7,15,16,3,13,11,8,12,7,11,28,15,13,9,1,10,5,7,11,4,5,7,19,13,8,12,8,10,8,12,5,7,48,5,8,12,10,20,6,12,7,7,8,7,11,9,7,9,7,22,6,7,4,10,3,16,58,6,14,14,4,9,11,12,8,6,10,10,58,2,5,2,7,8,4,6,10,10,17,7,13,5,4,9,7,7,19,6,29,4,21,7,4,6,5,8,7,7,4,7,6,5,9,5,4,13,6,5,6,38,17,6,14,8,6,4,7,8,5,3,13,7,7,8,3,6,8,7,7,7,3,3,9,8,2,4,8,4,8,5,6,6,19,5,6,10,6,5,1,9,14,10,10,3,9,8,10,6,8,12,7,5,9,8,5,8,8,6,10,6,10,24,5,8,9,4,6,13,10,1,6,3,6,6,3,6,24,5,11,4,8,9,4,16,7,8,3,13,10,12,15,6,5,6,8,5,5,8,5,5,2,4,3,5,5,9,6,7,6,3,13,9 +C[T>G]G,70,106,28,90,59,129,22,148,75,56,37,85,89,44,85,39,76,49,99,45,54,61,38,51,50,38,30,83,48,68,77,79,63,84,25,47,38,20,85,50,43,41,87,17,52,41,38,82,75,126,34,28,32,67,56,21,51,91,41,63,31,54,67,45,29,45,120,226,40,49,102,73,69,74,71,88,79,5,19,10,11,2,21,6,10,11,7,19,6,6,23,23,6,15,3,19,4,7,9,62,11,12,11,5,5,11,10,17,3,4,11,14,13,8,19,6,18,12,8,4,11,53,9,3,3,13,47,8,18,10,5,6,6,12,10,11,16,3,13,7,13,5,6,8,18,86,2,12,14,4,11,8,12,12,4,13,17,40,3,3,18,8,11,11,3,8,4,25,4,9,4,6,7,9,14,18,4,25,3,34,12,4,10,2,5,5,8,15,5,8,11,5,5,5,13,1,3,4,37,8,7,11,14,3,3,8,12,13,5,32,11,9,5,11,2,7,9,11,5,4,5,10,2,3,5,6,5,14,12,8,12,23,8,8,9,11,4,4,16,17,11,7,4,10,14,23,4,12,9,11,11,6,7,8,7,3,11,21,0,5,29,9,9,17,2,7,26,4,7,7,4,12,6,5,5,20,8,7,7,17,12,7,27,6,13,5,19,8,15,24,4,11,9,7,6,5,16,2,3,2,19,5,3,6,15,9,9,10,5,10,12 +C[T>G]T,107,78,43,96,62,161,24,251,93,67,49,89,97,37,71,51,92,67,88,53,107,69,38,32,82,25,32,92,33,81,73,155,63,68,26,118,38,16,89,50,39,57,96,27,81,34,32,89,75,144,22,47,50,62,41,23,50,143,31,68,35,111,82,49,61,69,153,257,41,66,125,81,74,85,59,83,66,13,145,9,9,13,37,8,7,12,5,32,10,10,33,36,8,25,19,16,6,17,19,55,39,26,11,7,12,11,17,23,12,14,13,25,26,10,98,10,26,13,19,9,13,93,12,12,13,13,47,8,17,25,10,13,12,12,26,12,14,11,23,9,28,5,9,9,29,111,5,30,20,7,33,26,28,10,10,20,17,859,6,11,27,11,55,15,10,15,24,30,18,17,5,12,17,12,17,32,6,38,7,75,20,16,13,10,8,6,11,11,6,7,13,12,7,14,19,5,4,7,62,16,10,24,17,10,4,9,15,19,26,31,12,8,13,9,8,17,12,7,11,7,10,15,11,7,11,15,12,20,8,5,21,35,8,9,13,14,7,7,23,19,12,24,6,10,18,41,8,15,22,24,14,13,10,9,11,9,13,35,5,14,24,9,11,28,3,6,33,15,5,13,10,8,16,5,8,52,18,19,10,22,11,4,26,13,18,8,32,17,21,51,15,14,11,10,8,6,13,13,9,10,23,8,4,7,25,21,10,19,6,10,10 +G[T>G]A,32,28,12,38,25,41,12,46,33,28,17,30,27,17,26,23,32,25,44,16,24,21,23,25,28,19,26,33,16,46,33,37,22,41,6,11,9,8,33,24,15,13,33,7,27,13,16,41,13,53,10,20,20,18,28,7,25,50,14,29,7,28,32,7,21,18,57,111,17,15,44,23,35,35,25,33,23,7,4,9,2,2,2,4,3,7,0,10,5,7,4,10,4,4,1,3,1,3,4,16,9,11,4,1,5,2,3,1,3,2,5,12,5,4,7,3,3,5,5,6,3,22,12,3,4,2,12,0,4,5,2,1,2,5,6,3,3,5,9,4,6,1,3,0,4,31,1,2,5,1,9,6,0,5,6,13,13,14,4,1,5,3,4,5,5,9,6,3,2,2,4,6,5,7,2,7,4,10,2,19,3,1,4,4,1,4,1,8,0,0,4,2,4,2,8,2,1,4,14,0,4,5,6,1,4,2,5,2,9,7,1,1,2,2,3,9,3,0,2,2,5,3,3,2,1,8,0,8,2,2,6,12,3,0,2,4,3,1,5,4,7,8,0,2,6,11,3,5,3,7,2,5,6,6,2,4,2,5,0,2,14,5,3,5,3,2,10,3,2,2,4,6,0,0,0,8,3,1,1,3,3,1,7,1,3,1,8,2,3,13,2,6,7,0,2,3,3,4,1,2,3,1,1,3,6,7,3,1,2,4,2 +G[T>G]C,18,22,7,29,19,21,4,37,19,10,15,16,27,9,15,10,21,10,33,13,26,17,13,10,11,14,19,30,14,23,27,29,27,19,3,12,7,2,27,19,7,9,20,2,15,12,10,25,23,44,9,14,16,19,16,7,22,21,4,13,8,27,10,10,14,18,49,54,5,10,25,20,28,22,11,23,14,1,6,5,4,3,8,2,3,3,3,8,4,1,11,7,2,4,5,4,1,4,5,17,8,14,3,2,2,6,5,2,2,1,4,5,2,2,5,4,4,2,7,6,2,22,1,3,1,3,13,0,5,4,2,6,1,2,4,3,1,0,5,2,6,1,2,5,3,19,1,2,3,1,7,6,2,4,6,6,6,16,1,7,3,4,5,7,2,2,2,10,2,7,0,5,5,4,3,8,0,4,1,6,0,2,2,1,3,0,1,2,0,2,5,4,3,1,4,1,1,7,13,5,1,4,3,4,0,2,8,3,4,11,2,2,3,0,0,4,1,4,1,4,1,2,1,1,2,1,1,6,2,2,6,6,4,9,3,5,2,4,6,9,5,5,3,3,5,15,1,2,3,4,5,2,3,1,4,2,2,4,1,2,11,5,5,4,2,5,10,2,2,2,0,3,1,3,4,1,0,2,3,8,2,0,4,3,7,1,12,2,5,3,4,9,3,1,1,3,4,3,2,2,0,3,1,1,3,5,5,3,2,4,1 +G[T>G]G,61,50,24,80,49,73,12,81,34,37,25,57,55,22,42,20,39,50,67,36,41,31,31,36,47,40,34,72,27,43,87,82,53,55,16,23,14,17,40,42,53,30,63,14,31,25,15,54,54,73,14,49,31,60,64,22,54,51,25,39,53,45,50,20,34,28,72,127,21,44,82,40,86,51,32,115,39,8,11,8,5,2,11,2,7,10,1,8,5,1,12,15,2,3,6,4,5,2,3,26,6,11,4,6,6,8,2,5,4,4,10,21,7,6,6,8,9,6,9,3,5,28,4,0,2,7,32,3,5,4,2,3,3,4,4,3,6,6,12,4,5,2,3,4,7,54,2,3,2,1,7,7,2,5,4,0,35,18,3,6,5,2,9,4,0,3,5,15,2,5,1,1,3,9,6,8,3,20,1,30,6,0,8,2,6,1,5,7,2,0,4,5,2,1,5,1,1,3,24,11,7,6,8,3,1,6,4,3,7,18,3,2,2,2,2,4,6,3,1,3,2,4,6,1,3,16,6,13,6,9,11,16,0,16,15,6,7,4,15,11,9,12,2,8,10,26,10,13,24,8,2,4,17,4,9,9,30,21,1,3,30,15,11,7,12,13,60,15,5,7,11,27,1,4,5,13,4,5,1,11,4,10,13,9,17,6,24,8,8,10,3,13,7,1,3,2,7,6,6,0,10,2,5,2,25,25,2,1,2,6,5 +G[T>G]T,78,55,27,72,38,84,7,105,57,29,29,56,58,23,54,32,51,31,67,32,55,47,23,37,35,24,27,59,30,48,65,79,40,58,20,44,28,12,62,36,39,21,63,11,33,22,10,62,42,106,17,24,28,63,36,17,43,67,26,34,13,43,59,25,28,39,86,178,23,38,59,47,49,58,45,49,43,10,39,5,4,5,15,5,6,9,4,25,8,3,15,18,7,9,9,13,3,13,4,27,19,16,10,5,9,8,4,10,6,5,13,16,15,5,40,9,21,7,6,9,6,50,6,13,8,9,40,4,3,14,5,5,2,13,4,8,8,6,16,10,9,5,12,3,10,55,1,17,9,4,16,6,3,4,5,18,11,157,4,5,14,5,18,10,6,12,6,26,4,5,2,8,8,4,10,14,1,17,4,39,7,4,8,7,9,7,4,10,6,2,12,6,3,4,16,4,3,6,30,13,7,16,6,2,0,2,9,2,11,22,5,3,3,4,7,10,7,8,5,7,1,5,7,6,7,7,2,13,2,9,9,20,3,3,14,8,4,3,11,7,2,12,7,4,9,20,9,3,13,7,10,9,6,7,7,3,4,11,5,5,28,7,5,13,7,7,16,10,4,3,4,6,7,3,2,29,6,4,0,10,2,3,12,7,7,3,17,9,15,24,2,10,10,4,2,5,8,2,4,5,11,3,1,1,20,7,11,6,9,7,6 +T[T>G]A,71,84,39,66,66,112,28,126,88,39,30,74,61,28,52,44,59,55,76,35,56,49,51,29,40,23,33,64,27,53,64,64,45,59,14,33,36,16,53,53,40,29,81,14,33,17,21,65,54,175,19,37,41,66,53,20,51,123,20,51,28,62,64,26,49,58,113,205,24,67,68,75,46,56,59,58,49,18,12,25,8,15,41,5,8,8,7,11,7,9,15,28,3,16,12,14,8,11,19,40,27,36,10,3,10,19,13,16,9,6,16,20,14,8,10,8,14,18,13,9,7,67,7,11,7,17,27,8,7,12,3,11,4,18,12,7,17,1,24,6,16,3,13,4,26,88,7,13,10,4,24,14,7,18,17,52,24,27,5,3,18,14,10,17,9,13,5,24,11,10,5,6,9,7,9,22,6,17,5,40,7,3,15,10,15,8,8,4,12,12,15,9,7,6,21,2,5,9,37,13,13,14,11,9,6,9,9,11,13,31,10,6,11,11,8,17,14,11,10,5,5,13,7,4,7,7,4,17,6,10,12,31,10,7,10,13,2,3,28,13,11,16,4,11,10,32,9,6,11,15,12,9,9,14,13,4,10,34,7,3,23,9,16,24,1,11,21,10,0,5,7,16,17,6,9,25,4,12,6,13,10,4,21,15,9,11,25,14,20,65,7,12,15,4,8,8,12,11,3,8,14,3,2,2,13,13,14,9,4,10,10 +T[T>G]C,54,48,23,45,49,77,10,76,44,30,28,44,48,21,30,32,42,26,54,30,43,30,30,21,30,19,14,57,19,41,56,35,41,39,13,22,28,12,41,27,31,25,64,9,33,15,16,40,42,83,15,22,39,35,45,19,38,48,17,34,14,33,40,21,34,37,84,111,27,50,65,37,46,51,34,51,34,11,16,7,11,7,9,0,7,10,6,18,4,3,19,18,4,11,13,12,5,5,12,30,17,18,10,7,11,7,7,10,5,7,12,15,8,10,11,7,12,11,4,11,10,45,2,3,8,9,19,6,10,10,4,3,6,12,15,13,3,9,12,2,8,4,5,10,10,57,2,12,9,4,17,15,9,3,7,16,13,31,5,3,4,7,8,7,3,5,10,18,2,5,1,10,11,8,19,18,13,18,5,35,6,9,10,5,4,7,4,10,4,2,13,13,6,7,12,3,1,5,26,20,7,8,10,4,4,9,11,2,8,15,6,9,2,4,9,4,13,6,8,2,2,7,4,3,8,9,2,13,7,6,10,19,10,12,6,8,4,9,16,10,9,10,3,5,4,21,2,8,13,19,12,5,5,8,5,6,8,17,7,11,23,5,6,15,5,5,19,4,5,10,1,7,7,5,7,19,4,12,6,5,5,4,20,4,8,6,22,6,11,21,7,5,6,8,4,4,7,9,5,4,6,7,9,4,13,7,8,8,7,7,5 +T[T>G]G,76,111,51,106,71,100,24,114,84,41,46,86,84,32,62,45,64,42,110,45,72,59,63,45,45,29,38,97,31,79,78,81,71,80,18,49,36,24,61,45,39,29,95,21,36,32,35,81,87,130,18,61,56,89,67,28,49,81,43,65,26,74,63,34,58,55,135,199,29,65,116,54,65,78,59,70,58,11,25,9,9,11,31,7,7,13,2,24,11,2,26,31,7,17,20,17,7,8,18,50,33,16,11,7,10,12,4,14,6,9,12,22,21,9,25,13,14,8,9,11,16,73,8,9,4,15,32,6,14,19,7,6,12,12,12,13,14,7,23,5,14,8,10,11,14,78,9,27,9,6,27,13,17,11,10,21,25,46,3,6,16,15,10,17,7,14,16,40,4,11,6,3,7,13,14,28,2,27,7,59,12,15,18,6,2,7,10,15,6,3,20,13,6,10,15,7,5,10,49,12,12,14,14,5,3,10,19,10,11,19,11,6,8,8,4,15,13,16,9,5,1,10,7,7,11,12,3,12,5,7,11,19,8,7,8,9,3,2,21,15,11,15,5,14,18,23,5,12,12,23,16,13,12,11,13,5,7,23,6,8,45,13,7,21,4,7,21,13,8,8,5,12,7,7,4,36,3,17,5,17,7,3,22,10,10,6,29,17,23,19,8,11,8,11,12,4,14,13,7,8,12,7,7,3,14,10,16,7,9,13,10 +T[T>G]T,160,184,70,134,120,218,39,222,187,96,65,110,168,61,92,71,112,89,131,75,121,104,79,80,76,50,53,136,50,125,114,134,104,119,41,113,69,28,122,89,61,56,122,37,79,57,64,120,107,270,45,79,78,132,85,35,79,203,47,102,56,120,101,57,73,121,204,346,71,122,142,102,109,118,79,110,109,35,140,31,31,30,54,21,18,26,13,59,25,19,40,68,13,37,25,27,18,23,31,95,46,55,24,20,18,40,25,34,23,16,17,40,50,13,71,19,43,24,25,27,31,128,25,18,14,29,80,11,33,47,27,19,19,33,30,20,26,19,50,20,31,14,19,14,45,195,12,37,40,12,63,38,31,21,34,58,49,224,13,19,26,32,41,42,11,31,21,73,17,23,27,25,19,18,47,76,12,58,13,92,27,21,28,20,14,23,20,30,17,13,40,19,17,18,38,10,14,20,101,38,22,37,20,12,6,23,42,16,45,50,29,11,15,18,13,29,37,29,21,21,22,29,16,14,11,15,17,34,30,14,26,59,22,12,27,26,9,15,43,33,25,37,11,26,19,73,16,22,29,44,28,31,22,16,18,21,21,57,21,20,51,34,25,34,14,13,56,21,6,16,16,24,29,13,17,62,27,18,14,30,28,12,59,20,25,17,51,22,37,66,18,32,27,15,8,14,36,14,16,15,35,12,12,18,37,29,28,20,10,25,27 diff --git a/data/hrdetect_labels_training.csv b/data/hrdetect_labels_training.csv new file mode 100644 index 0000000..3324f44 --- /dev/null +++ b/data/hrdetect_labels_training.csv @@ -0,0 +1,312 @@ +,hrdetect_score,hrd_label +PD10014a,0.9921155728572402,1 +PD11327a,0.7436649569087038,1 +PD11742a,0.9996702475172542,1 +PD11748a,0.9960815444986224,1 +PD11750a,0.845485855248796,1 +PD11751a,0.9999663540792584,1 +PD11755a,0.8679799775776993,1 +PD13296a,0.9999880181847662,1 +PD13297a,0.9996225363452812,1 +PD13299a,0.9999610960970764,1 +PD13311a,0.8856783126033689,1 +PD13627a,0.9994652809028994,1 +PD13771a,0.998084506825332,1 +PD18259a,0.9702141527906952,1 +PD22355a,0.9999309038941872,1 +PD22358a,0.9156813950961344,1 +PD23558a,0.9944357539377175,1 +PD23562a,0.9998188235861204,1 +PD23574a,0.9985281054216932,1 +PD23578a,0.994020046399172,1 +PD24186a,0.7787533443798653,1 +PD24191a,0.8986738292397455,1 +PD24202a,0.9996977769837422,1 +PD24337a,0.9999793069485714,1 +PD3890a,0.9979069940305124,1 +PD3904a,0.999801720213619,1 +PD3905a,0.9718950338953514,1 +PD3945a,0.9695134900883444,1 +PD4005a,0.9997142185057316,1 +PD4006a,0.999158419990776,1 +PD4107a,0.9866312298155752,1 +PD4115a,0.9989800530481668,1 +PD4116a,0.998443339426587,1 +PD4604a,0.9851619271299592,1 +PD4872a,0.7174977006176367,1 +PD4874a,0.8891504344256873,1 +PD4876a,0.9961596079891406,1 +PD4951a,0.945873490707685,1 +PD4952a,0.9996436782043204,1 +PD4953a,0.9710541112804376,1 +PD4954a,0.9985515369175956,1 +PD4955a,0.9993270306589424,1 +PD4956a,0.999973643289489,1 +PD4957a,0.8887371751482285,1 +PD4958a,0.9908245743399658,1 +PD4959a,0.9936654791341172,1 +PD5930a,0.996481233810399,1 +PD5935a,0.9994610197517236,1 +PD5945a,0.9999887963970392,1 +PD5948a,0.999950395694054,1 +PD5960a,0.9048243825495356,1 +PD6042a,0.077313009728731,1 +PD6406a,0.9891066208708656,1 +PD6409a,0.9609396782002292,1 +PD6411a,0.9485949453909864,1 +PD6413a,0.9995115473177684,1 +PD6415a,0.9998500660657936,1 +PD6684a,0.9997752832590558,1 +PD6731a2,0.8085424601878041,1 +PD7211a,0.9999267887808824,1 +PD7215a,0.9682186747436146,1 +PD7217a,0.972292511784699,1 +PD7250a,0.9999486490416504,1 +PD8610a,0.989535436303594,1 +PD8611a,0.9999063786959356,1 +PD8621a,0.9997994106863424,1 +PD8652a2,0.998312615568214,1 +PD8832a,0.9975084380676116,1 +PD8969a,0.9966934890812686,1 +PD8978a,0.9856216378822792,1 +PD8980a,0.9992348508596104,1 +PD8984a,0.9983582800352538,1 +PD9000a,0.9987175104256374,1 +PD9004a,0.9983763023445128,1 +PD9585a,0.9999772768184538,1 +PD9592a,0.9997142504037018,1 +PD9702a,0.9846231804392028,1 +PD10010a,0.058123829542205,0 +PD11336a,0.0065987980271229,0 +PD11337a,0.0037479865590692,0 +PD11338a,0.000219043142013,0 +PD11339a,0.0003171328822809,0 +PD11340a,0.3173600767917435,0 +PD11341a,0.000809858019007,0 +PD11342a,0.000120764732257,0 +PD11343a,0.0048097007008786,0 +PD11344a,0.0030783847580603,0 +PD11345a,0.013357567398917,0 +PD11346a,0.0052435042915519,0 +PD11347a,0.0776439503595642,0 +PD11348a,0.0013423901562509,0 +PD11349a,0.2816390624689816,0 +PD11355a,0.0007325307168634,0 +PD11357a,0.0001580329059512,0 +PD11359a,0.00064610967006,0 +PD11360a,0.001656110128721,0 +PD11361a,0.0001218128580435,0 +PD11364a,0.0025785705093721,0 +PD11366a,0.0001461946584873,0 +PD11367a,0.0205042599575199,0 +PD11368a,0.0120230552279704,0 +PD11369a,0.0029588168688038,0 +PD11374a,0.0016432417744533,0 +PD11375a,0.001056770784936,0 +PD11376a,0.0051646528738785,0 +PD11380a,0.0004608819744617,0 +PD11381a,0.0002237921266965,0 +PD11383a,0.000667028782202,0 +PD11384a,0.005042482045447,0 +PD11385a,0.001044787772817,0 +PD11386a,0.0018760979555642,0 +PD11388a,0.0027767268287482,0 +PD11393a,0.0003197493325689,0 +PD11394a,0.0001198616839283,0 +PD11395a,0.0003357998969762,0 +PD11396a,0.022395092112392,0 +PD11397a,0.0024869601587405,0 +PD11398a,0.0015281096601597,0 +PD11399a,0.0008395382947749,0 +PD11402a,0.0001885125879852,0 +PD11462a,0.0099330229708921,0 +PD11465a,0.0008304583452718,0 +PD11740a,0.000364230686217,0 +PD11741a,0.0004906941801612,0 +PD11744a,0.0001993888355396,0 +PD11745a,0.0052412072937174,0 +PD11752a,0.0012761193719103,0 +PD11756a,0.0002208697099313,0 +PD11760a,0.0041368204082971,0 +PD11761a,0.0117675491723239,0 +PD11762a,0.0012209506245712,0 +PD11766a,0.0018533115522866,0 +PD11769a,0.0053129363918643,0 +PD11816a,0.0005314256071474,0 +PD13164a,0.004152873683287,0 +PD13166a,0.0022635660261022,0 +PD13167a,0.1676045835645345,0 +PD13168a,0.0072744696433744,0 +PD13302a,0.0011546774935409,0 +PD13306a,0.0001713091175741,0 +PD13307a,0.0009005789013788,0 +PD13419a,0.0015572190523021,0 +PD13420a,5.3503164551685394e-05,0 +PD13422a,0.00198645447988,0 +PD13424a,0.0012964190349949,0 +PD13425a,0.809454259907136,0 +PD13427a,2.855524584550915e-05,0 +PD13428a,0.0021649474646173,0 +PD13603a,0.0071088252313353,0 +PD13605a,0.0090354992355032,0 +PD13606a,0.0386968228287798,0 +PD13607a,0.0012839600721651,0 +PD13608a,0.0013443428878127,0 +PD13609a,0.0125214717353538,0 +PD13619a,0.000259632837562,0 +PD13620a,0.2611655400898615,0 +PD13622a,0.0430928484362337,0 +PD13625a,0.014804792199554,0 +PD13626a,0.0002528551434275,0 +PD13629a,0.0012155558229236,0 +PD13630a,0.0047602302846016,0 +PD13631a,0.0007655532809715,0 +PD13752a,0.0337513746528471,0 +PD13754a,0.0010402922006071,0 +PD13757a,0.0001542459269322,0 +PD13758a,0.00040999897472,0 +PD13763a,0.0014322003333614,0 +PD13764a,0.0087069115687922,0 +PD13766a,0.0046947633622757,0 +PD13767a,0.0009564169412912,0 +PD13768a,3.098104936922846e-05,0 +PD13770a,0.0001299254656851,0 +PD14432a,0.0001128232303195,0 +PD14433a,0.0002673444832569,0 +PD14435a,0.0012494682941089,0 +PD14437a,0.0037975058294653,0 +PD14439a,6.524149520184152e-05,0 +PD14441a,0.0008441972394528,0 +PD14450a,0.0005034599983784,0 +PD14453a,0.0055487371753471,0 +PD14454a,0.0222673135832192,0 +PD14456a,3.761952799599634e-05,0 +PD14457a,0.0100836354483791,0 +PD14459a,0.0003567213241319,0 +PD14460a,0.001860828331418,0 +PD14461a,0.0006767645725853,0 +PD14462a,0.0004688276137902,0 +PD14465a,0.0021613497022094,0 +PD14467a,0.0036903661680869,0 +PD14468a,0.0019132064090388,0 +PD14471a,0.003671675549094,0 +PD14472a,8.083886045758618e-05,0 +PD14473a,0.0014370723184099,0 +PD17973a,0.0002664714906922,0 +PD17981a,0.001285990081851,0 +PD17991a,0.0006083626218199,0 +PD17994a,0.0001131805788906,0 +PD18022a,0.0007654130729773,0 +PD18045a,0.0079798439803479,0 +PD18046a,0.0039508519692607,0 +PD18047a,0.0082965660044041,0 +PD18049a,0.0009816731186434,0 +PD18050a,0.0252130248202745,0 +PD18100a,8.283840559122109e-05,0 +PD18101a,0.0001131608756994,0 +PD18116a,0.0002360280920777,0 +PD18149a,0.000447251739744,0 +PD18188a,0.008216467817194,0 +PD18189a,0.0059544754617788,0 +PD18251a,0.1214248502870466,0 +PD18257a,0.0225707495882226,0 +PD18258a,0.0007402617725187,0 +PD18264a,0.0005011549349449,0 +PD18269a,0.0001003116577186,0 +PD18730a,0.0002834968314666,0 +PD18733a,0.0002096216838617,0 +PD18734a,0.0012585109260788,0 +PD18748a,0.0003267271834962,0 +PD18751a,0.0010098752757144,0 +PD18754a,0.0083148530303674,0 +PD18756a,0.0015527101267008,0 +PD18768a,0.0011180787979685,0 +PD18771a,0.0007226535367266,0 +PD18775a,0.0012653411985678,0 +PD18776a,0.0018825597204072,0 +PD3851a,0.0005993893413181,0 +PD4069a,0.001549510230464,0 +PD4085a,0.0001812504229464,0 +PD4199a,0.0007424060506376,0 +PD4225a,0.0011355799014915,0 +PD4248a,0.0070314157764915,0 +PD4255a,0.0156205976344331,0 +PD4261a,0.0046095480163063,0 +PD4266a,0.0026773098278923,0 +PD4315a,0.0035309920860696,0 +PD4605a,0.0012445683342348,0 +PD4606a,0.0109462848219978,0 +PD4613a,4.920164919876247e-05,0 +PD4962a,0.0576775991605201,0 +PD4965a,0.0006912954006462,0 +PD4967a,0.0004280947907281,0 +PD4968a,0.0102926050857344,0 +PD4969a,0.0003142581685472,0 +PD4970a,8.00616421639375e-05,0 +PD4971a,0.0114898577618828,0 +PD4976a,0.0002746268399109,0 +PD4981a,5.6715132852982265e-05,0 +PD4985a,0.0002463589686173,0 +PD5936a,0.0136328301659266,0 +PD5946a,0.0050608631441431,0 +PD5951a,0.0063654665499572,0 +PD5959a,0.0026001482340839,0 +PD5961a,0.0009229532805857,0 +PD5964a,0.0002664722234298,0 +PD6041a,0.000471498708245,0 +PD6045a,0.0212865046025986,0 +PD6417a,0.0009981026745723,0 +PD6422a,0.0179050282740155,0 +PD6466b,0.0021117668320705,0 +PD6711a2,0.0098537809506838,0 +PD6719a,0.0803197944853348,0 +PD6720a,0.0005188418985576,0 +PD6721a,0.0036318342232745,0 +PD7069a,0.0033703087280474,0 +PD7199a,0.0003860644229329,0 +PD7201a,9.53812754319879e-05,0 +PD7206a,0.0003639697924023,0 +PD7207a,0.0002490037716147,0 +PD7210a,0.0004070794402447,0 +PD7214a,0.0003823435793838,0 +PD7218a,0.0019789876926898,0 +PD7219a,0.0040661716628672,0 +PD7220a,0.0129074991097802,0 +PD7221a,0.0012303283250028,0 +PD7238a,0.0022835255121251,0 +PD7243a,0.0050146221557264,0 +PD7322a,0.0003901421563195,0 +PD8609a,0.0103011518618715,0 +PD8614a,0.0005880901433704,0 +PD8615a,0.0002244311530933,0 +PD8617a,0.0002498391174376,0 +PD8618a,0.0048420294779625,0 +PD8619a,0.0035482109574312,0 +PD8622a,0.0019503719368142,0 +PD8623a,0.0039553536766588,0 +PD8828a,0.0001379274170551,0 +PD8965a,0.0667558239902694,0 +PD8973a,0.0010482358332446,0 +PD8977a,0.0004548304340607,0 +PD8979a,0.0062272521461858,0 +PD8981a,0.0267481964559222,0 +PD9001a,0.0002219927894075,0 +PD9063a,8.404592441562853e-05,0 +PD9065a,0.0010873333844904,0 +PD9067a,0.0011697144945796,0 +PD9193a,0.0186986504124649,0 +PD9539a,0.0025935367197644,0 +PD9541a,0.0011606398228113,0 +PD9577a,0.002703250449642,0 +PD9578a,1.6374245705505685e-05,0 +PD9582a,0.0480260286597582,0 +PD9591a,0.0007641922827081,0 +PD9593a,0.0021587270257888,0 +PD9597a,0.0004828577086953,0 +PD9599a,0.0095181718108071,0 +PD9605a,0.0202851185024245,0 +PD9752a,0.0347900227798625,0 +PD9754a,0.0029429167233711,0 +PD9755a,0.0020385105012353,0 +PD9756a,0.0014576930205518,0 +PD9759a,0.0019078985420521,0 From 325b76e5adb22b1c64058246fb972c4142cd45d5 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 25 Apr 2024 02:55:03 -0400 Subject: [PATCH 074/103] restructure --- .../{nmf_framework => models}/__init__.py | 0 .../{nmf_framework => models}/_utils_corrnmf.py | 0 .../{nmf_framework => models}/_utils_klnmf.py | 0 src/salamander/{nmf_framework => models}/corrnmf.py | 0 .../{nmf_framework => models}/corrnmf_det.py | 0 src/salamander/{nmf_framework => models}/klnmf.py | 0 src/salamander/{nmf_framework => models}/mvnmf.py | 0 .../{nmf_framework => models}/signature_nmf.py | 0 .../{nmf_framework => models}/standard_nmf.py | 0 tests/test_corrnmf.py | 4 ++-- .../{nmf_framework => }/initialization/data_mat.npy | Bin .../initialization/exposures_mat_flat.npy | Bin .../initialization/exposures_mat_nndsvd_seed1.npy | Bin .../initialization/exposures_mat_nndsvda_seed1.npy | Bin .../initialization/exposures_mat_nndsvdar_seed1.npy | Bin .../initialization/exposures_mat_random_seed1.npy | Bin .../exposures_mat_separableNMF_seed1.npy | Bin .../initialization/signatures_mat_flat.npy | Bin .../initialization/signatures_mat_nndsvd_seed1.npy | Bin .../initialization/signatures_mat_nndsvda_seed1.npy | Bin .../signatures_mat_nndsvdar_seed1.npy | Bin .../initialization/signatures_mat_random_seed1.npy | Bin .../signatures_mat_separableNMF_seed1.npy | Bin .../corrnmf/aux_nsigs1_dim1.npy | Bin .../corrnmf/aux_nsigs2_dim2.npy | Bin .../{nmf_framework => models}/corrnmf/counts.csv | 0 .../corrnmf/objective_init_nsigs1_dim1.npy | Bin .../corrnmf/objective_init_nsigs2_dim2.npy | Bin .../corrnmf/sample_embeddings_init_nsigs1_dim1.npy | Bin .../corrnmf/sample_embeddings_init_nsigs2_dim2.npy | Bin .../sample_embeddings_updated_nsigs1_dim1.npy | Bin .../sample_embeddings_updated_nsigs2_dim2.npy | Bin .../corrnmf/sample_scalings_init_nsigs1_dim1.npy | Bin .../corrnmf/sample_scalings_init_nsigs2_dim2.npy | Bin .../corrnmf/sample_scalings_updated_nsigs1_dim1.npy | Bin .../corrnmf/sample_scalings_updated_nsigs2_dim2.npy | Bin .../signature_embeddings_init_nsigs1_dim1.npy | Bin .../signature_embeddings_init_nsigs2_dim2.npy | Bin .../signature_embeddings_updated_nsigs1_dim1.npy | Bin .../signature_embeddings_updated_nsigs2_dim2.npy | Bin .../corrnmf/signature_scalings_init_nsigs1_dim1.npy | Bin .../corrnmf/signature_scalings_init_nsigs2_dim2.npy | Bin .../signature_scalings_updated_nsigs1_dim1.npy | Bin .../signature_scalings_updated_nsigs2_dim2.npy | Bin .../corrnmf/signatures_mat_init_nsigs1_dim1.npy | Bin .../corrnmf/signatures_mat_init_nsigs2_dim2.npy | Bin .../corrnmf/signatures_mat_updated_nsigs1_dim1.npy | Bin .../corrnmf/signatures_mat_updated_nsigs2_dim2.npy | Bin .../corrnmf/variance_init_nsigs1_dim1.npy | Bin .../corrnmf/variance_init_nsigs2_dim2.npy | Bin .../corrnmf/variance_updated_nsigs1_dim1.npy | Bin .../corrnmf/variance_updated_nsigs2_dim2.npy | Bin .../test_data/{nmf_framework => models}/counts.csv | 0 .../klnmf/H_init_nsigs1.npy | Bin .../klnmf/H_init_nsigs2.npy | Bin .../klnmf/WH_updated_joint_nsigs1.pkl | Bin .../klnmf/WH_updated_joint_nsigs2.pkl | Bin .../klnmf/W_init_nsigs1.npy | Bin .../klnmf/W_init_nsigs2.npy | Bin .../{nmf_framework => models}/klnmf/counts.csv | 0 .../klnmf/objective_init_nsigs1.npy | Bin .../klnmf/objective_init_nsigs2.npy | Bin .../mvnmf/H_init_nsigs1.npy | Bin .../mvnmf/H_init_nsigs2.npy | Bin .../mvnmf/H_updated_nsigs1.npy | Bin .../mvnmf/H_updated_nsigs2.npy | Bin .../mvnmf/W_init_nsigs1.npy | Bin .../mvnmf/W_init_nsigs2.npy | Bin .../mvnmf/W_updated_nsigs1.npy | Bin .../mvnmf/W_updated_nsigs2.npy | Bin .../{nmf_framework => models}/mvnmf/counts.csv | 0 .../mvnmf/objective_init_nsigs1.npy | Bin .../mvnmf/objective_init_nsigs2.npy | Bin .../utils_klnmf/H_nsigs1.npy | Bin .../utils_klnmf/H_nsigs2.npy | Bin .../utils_klnmf/H_updated_joint_nsigs1.npy | Bin .../utils_klnmf/H_updated_joint_nsigs2.npy | Bin .../utils_klnmf/H_updated_standard_nsigs1.npy | Bin .../utils_klnmf/H_updated_standard_nsigs2.npy | Bin .../utils_klnmf/W_nsigs1.npy | Bin .../utils_klnmf/W_nsigs2.npy | Bin .../utils_klnmf/W_updated_joint_nsigs1.npy | Bin .../utils_klnmf/W_updated_joint_nsigs2.npy | Bin .../utils_klnmf/W_updated_standard_nsigs1.npy | Bin .../utils_klnmf/W_updated_standard_nsigs2.npy | Bin .../utils_klnmf/counts.csv | 0 .../utils_klnmf/kl_divergence_nsigs1.npy | Bin .../utils_klnmf/kl_divergence_nsigs2.npy | Bin .../utils_klnmf/poisson_llh_nsigs1.npy | Bin .../utils_klnmf/poisson_llh_nsigs2.npy | Bin .../utils_klnmf/samplewise_kl_divergence_nsigs1.npy | Bin .../utils_klnmf/samplewise_kl_divergence_nsigs2.npy | Bin tests/test_initialization.py | 2 +- tests/test_klnmf.py | 4 ++-- tests/test_mvnmf.py | 4 ++-- tests/test_utils_klnmf.py | 4 ++-- 96 files changed, 9 insertions(+), 9 deletions(-) rename src/salamander/{nmf_framework => models}/__init__.py (100%) rename src/salamander/{nmf_framework => models}/_utils_corrnmf.py (100%) rename src/salamander/{nmf_framework => models}/_utils_klnmf.py (100%) rename src/salamander/{nmf_framework => models}/corrnmf.py (100%) rename src/salamander/{nmf_framework => models}/corrnmf_det.py (100%) rename src/salamander/{nmf_framework => models}/klnmf.py (100%) rename src/salamander/{nmf_framework => models}/mvnmf.py (100%) rename src/salamander/{nmf_framework => models}/signature_nmf.py (100%) rename src/salamander/{nmf_framework => models}/standard_nmf.py (100%) rename tests/test_data/{nmf_framework => }/initialization/data_mat.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/exposures_mat_flat.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/exposures_mat_nndsvd_seed1.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/exposures_mat_nndsvda_seed1.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/exposures_mat_nndsvdar_seed1.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/exposures_mat_random_seed1.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/exposures_mat_separableNMF_seed1.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/signatures_mat_flat.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/signatures_mat_nndsvd_seed1.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/signatures_mat_nndsvda_seed1.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/signatures_mat_nndsvdar_seed1.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/signatures_mat_random_seed1.npy (100%) rename tests/test_data/{nmf_framework => }/initialization/signatures_mat_separableNMF_seed1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/aux_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/aux_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/counts.csv (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/objective_init_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/objective_init_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/sample_embeddings_init_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/sample_embeddings_init_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/sample_embeddings_updated_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/sample_embeddings_updated_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/sample_scalings_init_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/sample_scalings_init_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/sample_scalings_updated_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/sample_scalings_updated_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signature_embeddings_init_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signature_embeddings_init_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signature_embeddings_updated_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signature_embeddings_updated_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signature_scalings_init_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signature_scalings_init_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signature_scalings_updated_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signature_scalings_updated_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signatures_mat_init_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signatures_mat_init_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signatures_mat_updated_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/signatures_mat_updated_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/variance_init_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/variance_init_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/variance_updated_nsigs1_dim1.npy (100%) rename tests/test_data/{nmf_framework => models}/corrnmf/variance_updated_nsigs2_dim2.npy (100%) rename tests/test_data/{nmf_framework => models}/counts.csv (100%) rename tests/test_data/{nmf_framework => models}/klnmf/H_init_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/klnmf/H_init_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/klnmf/WH_updated_joint_nsigs1.pkl (100%) rename tests/test_data/{nmf_framework => models}/klnmf/WH_updated_joint_nsigs2.pkl (100%) rename tests/test_data/{nmf_framework => models}/klnmf/W_init_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/klnmf/W_init_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/klnmf/counts.csv (100%) rename tests/test_data/{nmf_framework => models}/klnmf/objective_init_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/klnmf/objective_init_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/H_init_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/H_init_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/H_updated_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/H_updated_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/W_init_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/W_init_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/W_updated_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/W_updated_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/counts.csv (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/objective_init_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/mvnmf/objective_init_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/H_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/H_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/H_updated_joint_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/H_updated_joint_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/H_updated_standard_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/H_updated_standard_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/W_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/W_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/W_updated_joint_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/W_updated_joint_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/W_updated_standard_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/W_updated_standard_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/counts.csv (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/kl_divergence_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/kl_divergence_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/poisson_llh_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/poisson_llh_nsigs2.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/samplewise_kl_divergence_nsigs1.npy (100%) rename tests/test_data/{nmf_framework => models}/utils_klnmf/samplewise_kl_divergence_nsigs2.npy (100%) diff --git a/src/salamander/nmf_framework/__init__.py b/src/salamander/models/__init__.py similarity index 100% rename from src/salamander/nmf_framework/__init__.py rename to src/salamander/models/__init__.py diff --git a/src/salamander/nmf_framework/_utils_corrnmf.py b/src/salamander/models/_utils_corrnmf.py similarity index 100% rename from src/salamander/nmf_framework/_utils_corrnmf.py rename to src/salamander/models/_utils_corrnmf.py diff --git a/src/salamander/nmf_framework/_utils_klnmf.py b/src/salamander/models/_utils_klnmf.py similarity index 100% rename from src/salamander/nmf_framework/_utils_klnmf.py rename to src/salamander/models/_utils_klnmf.py diff --git a/src/salamander/nmf_framework/corrnmf.py b/src/salamander/models/corrnmf.py similarity index 100% rename from src/salamander/nmf_framework/corrnmf.py rename to src/salamander/models/corrnmf.py diff --git a/src/salamander/nmf_framework/corrnmf_det.py b/src/salamander/models/corrnmf_det.py similarity index 100% rename from src/salamander/nmf_framework/corrnmf_det.py rename to src/salamander/models/corrnmf_det.py diff --git a/src/salamander/nmf_framework/klnmf.py b/src/salamander/models/klnmf.py similarity index 100% rename from src/salamander/nmf_framework/klnmf.py rename to src/salamander/models/klnmf.py diff --git a/src/salamander/nmf_framework/mvnmf.py b/src/salamander/models/mvnmf.py similarity index 100% rename from src/salamander/nmf_framework/mvnmf.py rename to src/salamander/models/mvnmf.py diff --git a/src/salamander/nmf_framework/signature_nmf.py b/src/salamander/models/signature_nmf.py similarity index 100% rename from src/salamander/nmf_framework/signature_nmf.py rename to src/salamander/models/signature_nmf.py diff --git a/src/salamander/nmf_framework/standard_nmf.py b/src/salamander/models/standard_nmf.py similarity index 100% rename from src/salamander/nmf_framework/standard_nmf.py rename to src/salamander/models/standard_nmf.py diff --git a/tests/test_corrnmf.py b/tests/test_corrnmf.py index 4f344b2..5d4e9e5 100644 --- a/tests/test_corrnmf.py +++ b/tests/test_corrnmf.py @@ -3,10 +3,10 @@ import pytest from anndata import AnnData -from salamander.nmf_framework import corrnmf_det +from salamander.models import corrnmf_det PATH = "tests/test_data" -PATH_TEST_DATA = f"{PATH}/nmf_framework/corrnmf" +PATH_TEST_DATA = f"{PATH}/models/corrnmf" @pytest.fixture diff --git a/tests/test_data/nmf_framework/initialization/data_mat.npy b/tests/test_data/initialization/data_mat.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/data_mat.npy rename to tests/test_data/initialization/data_mat.npy diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_flat.npy b/tests/test_data/initialization/exposures_mat_flat.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/exposures_mat_flat.npy rename to tests/test_data/initialization/exposures_mat_flat.npy diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_nndsvd_seed1.npy b/tests/test_data/initialization/exposures_mat_nndsvd_seed1.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/exposures_mat_nndsvd_seed1.npy rename to tests/test_data/initialization/exposures_mat_nndsvd_seed1.npy diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_nndsvda_seed1.npy b/tests/test_data/initialization/exposures_mat_nndsvda_seed1.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/exposures_mat_nndsvda_seed1.npy rename to tests/test_data/initialization/exposures_mat_nndsvda_seed1.npy diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_nndsvdar_seed1.npy b/tests/test_data/initialization/exposures_mat_nndsvdar_seed1.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/exposures_mat_nndsvdar_seed1.npy rename to tests/test_data/initialization/exposures_mat_nndsvdar_seed1.npy diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_random_seed1.npy b/tests/test_data/initialization/exposures_mat_random_seed1.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/exposures_mat_random_seed1.npy rename to tests/test_data/initialization/exposures_mat_random_seed1.npy diff --git a/tests/test_data/nmf_framework/initialization/exposures_mat_separableNMF_seed1.npy b/tests/test_data/initialization/exposures_mat_separableNMF_seed1.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/exposures_mat_separableNMF_seed1.npy rename to tests/test_data/initialization/exposures_mat_separableNMF_seed1.npy diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_flat.npy b/tests/test_data/initialization/signatures_mat_flat.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/signatures_mat_flat.npy rename to tests/test_data/initialization/signatures_mat_flat.npy diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_nndsvd_seed1.npy b/tests/test_data/initialization/signatures_mat_nndsvd_seed1.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/signatures_mat_nndsvd_seed1.npy rename to tests/test_data/initialization/signatures_mat_nndsvd_seed1.npy diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_nndsvda_seed1.npy b/tests/test_data/initialization/signatures_mat_nndsvda_seed1.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/signatures_mat_nndsvda_seed1.npy rename to tests/test_data/initialization/signatures_mat_nndsvda_seed1.npy diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_nndsvdar_seed1.npy b/tests/test_data/initialization/signatures_mat_nndsvdar_seed1.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/signatures_mat_nndsvdar_seed1.npy rename to tests/test_data/initialization/signatures_mat_nndsvdar_seed1.npy diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_random_seed1.npy b/tests/test_data/initialization/signatures_mat_random_seed1.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/signatures_mat_random_seed1.npy rename to tests/test_data/initialization/signatures_mat_random_seed1.npy diff --git a/tests/test_data/nmf_framework/initialization/signatures_mat_separableNMF_seed1.npy b/tests/test_data/initialization/signatures_mat_separableNMF_seed1.npy similarity index 100% rename from tests/test_data/nmf_framework/initialization/signatures_mat_separableNMF_seed1.npy rename to tests/test_data/initialization/signatures_mat_separableNMF_seed1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/aux_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/aux_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/aux_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/aux_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/aux_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/aux_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/aux_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/aux_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/counts.csv b/tests/test_data/models/corrnmf/counts.csv similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/counts.csv rename to tests/test_data/models/corrnmf/counts.csv diff --git a/tests/test_data/nmf_framework/corrnmf/objective_init_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/objective_init_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/objective_init_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/objective_init_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/objective_init_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/objective_init_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/objective_init_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/objective_init_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/sample_embeddings_init_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/sample_embeddings_init_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/sample_embeddings_init_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/sample_embeddings_init_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/sample_embeddings_init_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/sample_embeddings_init_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/sample_embeddings_init_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/sample_embeddings_init_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/sample_embeddings_updated_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/sample_embeddings_updated_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/sample_embeddings_updated_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/sample_embeddings_updated_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/sample_embeddings_updated_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/sample_embeddings_updated_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/sample_embeddings_updated_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/sample_embeddings_updated_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/sample_scalings_init_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/sample_scalings_init_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/sample_scalings_init_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/sample_scalings_init_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/sample_scalings_init_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/sample_scalings_init_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/sample_scalings_init_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/sample_scalings_init_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/sample_scalings_updated_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/sample_scalings_updated_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/sample_scalings_updated_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/sample_scalings_updated_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/sample_scalings_updated_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/sample_scalings_updated_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/sample_scalings_updated_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/sample_scalings_updated_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signature_embeddings_init_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/signature_embeddings_init_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signature_embeddings_init_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/signature_embeddings_init_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signature_embeddings_init_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/signature_embeddings_init_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signature_embeddings_init_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/signature_embeddings_init_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signature_embeddings_updated_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/signature_embeddings_updated_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signature_embeddings_updated_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/signature_embeddings_updated_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signature_embeddings_updated_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/signature_embeddings_updated_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signature_embeddings_updated_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/signature_embeddings_updated_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signature_scalings_init_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/signature_scalings_init_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signature_scalings_init_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/signature_scalings_init_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signature_scalings_init_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/signature_scalings_init_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signature_scalings_init_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/signature_scalings_init_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signature_scalings_updated_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/signature_scalings_updated_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signature_scalings_updated_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/signature_scalings_updated_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signature_scalings_updated_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/signature_scalings_updated_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signature_scalings_updated_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/signature_scalings_updated_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signatures_mat_init_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/signatures_mat_init_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signatures_mat_init_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/signatures_mat_init_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signatures_mat_init_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/signatures_mat_init_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signatures_mat_init_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/signatures_mat_init_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signatures_mat_updated_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/signatures_mat_updated_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signatures_mat_updated_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/signatures_mat_updated_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/signatures_mat_updated_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/signatures_mat_updated_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/signatures_mat_updated_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/signatures_mat_updated_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/variance_init_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/variance_init_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/variance_init_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/variance_init_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/variance_init_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/variance_init_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/variance_init_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/variance_init_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/corrnmf/variance_updated_nsigs1_dim1.npy b/tests/test_data/models/corrnmf/variance_updated_nsigs1_dim1.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/variance_updated_nsigs1_dim1.npy rename to tests/test_data/models/corrnmf/variance_updated_nsigs1_dim1.npy diff --git a/tests/test_data/nmf_framework/corrnmf/variance_updated_nsigs2_dim2.npy b/tests/test_data/models/corrnmf/variance_updated_nsigs2_dim2.npy similarity index 100% rename from tests/test_data/nmf_framework/corrnmf/variance_updated_nsigs2_dim2.npy rename to tests/test_data/models/corrnmf/variance_updated_nsigs2_dim2.npy diff --git a/tests/test_data/nmf_framework/counts.csv b/tests/test_data/models/counts.csv similarity index 100% rename from tests/test_data/nmf_framework/counts.csv rename to tests/test_data/models/counts.csv diff --git a/tests/test_data/nmf_framework/klnmf/H_init_nsigs1.npy b/tests/test_data/models/klnmf/H_init_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/klnmf/H_init_nsigs1.npy rename to tests/test_data/models/klnmf/H_init_nsigs1.npy diff --git a/tests/test_data/nmf_framework/klnmf/H_init_nsigs2.npy b/tests/test_data/models/klnmf/H_init_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/klnmf/H_init_nsigs2.npy rename to tests/test_data/models/klnmf/H_init_nsigs2.npy diff --git a/tests/test_data/nmf_framework/klnmf/WH_updated_joint_nsigs1.pkl b/tests/test_data/models/klnmf/WH_updated_joint_nsigs1.pkl similarity index 100% rename from tests/test_data/nmf_framework/klnmf/WH_updated_joint_nsigs1.pkl rename to tests/test_data/models/klnmf/WH_updated_joint_nsigs1.pkl diff --git a/tests/test_data/nmf_framework/klnmf/WH_updated_joint_nsigs2.pkl b/tests/test_data/models/klnmf/WH_updated_joint_nsigs2.pkl similarity index 100% rename from tests/test_data/nmf_framework/klnmf/WH_updated_joint_nsigs2.pkl rename to tests/test_data/models/klnmf/WH_updated_joint_nsigs2.pkl diff --git a/tests/test_data/nmf_framework/klnmf/W_init_nsigs1.npy b/tests/test_data/models/klnmf/W_init_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/klnmf/W_init_nsigs1.npy rename to tests/test_data/models/klnmf/W_init_nsigs1.npy diff --git a/tests/test_data/nmf_framework/klnmf/W_init_nsigs2.npy b/tests/test_data/models/klnmf/W_init_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/klnmf/W_init_nsigs2.npy rename to tests/test_data/models/klnmf/W_init_nsigs2.npy diff --git a/tests/test_data/nmf_framework/klnmf/counts.csv b/tests/test_data/models/klnmf/counts.csv similarity index 100% rename from tests/test_data/nmf_framework/klnmf/counts.csv rename to tests/test_data/models/klnmf/counts.csv diff --git a/tests/test_data/nmf_framework/klnmf/objective_init_nsigs1.npy b/tests/test_data/models/klnmf/objective_init_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/klnmf/objective_init_nsigs1.npy rename to tests/test_data/models/klnmf/objective_init_nsigs1.npy diff --git a/tests/test_data/nmf_framework/klnmf/objective_init_nsigs2.npy b/tests/test_data/models/klnmf/objective_init_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/klnmf/objective_init_nsigs2.npy rename to tests/test_data/models/klnmf/objective_init_nsigs2.npy diff --git a/tests/test_data/nmf_framework/mvnmf/H_init_nsigs1.npy b/tests/test_data/models/mvnmf/H_init_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/H_init_nsigs1.npy rename to tests/test_data/models/mvnmf/H_init_nsigs1.npy diff --git a/tests/test_data/nmf_framework/mvnmf/H_init_nsigs2.npy b/tests/test_data/models/mvnmf/H_init_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/H_init_nsigs2.npy rename to tests/test_data/models/mvnmf/H_init_nsigs2.npy diff --git a/tests/test_data/nmf_framework/mvnmf/H_updated_nsigs1.npy b/tests/test_data/models/mvnmf/H_updated_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/H_updated_nsigs1.npy rename to tests/test_data/models/mvnmf/H_updated_nsigs1.npy diff --git a/tests/test_data/nmf_framework/mvnmf/H_updated_nsigs2.npy b/tests/test_data/models/mvnmf/H_updated_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/H_updated_nsigs2.npy rename to tests/test_data/models/mvnmf/H_updated_nsigs2.npy diff --git a/tests/test_data/nmf_framework/mvnmf/W_init_nsigs1.npy b/tests/test_data/models/mvnmf/W_init_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/W_init_nsigs1.npy rename to tests/test_data/models/mvnmf/W_init_nsigs1.npy diff --git a/tests/test_data/nmf_framework/mvnmf/W_init_nsigs2.npy b/tests/test_data/models/mvnmf/W_init_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/W_init_nsigs2.npy rename to tests/test_data/models/mvnmf/W_init_nsigs2.npy diff --git a/tests/test_data/nmf_framework/mvnmf/W_updated_nsigs1.npy b/tests/test_data/models/mvnmf/W_updated_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/W_updated_nsigs1.npy rename to tests/test_data/models/mvnmf/W_updated_nsigs1.npy diff --git a/tests/test_data/nmf_framework/mvnmf/W_updated_nsigs2.npy b/tests/test_data/models/mvnmf/W_updated_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/W_updated_nsigs2.npy rename to tests/test_data/models/mvnmf/W_updated_nsigs2.npy diff --git a/tests/test_data/nmf_framework/mvnmf/counts.csv b/tests/test_data/models/mvnmf/counts.csv similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/counts.csv rename to tests/test_data/models/mvnmf/counts.csv diff --git a/tests/test_data/nmf_framework/mvnmf/objective_init_nsigs1.npy b/tests/test_data/models/mvnmf/objective_init_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/objective_init_nsigs1.npy rename to tests/test_data/models/mvnmf/objective_init_nsigs1.npy diff --git a/tests/test_data/nmf_framework/mvnmf/objective_init_nsigs2.npy b/tests/test_data/models/mvnmf/objective_init_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/mvnmf/objective_init_nsigs2.npy rename to tests/test_data/models/mvnmf/objective_init_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/H_nsigs1.npy b/tests/test_data/models/utils_klnmf/H_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/H_nsigs1.npy rename to tests/test_data/models/utils_klnmf/H_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/H_nsigs2.npy b/tests/test_data/models/utils_klnmf/H_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/H_nsigs2.npy rename to tests/test_data/models/utils_klnmf/H_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/H_updated_joint_nsigs1.npy b/tests/test_data/models/utils_klnmf/H_updated_joint_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/H_updated_joint_nsigs1.npy rename to tests/test_data/models/utils_klnmf/H_updated_joint_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/H_updated_joint_nsigs2.npy b/tests/test_data/models/utils_klnmf/H_updated_joint_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/H_updated_joint_nsigs2.npy rename to tests/test_data/models/utils_klnmf/H_updated_joint_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/H_updated_standard_nsigs1.npy b/tests/test_data/models/utils_klnmf/H_updated_standard_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/H_updated_standard_nsigs1.npy rename to tests/test_data/models/utils_klnmf/H_updated_standard_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/H_updated_standard_nsigs2.npy b/tests/test_data/models/utils_klnmf/H_updated_standard_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/H_updated_standard_nsigs2.npy rename to tests/test_data/models/utils_klnmf/H_updated_standard_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/W_nsigs1.npy b/tests/test_data/models/utils_klnmf/W_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/W_nsigs1.npy rename to tests/test_data/models/utils_klnmf/W_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/W_nsigs2.npy b/tests/test_data/models/utils_klnmf/W_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/W_nsigs2.npy rename to tests/test_data/models/utils_klnmf/W_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/W_updated_joint_nsigs1.npy b/tests/test_data/models/utils_klnmf/W_updated_joint_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/W_updated_joint_nsigs1.npy rename to tests/test_data/models/utils_klnmf/W_updated_joint_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/W_updated_joint_nsigs2.npy b/tests/test_data/models/utils_klnmf/W_updated_joint_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/W_updated_joint_nsigs2.npy rename to tests/test_data/models/utils_klnmf/W_updated_joint_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/W_updated_standard_nsigs1.npy b/tests/test_data/models/utils_klnmf/W_updated_standard_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/W_updated_standard_nsigs1.npy rename to tests/test_data/models/utils_klnmf/W_updated_standard_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/W_updated_standard_nsigs2.npy b/tests/test_data/models/utils_klnmf/W_updated_standard_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/W_updated_standard_nsigs2.npy rename to tests/test_data/models/utils_klnmf/W_updated_standard_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/counts.csv b/tests/test_data/models/utils_klnmf/counts.csv similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/counts.csv rename to tests/test_data/models/utils_klnmf/counts.csv diff --git a/tests/test_data/nmf_framework/utils_klnmf/kl_divergence_nsigs1.npy b/tests/test_data/models/utils_klnmf/kl_divergence_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/kl_divergence_nsigs1.npy rename to tests/test_data/models/utils_klnmf/kl_divergence_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/kl_divergence_nsigs2.npy b/tests/test_data/models/utils_klnmf/kl_divergence_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/kl_divergence_nsigs2.npy rename to tests/test_data/models/utils_klnmf/kl_divergence_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/poisson_llh_nsigs1.npy b/tests/test_data/models/utils_klnmf/poisson_llh_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/poisson_llh_nsigs1.npy rename to tests/test_data/models/utils_klnmf/poisson_llh_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/poisson_llh_nsigs2.npy b/tests/test_data/models/utils_klnmf/poisson_llh_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/poisson_llh_nsigs2.npy rename to tests/test_data/models/utils_klnmf/poisson_llh_nsigs2.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/samplewise_kl_divergence_nsigs1.npy b/tests/test_data/models/utils_klnmf/samplewise_kl_divergence_nsigs1.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/samplewise_kl_divergence_nsigs1.npy rename to tests/test_data/models/utils_klnmf/samplewise_kl_divergence_nsigs1.npy diff --git a/tests/test_data/nmf_framework/utils_klnmf/samplewise_kl_divergence_nsigs2.npy b/tests/test_data/models/utils_klnmf/samplewise_kl_divergence_nsigs2.npy similarity index 100% rename from tests/test_data/nmf_framework/utils_klnmf/samplewise_kl_divergence_nsigs2.npy rename to tests/test_data/models/utils_klnmf/samplewise_kl_divergence_nsigs2.npy diff --git a/tests/test_initialization.py b/tests/test_initialization.py index e1c1d1c..420eb89 100644 --- a/tests/test_initialization.py +++ b/tests/test_initialization.py @@ -4,7 +4,7 @@ from salamander.initialization import initialize PATH = "tests/test_data" -PATH_TEST_DATA = f"{PATH}/nmf_framework/initialization" +PATH_TEST_DATA = f"{PATH}/initialization" METHODS_DET = ["flat"] METHODS_STOCH = [ diff --git a/tests/test_klnmf.py b/tests/test_klnmf.py index 820cc8e..4bf4393 100644 --- a/tests/test_klnmf.py +++ b/tests/test_klnmf.py @@ -5,10 +5,10 @@ import pytest from anndata import AnnData -from salamander.nmf_framework import klnmf +from salamander.models import klnmf PATH = "tests/test_data" -PATH_TEST_DATA = f"{PATH}/nmf_framework/klnmf" +PATH_TEST_DATA = f"{PATH}/models/klnmf" @pytest.fixture diff --git a/tests/test_mvnmf.py b/tests/test_mvnmf.py index 9d390e3..da7406b 100644 --- a/tests/test_mvnmf.py +++ b/tests/test_mvnmf.py @@ -3,10 +3,10 @@ import pytest from anndata import AnnData -from salamander.nmf_framework import mvnmf +from salamander.models import mvnmf PATH = "tests/test_data" -PATH_TEST_DATA = f"{PATH}/nmf_framework/mvnmf" +PATH_TEST_DATA = f"{PATH}/models/mvnmf" @pytest.fixture diff --git a/tests/test_utils_klnmf.py b/tests/test_utils_klnmf.py index 56b2b77..5879820 100644 --- a/tests/test_utils_klnmf.py +++ b/tests/test_utils_klnmf.py @@ -2,10 +2,10 @@ import pandas as pd import pytest -from salamander.nmf_framework import _utils_klnmf +from salamander.models import _utils_klnmf PATH = "tests/test_data" -PATH_TEST_DATA = f"{PATH}/nmf_framework/utils_klnmf" +PATH_TEST_DATA = f"{PATH}/models/utils_klnmf" @pytest.fixture From 56fc7306ef58ee0a35c73f86dfe8099ff555353e Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 25 Apr 2024 03:00:07 -0400 Subject: [PATCH 075/103] update init add model imports --- src/salamander/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/salamander/__init__.py b/src/salamander/__init__.py index 4aa8daf..3960f9e 100644 --- a/src/salamander/__init__.py +++ b/src/salamander/__init__.py @@ -3,11 +3,13 @@ ================================================================================ """ -# from .nmf_framework.klnmf import KLNMF +from .models.corrnmf_det import CorrNMFDet +from .models.klnmf import KLNMF +from .models.mvnmf import MvNMF from .plot import set_salamander_style __version__ = "0.3.2" -# __all__ = ["KLNMF"] +__all__ = ["CorrNMFDet", "KLNMF", "MvNMF"] set_salamander_style() From f72a72b1bb44adb50749e32e4db2bd8af68e939a Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 25 Apr 2024 03:17:33 -0400 Subject: [PATCH 076/103] improve init --- src/salamander/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/salamander/__init__.py b/src/salamander/__init__.py index 3960f9e..1e7f4ec 100644 --- a/src/salamander/__init__.py +++ b/src/salamander/__init__.py @@ -3,13 +3,14 @@ ================================================================================ """ +from . import plot as pl +from . import tools as tl from .models.corrnmf_det import CorrNMFDet from .models.klnmf import KLNMF from .models.mvnmf import MvNMF -from .plot import set_salamander_style __version__ = "0.3.2" __all__ = ["CorrNMFDet", "KLNMF", "MvNMF"] -set_salamander_style() +pl.set_salamander_style() From fff3c81384e867fa722acd3cff726a3a140ba6db Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 25 Apr 2024 04:27:09 -0400 Subject: [PATCH 077/103] improve init --- src/salamander/__init__.py | 6 +----- src/salamander/models/__init__.py | 8 +++++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/salamander/__init__.py b/src/salamander/__init__.py index 1e7f4ec..dc13819 100644 --- a/src/salamander/__init__.py +++ b/src/salamander/__init__.py @@ -3,14 +3,10 @@ ================================================================================ """ +from . import models from . import plot as pl from . import tools as tl -from .models.corrnmf_det import CorrNMFDet -from .models.klnmf import KLNMF -from .models.mvnmf import MvNMF __version__ = "0.3.2" -__all__ = ["CorrNMFDet", "KLNMF", "MvNMF"] - pl.set_salamander_style() diff --git a/src/salamander/models/__init__.py b/src/salamander/models/__init__.py index e16c76d..d6b9c43 100644 --- a/src/salamander/models/__init__.py +++ b/src/salamander/models/__init__.py @@ -1 +1,7 @@ -"" +""" +Multiple NMF variants +""" + +from .corrnmf_det import CorrNMFDet +from .klnmf import KLNMF +from .mvnmf import MvNMF From 12a8dc941ba40702da552f33df135595e615dc94 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Thu, 25 Apr 2024 04:27:35 -0400 Subject: [PATCH 078/103] fix bug --- src/salamander/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/salamander/utils.py b/src/salamander/utils.py index 1329999..0f87e1e 100644 --- a/src/salamander/utils.py +++ b/src/salamander/utils.py @@ -142,7 +142,7 @@ def match_to_catalog( """ cosine_sim = 1 - pairwise_distances(signatures, catalog, metric=metric) matches_indices = [np.argmax(row) for row in cosine_sim] - matches = catalog.loc[matches_indices, :] + matches = catalog.iloc[matches_indices, :] return matches From a4af206544b11260e44d0352328ca8f3546e130d Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Fri, 26 Apr 2024 14:35:28 -0400 Subject: [PATCH 079/103] update mypy config --- mypy.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy.ini b/mypy.ini index 66ebce1..f3f723b 100644 --- a/mypy.ini +++ b/mypy.ini @@ -19,6 +19,9 @@ ignore_missing_imports = True [mypy-fastcluster.*] ignore_missing_imports = True +[mypy-mudata.*] +ignore_missing_imports = True + [mypy-numba.*] ignore_missing_imports = True From 91714dbe4b9f32d5f33fbde5c29d4c6ee08970a9 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 14:58:46 -0400 Subject: [PATCH 080/103] improve api --- src/salamander/__init__.py | 6 ++++++ src/salamander/models/__init__.py | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/salamander/__init__.py b/src/salamander/__init__.py index dc13819..bad5934 100644 --- a/src/salamander/__init__.py +++ b/src/salamander/__init__.py @@ -10,3 +10,9 @@ __version__ = "0.3.2" pl.set_salamander_style() + +__all__ = [ + "models", + "pl", + "tl", +] diff --git a/src/salamander/models/__init__.py b/src/salamander/models/__init__.py index d6b9c43..0d3f242 100644 --- a/src/salamander/models/__init__.py +++ b/src/salamander/models/__init__.py @@ -1,7 +1,13 @@ """ -Multiple NMF variants +A collection of NMF algorithms """ from .corrnmf_det import CorrNMFDet from .klnmf import KLNMF from .mvnmf import MvNMF + +__all__ = [ + "CorrNMFDet", + "KLNMF", + "MvNMF", +] From e11c66fe65402276828f1de6c59e0128e50b14b5 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 15:00:51 -0400 Subject: [PATCH 081/103] fix type mypy complains without explicitly casting the np.argmax return values to integers --- src/salamander/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/salamander/utils.py b/src/salamander/utils.py index 0f87e1e..eab2073 100644 --- a/src/salamander/utils.py +++ b/src/salamander/utils.py @@ -141,8 +141,8 @@ def match_to_catalog( Find the best matching signatures in catalog for all signatures. """ cosine_sim = 1 - pairwise_distances(signatures, catalog, metric=metric) - matches_indices = [np.argmax(row) for row in cosine_sim] - matches = catalog.iloc[matches_indices, :] + matches_indices = [int(np.argmax(row)) for row in cosine_sim] + matches = catalog.iloc[matches_indices] return matches From acf76a5c3027ef3b6837b7f658f5b232a2814cfe Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 15:03:12 -0400 Subject: [PATCH 082/103] improve function name The function 'get_obs_order' is necessary for the multimodal exposure plot and should therefore not be private. --- src/salamander/plot.py | 4 ++-- tests/test_plot.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/salamander/plot.py b/src/salamander/plot.py index 00ba51c..c9b5ef3 100644 --- a/src/salamander/plot.py +++ b/src/salamander/plot.py @@ -541,7 +541,7 @@ def barplot(adata: AnnData, **kwargs) -> Axes | Iterable[Axes]: return barplot_pandas(adata.to_df(), **kwargs) -def _get_obs_order(data: pd.DataFrame, normalize: bool = True) -> np.ndarray: +def get_obs_order(data: pd.DataFrame, normalize: bool = True) -> np.ndarray: """ Compute the aesthetically most pleasing order of the observations of a non-negative data array of shape (n_obs, n_dimensions) for a @@ -608,7 +608,7 @@ def _reorder_data( The reorderd annotated data. """ if obs_order is None: - obs_order = _get_obs_order(data, normalize=normalize) + obs_order = get_obs_order(data, normalize=normalize) data_reordered = data.loc[obs_order, :] diff --git a/tests/test_plot.py b/tests/test_plot.py index 0114632..788033f 100644 --- a/tests/test_plot.py +++ b/tests/test_plot.py @@ -14,7 +14,7 @@ def data(): def test_get_obs_order_normalized(data): - obs_order = plot._get_obs_order(data, normalize=True) + obs_order = plot.get_obs_order(data, normalize=True) # A next to D position_a = np.where(obs_order == "a")[0][0] @@ -28,7 +28,7 @@ def test_get_obs_order_normalized(data): def test_get_obs_order_unnormalized(data): - obs_order = plot._get_obs_order(data, normalize=False) + obs_order = plot.get_obs_order(data, normalize=False) # A as far away from D as possible position_a = np.where(obs_order == "a")[0][0] From 02ff59b86abec70f24590e0b02413beb4aef48b6 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 15:03:43 -0400 Subject: [PATCH 083/103] improve types --- src/salamander/models/corrnmf.py | 2 +- src/salamander/models/signature_nmf.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/salamander/models/corrnmf.py b/src/salamander/models/corrnmf.py index c1ab83a..60c70a8 100644 --- a/src/salamander/models/corrnmf.py +++ b/src/salamander/models/corrnmf.py @@ -208,7 +208,7 @@ def _get_embedding_plot_adata( return plot_adata, basis - def _get_default_embedding_plot_annotations(self) -> np.ndarray: + def _get_default_embedding_plot_annotations(self) -> list[str]: """ The embedding plot defaults to annotating the signature embeddings. """ diff --git a/src/salamander/models/signature_nmf.py b/src/salamander/models/signature_nmf.py index 6147ebb..3dba3f6 100644 --- a/src/salamander/models/signature_nmf.py +++ b/src/salamander/models/signature_nmf.py @@ -187,16 +187,16 @@ def __init__( self.history: dict[str, Any] = {} @property - def mutation_types(self) -> np.ndarray: - return self.adata.var_names.to_numpy(dtype=str) + def mutation_types(self) -> list[str]: + return list(self.adata.var_names) @property - def signature_names(self) -> np.ndarray: - return self.asignatures.obs_names.to_numpy(dtype=str) + def signature_names(self) -> list[str]: + return list(self.asignatures.obs_names) @property - def sample_names(self) -> np.ndarray: - return self.adata.obs_names.to_numpy(dtype=str) + def sample_names(self) -> list[str]: + return list(self.adata.obs_names) @property def signatures(self) -> pd.DataFrame: From a3e339e4470944d6783da0c23ba9a76b4c79b305 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 15:04:48 -0400 Subject: [PATCH 084/103] add tests --- .../multimodal_corrnmf/model0_counts.csv | 97 +++++ .../models/multimodal_corrnmf/model0_p.npy | Bin 0 -> 15488 bytes .../model0_sample_scalings_init.npy | Bin 0 -> 208 bytes .../model0_sample_scalings_updated.npy | Bin 0 -> 208 bytes .../model0_signature_embeddings_init.npy | Bin 0 -> 160 bytes .../model0_signature_embeddings_updated.npy | Bin 0 -> 160 bytes .../model0_signature_scalings_init.npy | Bin 0 -> 144 bytes .../model0_signature_scalings_updated.npy | Bin 0 -> 144 bytes .../model0_signatures_mat_init.npy | Bin 0 -> 1664 bytes .../model0_signatures_mat_updated.npy | Bin 0 -> 1664 bytes .../multimodal_corrnmf/model1_counts.csv | 97 +++++ .../models/multimodal_corrnmf/model1_p.npy | Bin 0 -> 23168 bytes .../model1_sample_scalings_init.npy | Bin 0 -> 208 bytes .../model1_sample_scalings_updated.npy | Bin 0 -> 208 bytes .../model1_signature_embeddings_init.npy | Bin 0 -> 176 bytes .../model1_signature_embeddings_updated.npy | Bin 0 -> 176 bytes .../model1_signature_scalings_init.npy | Bin 0 -> 152 bytes .../model1_signature_scalings_updated.npy | Bin 0 -> 152 bytes .../model1_signatures_mat_init.npy | Bin 0 -> 2432 bytes .../model1_signatures_mat_updated.npy | Bin 0 -> 2432 bytes .../multimodal_corrnmf/objective_init.npy | Bin 0 -> 136 bytes .../sample_embeddings_init.npy | Bin 0 -> 288 bytes .../sample_embeddings_updated.npy | Bin 0 -> 288 bytes .../multimodal_corrnmf/variance_init.npy | Bin 0 -> 136 bytes .../multimodal_corrnmf/variance_updated.npy | Bin 0 -> 136 bytes tests/test_mmcorrnmf.py | 347 ++++++++++++++++++ 26 files changed, 541 insertions(+) create mode 100644 tests/test_data/models/multimodal_corrnmf/model0_counts.csv create mode 100644 tests/test_data/models/multimodal_corrnmf/model0_p.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model0_sample_scalings_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model0_sample_scalings_updated.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model0_signature_embeddings_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model0_signature_embeddings_updated.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model0_signature_scalings_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model0_signature_scalings_updated.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model0_signatures_mat_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model0_signatures_mat_updated.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model1_counts.csv create mode 100644 tests/test_data/models/multimodal_corrnmf/model1_p.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model1_sample_scalings_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model1_sample_scalings_updated.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model1_signature_embeddings_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model1_signature_embeddings_updated.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model1_signature_scalings_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model1_signature_scalings_updated.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model1_signatures_mat_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/model1_signatures_mat_updated.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/objective_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/sample_embeddings_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/sample_embeddings_updated.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/variance_init.npy create mode 100644 tests/test_data/models/multimodal_corrnmf/variance_updated.npy create mode 100644 tests/test_mmcorrnmf.py diff --git a/tests/test_data/models/multimodal_corrnmf/model0_counts.csv b/tests/test_data/models/multimodal_corrnmf/model0_counts.csv new file mode 100644 index 0000000..b05dddb --- /dev/null +++ b/tests/test_data/models/multimodal_corrnmf/model0_counts.csv @@ -0,0 +1,97 @@ +modality0,sample1,sample2,sample3,sample4,sample5,sample6,sample7,sample8,sample9,sample10 +A[C>A]A,76,51,59,154,39,22,99,113,53,279 +A[C>A]C,53,21,34,117,47,28,62,132,45,245 +A[C>A]G,5,6,8,23,10,5,15,23,9,31 +A[C>A]T,44,25,25,126,26,22,56,107,28,258 +C[C>A]A,43,34,41,111,17,14,82,120,24,255 +C[C>A]C,41,16,20,107,20,12,47,122,27,192 +C[C>A]G,11,4,5,16,7,4,7,21,4,24 +C[C>A]T,47,28,44,105,24,12,46,117,35,263 +G[C>A]A,58,50,32,77,31,16,58,66,31,172 +G[C>A]C,27,22,22,65,27,12,32,93,16,131 +G[C>A]G,14,13,8,14,7,11,10,17,10,21 +G[C>A]T,41,25,23,65,22,15,30,69,14,174 +T[C>A]A,99,41,67,81,33,23,844,85,31,793 +T[C>A]C,88,31,36,89,54,19,333,85,47,340 +T[C>A]G,14,5,9,14,8,7,69,14,5,49 +T[C>A]T,140,89,70,108,57,27,397,101,37,533 +A[C>G]A,51,10,49,80,22,13,120,106,16,169 +A[C>G]C,43,5,23,53,14,8,37,61,19,94 +A[C>G]G,15,4,14,23,7,5,8,24,7,33 +A[C>G]T,37,13,43,79,17,6,90,113,21,190 +C[C>G]A,40,4,33,56,9,14,118,61,16,131 +C[C>G]C,29,7,21,58,18,10,36,78,15,96 +C[C>G]G,7,6,11,28,5,4,15,43,8,26 +C[C>G]T,52,5,35,76,19,13,132,107,19,172 +G[C>G]A,14,5,14,39,10,10,40,69,9,64 +G[C>G]C,22,12,12,20,9,12,23,62,11,70 +G[C>G]G,6,3,8,13,4,5,6,20,2,20 +G[C>G]T,34,7,20,55,14,9,56,94,7,117 +T[C>G]A,467,9,73,79,47,26,5724,94,27,2286 +T[C>G]C,157,15,41,69,27,11,1074,111,21,564 +T[C>G]G,22,2,10,17,4,5,245,17,9,104 +T[C>G]T,537,17,117,149,103,40,6300,208,71,3000 +A[C>T]A,105,35,68,117,62,31,174,141,66,269 +A[C>T]C,55,20,29,65,37,25,48,80,41,112 +A[C>T]G,225,133,129,162,196,145,133,146,149,120 +A[C>T]T,79,38,53,122,44,23,85,120,38,252 +C[C>T]A,119,48,47,62,56,30,291,101,51,213 +C[C>T]C,79,50,36,53,55,21,80,78,49,122 +C[C>T]G,139,83,83,73,114,87,154,89,102,101 +C[C>T]T,102,44,64,126,55,26,187,163,56,272 +G[C>T]A,101,47,40,74,41,37,163,124,49,166 +G[C>T]C,86,39,32,71,60,21,81,80,52,130 +G[C>T]G,197,140,117,100,156,95,121,117,109,90 +G[C>T]T,60,37,44,73,39,30,103,94,45,161 +T[C>T]A,623,67,103,96,82,44,7108,107,110,3738 +T[C>T]C,235,63,67,71,83,34,1283,99,70,749 +T[C>T]G,161,77,62,67,77,61,804,88,77,305 +T[C>T]T,387,70,92,99,92,48,3227,138,66,1728 +A[T>A]A,24,14,29,57,27,21,44,58,23,93 +A[T>A]C,22,10,21,42,29,9,26,49,41,92 +A[T>A]G,37,16,22,54,17,22,35,65,24,76 +A[T>A]T,63,39,44,72,26,20,51,118,30,153 +C[T>A]A,19,9,21,44,11,11,21,64,10,79 +C[T>A]C,29,12,19,82,10,13,18,102,14,147 +C[T>A]G,25,10,27,67,14,10,29,69,17,91 +C[T>A]T,37,8,39,119,15,7,34,91,24,178 +G[T>A]A,14,9,10,40,9,3,22,41,6,57 +G[T>A]C,17,12,12,37,14,4,14,39,13,62 +G[T>A]G,28,15,14,41,10,7,22,40,12,59 +G[T>A]T,30,7,32,71,8,4,17,72,12,105 +T[T>A]A,76,20,35,63,33,22,45,77,33,97 +T[T>A]C,25,8,20,28,13,6,23,45,10,83 +T[T>A]G,16,5,9,36,12,4,10,40,7,49 +T[T>A]T,41,26,39,121,21,10,43,117,21,199 +A[T>C]A,98,41,85,113,54,42,88,158,73,153 +A[T>C]C,57,18,23,56,19,17,36,86,15,86 +A[T>C]G,84,15,33,57,30,24,58,132,34,99 +A[T>C]T,100,35,67,116,71,39,76,168,79,152 +C[T>C]A,47,12,31,41,22,11,24,74,21,63 +C[T>C]C,53,15,20,81,19,14,25,110,19,96 +C[T>C]G,46,13,27,46,21,15,28,89,22,44 +C[T>C]T,42,13,35,66,33,20,45,114,27,111 +G[T>C]A,73,22,30,46,45,18,50,82,33,67 +G[T>C]C,42,16,18,36,18,12,23,48,20,49 +G[T>C]G,47,15,24,50,24,10,26,65,30,55 +G[T>C]T,67,16,44,66,37,13,37,74,32,67 +T[T>C]A,62,28,37,40,34,15,52,77,38,83 +T[T>C]C,62,21,26,45,28,14,37,74,20,89 +T[T>C]G,35,21,20,20,15,10,22,55,18,36 +T[T>C]T,68,24,48,77,33,22,82,100,37,94 +A[T>G]A,22,7,12,35,11,6,17,50,16,52 +A[T>G]C,8,4,13,19,9,10,12,36,9,24 +A[T>G]G,14,5,11,51,11,10,20,45,6,75 +A[T>G]T,33,8,20,30,22,17,28,47,13,50 +C[T>G]A,15,1,9,21,10,4,15,30,8,26 +C[T>G]C,10,5,11,28,7,7,7,28,10,41 +C[T>G]G,32,6,16,40,12,12,23,63,11,68 +C[T>G]T,31,13,15,34,15,14,31,65,11,131 +G[T>G]A,6,1,13,23,5,3,9,27,2,24 +G[T>G]C,9,2,7,19,6,3,7,19,4,23 +G[T>G]G,17,7,11,44,7,31,15,58,9,84 +G[T>G]T,23,3,21,29,9,5,18,42,5,61 +T[T>G]A,25,5,23,31,14,11,15,53,12,56 +T[T>G]C,21,3,10,17,9,8,13,32,6,40 +T[T>G]G,27,4,16,57,10,8,27,46,15,67 +T[T>G]T,48,18,34,80,27,22,57,110,27,123 diff --git a/tests/test_data/models/multimodal_corrnmf/model0_p.npy b/tests/test_data/models/multimodal_corrnmf/model0_p.npy new file mode 100644 index 0000000000000000000000000000000000000000..315c4d52bb52ce59f01233d2b36e4d8cb0951ae4 GIT binary patch literal 15488 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 OIwnS}Z_H&WvhaVD47b$) literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model0_sample_scalings_init.npy b/tests/test_data/models/multimodal_corrnmf/model0_sample_scalings_init.npy new file mode 100644 index 0000000000000000000000000000000000000000..b2861acbbde0b3fb0defd0b1132ed615fba4980e GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41FjB(o%a|#2eMy z7fK3F&Q@~Rx1L>vXP=ydVy5Yn(*G(BKaa<|KM7ED*emKK@^6)_gP7{`o636?9RMME BJXQbz literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model0_sample_scalings_updated.npy b/tests/test_data/models/multimodal_corrnmf/model0_sample_scalings_updated.npy new file mode 100644 index 0000000000000000000000000000000000000000..b038d1ec3b8533e134990fdfcc57e5222e1907e5 GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL411^P55$S|bd50TcGZ)NEm2@zCce6Qoj=Y0h+ngERWpWOJ z{yxlmQY_Wg-*z5VGMIY@g_SK57y?MZXOZ)0M#WtVp E0o{@<3jhEB literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model0_signature_embeddings_updated.npy b/tests/test_data/models/multimodal_corrnmf/model0_signature_embeddings_updated.npy new file mode 100644 index 0000000000000000000000000000000000000000..2078ae46cae687e58f7d0b7c1b67a8dca6c7dbb4 GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Pzeb3bhL411_s|c56AVueU!rscPo>x1a1ot?#Cs5Iks~b92j9wUZz0 E0n6$vl>h($ literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model0_signature_scalings_init.npy b/tests/test_data/models/multimodal_corrnmf/model0_signature_scalings_init.npy new file mode 100644 index 0000000000000000000000000000000000000000..6d8806c09b7db9757249ef89e554eec62b5507d0 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= YXCxM+0{I$7I+{8PwF(pfE(SCJ0177?T>t<8 literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model0_signature_scalings_updated.npy b/tests/test_data/models/multimodal_corrnmf/model0_signature_scalings_updated.npy new file mode 100644 index 0000000000000000000000000000000000000000..5e6d3b0abc68e469bb4fc3ff33c3411e6d6d0e5e GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= mXCxM+0{I$7I+{8PwF(pfE{2}0Kaxso_A_Yz(%EXe!X5xZ10o*) literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model0_signatures_mat_init.npy b/tests/test_data/models/multimodal_corrnmf/model0_signatures_mat_init.npy new file mode 100644 index 0000000000000000000000000000000000000000..bd982238393ff192eab5e2d00c7e943569b2a866 GIT binary patch literal 1664 zcmbW0`9Bm01IF2Udlexj4ZW_e8OPfy-jL%#8q}1dC7QephQTmqOk;)^gCtkhNabi% zCfBZ8#2amP+h$Okm3Ozi)#TW+#qvgnYwf?V&u`E3EEz zEjlwB@{%!KBbD^r%trP!vA3-=0Ru6YDDiQb;L%sYcBKbGHl{_oWo zALr6OGcigO!WsS11pI$zzg(j^18^j)&}&%uZ)|7Prf<^F*5#?W#?le2BZ>tLRT?zU zk36_7D?kfP^jqbF5Xb|j9(`Muh4%f~nt_)i@G3AydQFs$sFTgD23`L~#zw|EqDv5_ z?o@lN@$g22n(FlX{L?7a)+(*#aiG&>Wc@ULcd&r_X{x%Q14R#H&- zD3Dl%PPBFZaO2m}4(xm9dacc%2)q9lz5aJa32ZDSPQ!$F_>53?yuZzZ{f&dI>V6@3 zDeL9d)XLydW0xFmoCMC=od=kE3lPxx%csI6F3NizF7J4fi1vD(fxpQYAij>=;eNCn zyjI4`TgT6WBTy~dXCOvXWuqwkU?Ca@%9e)nPD5pWj509JLA~Kbm%3RR%#3s*FID8? zvA1=_5BeF1nIuWO>NCMLr3AXF=726~>|Q#Vh-K&cx*+*kR8QQB$yU#Yo?Z^s`WYQ- z`&ErtYs=v=s%8J>&um0??zWgthIV$AYC~h|dSFlkr6GF>KYfp!(z9tpui{K|0!_FFpjP)3wQHeFF-{U_FM6uaLAJsvqSS{cs@-tRpcdt(rDaKu402+ispdaS`3M; zY|E+2IE-(}&zE(uVgB8t`08dSq<03;6vpX@%zLqIlAnQ5Er~$WH3&Hz3s(v=1+PB} z-@Fu&(Rn2772!kz`kzUORjCYIzLsrwsAko#mAQF3DFNd|dxlG68h8nAWtS?#Fm@(b zpDpKLGGo!fhs=Y({8_%1fRCX^C-nW_dct(O_cV){g|dgEdY*O>=$d=z6y0_JlFx;bBycs3&l+rQnL(UaZU6oUJ@%K4@CmdMy{o^@a%1$w;|+&G0HLXIvi z7B_gJ?}fRy)d?1OLpEszeNp(Z5OdSvdMIX;mekZ(9E^XKd(p&Q9X+W%D4*kFa#w(Z Wob3nA$g5jQh%}6dQWkfqr{fP|QQXS_ literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model0_signatures_mat_updated.npy b/tests/test_data/models/multimodal_corrnmf/model0_signatures_mat_updated.npy new file mode 100644 index 0000000000000000000000000000000000000000..57d3796a0bd73a0bae30419e6a4375f3a88756c4 GIT binary patch literal 1664 zcmbV||34E51IK+Q;weNqX_bt}@YvH4I-h6Rh*~RO(zN-u&3p?p?M7^En6O0TX_70Y zI|<=ZkMrVCN)ENFPFmluTwEt5`sQ5sFFfzx-tQmZ<=evO+nL&0BCUNyE>FlwCwdcz z8{;+*Nd#hCYI;U`OiEO0I+yo%_m4>y@;-NAd`ueevpafukO*Wa5+R51|6-i4D(zd7 zxF3?T?tcCLBB)jg7*u%{B25O#-AV;MTDOdKQzFqc^v|re$q-y=S*mC+-48PxN%LFD zeh4`}O*v6nNFLw3OVE~wIl0zO9bOu;{T6E1`{u%=?n0?`I|bLe2yey`SZFKJeX5Bn z!K&1%2%W)5cv?4DfALa*Z3nuzBhwr>)-=_6&dMMh^XXf0kO{gbq`q@y6e#~NQkAb} zpj}o%uKqR~g<^6m<9aF*G$u9PFDo(p`dG-55Eb4%&7>+f=E7d;wR%#g6#Pwh;>gPd zFrgnTw$$^$tDANIvp8FY9}5hr_1AOp_Nr;u=HPu;#*iL0b(3Pnf}?JliwI}tR=zc& z$3gG8U)ySfI2^FJJjuOALAd>rUJ0=PSG$f53Ae^!oOsRj*eN+~%c9vj+0oDrYCI%8 znGVu#)G{;og*f^D%ywkDcP8Q)XM+|+hXe79XxzXu z6{Zt*$9O40Xt1M|*rW(SuI_NXzjh~BrYWC{uTbGz8Nq$FhKe#bqZh;OGK7c}GS9s~ zfbmK=6ViQ-??%p4xhwZl#gbA z!bGIp^Zc{~oo5}_Ssf~cGbN+Gq9+6F#JyjdP_nSDdP}%Ch>8BrB0)Rr1O|=SPh?LE zU_iOv|2~T%%$XC5iz9MyhUfiV?{ER`mHoatF z=O8fOqM--@a2EG}%X}n7;G3FVJKk-^nlQC`+FOZ@@-^G~x;Mdgtsiap%XCy<^wUl# zk3nS5$u|GdXx!LQf8pUBDVpt;M@+rThHWA@kZK-^`S3Gr?ssws+v&lq`^gyhxy*l0 zOGD$IzckPPn-2TIMt2%j0Nqf^55DFM1lFu4^t`9y;*c9Xp+*R!fsDlGto=ChCcaAeYVro-DqSEZ<(_dpg+@18cStkWq`x0N#1s>jr#s{ygC6Y+Mv}O z3NX;S)o^M>62wn09U@68P+`%>YT@qy!{YG3&ZMuQI&I)jo!JAI#m57K22$bZaIS|o zAVaafR#TiuIZhNj9@f{yAmG8DIws$^gLW=^>Sb3BcF!vcZW;K(PN_IH=9P>8l#D-k zT_FIEJ0r2UvIjY4`-Y}(QlTumVC55(j|KgOt&25sw0^@7w4P$Z@^;}@k62lFVX!nl zMrSP?e*Kr(y?GH_lNKf|Z^vS%6Y-2CO$ev3WtCKp2u_RS{O2cRSQ4%uTE>k5GeSIZ zcaac=Wa^Sxh7hM8O=x<0LRKQw8a|J%ord`2yIxxy z1kgL)ThX*38m_;+s100Ag(T~p(ZYjhREO5t=oz|UOgG9~)tH4k7x&qbx#gHJA1(>4 z3P$oT1h`z?gJN;ZQAI%#mbG_od!ikSJmQolz$^u%Jm#}{z9GU&*WEuZ-U|!5mHou! VV5oaPxpw(SBVq3fO|~=-e*sex%x(Yx literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model1_counts.csv b/tests/test_data/models/multimodal_corrnmf/model1_counts.csv new file mode 100644 index 0000000..3c32519 --- /dev/null +++ b/tests/test_data/models/multimodal_corrnmf/model1_counts.csv @@ -0,0 +1,97 @@ +modality1,sample1,sample2,sample3,sample4,sample5,sample6,sample7,sample8,sample9,sample10 +a,25,126,270,42,119,51,127,27,99,121 +b,21,63,220,26,76,23,90,20,62,125 +c,7,16,45,12,15,12,12,4,15,17 +d,19,48,157,32,61,38,60,24,56,130 +e,21,105,174,33,115,40,79,16,82,111 +f,15,41,137,21,67,20,65,9,47,96 +g,1,29,27,13,22,7,25,6,7,20 +h,21,77,149,24,108,20,57,24,46,111 +i,19,123,150,21,117,47,82,18,58,90 +j,16,47,101,18,48,24,49,6,32,84 +k,3,19,21,7,13,8,18,4,10,16 +l,17,81,140,21,86,23,58,15,30,89 +m,35,160,223,36,147,228,136,28,844,108 +n,36,97,180,42,119,80,126,26,333,100 +o,6,20,29,14,23,17,25,5,69,12 +p,30,247,263,39,217,123,165,26,397,141 +q,18,32,187,21,63,22,104,11,120,184 +r,11,30,104,22,40,10,64,6,37,77 +s,2,26,59,20,15,3,21,3,8,49 +t,22,52,165,19,59,23,97,7,90,165 +u,11,41,98,16,38,17,34,8,118,100 +v,9,18,90,26,27,12,44,6,36,83 +w,6,25,57,24,16,5,22,2,15,44 +x,17,26,177,22,51,26,67,5,132,120 +y,10,20,104,10,34,15,39,1,40,84 +z,10,24,70,11,23,11,41,4,23,78 +aa,4,8,28,8,8,3,6,1,6,26 +ab,16,19,132,11,35,15,54,8,56,134 +ac,141,65,411,58,127,1007,146,6,5724,205 +ad,53,41,232,28,67,191,100,10,1074,117 +ae,10,9,40,8,5,35,13,2,245,26 +af,170,107,731,84,236,1123,200,7,6300,283 +ag,32,118,252,60,125,67,175,35,174,161 +ah,19,58,120,24,78,40,66,21,48,88 +ai,60,203,283,102,379,102,268,89,133,165 +aj,27,86,225,32,85,38,90,28,85,154 +ak,23,85,165,34,96,81,136,23,291,110 +al,27,59,130,26,87,53,107,31,80,63 +am,50,131,164,65,225,67,191,57,154,120 +an,42,99,217,34,95,82,102,30,187,130 +ao,16,72,187,41,89,82,123,21,163,144 +ap,19,77,137,34,111,56,100,32,81,76 +aq,45,155,162,88,336,96,197,61,121,126 +ar,20,69,154,30,98,40,114,30,103,92 +as,161,124,476,63,192,2105,202,32,7108,160 +at,59,107,263,50,185,399,163,35,1283,104 +au,44,90,179,69,185,194,174,38,804,98 +av,104,95,382,63,158,998,169,35,3227,129 +aw,21,45,99,10,62,11,49,16,44,71 +ax,24,29,67,20,35,20,33,17,26,57 +ay,13,28,100,9,32,22,41,7,35,61 +az,19,86,168,30,105,22,71,19,51,87 +ba,10,19,79,10,19,5,27,3,21,65 +bb,11,35,74,12,30,11,30,7,18,74 +bc,12,19,104,12,28,14,46,5,29,67 +bd,11,25,138,20,35,13,55,10,34,128 +be,9,9,77,8,29,10,16,3,22,42 +bf,8,13,45,5,15,5,19,4,14,27 +bg,7,30,92,12,22,10,23,7,22,47 +bh,6,27,77,12,30,6,32,6,17,72 +bi,22,91,152,22,91,23,72,19,45,76 +bj,10,30,77,10,28,12,36,10,23,45 +bk,7,11,57,13,17,11,34,4,10,30 +bl,14,72,166,33,77,23,78,16,43,113 +bm,41,105,287,42,156,63,121,29,88,186 +bn,17,53,106,13,40,12,34,16,36,128 +bo,27,52,160,27,64,35,75,21,58,114 +bp,42,93,300,55,120,50,91,37,76,186 +bq,13,35,98,19,60,13,43,11,24,94 +br,4,35,106,27,43,16,49,14,25,114 +bs,16,39,96,23,39,19,34,10,28,95 +bt,18,40,154,24,59,19,65,11,45,116 +bu,18,57,146,22,87,29,81,10,50,103 +bv,15,31,65,14,55,19,57,8,23,66 +bw,11,34,85,22,38,13,40,5,26,60 +bx,18,65,167,28,76,22,71,23,37,89 +by,13,61,156,27,104,24,74,16,52,125 +bz,14,59,124,24,67,26,53,10,37,116 +ca,11,49,70,18,40,19,33,11,22,61 +cb,14,80,170,40,105,24,91,25,82,123 +cc,11,16,65,13,45,12,32,6,17,57 +cd,4,15,20,1,23,6,11,7,12,30 +ce,10,18,65,11,25,10,24,3,20,73 +cf,11,31,59,17,32,11,49,4,28,59 +cg,5,9,56,5,18,8,21,2,15,35 +ch,4,20,48,8,11,8,21,2,7,39 +ci,12,24,94,12,24,12,33,8,23,63 +cj,19,36,105,11,41,17,35,6,31,85 +ck,4,13,54,7,12,6,9,3,9,37 +cl,6,7,20,6,16,9,8,3,7,19 +cm,10,17,99,9,27,6,22,6,15,70 +cn,9,21,65,8,21,7,28,5,18,66 +co,12,32,109,12,33,17,34,1,15,70 +cp,5,20,51,8,22,10,32,6,13,46 +cq,10,20,81,12,23,9,30,9,27,82 +cr,26,60,145,31,73,24,77,7,57,146 diff --git a/tests/test_data/models/multimodal_corrnmf/model1_p.npy b/tests/test_data/models/multimodal_corrnmf/model1_p.npy new file mode 100644 index 0000000000000000000000000000000000000000..70327d5434b074fe9fa3b5812a64bfe1fd567a56 GIT binary patch literal 23168 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 XRCwdT6&TWDsO=4#C@!8cu3Y>No>lKR literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model1_sample_scalings_init.npy b/tests/test_data/models/multimodal_corrnmf/model1_sample_scalings_init.npy new file mode 100644 index 0000000000000000000000000000000000000000..181f85d55c7b371e8e0e39768103573555398d67 GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41FqHI>$O7p#2qfY%hh?|E$Ogo#?AA3l?o1To?WxPL(b)zLKfn;IG80CE}vskn})u*_MwI4*QwSf_0ZFI&}L?<*U0X>i_`1 Ca6Q}r literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model1_sample_scalings_updated.npy b/tests/test_data/models/multimodal_corrnmf/model1_sample_scalings_updated.npy new file mode 100644 index 0000000000000000000000000000000000000000..aa41a8b6107be3475e7a66affd78ac142b75c7f8 GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL41FlbU_ivmJ5_izJDd)6orKH0K-J`GnO;B*Sl9POl?TffW z%2cN(tCcTHS>T;8ANCV5_Y-+%ju?wzHZ%^vPQmiuo5=Yr$= VTYKKh&pY+bo+onVhh_GU?E(BCHZK4G literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model1_signature_embeddings_updated.npy b/tests/test_data/models/multimodal_corrnmf/model1_signature_embeddings_updated.npy new file mode 100644 index 0000000000000000000000000000000000000000..2673ea36b5cb847e3d8169f6d08f862a4040a3e6 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3dWi`3bhL411?dfY%STGYx}ihUdya5|7X9ML&<&V#|QhpKKFI)`*Uo6 V%(VoUHoLd>OVu7Umo9p24*;AfG!_5= literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model1_signature_scalings_init.npy b/tests/test_data/models/multimodal_corrnmf/model1_signature_scalings_init.npy new file mode 100644 index 0000000000000000000000000000000000000000..047c0d9b08d60f22875dac9130262ce53c8caa57 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= YXCxM+0{I%oI+{8PwF(pfE(RO`0Oe{MUH||9 literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model1_signature_scalings_updated.npy b/tests/test_data/models/multimodal_corrnmf/model1_signature_scalings_updated.npy new file mode 100644 index 0000000000000000000000000000000000000000..67c7f96910db86b438487d5ac1e1f70bbc28e653 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= vXCxM+0{I%oI+{8PwF(pfE(YPfOxMcR*fV_LYTCM`+n(W4+`IKJ=Gy}RVlO7G literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/model1_signatures_mat_init.npy b/tests/test_data/models/multimodal_corrnmf/model1_signatures_mat_init.npy new file mode 100644 index 0000000000000000000000000000000000000000..4e0ee57747ed3221b780d7fa5adbcb3b0a083a80 GIT binary patch literal 2432 zcmbW1`6CpF1I2YnN}`JxyLFzy*grqM92oCzJ0ql40hB&^60 zm2xCYYqheiScH7Dkt4po|HAkE_CCM7_se@3Cr;WDT~`T&3Y=GR_h8dmN`|sZMhtx= zHCZJ_04tb9^QQ!`+&%usEoi=Mj}^}Lq6K=a_nOYO)cs|F1SsnR}Xk5@YaP zJjJ}-FdnqD`c4NBY zl9MyhVfgKVt|zZN9i_6G!CSnOp}k47;B=-51{(Z4?!=|xd1XOoOkXfA9v765GveSt zx?#GnfD??(Z;?8^HDP_Lp?mWMDjEq+hbbFep*HqxX6hLYL+0$MRv8{zOgjo~r=n5! zB);r*K_q&g$mLCldDIB0M=Ycotxww8dSS%2bL83nPz24Z3yd~}Vf0Ytpn_H~j*dS%)>-9= z5d)S%EG-Nja|P{1V^p-ze0~Uq)37F(U{I8F2AfI~2+AXY=%vUsOHycfsHnPV`ZNxk z?oRaT@8RNy*~a5#1|&S(<)G#IgM|rC71MoXTv*zCPfQl3K}BcI{^=+S*3-&w>a;vy zp7r-G)|@>?i91fEKF~+AS6qT{u?I|QcGDEsdceO~pMQTk8n4WjVzP&9fc3iw-I@*H z(F@+~{`Co5XNXFU*NX8`*iLWaSpi0`Z(+nu$0GYsF{4i_4fFSCg1iD6hRbJmCKPZ% zP<MZIw)d{oGzbW-}Ld z9|G=`t7M=dz>`0($iu&h#fww&=TQEWx#8IB1jLZt7WcYDqNdH>IIM|*(pO~_;%iPL zoM|x-@jM%|>KFf$u`>Z|<7)o;zG#H(oOxTC7KM=h^+^FvT zK|FFeCG}tv%-G>(=Znr^{});FbHc@Vlzc?@+vjK`(k))tb)_QRO{Py~T^5oTZ?QDl z*KtCzOOtI?gQ2dpuuVGoc)|F5yP~-mqmP@vSedQ#Qh!wyzAXX^rt1kNTpzs2ir*q2 z8VKpu@F`0k9ir9Ak60Pu$OtTLFX-{Z`@zgHiXzgz z8ilmX9Z@fWLowD~@Ram{0WXzN(SLG+u=Z}iNL8%?uzCOG++_;v3O#chW&&`A+qN&S zj0M3G1@Gix3bxHKtrOI_@G?^kTzf?w<4YU52v>vA-#@p-B8P>U_S$X=`2rLIV)N9w z?#K``eYgy7JhmK;Cfc}Rt9j|?y1!QbWJhJ?vI7-+_6D^s5v*XvRFrtdr{VDKl!~W> z5Zn$dUQ6GU{rYzfdT95>0h%x!ON+cXqr~^sLeC*vL&!%3b z;H%G4mz=K;s&j>ZBNi&6Q1(ZsL=7Kgf9(nRv;fHT_18CLQb4+;W|DgFC?u*9H5L~* zuq0S$8Lj-M3UB0}_P^rbtor_h*n9++hQaCqZH_N{60VK8-mKwkBKFIQOKRQnw~lz z0bx}J)BdX;C_-5X)ncIg_wkrXB`yw5mA2Kr@%35~ zytn@{*k#U#(ERoLr>-!tHb#hKYw3raE+Kin!wf{HmVeebcNUJrI(i}5E_kr>`?d>S zx>&8A{aYC}_=F01UqZ8U z(LvCr&7zhiy`cGFc=$`t=k8@&J=y|7Px7OAgO38}mO_2cgr$TTNbsgR$qQ>*{BN(K2fq zp`Gf8D+G4o4FMA9sjXzby%$n!x>uh~Wgz8Cv}b8~C^CrIw^n{NcW=rk6V(?2UC9VU>{Nt<%d^4Xs}07Ph*c_q~iZ8(OX8HQ}+uw zI2>nTJSmU_VJpq94@c7AG9^O3W*r8WsqLy+FbCQrWw)tz2}obg`l)o#3DU}agM86M zT-3XG;IgC#_UUUY2*yQY@>k|{n<6%@o;mGyh8K->^0(5{XOr+xNHejN8UnGr?(zN; ziSSweVh|Tbf%;T?hAJ->RaAcooqBIrS2Rw#>~cqa$KJp6Y>uI>)nS@+CkXustDnEk zxM5aOy=Y7+64lF9+$ZdG)CQD9`Z^?m{cLak+Vn}Nt5JPSJg=bh<@VAE4g*%F&&pL; zhhS+*^?cd~FX+|GoKY&%f!zn)72LlJ+k?fwN&S(cx#APbnA3V zdSdEva95vw4D8=Wl+q*OP_d@IEN(#qd4FQJ`jrq+aZnorL}hd&klgENd2 z9v4Yk>)G#)$HD98l(UA}VUSBT$am8O9#g%>`70EFXUPG9Fl9p@#p0{Il6N?N;l|rH0X1|i%mGu8C+WR4q^|yZ|{v+%9?@ z2L&w?*%OZGXwy6@&DhL=gZC4?S#u6}wjU34T5xe+k3yA6<$x^S^;%Dbf%<#J%<`A6 zkeP@G%o)vwkax?;x#O46{hYf#_ihYG%LXm>Ct{KQk8`qrYYZZ~>TX18T*G7sm#?<0 z6DtqoeT^bpp-G_?yGw;)ntf}zxU3SZ4cdPRzfD2on?~V(pI?NLcDDJ<_FP!2R90?? z$b`6no!q0zCJd`&#xV_xu~t7_vTk2Bgbi|(RE5vs*0CV2VgVa-X^uCY5@^tV8F|tu zJP6@GHSbIBr+~l8kKf-i6#uxTjMnw~fw##_G}n#}qqnch;QT6;u)A~~+?379wDYEg0_H)9E zjhmS0mZ$&9Iw4 za)k_~-W8*^b5v|UDCwFh>Vk{|t4CfJc))yq_q(EeBH}YW1l)NJ$ocux>q-qT+$ro? zl%&$}uUf~>r=gapdG_6Ri=G~wvOG?Nm9gMi`}pXjq9@uDe=1rsi@^AojgN)}I3S8t zP<%v!5UFv>XecTOJmcNk)@}xfG8i0sVHAs}oq`(R_k)Ku^=+Cn3ELyS&$|rSAS_^S z)^Eqd5Y$3AbhP`&o^}6{wR)U@#)XRuJ~q*a)tQ;TelH#pUCy`tkLSY9-^(F>i4D&D zm#ny18jK5sWw-psKt<@#*;!>O_*AS$c$c{-Rv#HSWfqR8;iiloF%h_KDa%1A^pF+E-*a#cGB**fmC)<5!{ma#G#lwo2aSd?`-nFEip zySl1>hJz#^>&KP#N7d6Ehc{XKBW1~T*R%EE=v5XP-!~eLsks*i+AHZe%UatO85xWo z!A;K#grbqz$z-MN`89tUwk~|*xs`>Ya@`5O7laTP*D*=527YMq zPSZ&?C*jPi?*UiLQ}D$h;hW)Y8cN6skrqo+vpbpRl{f%c^d*6 z?+a2oJTE}VmPasV#A952gm~%44^B*#i;C&`WA=Ngeb*LmSO$JRDns+YM!`(iVC&N$ z^h{q{7h;ZYI;zqf-vHzthAeq!xJv6%9S+uooaU^82W&* z-%6l7_7JE_+_7b6Zwx={c5ArniS#F#xj7L*fRnA(yo5dG+l5`U0#4v_a61HH0ryjr zT+wVM`fFTgG{w%tbERdeaw{7(@q71NHKt-=p|hdxB^}zmM-*&CeUZ1;|A$nyBU*@- z>OGlG;LfzTyD8WrCANg3cGDUBTQ>|k2s%R1;$aZm<0P)=&u=TNp`+x}q3F$IADC)n z*WNfwM{4*Ar-*b4ZE>}Q#P2~U+vaBQP|_8h;s$qov}uqwuD5F#3InnAhU35^8rb7g zwGvt`Xuh1(AbG?M+g{1M&=B>*mjS~AOTT;L;Y3$;avKALRucG06A?R%K!iX literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/sample_embeddings_init.npy b/tests/test_data/models/multimodal_corrnmf/sample_embeddings_init.npy new file mode 100644 index 0000000000000000000000000000000000000000..2016f3585f4198e8322d08f8160997efb747221a GIT binary patch literal 288 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Wf%nItsN4WCN~{xzdIcl78)%EqZ=V>+)^;Og_f7l7D~OznuNM%uM{Y z{RO4&6&EIa-+%GHZHiCh<^6wtb6%HN|7E}Hvk8R)x9-`W+Tay6ukF?T1(7?NX5am} z|9JKc{;Q8Z+NZqubl~chH~Tl|3NaihKd^tob)l-Ap3nC;aD0?EKk;(^?efij+Mh1% mXHcKHGw#Lj{b60H&!1&2v6o(QQ22l4#{DI6I=v=RckKZlV|HZ# literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/sample_embeddings_updated.npy b/tests/test_data/models/multimodal_corrnmf/sample_embeddings_updated.npy new file mode 100644 index 0000000000000000000000000000000000000000..9707161c10f1679266cf286471401489efac1b89 GIT binary patch literal 288 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u zBo?Fsxf(_~3Wf%nItsN4WCJe6?tjafgMaUDulY8UZSie;`~NR?rak&?ulOPUOvc>X z_Kro<+M7;%+iw^3fPVq^#r^RMa=v(P|GGc1oXa)X^`5b( zcimm>&Uf$a-&CLe6Q1*W|Jk_;O-tps?QdP6e&$ia^Zj=nfBVNAd%j=Ct<1C{;PQU% lnNze^`v2aa*spzQ>F;g!e0Gm*U)*22e|g!&=_R#y>;bnCb$I{) literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/variance_init.npy b/tests/test_data/models/multimodal_corrnmf/variance_init.npy new file mode 100644 index 0000000000000000000000000000000000000000..c4f032c5d522e968eedd177b02f91ccb263bdb1f GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alV(@45LNc)qg-06fhfn*aa+ literal 0 HcmV?d00001 diff --git a/tests/test_data/models/multimodal_corrnmf/variance_updated.npy b/tests/test_data/models/multimodal_corrnmf/variance_updated.npy new file mode 100644 index 0000000000000000000000000000000000000000..c4f032c5d522e968eedd177b02f91ccb263bdb1f GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46alV(@45LNc)qg-06fhfn*aa+ literal 0 HcmV?d00001 diff --git a/tests/test_mmcorrnmf.py b/tests/test_mmcorrnmf.py new file mode 100644 index 0000000..e2bbda5 --- /dev/null +++ b/tests/test_mmcorrnmf.py @@ -0,0 +1,347 @@ +import numpy as np +import pandas as pd +import pytest +from anndata import AnnData +from mudata import MuData + +from salamander.models.mmcorrnmf import MultimodalCorrNMF + +PATH = "tests/test_data" +PATH_TEST_DATA = f"{PATH}/models/multimodal_corrnmf" +N_MOD = 2 +NS_SIGNATURES = [2, 3] +DIM_EMBEDDINGS = 2 + + +@pytest.fixture +def counts(): + """ + Input count data. + """ + return { + f"mod{n}": pd.read_csv(f"{PATH_TEST_DATA}/model{n}_counts.csv", index_col=0).T + for n in range(N_MOD) + } + + +@pytest.fixture +def sample_scalings_init(): + return { + f"mod{n}": np.load(f"{PATH_TEST_DATA}/model{n}_sample_scalings_init.npy") + for n in range(N_MOD) + } + + +@pytest.fixture +def sample_embeddings_init(): + """ + Initial joint sample embeddings. + """ + return np.load(f"{PATH_TEST_DATA}/sample_embeddings_init.npy").T + + +@pytest.fixture +def mdata(counts, sample_scalings_init, sample_embeddings_init): + adatas = {mod_name: AnnData(data) for mod_name, data in counts.items()} + mdata = MuData(adatas) + mdata.obsm["embeddings"] = sample_embeddings_init + + for mod_name in mdata.mod.keys(): + mdata[mod_name].obs["scalings"] = sample_scalings_init[mod_name] + + return mdata + + +@pytest.fixture +def signatures_mat_init(): + return { + f"mod{n}": np.load(f"{PATH_TEST_DATA}/model{n}_signatures_mat_init.npy").T + for n in range(N_MOD) + } + + +@pytest.fixture +def signature_scalings_init(): + return { + f"mod{n}": np.load(f"{PATH_TEST_DATA}/model{n}_signature_scalings_init.npy") + for n in range(N_MOD) + } + + +@pytest.fixture +def signature_embeddings_init(): + return { + f"mod{n}": np.load(f"{PATH_TEST_DATA}/model{n}_signature_embeddings_init.npy").T + for n in range(N_MOD) + } + + +@pytest.fixture +def asignatures_init( + mdata, signatures_mat_init, signature_scalings_init, signature_embeddings_init +): + asignatures = {} + for mod_name in mdata.mod: + asigs = AnnData(signatures_mat_init[mod_name]) + asigs.var_names = mdata[mod_name].var_names + asigs.obs["scalings"] = signature_scalings_init[mod_name] + asigs.obsm["embeddings"] = signature_embeddings_init[mod_name] + asignatures[mod_name] = asigs + return asignatures + + +@pytest.fixture +def _ps(): + return { + f"mod{n}": np.load(f"{PATH_TEST_DATA}/model{n}_p.npy") for n in range(N_MOD) + } + + +@pytest.fixture +def _auxs(counts, _ps): + return { + mod_name: np.einsum("vd,vkd->kd", data.T.values, _ps[mod_name]) + for mod_name, data in counts.items() + } + + +@pytest.fixture +def variance_init(): + """ + Initial joint variance. + """ + return np.load(f"{PATH_TEST_DATA}/variance_init.npy") + + +@pytest.fixture +def model_init( + mdata, + asignatures_init, + variance_init, +): + model = MultimodalCorrNMF( + ns_signatures=NS_SIGNATURES, dim_embeddings=DIM_EMBEDDINGS + ) + model.mdata = mdata + model.asignatures = asignatures_init + model.compute_exposures() + model.variance = variance_init + return model + + +def test_init_signature_names(model_init): + # one given signature per modality + given_sig_name = "A" + given_parameters = {} + + for mod_name, adata in model_init.mdata.mod.items(): + asigs = AnnData(np.zeros((1, adata.n_vars))) + asigs.obs_names = [given_sig_name] + asigs.var_names = adata.var_names + given_parameters[mod_name] = {"asignatures": asigs} + + model_init._initialize(given_parameters) + + for mod_name, asigs in model_init.asignatures.items(): + for k, sig_name in enumerate(asigs.obs_names): + if k == 0: + assert sig_name == given_sig_name + else: + assert sig_name == f"{mod_name} Sig{k}" + + +@pytest.fixture +def objective_init(): + return np.load(f"{PATH_TEST_DATA}/objective_init.npy") + + +def test_objective_function(model_init, objective_init): + assert np.allclose(model_init.objective_function(), objective_init) + + +@pytest.fixture +def signatures_mat_updated(): + return { + f"mod{n}": np.load(f"{PATH_TEST_DATA}/model{n}_signatures_mat_updated.npy").T + for n in range(N_MOD) + } + + +@pytest.fixture +def sample_scalings_updated(): + return { + f"mod{n}": np.load(f"{PATH_TEST_DATA}/model{n}_sample_scalings_updated.npy") + for n in range(N_MOD) + } + + +@pytest.fixture +def sample_embeddings_updated(): + return np.load(f"{PATH_TEST_DATA}/sample_embeddings_updated.npy").T + + +@pytest.fixture +def signature_scalings_updated(): + return { + f"mod{n}": np.load(f"{PATH_TEST_DATA}/model{n}_signature_scalings_updated.npy") + for n in range(N_MOD) + } + + +@pytest.fixture +def signature_embeddings_updated(): + return { + f"mod{n}": np.load( + f"{PATH_TEST_DATA}/model{n}_signature_embeddings_updated.npy" + ).T + for n in range(N_MOD) + } + + +@pytest.fixture +def variance_updated(): + return np.load(f"{PATH_TEST_DATA}/variance_updated.npy") + + +class TestUpdatesMultimodalCorrNMF: + def test_update_signatures(self, model_init, signatures_mat_updated): + model_init.update_signatures() + + for mod_name, sigs in model_init.asignatures.items(): + assert np.allclose(sigs.X, signatures_mat_updated[mod_name]) + + def test_update_sample_scalings(self, model_init, sample_scalings_updated): + model_init.update_sample_scalings() + + for mod_name, adata in model_init.mdata.mod.items(): + assert np.allclose(adata.obs["scalings"], sample_scalings_updated[mod_name]) + + def test_update_signature_scalings( + self, model_init, _auxs, signature_scalings_updated + ): + model_init.update_signature_scalings(_auxs) + + for mod_name, sigs in model_init.asignatures.items(): + assert np.allclose( + sigs.obs["scalings"], signature_scalings_updated[mod_name] + ) + + def test_compute_aux(self, model_init, _auxs): + auxs = model_init._compute_auxs() + + for mod_name, aux in auxs.items(): + assert np.allclose(aux, _auxs[mod_name]) + + def test_update_signature_embeddings( + self, model_init, _auxs, signature_embeddings_updated + ): + model_init.update_signature_embeddings(_auxs) + + for mod_name, asigs in model_init.asignatures.items(): + assert np.allclose( + asigs.obsm["embeddings"], signature_embeddings_updated[mod_name] + ) + + def test_update_sample_embeddings( + self, model_init, _auxs, sample_embeddings_updated + ): + model_init.update_sample_embeddings(_auxs) + assert np.allclose( + model_init.mdata.obsm["embeddings"], sample_embeddings_updated + ) + + def test_update_variance(self, model_init, variance_updated): + model_init.update_variance() + assert np.allclose(model_init.variance, variance_updated) + + +@pytest.mark.parametrize( + "ns_signatures,dim_embeddings", [([1, 2], 1), ([2, 2], 1), ([2, 2], 2)] +) +class TestGivenParametersMultimodalCorrNMF: + @pytest.fixture() + def model(self, ns_signatures, dim_embeddings): + model = MultimodalCorrNMF( + ns_signatures=ns_signatures, + dim_embeddings=dim_embeddings, + max_iterations=3, + ) + return model + + def test_given_asignatures(self, model, mdata): + mod_name0, mod_name1 = mdata.mod.keys() + n_sigs0 = model.ns_signatures[0] + + for n_given_sigs in range(1, n_sigs0 + 1): + given_asigs0 = mdata.mod[mod_name0][:n_given_sigs, :].copy() + given_asigs0.X = given_asigs0.X.astype(float) + given_asigs0.X /= np.sum(given_asigs0.X, axis=1, keepdims=True) + given_parameters = {mod_name0: {"asignatures": given_asigs0}} + model.fit(mdata, given_parameters=given_parameters) + assert np.allclose( + given_asigs0.X, model.asignatures[mod_name0].X[:n_given_sigs, :] + ) + assert not np.allclose( + given_asigs0.X, + model.asignatures[mod_name1].X[:n_given_sigs, :], + ) + # check if other mod0 signatures are updated + if n_given_sigs < n_sigs0: + sigs0_other = model.asignatures[mod_name0].X[n_given_sigs:, :].copy() + model._update_parameters(given_parameters) + assert not np.allclose( + sigs0_other, model.asignatures[mod_name0].X[n_given_sigs:, :] + ) + + def test_given_signature_scalings(self, model, mdata): + mod_name0, mod_name1 = mdata.mod.keys() + n_sigs0 = model.ns_signatures[0] + given_sig_scalings0 = np.random.uniform(size=n_sigs0) + given_parameters = {mod_name0: {"signature_scalings": given_sig_scalings0}} + model.fit(mdata, given_parameters=given_parameters) + assert np.allclose( + given_sig_scalings0, model.asignatures[mod_name0].obs["scalings"] + ) + assert not np.allclose( + given_sig_scalings0, model.asignatures[mod_name1].obs["scalings"][:n_sigs0] + ) + + def test_given_signature_embeddings(self, model, mdata): + mod_name0, mod_name1 = mdata.mod.keys() + n_sigs0 = model.ns_signatures[0] + given_sig_embeddings0 = np.random.uniform(size=(n_sigs0, model.dim_embeddings)) + given_parameters = {mod_name0: {"signature_embeddings": given_sig_embeddings0}} + model.fit(mdata, given_parameters=given_parameters) + assert np.allclose( + given_sig_embeddings0, model.asignatures[mod_name0].obsm["embeddings"] + ) + assert not np.allclose( + given_sig_embeddings0, + model.asignatures[mod_name1].obsm["embeddings"][:n_sigs0, :], + ) + + def test_given_sample_scalings(self, model, mdata): + mod_name0, mod_name1 = mdata.mod.keys() + given_sample_scalings0 = np.random.uniform(size=mdata.n_obs) + given_parameters = {mod_name0: {"sample_scalings": given_sample_scalings0}} + model.fit(mdata, given_parameters=given_parameters) + assert np.allclose( + given_sample_scalings0, model.mdata.mod[mod_name0].obs["scalings"] + ) + assert not np.allclose( + given_sample_scalings0, model.mdata.mod[mod_name1].obs["scalings"] + ) + + def test_given_sample_embeddings(self, model, mdata): + given_sample_embeddings = np.random.uniform( + size=(mdata.n_obs, model.dim_embeddings) + ) + given_parameters = {"sample_embeddings": given_sample_embeddings} + model.fit(mdata, given_parameters=given_parameters) + assert np.allclose(given_sample_embeddings, model.mdata.obsm["embeddings"]) + + def test_given_variance(self, model, mdata): + given_variance = 3.0 + given_parameters = {"variance": given_variance} + model.fit(mdata, given_parameters=given_parameters) + assert np.allclose(given_variance, model.variance) From dbb762ab11d5285570a06be85a4be78449a71be7 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 15:05:16 -0400 Subject: [PATCH 085/103] add multimodal init --- src/salamander/initialization/initialize.py | 88 ++++++++++++++++++++- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/src/salamander/initialization/initialize.py b/src/salamander/initialization/initialize.py index 4ada538..dde7d29 100644 --- a/src/salamander/initialization/initialize.py +++ b/src/salamander/initialization/initialize.py @@ -7,6 +7,7 @@ from typing import Any import anndata as ad +import mudata as md import numpy as np from ..utils import ( @@ -381,13 +382,94 @@ def initialize_corrnmf( variance = 1.0 return asignatures, variance + + +def check_given_parameters_mmcorrnmf( + mdata: md.MuData, + ns_signatures: list[int], + dim_embeddings: int, + given_parameters: dict[str, Any], +) -> None: + valid_keys = list(mdata.mod.keys()) + ["sample_embeddings", "variance"] + dict_checker("given_parameters", given_parameters, valid_keys) + + for (mod_name, adata), n_signatures in zip(mdata.mod.items(), ns_signatures): + if mod_name in given_parameters: + given_parameters_mod = given_parameters[mod_name] + else: + given_parameters_mod = {} + + check_given_parameters_corrnmf( + adata, n_signatures, dim_embeddings, given_parameters_mod + ) + if "sample_embeddings" in given_parameters_mod: + raise KeyError( + "The sample embeddings are shared across modalities in multimodal " + "correlated NMF. They cannot be provided as given parameters on the " + "modality level." + ) + if "variance" in given_parameters_mod: + raise KeyError( + "The variance parameters of multimodal correlated NMF is shared " + "across modalies. It cannot be provided as a given parameter on the " + "modality level." + ) + + +def initialize_mmcorrnmf( + mdata: md.MuData, + ns_signatures: list[int], + dim_embeddings: int, + method: _Init_methods = "nndsvd", + given_parameters: dict[str, Any] | None = None, + **kwargs, +) -> tuple[dict[str, ad.AnnData], float]: + """ + Initialize the MuData object and the annotated signatures of all modalities. + + Multimodal correlated NMF shares the sample embeddings across modalities. + There are sample biases, signatures, and signature scalings & embeddings for + each individual modality. + """ + given_parameters = {} if given_parameters is None else given_parameters.copy() + check_given_parameters_mmcorrnmf( + mdata, ns_signatures, dim_embeddings, given_parameters + ) + asignatures = {} + + for (mod_name, adata), n_signatures in zip(mdata.mod.items(), ns_signatures): + if mod_name in given_parameters: + given_parameters_mod = given_parameters[mod_name] + else: + given_parameters_mod = {} + + asigs, _ = initialize_corrnmf( + adata, + n_signatures, + dim_embeddings, + method, + given_parameters_mod, + initialize_sample_embeddings=False, + **kwargs, + ) + if "asignatures" in given_parameters_mod: + n_given_sigs = given_parameters_mod["asignatures"].n_obs + else: + n_given_sigs = 0 + + sig_names_new = [ + f"{mod_name} " + sig_name for sig_name in asigs.obs_names[n_given_sigs:] + ] + asigs.obs_names = list(asigs.obs_names[:n_given_sigs]) + sig_names_new + asignatures[mod_name] = asigs + if "sample_embeddings" in given_parameters: - adata.obsm["embeddings"] = given_parameters["sample_embeddings"] + mdata.obsm["embeddings"] = given_parameters["sample_embeddings"] else: - adata.obsm["embeddings"] = np.random.multivariate_normal( + mdata.obsm["embeddings"] = np.random.multivariate_normal( np.zeros(dim_embeddings), np.identity(dim_embeddings), - size=adata.n_obs, + size=mdata.n_obs, ) if "variance" in given_parameters: From 90ae921504ce74ffa1eb9d0bca5df6ac5d04d406 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 15:08:33 -0400 Subject: [PATCH 086/103] add multimodal corrnmf --- src/salamander/models/mmcorrnmf.py | 717 +++++++++++++++++++++++++++++ 1 file changed, 717 insertions(+) create mode 100644 src/salamander/models/mmcorrnmf.py diff --git a/src/salamander/models/mmcorrnmf.py b/src/salamander/models/mmcorrnmf.py new file mode 100644 index 0000000..6b25c7f --- /dev/null +++ b/src/salamander/models/mmcorrnmf.py @@ -0,0 +1,717 @@ +""" +Multimodal correlated NMF fits multiple correlated NMF models jointly. + +Assuming that the input data of each modality originates from the identical samples, +multimodal correlated NMF fixes the sample embeddings accross modalities and learns +signature embeddings of all modalities in a shared embedding space. +""" + +from __future__ import annotations + +import warnings +from typing import TYPE_CHECKING, Any, Iterable, Literal + +import anndata as ad +import matplotlib.pyplot as plt +import mudata as md +import numpy as np +import pandas as pd + +from .. import plot as pl +from .. import tools as tl +from ..initialization.initialize import EPSILON, _Init_methods, initialize_mmcorrnmf +from ..utils import _get_basis_obsm, dict_checker, type_checker, value_checker +from . import _utils_corrnmf +from ._utils_klnmf import samplewise_kl_divergence, update_W + +if TYPE_CHECKING: + from matplotlib.axes import Axes + from matplotlib.typing import ColorType + from seaborn.matrix import ClusterGrid + + from .signature_nmf import _Dim_reduction_methods + + +class MultimodalCorrNMF: + def __init__( + self, + ns_signatures: list[int], + dim_embeddings: int | None = None, + init_method: _Init_methods = "nndsvd", + min_iterations: int = 500, + max_iterations: int = 10000, + conv_test_freq: int = 10, + tol: float = 1e-7, + ): + self.ns_signatures = ns_signatures + + if dim_embeddings is None: + dim_embeddings = np.max(ns_signatures) + + self.dim_embeddings = dim_embeddings + self.init_method = init_method + self.min_iterations = min_iterations + self.max_iterations = max_iterations + self.conv_test_freq = conv_test_freq + self.tol = tol + self.variance = 1.0 + + # initialize data/fitting dependent attributes + mod_names_default = [f"mod{n}" for n in range(1, len(ns_signatures) + 1)] + self.mdata = md.MuData( + {mod_name: ad.AnnData() for mod_name in mod_names_default} + ) + self.asignatures = {mod_name: ad.AnnData() for mod_name in mod_names_default} + self.history: dict[str, Any] = {} + self.signature_correlation = np.empty((sum(ns_signatures), sum(ns_signatures))) + self.signature_correlation[:] = np.nan + + @property + def mod_names(self) -> list[str]: + return list(self.mdata.mod.keys()) + + @property + def mutation_types(self) -> dict[str, list[str]]: + return { + mod_name: list(adata.var_names) + for mod_name, adata in self.mdata.mod.items() + } + + @property + def signature_names(self) -> dict[str, list[str]]: + return { + mod_name: list(asigs.obs_names) + for mod_name, asigs in self.asignatures.items() + } + + @property + def sample_names(self) -> list[str]: + return list(self.mdata.obs_names) + + @property + def signatures(self) -> dict[str, pd.DataFrame]: + return {mod_name: asigs.to_df() for mod_name, asigs in self.asignatures.items()} + + @property + def exposures(self) -> dict[str, pd.DataFrame]: + exposures_all = {} + for mod_name in self.mod_names: + exposures_all[mod_name] = pd.DataFrame( + self.mdata[mod_name].obsm["exposures"], + index=self.sample_names, + columns=self.asignatures[mod_name].obs_names, + ) + return exposures_all + + def compute_exposures(self) -> None: + for mod_name in self.mod_names: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + adata.obsm["exposures"] = _utils_corrnmf.compute_exposures( + asigs.obs["scalings"].values, + adata.obs["scalings"].values, + asigs.obsm["embeddings"], + self.mdata.obsm["embeddings"], + ) + + def compute_reconstruction(self) -> None: + for mod_name in self.mod_names: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + adata.obsm["X_reconstructed"] = adata.obsm["exposures"] @ asigs.X + + @property + def data_reconstructed(self) -> dict[str, pd.DataFrame]: + for adata in self.mdata.mod.values(): + if "X_reconstructed" not in adata.obsm: + self.compute_reconstruction() + + data_reconstructed_all = {} + for mod_name, adata in self.mdata.mod.items(): + data_reconstructed_all[mod_name] = pd.DataFrame( + adata.obsm["X_reconstructed"], + index=adata.obs_names, + columns=adata.var_names, + ) + return data_reconstructed_all + + def compute_reconstruction_errors(self) -> None: + self.compute_exposures() + + for mod_name in self.mod_names: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + errors = samplewise_kl_divergence( + adata.X.T, asigs.X.T, adata.obsm["exposures"].T + ) + adata.obs["reconstruction_error"] = errors + + self.mdata.update() + + @property + def reconstruction_errors(self) -> dict[str, float]: + return { + mod_name: np.sum(adata.obs["reconstruction_error"]) + for mod_name, adata in self.mdata.mod.items() + } + + @property + def reconstruction_error(self) -> float: + return np.sum(list(self.reconstruction_errors.values())) + + def objective_function(self) -> float: + """ + The ELBO of multimodal correlated NMF. + """ + elbo = 0.0 + + for mod_name in self.mod_names: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + elbo += _utils_corrnmf.elbo_corrnmf( + adata.X, + asigs.X, + adata.obsm["exposures"], + asigs.obsm["embeddings"], + self.mdata.obsm["embeddings"], + self.variance, + penalize_sample_embeddings=False, + ) + + elbo -= ( + 0.5 + * self.dim_embeddings + * self.mdata.n_obs + * np.log(2 * np.pi * self.variance) + ) + elbo -= np.sum(self.mdata.obsm["embeddings"] ** 2) / (2 * self.variance) + return elbo + + @property + def objective(self) -> Literal["minimize", "maximize"]: + return "maximize" + + def _setup_mdata(self, mdata: md.MuData): + type_checker("mdata", mdata, md.MuData) + n_mod_expected = len(self.ns_signatures) + + if mdata.n_mod != n_mod_expected: + raise ValueError(f"The data has to have {n_mod_expected} many modalities.") + + sample_names_expected = list(mdata.mod.values())[0].obs_names + + for adata in mdata.mod.values(): + if not all(adata.obs_names == sample_names_expected): + raise ValueError( + "The sample names of the different modalities are not identical." + ) + + self.mdata = mdata + + def _initialize( + self, + given_parameters: dict[str, Any] | None = None, + init_kwargs: dict[str, Any] | None = None, + ) -> None: + init_kwargs = {} if init_kwargs is None else init_kwargs.copy() + self.asignatures, self.variance = initialize_mmcorrnmf( + self.mdata, + self.ns_signatures, + self.dim_embeddings, + self.init_method, + given_parameters, + **init_kwargs, + ) + self.compute_exposures() + + def _compute_auxs(self) -> dict[str, np.ndarray]: + r""" + auxs: dict[str, np.ndarray] + For every modality + aux_kd = \sum_v X_vd * p_vkd + is used for updating the signatures and the sample embeddidngs. + """ + auxs = {} + for mod_name in self.mod_names: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + auxs[mod_name] = _utils_corrnmf.compute_aux( + adata.X, asigs.X, adata.obsm["exposures"] + ) + return auxs + + def update_sample_scalings( + self, + given_parameters: dict[str, Any] | None = None, + ) -> None: + if given_parameters is None: + given_parameters = {} + + for mod_name, adata in self.mdata.mod.items(): + if mod_name in given_parameters: + given_parameters_mod = given_parameters[mod_name] + else: + given_parameters_mod = {} + + if "sample_scalings" not in given_parameters_mod: + asigs = self.asignatures[mod_name] + adata.obs["scalings"] = _utils_corrnmf.update_sample_scalings( + adata.X, + asigs.obs["scalings"].values, + asigs.obsm["embeddings"], + self.mdata.obsm["embeddings"], + ) + + def update_signature_scalings( + self, + auxs: dict[str, np.ndarray], + given_parameters: dict[str, Any] | None = None, + ) -> None: + if given_parameters is None: + given_parameters = {} + + for mod_name, asigs in self.asignatures.items(): + if mod_name in given_parameters: + given_parameters_mod = given_parameters[mod_name] + else: + given_parameters_mod = {} + + if "signature_scalings" not in given_parameters_mod: + asigs.obs["scalings"] = _utils_corrnmf.update_signature_scalings( + auxs[mod_name], + self.mdata[mod_name].obs["scalings"].values, + asigs.obsm["embeddings"], + self.mdata.obsm["embeddings"], + ) + + def update_variance(self, given_parameters: dict[str, Any] | None = None) -> None: + if given_parameters is None: + given_parameters = {} + + if "variance" not in given_parameters: + signature_embeddings = np.concatenate( + [asigs.obsm["embeddings"] for asigs in self.asignatures.values()] + ) + embeddings = np.concatenate( + [signature_embeddings, self.mdata.obsm["embeddings"]] + ) + variance = np.mean(embeddings**2) + self.variance = np.clip(variance, EPSILON, None) + + def update_signatures(self, given_parameters: dict[str, Any] | None = None) -> None: + if given_parameters is None: + given_parameters = {} + + for mod_name, asigs in self.asignatures.items(): + if mod_name in given_parameters: + given_parameters_mod = given_parameters[mod_name] + else: + given_parameters_mod = {} + + if "asignatures" in given_parameters_mod: + n_given_signatures = given_parameters_mod["asignatures"].n_obs + else: + n_given_signatures = 0 + + W = update_W( + self.mdata[mod_name].X.T, + asigs.X.T, + self.mdata[mod_name].obsm["exposures"].T, + n_given_signatures=n_given_signatures, + ) + asigs.X = W.T + + def update_signature_embeddings( + self, + auxs: dict[str, np.ndarray], + given_parameters: dict[str, Any] | None = None, + ) -> None: + """ + Update all signature embeddings by optimizing + the surrogate objective function using scipy.optimize.minimize + with the 'Newton-CG' method. + """ + if given_parameters is None: + given_parameters = {} + + outer_prods_sample_embeddings = np.einsum( + "Dm,Dn->Dmn", + self.mdata.obsm["embeddings"], + self.mdata.obsm["embeddings"], + ) + + for mod_name, asigs in self.asignatures.items(): + if mod_name in given_parameters: + given_parameters_mod = given_parameters[mod_name] + else: + given_parameters_mod = {} + + if "signature_embeddings" not in given_parameters_mod: + aux = auxs[mod_name] + for k, aux_row in enumerate(aux): + embedding_init = asigs.obsm["embeddings"][k, :] + asigs.obsm["embeddings"][k, :] = _utils_corrnmf.update_embedding( + embedding_init, + self.mdata.obsm["embeddings"], + asigs.obs["scalings"][k], + self.mdata[mod_name].obs["scalings"].values, + self.variance, + aux_row, + outer_prods_sample_embeddings, + ) + + def update_sample_embeddings(self, auxs: dict[str, np.ndarray]) -> None: + sig_embeddings = np.concatenate( + [asigs.obsm["embeddings"] for asigs in self.asignatures.values()] + ) + outer_prods_sig_embeddings = np.einsum( + "Km,Kn->Kmn", sig_embeddings, sig_embeddings + ) + sig_scalings = np.concatenate( + [asigs.obs["scalings"] for asigs in self.asignatures.values()] + ) + aux = np.concatenate([aux for aux in auxs.values()]) + + for d, aux_col in enumerate(aux.T): + embedding_init = self.mdata.obsm["embeddings"][d, :] + scalings = np.concatenate( + [ + np.repeat(adata.obs["scalings"][d], n_signatures) + for adata, n_signatures in zip( + self.mdata.mod.values(), self.ns_signatures + ) + ] + ) + self.mdata.obsm["embeddings"][d, :] = _utils_corrnmf.update_embedding( + embedding_init, + sig_embeddings, + scalings, + sig_scalings, + self.variance, + aux_col, + outer_prods_sig_embeddings, + options={"maxiter": 3}, + ) + + def update_embeddings( + self, + auxs: dict[str, np.ndarray], + given_parameters: dict[str, Any] | None = None, + ) -> None: + if given_parameters is None: + given_parameters = {} + + self.update_signature_embeddings(auxs, given_parameters) + + if "sample_embeddings" not in given_parameters: + self.update_sample_embeddings(auxs) + + def _update_parameters(self, given_parameters: dict[str, Any] | None = None): + if given_parameters is None: + given_parameters = {} + + self.update_sample_scalings(given_parameters) + self.compute_exposures() + auxs = self._compute_auxs() + self.update_signature_scalings(auxs, given_parameters) + self.update_embeddings(auxs, given_parameters) + self.update_variance(given_parameters) + self.update_signatures(given_parameters) + + def fit( + self, + mdata: md.MuData, + given_parameters: dict[str, Any] | None = None, + init_kwargs: dict[str, Any] | None = None, + history: bool = True, + verbose: Literal[0, 1] = 0, + verbosity_freq: int = 100, + ) -> MultimodalCorrNMF: + self._setup_mdata(mdata) + self._initialize(given_parameters, init_kwargs) + of_values = [self.objective_function()] + n_iteration = 0 + converged = False + + while not converged: + n_iteration += 1 + + if verbose and n_iteration % verbosity_freq == 0: + print(f"iteration: {n_iteration}; objective: {of_values[-1]:.2f}") + + self._update_parameters(given_parameters) + + if n_iteration % self.conv_test_freq == 0: + prev_of_value = of_values[-1] + of_values.append(self.objective_function()) + rel_change_nominator = np.abs(prev_of_value - of_values[-1]) + rel_change = rel_change_nominator / np.abs(prev_of_value) + converged = rel_change < self.tol and n_iteration >= self.min_iterations + + converged |= n_iteration >= self.max_iterations + + if history: + self.history["objective_function"] = of_values[1:] + + self.mdata.update() + return self + + def plot_history(self, outfile: str | None = None, **kwargs) -> Axes: + if not self.history: + raise ValueError( + "No history available, the model has to be fitted first. " + "Remember to set 'history' to 'True' when calling 'fit()'." + ) + + ax = pl.history( + values=self.history["objective_function"], + conv_test_freq=self.conv_test_freq, + **kwargs, + ) + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") + + return ax + + def plot_signatures( + self, + colors: dict[str, ColorType | list[ColorType]] | None = None, + annotate_mutation_types: bool = False, + figsize: tuple[float, float] | None = None, + outfile: str | None = None, + **kwargs, + ): + colors = {} if colors is None else colors.copy() + dict_checker("colors", colors, self.mod_names) + max_n_signatures = np.max(self.ns_signatures) + + if figsize is None: + figsize = (4 * self.mdata.n_mod, max_n_signatures) + + fig, axes = plt.subplots(max_n_signatures, self.mdata.n_mod, figsize=figsize) + + for mod_name, axs in zip(self.mod_names, axes.T): + sigs = self.asignatures[mod_name] + cols = colors[mod_name] if mod_name in colors else None + n_sigs = sigs.n_obs + pl.barplot( + sigs, + colors=cols, + annotate_vars=annotate_mutation_types, + axes=axs[:n_sigs], + **kwargs, + ) + for ax in axs[n_sigs:]: + fig.delaxes(ax) + + plt.tight_layout() + + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") + + return axes + + def plot_exposures( + self, + sample_order: np.ndarray | None = None, + reorder_signatures: bool = True, + annotate_samples: bool = True, + colors: dict[str, Iterable[ColorType]] | None = None, + axes: Iterable[Axes] | None = None, + outfile: str | None = None, + **kwargs, + ) -> Iterable[Axes]: + """ + Visualize the exposures as a stacked bar chart, + see plot.py for the implementation. + + Input: + ------ + **kwargs: + arguments to be passed to exposure_plot + """ + if axes is None: + _, axes = plt.subplots(self.mdata.n_mod, figsize=(20, 3 * self.mdata.n_mod)) + + colors = {} if colors is None else colors.copy() + dict_checker("colors", colors, self.mod_names) + exposures = self.exposures + + if sample_order is None: + exposures_all_normalized = pd.concat( + [df.div(df.sum(axis=1), axis=0) for df in exposures.values()], axis=1 + ) + sample_order = pl.get_obs_order(exposures_all_normalized) + + for n, (mod_name, ax) in enumerate(zip(self.mod_names, axes)): + expos = exposures[mod_name] + cols = colors[mod_name] if mod_name in colors else None + + if n < self.mdata.n_mod - 1: + annotate = False + else: + annotate = annotate_samples + + ax = pl.stacked_barplot( + data=expos, + obs_order=sample_order, + reorder_dimensions=reorder_signatures, + annotate_obs=annotate, + colors=cols, + ax=ax, + **kwargs, + ) + ax.set_title(f"{self.mod_names[n]} signature exposures") + + plt.tight_layout() + + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") + + return axes + + def compute_correlation( + self, data: Literal["samples", "signatures"] = "signatures", **kwargs + ) -> None: + """ + Compute the signature or sample correlation. The signature + correlation is stored as a new model attribute, the sample correlation + is stored in mdata. + """ + value_checker("data", data, ["samples", "signatures"]) + + for adata in self.mdata.mod.values(): + assert "exposures" in adata.obsm, ( + "Computing the sample or signature correlation " + "requires fitting the NMF model." + ) + + values = np.concatenate( + [adata.obsm["exposures"] for adata in self.mdata.mod.values()], axis=1 + ) + + if data == "signatures": + values = values.T + + correlation = tl.correlation_numpy(values, **kwargs) + + if data == "samples": + self.mdata.obsp["X_correlation"] = correlation + else: + self.signature_correlation = correlation + + def correlation( + self, data: Literal["samples", "signatures"] = "signatures" + ) -> pd.DataFrame: + """ + Dataframe of the signature or sample correlation. + """ + value_checker("data", data, ["samples", "signatures"]) + + if data == "samples": + if "X_correlation" not in self.mdata.obsp: + self.compute_correlation("samples") + values = self.mdata.obsp["X_correlation"] + names = self.sample_names + + else: + if np.isnan(self.signature_correlation).all(): + self.compute_correlation("signatures") + values = self.signature_correlation + names = sum(self.signature_names.values(), []) + + correlation_df = pd.DataFrame(values, index=names, columns=names) + return correlation_df + + def plot_correlation( + self, + data: Literal["samples", "signatures"] = "signatures", + annot: bool | None = None, + outfile: str | None = None, + **kwargs, + ) -> ClusterGrid: + """ + Plot the signature or sample correlation. + """ + value_checker("data", data, ["samples", "signatures"]) + corr = self.correlation(data=data) + + if annot is None: + annot = False if data == "samples" else True + + clustergrid = pl.correlation_pandas(corr, annot=annot, **kwargs) + + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") + + return clustergrid + + def reduce_dimension_embeddings( + self, method: _Dim_reduction_methods = "umap", n_components: int = 2, **kwargs + ) -> None: + adatas = list(self.asignatures.values()) + [self.mdata] + tl.reduce_dimension_multiple( + adatas=adatas, + basis="embeddings", + method=method, + n_components=n_components, + **kwargs, + ) + + def _get_embedding_plot_adata( + self, method: _Dim_reduction_methods = "umap" + ) -> tuple[ad.AnnData, str]: + """ + Plot the exposures directly if the number of signatures is at most 2. + """ + adata_base = ad.AnnData(X=np.zeros((self.mdata.n_obs, 1))) + adata_base.obsm["embeddings"] = self.mdata.obsm["embeddings"] + adata_base.obsm[f"X_{method}"] = _get_basis_obsm(self.mdata, method) + adatas = list(self.asignatures.values()) + [adata_base] + plot_adata = ad.concat(adatas) + + if self.dim_embeddings <= 2: + warnings.warn( + f"The embedding dimension is {self.dim_embeddings}. " + "The embeddings are plotted without an additional " + "dimensionality reduction.", + UserWarning, + ) + basis = "embeddings" + else: + basis = method + + return plot_adata, basis + + def _get_default_embedding_plot_annotations(self) -> list[str]: + """ + The embedding plot defaults to annotating the signature embeddings. + """ + return sum(self.signature_names.values(), []) + + def plot_embeddings( + self, + method: _Dim_reduction_methods = "umap", + n_components: int = 2, + dimensions: tuple[int, int] = (0, 1), + annotations: Iterable[str] | None = None, + outfile: str | None = None, + **kwargs, + ) -> Axes: + self.reduce_dimension_embeddings(method=method, n_components=n_components) + adata, basis = self._get_embedding_plot_adata(method=method) + + if annotations is None: + annotations = self._get_default_embedding_plot_annotations() + + ax = pl.embedding( + adata=adata, + basis=basis, + dimensions=dimensions, + annotations=annotations, + **kwargs, + ) + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") + + return ax From 7be4ef7507c9ddbc4dcc1255896b8f3bb1767efe Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 15:11:55 -0400 Subject: [PATCH 087/103] add multimodal correlated NMF --- src/salamander/models/__init__.py | 2 + src/salamander/models/mmcorrnmf.py | 717 +++++++++++++++++++++++++++++ 2 files changed, 719 insertions(+) create mode 100644 src/salamander/models/mmcorrnmf.py diff --git a/src/salamander/models/__init__.py b/src/salamander/models/__init__.py index 0d3f242..8e0ee0f 100644 --- a/src/salamander/models/__init__.py +++ b/src/salamander/models/__init__.py @@ -4,10 +4,12 @@ from .corrnmf_det import CorrNMFDet from .klnmf import KLNMF +from .mmcorrnmf import MultimodalCorrNMF from .mvnmf import MvNMF __all__ = [ "CorrNMFDet", "KLNMF", + "MultimodalCorrNMF", "MvNMF", ] diff --git a/src/salamander/models/mmcorrnmf.py b/src/salamander/models/mmcorrnmf.py new file mode 100644 index 0000000..6b25c7f --- /dev/null +++ b/src/salamander/models/mmcorrnmf.py @@ -0,0 +1,717 @@ +""" +Multimodal correlated NMF fits multiple correlated NMF models jointly. + +Assuming that the input data of each modality originates from the identical samples, +multimodal correlated NMF fixes the sample embeddings accross modalities and learns +signature embeddings of all modalities in a shared embedding space. +""" + +from __future__ import annotations + +import warnings +from typing import TYPE_CHECKING, Any, Iterable, Literal + +import anndata as ad +import matplotlib.pyplot as plt +import mudata as md +import numpy as np +import pandas as pd + +from .. import plot as pl +from .. import tools as tl +from ..initialization.initialize import EPSILON, _Init_methods, initialize_mmcorrnmf +from ..utils import _get_basis_obsm, dict_checker, type_checker, value_checker +from . import _utils_corrnmf +from ._utils_klnmf import samplewise_kl_divergence, update_W + +if TYPE_CHECKING: + from matplotlib.axes import Axes + from matplotlib.typing import ColorType + from seaborn.matrix import ClusterGrid + + from .signature_nmf import _Dim_reduction_methods + + +class MultimodalCorrNMF: + def __init__( + self, + ns_signatures: list[int], + dim_embeddings: int | None = None, + init_method: _Init_methods = "nndsvd", + min_iterations: int = 500, + max_iterations: int = 10000, + conv_test_freq: int = 10, + tol: float = 1e-7, + ): + self.ns_signatures = ns_signatures + + if dim_embeddings is None: + dim_embeddings = np.max(ns_signatures) + + self.dim_embeddings = dim_embeddings + self.init_method = init_method + self.min_iterations = min_iterations + self.max_iterations = max_iterations + self.conv_test_freq = conv_test_freq + self.tol = tol + self.variance = 1.0 + + # initialize data/fitting dependent attributes + mod_names_default = [f"mod{n}" for n in range(1, len(ns_signatures) + 1)] + self.mdata = md.MuData( + {mod_name: ad.AnnData() for mod_name in mod_names_default} + ) + self.asignatures = {mod_name: ad.AnnData() for mod_name in mod_names_default} + self.history: dict[str, Any] = {} + self.signature_correlation = np.empty((sum(ns_signatures), sum(ns_signatures))) + self.signature_correlation[:] = np.nan + + @property + def mod_names(self) -> list[str]: + return list(self.mdata.mod.keys()) + + @property + def mutation_types(self) -> dict[str, list[str]]: + return { + mod_name: list(adata.var_names) + for mod_name, adata in self.mdata.mod.items() + } + + @property + def signature_names(self) -> dict[str, list[str]]: + return { + mod_name: list(asigs.obs_names) + for mod_name, asigs in self.asignatures.items() + } + + @property + def sample_names(self) -> list[str]: + return list(self.mdata.obs_names) + + @property + def signatures(self) -> dict[str, pd.DataFrame]: + return {mod_name: asigs.to_df() for mod_name, asigs in self.asignatures.items()} + + @property + def exposures(self) -> dict[str, pd.DataFrame]: + exposures_all = {} + for mod_name in self.mod_names: + exposures_all[mod_name] = pd.DataFrame( + self.mdata[mod_name].obsm["exposures"], + index=self.sample_names, + columns=self.asignatures[mod_name].obs_names, + ) + return exposures_all + + def compute_exposures(self) -> None: + for mod_name in self.mod_names: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + adata.obsm["exposures"] = _utils_corrnmf.compute_exposures( + asigs.obs["scalings"].values, + adata.obs["scalings"].values, + asigs.obsm["embeddings"], + self.mdata.obsm["embeddings"], + ) + + def compute_reconstruction(self) -> None: + for mod_name in self.mod_names: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + adata.obsm["X_reconstructed"] = adata.obsm["exposures"] @ asigs.X + + @property + def data_reconstructed(self) -> dict[str, pd.DataFrame]: + for adata in self.mdata.mod.values(): + if "X_reconstructed" not in adata.obsm: + self.compute_reconstruction() + + data_reconstructed_all = {} + for mod_name, adata in self.mdata.mod.items(): + data_reconstructed_all[mod_name] = pd.DataFrame( + adata.obsm["X_reconstructed"], + index=adata.obs_names, + columns=adata.var_names, + ) + return data_reconstructed_all + + def compute_reconstruction_errors(self) -> None: + self.compute_exposures() + + for mod_name in self.mod_names: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + errors = samplewise_kl_divergence( + adata.X.T, asigs.X.T, adata.obsm["exposures"].T + ) + adata.obs["reconstruction_error"] = errors + + self.mdata.update() + + @property + def reconstruction_errors(self) -> dict[str, float]: + return { + mod_name: np.sum(adata.obs["reconstruction_error"]) + for mod_name, adata in self.mdata.mod.items() + } + + @property + def reconstruction_error(self) -> float: + return np.sum(list(self.reconstruction_errors.values())) + + def objective_function(self) -> float: + """ + The ELBO of multimodal correlated NMF. + """ + elbo = 0.0 + + for mod_name in self.mod_names: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + elbo += _utils_corrnmf.elbo_corrnmf( + adata.X, + asigs.X, + adata.obsm["exposures"], + asigs.obsm["embeddings"], + self.mdata.obsm["embeddings"], + self.variance, + penalize_sample_embeddings=False, + ) + + elbo -= ( + 0.5 + * self.dim_embeddings + * self.mdata.n_obs + * np.log(2 * np.pi * self.variance) + ) + elbo -= np.sum(self.mdata.obsm["embeddings"] ** 2) / (2 * self.variance) + return elbo + + @property + def objective(self) -> Literal["minimize", "maximize"]: + return "maximize" + + def _setup_mdata(self, mdata: md.MuData): + type_checker("mdata", mdata, md.MuData) + n_mod_expected = len(self.ns_signatures) + + if mdata.n_mod != n_mod_expected: + raise ValueError(f"The data has to have {n_mod_expected} many modalities.") + + sample_names_expected = list(mdata.mod.values())[0].obs_names + + for adata in mdata.mod.values(): + if not all(adata.obs_names == sample_names_expected): + raise ValueError( + "The sample names of the different modalities are not identical." + ) + + self.mdata = mdata + + def _initialize( + self, + given_parameters: dict[str, Any] | None = None, + init_kwargs: dict[str, Any] | None = None, + ) -> None: + init_kwargs = {} if init_kwargs is None else init_kwargs.copy() + self.asignatures, self.variance = initialize_mmcorrnmf( + self.mdata, + self.ns_signatures, + self.dim_embeddings, + self.init_method, + given_parameters, + **init_kwargs, + ) + self.compute_exposures() + + def _compute_auxs(self) -> dict[str, np.ndarray]: + r""" + auxs: dict[str, np.ndarray] + For every modality + aux_kd = \sum_v X_vd * p_vkd + is used for updating the signatures and the sample embeddidngs. + """ + auxs = {} + for mod_name in self.mod_names: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + auxs[mod_name] = _utils_corrnmf.compute_aux( + adata.X, asigs.X, adata.obsm["exposures"] + ) + return auxs + + def update_sample_scalings( + self, + given_parameters: dict[str, Any] | None = None, + ) -> None: + if given_parameters is None: + given_parameters = {} + + for mod_name, adata in self.mdata.mod.items(): + if mod_name in given_parameters: + given_parameters_mod = given_parameters[mod_name] + else: + given_parameters_mod = {} + + if "sample_scalings" not in given_parameters_mod: + asigs = self.asignatures[mod_name] + adata.obs["scalings"] = _utils_corrnmf.update_sample_scalings( + adata.X, + asigs.obs["scalings"].values, + asigs.obsm["embeddings"], + self.mdata.obsm["embeddings"], + ) + + def update_signature_scalings( + self, + auxs: dict[str, np.ndarray], + given_parameters: dict[str, Any] | None = None, + ) -> None: + if given_parameters is None: + given_parameters = {} + + for mod_name, asigs in self.asignatures.items(): + if mod_name in given_parameters: + given_parameters_mod = given_parameters[mod_name] + else: + given_parameters_mod = {} + + if "signature_scalings" not in given_parameters_mod: + asigs.obs["scalings"] = _utils_corrnmf.update_signature_scalings( + auxs[mod_name], + self.mdata[mod_name].obs["scalings"].values, + asigs.obsm["embeddings"], + self.mdata.obsm["embeddings"], + ) + + def update_variance(self, given_parameters: dict[str, Any] | None = None) -> None: + if given_parameters is None: + given_parameters = {} + + if "variance" not in given_parameters: + signature_embeddings = np.concatenate( + [asigs.obsm["embeddings"] for asigs in self.asignatures.values()] + ) + embeddings = np.concatenate( + [signature_embeddings, self.mdata.obsm["embeddings"]] + ) + variance = np.mean(embeddings**2) + self.variance = np.clip(variance, EPSILON, None) + + def update_signatures(self, given_parameters: dict[str, Any] | None = None) -> None: + if given_parameters is None: + given_parameters = {} + + for mod_name, asigs in self.asignatures.items(): + if mod_name in given_parameters: + given_parameters_mod = given_parameters[mod_name] + else: + given_parameters_mod = {} + + if "asignatures" in given_parameters_mod: + n_given_signatures = given_parameters_mod["asignatures"].n_obs + else: + n_given_signatures = 0 + + W = update_W( + self.mdata[mod_name].X.T, + asigs.X.T, + self.mdata[mod_name].obsm["exposures"].T, + n_given_signatures=n_given_signatures, + ) + asigs.X = W.T + + def update_signature_embeddings( + self, + auxs: dict[str, np.ndarray], + given_parameters: dict[str, Any] | None = None, + ) -> None: + """ + Update all signature embeddings by optimizing + the surrogate objective function using scipy.optimize.minimize + with the 'Newton-CG' method. + """ + if given_parameters is None: + given_parameters = {} + + outer_prods_sample_embeddings = np.einsum( + "Dm,Dn->Dmn", + self.mdata.obsm["embeddings"], + self.mdata.obsm["embeddings"], + ) + + for mod_name, asigs in self.asignatures.items(): + if mod_name in given_parameters: + given_parameters_mod = given_parameters[mod_name] + else: + given_parameters_mod = {} + + if "signature_embeddings" not in given_parameters_mod: + aux = auxs[mod_name] + for k, aux_row in enumerate(aux): + embedding_init = asigs.obsm["embeddings"][k, :] + asigs.obsm["embeddings"][k, :] = _utils_corrnmf.update_embedding( + embedding_init, + self.mdata.obsm["embeddings"], + asigs.obs["scalings"][k], + self.mdata[mod_name].obs["scalings"].values, + self.variance, + aux_row, + outer_prods_sample_embeddings, + ) + + def update_sample_embeddings(self, auxs: dict[str, np.ndarray]) -> None: + sig_embeddings = np.concatenate( + [asigs.obsm["embeddings"] for asigs in self.asignatures.values()] + ) + outer_prods_sig_embeddings = np.einsum( + "Km,Kn->Kmn", sig_embeddings, sig_embeddings + ) + sig_scalings = np.concatenate( + [asigs.obs["scalings"] for asigs in self.asignatures.values()] + ) + aux = np.concatenate([aux for aux in auxs.values()]) + + for d, aux_col in enumerate(aux.T): + embedding_init = self.mdata.obsm["embeddings"][d, :] + scalings = np.concatenate( + [ + np.repeat(adata.obs["scalings"][d], n_signatures) + for adata, n_signatures in zip( + self.mdata.mod.values(), self.ns_signatures + ) + ] + ) + self.mdata.obsm["embeddings"][d, :] = _utils_corrnmf.update_embedding( + embedding_init, + sig_embeddings, + scalings, + sig_scalings, + self.variance, + aux_col, + outer_prods_sig_embeddings, + options={"maxiter": 3}, + ) + + def update_embeddings( + self, + auxs: dict[str, np.ndarray], + given_parameters: dict[str, Any] | None = None, + ) -> None: + if given_parameters is None: + given_parameters = {} + + self.update_signature_embeddings(auxs, given_parameters) + + if "sample_embeddings" not in given_parameters: + self.update_sample_embeddings(auxs) + + def _update_parameters(self, given_parameters: dict[str, Any] | None = None): + if given_parameters is None: + given_parameters = {} + + self.update_sample_scalings(given_parameters) + self.compute_exposures() + auxs = self._compute_auxs() + self.update_signature_scalings(auxs, given_parameters) + self.update_embeddings(auxs, given_parameters) + self.update_variance(given_parameters) + self.update_signatures(given_parameters) + + def fit( + self, + mdata: md.MuData, + given_parameters: dict[str, Any] | None = None, + init_kwargs: dict[str, Any] | None = None, + history: bool = True, + verbose: Literal[0, 1] = 0, + verbosity_freq: int = 100, + ) -> MultimodalCorrNMF: + self._setup_mdata(mdata) + self._initialize(given_parameters, init_kwargs) + of_values = [self.objective_function()] + n_iteration = 0 + converged = False + + while not converged: + n_iteration += 1 + + if verbose and n_iteration % verbosity_freq == 0: + print(f"iteration: {n_iteration}; objective: {of_values[-1]:.2f}") + + self._update_parameters(given_parameters) + + if n_iteration % self.conv_test_freq == 0: + prev_of_value = of_values[-1] + of_values.append(self.objective_function()) + rel_change_nominator = np.abs(prev_of_value - of_values[-1]) + rel_change = rel_change_nominator / np.abs(prev_of_value) + converged = rel_change < self.tol and n_iteration >= self.min_iterations + + converged |= n_iteration >= self.max_iterations + + if history: + self.history["objective_function"] = of_values[1:] + + self.mdata.update() + return self + + def plot_history(self, outfile: str | None = None, **kwargs) -> Axes: + if not self.history: + raise ValueError( + "No history available, the model has to be fitted first. " + "Remember to set 'history' to 'True' when calling 'fit()'." + ) + + ax = pl.history( + values=self.history["objective_function"], + conv_test_freq=self.conv_test_freq, + **kwargs, + ) + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") + + return ax + + def plot_signatures( + self, + colors: dict[str, ColorType | list[ColorType]] | None = None, + annotate_mutation_types: bool = False, + figsize: tuple[float, float] | None = None, + outfile: str | None = None, + **kwargs, + ): + colors = {} if colors is None else colors.copy() + dict_checker("colors", colors, self.mod_names) + max_n_signatures = np.max(self.ns_signatures) + + if figsize is None: + figsize = (4 * self.mdata.n_mod, max_n_signatures) + + fig, axes = plt.subplots(max_n_signatures, self.mdata.n_mod, figsize=figsize) + + for mod_name, axs in zip(self.mod_names, axes.T): + sigs = self.asignatures[mod_name] + cols = colors[mod_name] if mod_name in colors else None + n_sigs = sigs.n_obs + pl.barplot( + sigs, + colors=cols, + annotate_vars=annotate_mutation_types, + axes=axs[:n_sigs], + **kwargs, + ) + for ax in axs[n_sigs:]: + fig.delaxes(ax) + + plt.tight_layout() + + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") + + return axes + + def plot_exposures( + self, + sample_order: np.ndarray | None = None, + reorder_signatures: bool = True, + annotate_samples: bool = True, + colors: dict[str, Iterable[ColorType]] | None = None, + axes: Iterable[Axes] | None = None, + outfile: str | None = None, + **kwargs, + ) -> Iterable[Axes]: + """ + Visualize the exposures as a stacked bar chart, + see plot.py for the implementation. + + Input: + ------ + **kwargs: + arguments to be passed to exposure_plot + """ + if axes is None: + _, axes = plt.subplots(self.mdata.n_mod, figsize=(20, 3 * self.mdata.n_mod)) + + colors = {} if colors is None else colors.copy() + dict_checker("colors", colors, self.mod_names) + exposures = self.exposures + + if sample_order is None: + exposures_all_normalized = pd.concat( + [df.div(df.sum(axis=1), axis=0) for df in exposures.values()], axis=1 + ) + sample_order = pl.get_obs_order(exposures_all_normalized) + + for n, (mod_name, ax) in enumerate(zip(self.mod_names, axes)): + expos = exposures[mod_name] + cols = colors[mod_name] if mod_name in colors else None + + if n < self.mdata.n_mod - 1: + annotate = False + else: + annotate = annotate_samples + + ax = pl.stacked_barplot( + data=expos, + obs_order=sample_order, + reorder_dimensions=reorder_signatures, + annotate_obs=annotate, + colors=cols, + ax=ax, + **kwargs, + ) + ax.set_title(f"{self.mod_names[n]} signature exposures") + + plt.tight_layout() + + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") + + return axes + + def compute_correlation( + self, data: Literal["samples", "signatures"] = "signatures", **kwargs + ) -> None: + """ + Compute the signature or sample correlation. The signature + correlation is stored as a new model attribute, the sample correlation + is stored in mdata. + """ + value_checker("data", data, ["samples", "signatures"]) + + for adata in self.mdata.mod.values(): + assert "exposures" in adata.obsm, ( + "Computing the sample or signature correlation " + "requires fitting the NMF model." + ) + + values = np.concatenate( + [adata.obsm["exposures"] for adata in self.mdata.mod.values()], axis=1 + ) + + if data == "signatures": + values = values.T + + correlation = tl.correlation_numpy(values, **kwargs) + + if data == "samples": + self.mdata.obsp["X_correlation"] = correlation + else: + self.signature_correlation = correlation + + def correlation( + self, data: Literal["samples", "signatures"] = "signatures" + ) -> pd.DataFrame: + """ + Dataframe of the signature or sample correlation. + """ + value_checker("data", data, ["samples", "signatures"]) + + if data == "samples": + if "X_correlation" not in self.mdata.obsp: + self.compute_correlation("samples") + values = self.mdata.obsp["X_correlation"] + names = self.sample_names + + else: + if np.isnan(self.signature_correlation).all(): + self.compute_correlation("signatures") + values = self.signature_correlation + names = sum(self.signature_names.values(), []) + + correlation_df = pd.DataFrame(values, index=names, columns=names) + return correlation_df + + def plot_correlation( + self, + data: Literal["samples", "signatures"] = "signatures", + annot: bool | None = None, + outfile: str | None = None, + **kwargs, + ) -> ClusterGrid: + """ + Plot the signature or sample correlation. + """ + value_checker("data", data, ["samples", "signatures"]) + corr = self.correlation(data=data) + + if annot is None: + annot = False if data == "samples" else True + + clustergrid = pl.correlation_pandas(corr, annot=annot, **kwargs) + + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") + + return clustergrid + + def reduce_dimension_embeddings( + self, method: _Dim_reduction_methods = "umap", n_components: int = 2, **kwargs + ) -> None: + adatas = list(self.asignatures.values()) + [self.mdata] + tl.reduce_dimension_multiple( + adatas=adatas, + basis="embeddings", + method=method, + n_components=n_components, + **kwargs, + ) + + def _get_embedding_plot_adata( + self, method: _Dim_reduction_methods = "umap" + ) -> tuple[ad.AnnData, str]: + """ + Plot the exposures directly if the number of signatures is at most 2. + """ + adata_base = ad.AnnData(X=np.zeros((self.mdata.n_obs, 1))) + adata_base.obsm["embeddings"] = self.mdata.obsm["embeddings"] + adata_base.obsm[f"X_{method}"] = _get_basis_obsm(self.mdata, method) + adatas = list(self.asignatures.values()) + [adata_base] + plot_adata = ad.concat(adatas) + + if self.dim_embeddings <= 2: + warnings.warn( + f"The embedding dimension is {self.dim_embeddings}. " + "The embeddings are plotted without an additional " + "dimensionality reduction.", + UserWarning, + ) + basis = "embeddings" + else: + basis = method + + return plot_adata, basis + + def _get_default_embedding_plot_annotations(self) -> list[str]: + """ + The embedding plot defaults to annotating the signature embeddings. + """ + return sum(self.signature_names.values(), []) + + def plot_embeddings( + self, + method: _Dim_reduction_methods = "umap", + n_components: int = 2, + dimensions: tuple[int, int] = (0, 1), + annotations: Iterable[str] | None = None, + outfile: str | None = None, + **kwargs, + ) -> Axes: + self.reduce_dimension_embeddings(method=method, n_components=n_components) + adata, basis = self._get_embedding_plot_adata(method=method) + + if annotations is None: + annotations = self._get_default_embedding_plot_annotations() + + ax = pl.embedding( + adata=adata, + basis=basis, + dimensions=dimensions, + annotations=annotations, + **kwargs, + ) + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") + + return ax From 68e4d57ae027faeef137750355ef39a4511d59e9 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 15:18:40 -0400 Subject: [PATCH 088/103] standardize typing imports --- src/salamander/models/corrnmf.py | 4 +--- src/salamander/models/corrnmf_det.py | 5 +---- src/salamander/models/klnmf.py | 4 +--- src/salamander/models/mvnmf.py | 4 +--- src/salamander/models/signature_nmf.py | 4 +--- src/salamander/models/standard_nmf.py | 4 +--- src/salamander/plot.py | 4 +--- src/salamander/tools.py | 4 +--- src/salamander/utils.py | 4 +--- 9 files changed, 9 insertions(+), 28 deletions(-) diff --git a/src/salamander/models/corrnmf.py b/src/salamander/models/corrnmf.py index 60c70a8..38f3bf8 100644 --- a/src/salamander/models/corrnmf.py +++ b/src/salamander/models/corrnmf.py @@ -1,7 +1,7 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Literal import anndata as ad import numpy as np @@ -15,8 +15,6 @@ from .signature_nmf import SignatureNMF if TYPE_CHECKING: - from typing import Any, Literal - from ..initialization.methods import _Init_methods from .signature_nmf import _Dim_reduction_methods diff --git a/src/salamander/models/corrnmf_det.py b/src/salamander/models/corrnmf_det.py index 33d90c4..504205a 100644 --- a/src/salamander/models/corrnmf_det.py +++ b/src/salamander/models/corrnmf_det.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import Any import numpy as np @@ -9,9 +9,6 @@ from ._utils_klnmf import update_W from .corrnmf import CorrNMF -if TYPE_CHECKING: - from typing import Any - class CorrNMFDet(CorrNMF): """ diff --git a/src/salamander/models/klnmf.py b/src/salamander/models/klnmf.py index 32d9d68..630a3fd 100644 --- a/src/salamander/models/klnmf.py +++ b/src/salamander/models/klnmf.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Literal import numpy as np @@ -9,8 +9,6 @@ from .standard_nmf import StandardNMF if TYPE_CHECKING: - from typing import Any, Literal - from ..initialization.methods import _Init_methods _FITTING_KWARGS = ["weights_kl", "weights_lhalf"] diff --git a/src/salamander/models/mvnmf.py b/src/salamander/models/mvnmf.py index c983207..e34e505 100644 --- a/src/salamander/models/mvnmf.py +++ b/src/salamander/models/mvnmf.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Literal import numpy as np from numba import njit @@ -11,8 +11,6 @@ from .standard_nmf import StandardNMF if TYPE_CHECKING: - from typing import Any, Literal - from ..initialization.methods import _Init_methods _DEFAULT_FITTING_KWARGS = None diff --git a/src/salamander/models/signature_nmf.py b/src/salamander/models/signature_nmf.py index 3dba3f6..3bde302 100644 --- a/src/salamander/models/signature_nmf.py +++ b/src/salamander/models/signature_nmf.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Iterable, Literal, get_args +from typing import TYPE_CHECKING, Any, Iterable, Literal, get_args import matplotlib.pyplot as plt import numpy as np @@ -15,8 +15,6 @@ from ..utils import match_signatures_pair, type_checker, value_checker if TYPE_CHECKING: - from typing import Any - from matplotlib.axes import Axes from seaborn.matrix import ClusterGrid diff --git a/src/salamander/models/standard_nmf.py b/src/salamander/models/standard_nmf.py index bd7f9da..d3da79e 100644 --- a/src/salamander/models/standard_nmf.py +++ b/src/salamander/models/standard_nmf.py @@ -1,7 +1,7 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import anndata as ad @@ -10,8 +10,6 @@ from .signature_nmf import SignatureNMF if TYPE_CHECKING: - from typing import Any - from .signature_nmf import _Dim_reduction_methods diff --git a/src/salamander/plot.py b/src/salamander/plot.py index c9b5ef3..4648cfb 100644 --- a/src/salamander/plot.py +++ b/src/salamander/plot.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable +from typing import TYPE_CHECKING, Any, Iterable import fastcluster import matplotlib as mpl @@ -17,8 +17,6 @@ from .utils import _get_basis_obsm, _get_basis_obsp, match_to_catalog if TYPE_CHECKING: - from typing import Any - from anndata import AnnData from matplotlib.colors import Colormap from matplotlib.typing import ColorType diff --git a/src/salamander/tools.py b/src/salamander/tools.py index f5a53f3..1eb73b6 100644 --- a/src/salamander/tools.py +++ b/src/salamander/tools.py @@ -1,7 +1,7 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Iterable import numpy as np import pandas as pd @@ -9,8 +9,6 @@ from .utils import _get_basis_obsm, value_checker if TYPE_CHECKING: - from typing import Iterable - from anndata import AnnData diff --git a/src/salamander/utils.py b/src/salamander/utils.py index eab2073..d00c2a3 100644 --- a/src/salamander/utils.py +++ b/src/salamander/utils.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Iterable import numpy as np import pandas as pd @@ -9,8 +9,6 @@ from sklearn.metrics import pairwise_distances if TYPE_CHECKING: - from typing import Any, Iterable - from anndata import AnnData EPSILON = np.finfo(np.float32).eps From 00b22e0c79151c285d8cef2c67e2beb329071602 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 15:23:16 -0400 Subject: [PATCH 089/103] improve type hints --- src/salamander/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/salamander/utils.py b/src/salamander/utils.py index d00c2a3..e62b7b9 100644 --- a/src/salamander/utils.py +++ b/src/salamander/utils.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: from anndata import AnnData + from mudata import MuData EPSILON = np.finfo(np.float32).eps @@ -100,7 +101,7 @@ def value_checker(arg_name: str, arg: Any, allowed_values: Iterable[Any]) -> Non ) -def _get_basis_obsm(adata: AnnData, basis: str) -> np.ndarray: +def _get_basis_obsm(adata: AnnData | MuData, basis: str) -> np.ndarray: """ Get the multidimensional observation annotations named 'basis'. Tries to recover 'X_basis' if 'basis' is not a key of adata.obsm. @@ -113,7 +114,7 @@ def _get_basis_obsm(adata: AnnData, basis: str) -> np.ndarray: raise KeyError(f"Could not find '{basis}' or 'X_{basis}' in .obsm") -def _get_basis_obsp(adata: AnnData, basis: str) -> np.ndarray: +def _get_basis_obsp(adata: AnnData | MuData, basis: str) -> np.ndarray: """ Get the pairwise observation annotations named 'basis'. Tries to recover 'X_basis' if 'basis' is not a key of adata.obsp. From bdd46c28714e5a6d30d62506be7c97520bbd4ce8 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 15:40:23 -0400 Subject: [PATCH 090/103] improve doc --- src/salamander/models/_utils_corrnmf.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/salamander/models/_utils_corrnmf.py b/src/salamander/models/_utils_corrnmf.py index d05e4c7..85efc57 100644 --- a/src/salamander/models/_utils_corrnmf.py +++ b/src/salamander/models/_utils_corrnmf.py @@ -183,7 +183,7 @@ def update_sample_scalings( def objective_function_embedding( embedding: np.ndarray, embeddings_other: np.ndarray, - scaling: float, + scaling: float | np.ndarray, scalings_other: np.ndarray, variance: float, aux_vector: np.ndarray, @@ -203,9 +203,12 @@ def objective_function_embedding( all sample embeddings. If 'embedding' is a sample embedding, 'embeddings_other' are all signature embeddings. - scaling : float + scaling : float | np.ndarray The scaling of the signature or sample corresponding to the embedding. + In correlated NMF, 'scaling' is a float. In multimodal correlated NMF, + 'scaling' is an array if 'embedding' is a sample embedding because + it depends on the modality. scalings_other : np.ndarray shape (n_samples | n_signatures,) @@ -248,7 +251,7 @@ def objective_function_embedding( def gradient_embedding( embedding: np.ndarray, embeddings_other: np.ndarray, - scaling: float, + scaling: float | np.ndarray, scalings_other: np.ndarray, variance: float, summand_grad: np.ndarray, @@ -272,6 +275,9 @@ def gradient_embedding( scaling : float The scaling of the signature or sample corresponding to the embedding. + In correlated NMF, 'scaling' is a float. In multimodal correlated NMF, + 'scaling' is an array if 'embedding' is a sample embedding because + it depends on the modality. scalings_other : np.ndarray shape (n_samples | n_signatures,) @@ -307,7 +313,7 @@ def gradient_embedding( def hessian_embedding( embedding: np.ndarray, embeddings_other: np.ndarray, - scaling: float, + scaling: float | np.ndarray, scalings_other: np.ndarray, variance: float, outer_prods_embeddings_other: np.ndarray, @@ -331,6 +337,9 @@ def hessian_embedding( scaling : float The scaling of the signature or sample corresponding to the embedding. + In correlated NMF, 'scaling' is a float. In multimodal correlated NMF, + 'scaling' is an array if 'embedding' is a sample embedding because + it depends on the modality. scalings_other : np.ndarray shape (n_samples | n_signatures,) @@ -366,13 +375,17 @@ def hessian_embedding( def update_embedding( embedding_init: np.ndarray, embeddings_other: np.ndarray, - scaling: float, + scaling: float | np.ndarray, scalings_other: np.ndarray, variance: float, aux_vec: np.ndarray, outer_prods_embeddings_other: np.ndarray, **kwargs, ) -> np.ndarray: + """ + Optimize a signature or sample embedding using scipy. + """ + def objective_fun(embedding): return objective_function_embedding( embedding, From ae3d22411f8b308d2a19248464a0ef6ebd774177 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 17:36:16 -0400 Subject: [PATCH 091/103] remove parameter The new implementation of multimodal correlated NMF doesn't use the 'add_penalty' parameter --- src/salamander/models/_utils_corrnmf.py | 27 +++---------------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/src/salamander/models/_utils_corrnmf.py b/src/salamander/models/_utils_corrnmf.py index 85efc57..eecfc6a 100644 --- a/src/salamander/models/_utils_corrnmf.py +++ b/src/salamander/models/_utils_corrnmf.py @@ -187,7 +187,6 @@ def objective_function_embedding( scalings_other: np.ndarray, variance: float, aux_vector: np.ndarray, - add_penalty: bool = True, ) -> float: r""" The negative objective function of a signature or sample embedding in CorrNMF. @@ -225,10 +224,6 @@ def objective_function_embedding( where X is the data matrix and p are the auxiliary parameters of CorrNMF. If 'embedding' is a signature embedding, the corresponding row is provided. If 'embedding' is a sample embedding, the corresponding column is provided. - - add_penalty : bool, default=True - If True, the norm of the embedding will be penalized. - This argument is useful for the implementation of multimodal CorrNMF. """ n_embeddings_other = embeddings_other.shape[0] of_value = 0.0 @@ -240,10 +235,7 @@ def objective_function_embedding( of_value += scalar_products[i] * aux_vector[i] of_value -= np.sum(np.exp(scaling + scalings_other + scalar_products)) - - if add_penalty: - of_value -= np.dot(embedding, embedding) / (2 * variance) - + of_value -= np.dot(embedding, embedding) / (2 * variance) return -of_value @@ -255,7 +247,6 @@ def gradient_embedding( scalings_other: np.ndarray, variance: float, summand_grad: np.ndarray, - add_penalty: bool = True, ) -> np.ndarray: r""" The negative gradient of the objective function w.r.t. a signature or @@ -290,10 +281,6 @@ def gradient_embedding( summand_grad : np.ndarray shape (dim_embeddings,). A signature/sample-independent summand. - - add_penalty : bool, default=True - If True, the norm of the embedding will be penalized. - This argument is useful for the implementation of multimodal CorrNMF. """ scalar_products = embeddings_other.dot(embedding) gradient = -np.sum( @@ -302,10 +289,7 @@ def gradient_embedding( axis=0, ) gradient += summand_grad - - if add_penalty: - gradient -= embedding / variance - + gradient -= embedding / variance return -gradient @@ -317,7 +301,6 @@ def hessian_embedding( scalings_other: np.ndarray, variance: float, outer_prods_embeddings_other: np.ndarray, - add_penalty: bool = True, ) -> np.ndarray: r""" The negative Hessian of the objective function w.r.t. a signature or @@ -352,10 +335,6 @@ def hessian_embedding( outer_prods_embeddings_other : np.ndarray shape (n_samples | n_signatures, dim_embeddings, dim_embeddings) - - add_penalty : bool, default=True - Set to True, the norm of the embedding will be penalized. - This argument is useful for the implementation of multimodal CorrNMF. """ n_embeddings_other, dim_embeddings = embeddings_other.shape scalar_products = embeddings_other.dot(embedding) @@ -366,7 +345,7 @@ def hessian_embedding( for m2 in range(dim_embeddings): for i in range(n_embeddings_other): hessian[m1, m2] -= scalings[i] * outer_prods_embeddings_other[i, m1, m2] - if add_penalty and m1 == m2: + if m1 == m2: hessian[m1, m2] -= 1 / variance return -hessian From 5081d62fc82a82fcf70ed8eba85b849fdf9c8e9a Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 17:46:39 -0400 Subject: [PATCH 092/103] remove parameter --- src/salamander/models/corrnmf_det.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/salamander/models/corrnmf_det.py b/src/salamander/models/corrnmf_det.py index 504205a..4404ca3 100644 --- a/src/salamander/models/corrnmf_det.py +++ b/src/salamander/models/corrnmf_det.py @@ -85,9 +85,7 @@ def update_signatures(self, given_parameters: dict[str, Any] | None = None) -> N ) self.asignatures.X = W.T - def update_signature_embeddings( - self, aux: np.ndarray, outer_prods_sample_embeddings: np.ndarray | None = None - ) -> None: + def update_signature_embeddings(self, aux: np.ndarray) -> None: r""" Update all signature embeddings by optimizing the surrogate objective function using scipy.optimize.minimize @@ -97,13 +95,11 @@ def update_signature_embeddings( aux_kd = \sum_v X_vd * p_vkd is used for updating the signatures and the sample embeddidngs. """ - if outer_prods_sample_embeddings is None: - outer_prods_sample_embeddings = np.einsum( - "Dm,Dn->Dmn", - self.adata.obsm["embeddings"], - self.adata.obsm["embeddings"], - ) - + outer_prods_sample_embeddings = np.einsum( + "Dm,Dn->Dmn", + self.adata.obsm["embeddings"], + self.adata.obsm["embeddings"], + ) for k, aux_row in enumerate(aux): embedding_init = self.asignatures.obsm["embeddings"][k, :] self.asignatures.obsm["embeddings"][k, :] = _utils_corrnmf.update_embedding( From 38df8e101616dbe48a3ec96816d7520f123864fe Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 17:47:01 -0400 Subject: [PATCH 093/103] Revert "remove parameter" This reverts commit 5081d62fc82a82fcf70ed8eba85b849fdf9c8e9a. --- src/salamander/models/corrnmf_det.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/salamander/models/corrnmf_det.py b/src/salamander/models/corrnmf_det.py index 4404ca3..504205a 100644 --- a/src/salamander/models/corrnmf_det.py +++ b/src/salamander/models/corrnmf_det.py @@ -85,7 +85,9 @@ def update_signatures(self, given_parameters: dict[str, Any] | None = None) -> N ) self.asignatures.X = W.T - def update_signature_embeddings(self, aux: np.ndarray) -> None: + def update_signature_embeddings( + self, aux: np.ndarray, outer_prods_sample_embeddings: np.ndarray | None = None + ) -> None: r""" Update all signature embeddings by optimizing the surrogate objective function using scipy.optimize.minimize @@ -95,11 +97,13 @@ def update_signature_embeddings(self, aux: np.ndarray) -> None: aux_kd = \sum_v X_vd * p_vkd is used for updating the signatures and the sample embeddidngs. """ - outer_prods_sample_embeddings = np.einsum( - "Dm,Dn->Dmn", - self.adata.obsm["embeddings"], - self.adata.obsm["embeddings"], - ) + if outer_prods_sample_embeddings is None: + outer_prods_sample_embeddings = np.einsum( + "Dm,Dn->Dmn", + self.adata.obsm["embeddings"], + self.adata.obsm["embeddings"], + ) + for k, aux_row in enumerate(aux): embedding_init = self.asignatures.obsm["embeddings"][k, :] self.asignatures.obsm["embeddings"][k, :] = _utils_corrnmf.update_embedding( From 447dec9fe7cf040b4bc6baf3e753f057f7d8142d Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Mon, 29 Apr 2024 17:48:28 -0400 Subject: [PATCH 094/103] remove parameter --- src/salamander/models/corrnmf_det.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/salamander/models/corrnmf_det.py b/src/salamander/models/corrnmf_det.py index 504205a..4404ca3 100644 --- a/src/salamander/models/corrnmf_det.py +++ b/src/salamander/models/corrnmf_det.py @@ -85,9 +85,7 @@ def update_signatures(self, given_parameters: dict[str, Any] | None = None) -> N ) self.asignatures.X = W.T - def update_signature_embeddings( - self, aux: np.ndarray, outer_prods_sample_embeddings: np.ndarray | None = None - ) -> None: + def update_signature_embeddings(self, aux: np.ndarray) -> None: r""" Update all signature embeddings by optimizing the surrogate objective function using scipy.optimize.minimize @@ -97,13 +95,11 @@ def update_signature_embeddings( aux_kd = \sum_v X_vd * p_vkd is used for updating the signatures and the sample embeddidngs. """ - if outer_prods_sample_embeddings is None: - outer_prods_sample_embeddings = np.einsum( - "Dm,Dn->Dmn", - self.adata.obsm["embeddings"], - self.adata.obsm["embeddings"], - ) - + outer_prods_sample_embeddings = np.einsum( + "Dm,Dn->Dmn", + self.adata.obsm["embeddings"], + self.adata.obsm["embeddings"], + ) for k, aux_row in enumerate(aux): embedding_init = self.asignatures.obsm["embeddings"][k, :] self.asignatures.obsm["embeddings"][k, :] = _utils_corrnmf.update_embedding( From 8be652a9a0c6718da9fd37b87fcb6e3a8dc24a8b Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 30 Apr 2024 15:19:00 -0400 Subject: [PATCH 095/103] update lock file --- poetry.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1dc5149..1ad7019 100644 --- a/poetry.lock +++ b/poetry.lock @@ -269,13 +269,13 @@ test = ["scipy (>=1.6.3)"] [[package]] name = "filelock" -version = "3.13.4" +version = "3.14.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, - {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, + {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, + {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, ] [package.extras] @@ -1314,13 +1314,13 @@ files = [ [[package]] name = "threadpoolctl" -version = "3.4.0" +version = "3.5.0" description = "threadpoolctl" optional = false python-versions = ">=3.8" files = [ - {file = "threadpoolctl-3.4.0-py3-none-any.whl", hash = "sha256:8f4c689a65b23e5ed825c8436a92b818aac005e0f3715f6a1664d7c7ee29d262"}, - {file = "threadpoolctl-3.4.0.tar.gz", hash = "sha256:f11b491a03661d6dd7ef692dd422ab34185d982466c49c8f98c8f716b5c93196"}, + {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, + {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, ] [[package]] @@ -1336,13 +1336,13 @@ files = [ [[package]] name = "tox" -version = "4.14.2" +version = "4.15.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.8" files = [ - {file = "tox-4.14.2-py3-none-any.whl", hash = "sha256:2900c4eb7b716af4a928a7fdc2ed248ad6575294ed7cfae2ea41203937422847"}, - {file = "tox-4.14.2.tar.gz", hash = "sha256:0defb44f6dafd911b61788325741cc6b2e12ea71f987ac025ad4d649f1f1a104"}, + {file = "tox-4.15.0-py3-none-any.whl", hash = "sha256:300055f335d855b2ab1b12c5802de7f62a36d4fd53f30bd2835f6a201dda46ea"}, + {file = "tox-4.15.0.tar.gz", hash = "sha256:7a0beeef166fbe566f54f795b4906c31b428eddafc0102ac00d20998dd1933f6"}, ] [package.dependencies] @@ -1445,13 +1445,13 @@ tbb = ["tbb (>=2019.0)"] [[package]] name = "virtualenv" -version = "20.26.0" +version = "20.26.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.0-py3-none-any.whl", hash = "sha256:0846377ea76e818daaa3e00a4365c018bc3ac9760cbb3544de542885aad61fb3"}, - {file = "virtualenv-20.26.0.tar.gz", hash = "sha256:ec25a9671a5102c8d2657f62792a27b48f016664c6873f6beed3800008577210"}, + {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"}, + {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}, ] [package.dependencies] From 2db3dcb7b9120a4b2b796f3c23b6dc3cd5e713eb Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 30 Apr 2024 15:28:19 -0400 Subject: [PATCH 096/103] improve scatter The scatter and embedding plot now have color and zorder options. This is useful for plotting multiple subgroups of points, e.g., the signature and sample embeddings in correlated NMF. --- src/salamander/plot.py | 102 ++++++++++++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 17 deletions(-) diff --git a/src/salamander/plot.py b/src/salamander/plot.py index 4648cfb..2f50b3a 100644 --- a/src/salamander/plot.py +++ b/src/salamander/plot.py @@ -20,6 +20,7 @@ from anndata import AnnData from matplotlib.colors import Colormap from matplotlib.typing import ColorType + from mudata import MuData from seaborn.matrix import ClusterGrid @@ -100,18 +101,38 @@ def _annotate_plot( def _scatter_1d( - data: np.ndarray, xlabel: str | None = None, ax: Axes | None = None, **kwargs + data: np.ndarray, + xlabel: str | None = None, + color: list[ColorType] | None = None, + zorder: list[int] | None = None, + ax: Axes | None = None, + **kwargs, ) -> Axes: if data.ndim != 1: - raise ValueError(f"The datapoints of {data} (rows) have to be one-dimensional.") + raise ValueError("The data have to be one-dimensional.") if ax is None: _, ax = plt.subplots(figsize=(4, 1)) + if zorder is None: + zorder = len(data) * [1] + ax.spines[["left", "bottom"]].set_visible(False) ax.get_yaxis().set_visible(False) - ax.axhline(y=0, color="black", zorder=1) - sns.scatterplot(x=data, y=np.zeros_like(data), ax=ax, zorder=2, **kwargs) + ax.axhline(y=0, color="black", zorder=np.min(zorder) - 1) + + for zord in np.unique(zorder): + subgroup = np.where(zorder == zord)[0] + subdata = data[subgroup] + subgroup_color = [color[d] for d in subgroup] if color is not None else None + sns.scatterplot( + x=subdata, + y=np.zeros_like(subdata), + color=subgroup_color, + zorder=zord, + ax=ax, + **kwargs, + ) if xlabel: ax.set_xlabel(xlabel) @@ -124,6 +145,8 @@ def _scatter_2d( xlabel: str | None = None, ylabel: str | None = None, ticks: bool = True, + color: list[ColorType] | None = None, + zorder: list[int] | None = None, ax: Axes | None = None, **kwargs, ) -> Axes: @@ -131,12 +154,26 @@ def _scatter_2d( The rows (!) of 'data' are assumed to be the data points. """ if data.shape[1] != 2: - raise ValueError(f"The datapoints of {data} (rows) have to be two-dimensional.") + raise ValueError("The datapoints (rows) have to be two-dimensional.") if ax is None: _, ax = plt.subplots(figsize=(4, 4)) - sns.scatterplot(x=data[:, 0], y=data[:, 1], ax=ax, **kwargs) + if zorder is None: + zorder = len(data) * [1] + + for zord in np.unique(zorder): + subgroup = np.where(zorder == zord)[0] + subdata = data[subgroup] + subgroup_color = [color[d] for d in subgroup] if color is not None else None + sns.scatterplot( + x=subdata[:, 0], + y=subdata[:, 1], + color=subgroup_color, + zorder=zord, + ax=ax, + **kwargs, + ) if xlabel: ax.set_xlabel(xlabel) @@ -155,6 +192,8 @@ def scatter_numpy( xlabel: str | None = None, ylabel: str | None = None, ticks: bool = True, + color: list[ColorType] | None = None, + zorder: list[int] | None = None, annotations: Iterable[str] | None = None, annotation_kwargs: dict[str, Any] | None = None, adjust_annotations: bool = True, @@ -163,10 +202,10 @@ def scatter_numpy( **kwargs, ) -> Axes: if data.ndim == 1 or data.shape[1] == 1: - ax = _scatter_1d(data, xlabel, ax, **kwargs) + ax = _scatter_1d(data, xlabel, color, zorder, ax, **kwargs) data_2d = np.vstack([data.flatten(), np.zeros_like(data.flatten())]).T elif data.ndim == 2 and data.shape[1] == 2: - ax = _scatter_2d(data, xlabel, ylabel, ticks, ax, **kwargs) + ax = _scatter_2d(data, xlabel, ylabel, ticks, color, zorder, ax, **kwargs) data_2d = data else: raise ValueError( @@ -188,13 +227,25 @@ def scatter_numpy( return ax -def scatter(adata: AnnData, x: str, y: str | None = None, **kwargs) -> Axes: +def scatter( + adata: AnnData | MuData, + x: str, + y: str | None = None, + ticks: bool = True, + color: str | None = None, + zorder: str | None = None, + **kwargs, +) -> Axes: if y is None: - data = adata.obs(x).to_numpy() + data = adata.obs[x].to_numpy() else: data = adata.obs[[x, y]].to_numpy() - ax = scatter_numpy(data, xlabel=x, ylabel=y, **kwargs) + col = list(adata.obs[color]) if color is not None else None + zord = list(adata.obs[zorder]) if zorder is not None else None + ax = scatter_numpy( + data, xlabel=x, ylabel=y, ticks=ticks, color=col, zorder=zord, **kwargs + ) return ax @@ -203,31 +254,39 @@ def embedding_numpy( dimensions: tuple[int, int] = (0, 1), xlabel: str | None = None, ylabel: str | None = None, + ticks: bool = True, + color: list[ColorType] | None = None, + zorder: list[int] | None = None, **kwargs, ) -> Axes: if data.ndim == 2 and data.shape[1] > 2: data = data[:, dimensions] - ax = scatter_numpy(data, xlabel=xlabel, ylabel=ylabel, **kwargs) + ax = scatter_numpy(data, xlabel, ylabel, ticks, color, zorder, **kwargs) return ax +# fmt: off def _basisobsm2name(basis: str) -> str: name = ( - "PC" - if basis == "pca" - else "tSNE" if basis == "tsne" else "UMAP" if basis == "umap" else basis + "PC" if basis == "pca" + else "tSNE" if basis == "tsne" + else "UMAP" if basis == "umap" + else basis ) return name +# fmt: on def embedding( - adata: AnnData, + adata: AnnData | MuData, basis: str, dimensions: tuple[int, int] = (0, 1), xlabel: str | None = None, ylabel: str | None = None, ticks: bool | None = None, + color: str | None = None, + zorder: str | None = None, **kwargs, ) -> Axes: data = _get_basis_obsm(adata, basis) @@ -243,8 +302,17 @@ def embedding( if ticks is None: ticks = False if basis in ["tsne", "umap"] else True + col = list(adata.obs[color]) if color is not None else None + zord = list(adata.obs[zorder]) if zorder is not None else None ax = embedding_numpy( - data, dimensions=dimensions, xlabel=xlabel, ylabel=ylabel, ticks=ticks, **kwargs + data, + dimensions=dimensions, + xlabel=xlabel, + ylabel=ylabel, + ticks=ticks, + color=col, + zorder=zord, + **kwargs, ) return ax From a0e433949f830fcb198e22db79ff9fcae73f1fd5 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 30 Apr 2024 15:30:34 -0400 Subject: [PATCH 097/103] improve plot Add functions to plot .obs and .obsm attributes of multiple AnnData and MuData objects. --- src/salamander/plot.py | 60 ++++++++++++++++++++++++++++++++++++++++- src/salamander/tools.py | 3 ++- src/salamander/utils.py | 39 ++++++++++++++++++++++----- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/src/salamander/plot.py b/src/salamander/plot.py index 2f50b3a..70bc1d8 100644 --- a/src/salamander/plot.py +++ b/src/salamander/plot.py @@ -14,7 +14,7 @@ from scipy.spatial.distance import pdist from .consts import COLORS_INDEL83, COLORS_SBS96, INDEL_TYPES_83, SBS_TYPES_96 -from .utils import _get_basis_obsm, _get_basis_obsp, match_to_catalog +from .utils import _concat_light, _get_basis_obsm, _get_basis_obsp, match_to_catalog if TYPE_CHECKING: from anndata import AnnData @@ -249,6 +249,24 @@ def scatter( return ax +def scatter_multiple( + adatas: Iterable[AnnData | MuData], + x: str, + y: str | None = None, + ticks: bool = True, + color: str | None = None, + zorder: str | None = None, + **kwargs, +) -> Axes: + obs_keys = [x, y, color, zorder] + obs_keys = [key for key in obs_keys if key is not None] + combined = _concat_light(adatas, obs_keys=obs_keys) + ax = scatter( + adata=combined, x=x, y=y, ticks=ticks, color=color, zorder=zorder, **kwargs + ) + return ax + + def embedding_numpy( data: np.ndarray, dimensions: tuple[int, int] = (0, 1), @@ -317,18 +335,58 @@ def embedding( return ax +def embedding_multiple( + adatas: Iterable[AnnData | MuData], + basis: str, + dimensions: tuple[int, int] = (0, 1), + xlabel: str | None = None, + ylabel: str | None = None, + ticks: bool | None = None, + color: str | None = None, + zorder: str | None = None, + **kwargs, +) -> Axes: + obs_keys = [color, zorder] + obs_keys = [key for key in obs_keys if key is not None] + combined = _concat_light(adatas, obs_keys=obs_keys, obsm_keys=[basis]) + ax = embedding( + adata=combined, + basis=basis, + dimensions=dimensions, + xlabel=xlabel, + ylabel=ylabel, + ticks=ticks, + color=color, + zorder=zorder, + **kwargs, + ) + return ax + + def pca(adata: AnnData, **kwargs) -> Axes: return embedding(adata, basis="pca", **kwargs) +def pca_multiple(adatas: Iterable[AnnData | MuData], **kwargs) -> Axes: + return embedding_multiple(adatas, basis="pca", **kwargs) + + def tsne(adata: AnnData, **kwargs) -> Axes: return embedding(adata, basis="tsne", **kwargs) +def tsne_multiple(adatas: Iterable[AnnData | MuData], **kwargs) -> Axes: + return embedding_multiple(adatas, basis="tsne", **kwargs) + + def umap(adata: AnnData, **kwargs) -> Axes: return embedding(adata, basis="umap", **kwargs) +def umap_multiple(adatas: Iterable[AnnData | MuData], **kwargs) -> Axes: + return embedding_multiple(adatas, basis="umap", **kwargs) + + def correlation_pandas( corr: pd.DataFrame, figsize: tuple[float, float] = (4.0, 4.0), diff --git a/src/salamander/tools.py b/src/salamander/tools.py index 1eb73b6..3cfbe3a 100644 --- a/src/salamander/tools.py +++ b/src/salamander/tools.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: from anndata import AnnData + from mudata import MuData def _pca(data: np.ndarray, n_components: int = 2, **kwargs) -> np.ndarray: @@ -134,7 +135,7 @@ def reduce_dimension( def reduce_dimension_multiple( - adatas: Iterable[AnnData], basis: str, method="umap", **kwargs + adatas: Iterable[AnnData | MuData], basis: str, method="umap", **kwargs ) -> None: """ Compute a joint dimensionality reduction of the same multi-dimensional observation diff --git a/src/salamander/utils.py b/src/salamander/utils.py index e62b7b9..74400cf 100644 --- a/src/salamander/utils.py +++ b/src/salamander/utils.py @@ -1,17 +1,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Iterable +from typing import Any, Iterable +import anndata as ad +import mudata as md import numpy as np import pandas as pd from numba import njit from scipy.optimize import linear_sum_assignment from sklearn.metrics import pairwise_distances -if TYPE_CHECKING: - from anndata import AnnData - from mudata import MuData - EPSILON = np.finfo(np.float32).eps @@ -101,7 +99,7 @@ def value_checker(arg_name: str, arg: Any, allowed_values: Iterable[Any]) -> Non ) -def _get_basis_obsm(adata: AnnData | MuData, basis: str) -> np.ndarray: +def _get_basis_obsm(adata: ad.AnnData | md.MuData, basis: str) -> np.ndarray: """ Get the multidimensional observation annotations named 'basis'. Tries to recover 'X_basis' if 'basis' is not a key of adata.obsm. @@ -114,7 +112,7 @@ def _get_basis_obsm(adata: AnnData | MuData, basis: str) -> np.ndarray: raise KeyError(f"Could not find '{basis}' or 'X_{basis}' in .obsm") -def _get_basis_obsp(adata: AnnData | MuData, basis: str) -> np.ndarray: +def _get_basis_obsp(adata: ad.AnnData | md.MuData, basis: str) -> np.ndarray: """ Get the pairwise observation annotations named 'basis'. Tries to recover 'X_basis' if 'basis' is not a key of adata.obsp. @@ -127,6 +125,33 @@ def _get_basis_obsp(adata: AnnData | MuData, basis: str) -> np.ndarray: raise KeyError(f"Could not find '{basis}' or 'X_{basis}' in .obsp") +def _concat_light( + adatas: Iterable[ad.AnnData | md.MuData], + obs_keys: Iterable[str] | None = None, + obsm_keys: Iterable[str] | None = None, +) -> ad.AnnData: + """ + Concatenate multiple AnnData or MuData objects without copying all + the data, but only the relavant 'obs_keys' and 'obsm_keys'. + """ + # avoid copying all the data + n_obs_total = sum(adata.n_obs for adata in adatas) + combined = ad.AnnData(X=np.zeros((n_obs_total, 1))) + combined.obs_names = np.concatenate([adata.obs_names for adata in adatas]) + + if obs_keys is not None: + for key in obs_keys: + combined.obs[key] = np.concatenate([adata.obs[key] for adata in adatas]) + + if obsm_keys is not None: + for key in obsm_keys: + combined.obsm[key] = np.concatenate( + [_get_basis_obsm(adata, key) for adata in adatas] + ) + + return combined + + @njit def normalize_WH(W: np.ndarray, H: np.ndarray) -> tuple[np.ndarray, np.ndarray]: normalization_factor = np.sum(W, axis=0) From 9a31e7191ec2d7f37aa36c5ba6f61bfb79ea5f2b Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 30 Apr 2024 17:04:11 -0400 Subject: [PATCH 098/103] improve embedding plot Add color and zorder options to the embedding plot. I also removed the abstraction of the embedding plot in signature_nmf to improve the readability of the code. --- src/salamander/models/corrnmf.py | 68 +++++++++++++++++--------- src/salamander/models/mmcorrnmf.py | 66 ++++++++++--------------- src/salamander/models/signature_nmf.py | 50 +++---------------- src/salamander/models/standard_nmf.py | 51 +++++++++++-------- 4 files changed, 111 insertions(+), 124 deletions(-) diff --git a/src/salamander/models/corrnmf.py b/src/salamander/models/corrnmf.py index 38f3bf8..f0ddebd 100644 --- a/src/salamander/models/corrnmf.py +++ b/src/salamander/models/corrnmf.py @@ -1,20 +1,23 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any, Iterable, Literal -import anndata as ad +import matplotlib.pyplot as plt import numpy as np from scipy.spatial.distance import squareform +from .. import plot as pl +from .. import tools as tl from ..initialization.initialize import initialize_corrnmf -from ..tools import reduce_dimension_multiple from ..utils import value_checker from . import _utils_corrnmf from ._utils_klnmf import samplewise_kl_divergence from .signature_nmf import SignatureNMF if TYPE_CHECKING: + from matplotlib.axes import Axes + from ..initialization.methods import _Init_methods from .signature_nmf import _Dim_reduction_methods @@ -174,25 +177,25 @@ def compute_correlation_scaled( else: self.asignatures.obsp["correlation"] = correlation - def reduce_dimension_embeddings( - self, method: _Dim_reduction_methods = "umap", n_components: int = 2, **kwargs - ) -> None: - reduce_dimension_multiple( - adatas=[self.asignatures, self.adata], + def plot_embeddings( + self, + method: _Dim_reduction_methods = "umap", + n_components: int = 2, + dimensions: tuple[int, int] = (0, 1), + color: str | None = None, + zorder: str | None = None, + annotations: Iterable[str] | None = None, + outfile: str | None = None, + **kwargs, + ) -> Axes: + adatas = [self.asignatures, self.adata] + tl.reduce_dimension_multiple( + adatas=adatas, basis="embeddings", method=method, n_components=n_components, **kwargs, ) - - def _get_embedding_plot_adata( - self, method: _Dim_reduction_methods = "umap" - ) -> tuple[ad.AnnData, str]: - """ - Plot the embeddings directly if the embedding dimension is at most 2. - """ - plot_adata = ad.concat([self.asignatures, self.adata]) - if self.dim_embeddings <= 2: warnings.warn( f"The embedding dimension is {self.dim_embeddings}. " @@ -204,10 +207,29 @@ def _get_embedding_plot_adata( else: basis = method - return plot_adata, basis + if color is None: + color = "color_embedding" + self.asignatures.obs[color] = self.n_signatures * ["black"] + self.adata.obs[color] = self.adata.n_obs * ["#1f77b4"] # default blue + + if zorder is None: + zorder = "zorder_embedding" + self.asignatures.obs[zorder] = self.n_signatures * [2] + self.adata.obs[zorder] = self.adata.n_obs * [1] + + if annotations is None: + annotations = self.signature_names + + ax = pl.embedding_multiple( + adatas=adatas, + basis=basis, + dimensions=dimensions, + color=color, + zorder=zorder, + annotations=annotations, + **kwargs, + ) + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") - def _get_default_embedding_plot_annotations(self) -> list[str]: - """ - The embedding plot defaults to annotating the signature embeddings. - """ - return self.signature_names + return ax diff --git a/src/salamander/models/mmcorrnmf.py b/src/salamander/models/mmcorrnmf.py index 6b25c7f..de5d52b 100644 --- a/src/salamander/models/mmcorrnmf.py +++ b/src/salamander/models/mmcorrnmf.py @@ -20,7 +20,7 @@ from .. import plot as pl from .. import tools as tl from ..initialization.initialize import EPSILON, _Init_methods, initialize_mmcorrnmf -from ..utils import _get_basis_obsm, dict_checker, type_checker, value_checker +from ..utils import dict_checker, type_checker, value_checker from . import _utils_corrnmf from ._utils_klnmf import samplewise_kl_divergence, update_W @@ -646,30 +646,24 @@ def plot_correlation( return clustergrid - def reduce_dimension_embeddings( - self, method: _Dim_reduction_methods = "umap", n_components: int = 2, **kwargs - ) -> None: + def plot_embeddings( + self, + method: _Dim_reduction_methods = "umap", + n_components: int = 2, + dimensions: tuple[int, int] = (0, 1), + color: str | None = None, + zorder: str | None = None, + annotations: Iterable[str] | None = None, + outfile: str | None = None, + **kwargs, + ) -> Axes: adatas = list(self.asignatures.values()) + [self.mdata] tl.reduce_dimension_multiple( adatas=adatas, basis="embeddings", method=method, n_components=n_components, - **kwargs, ) - - def _get_embedding_plot_adata( - self, method: _Dim_reduction_methods = "umap" - ) -> tuple[ad.AnnData, str]: - """ - Plot the exposures directly if the number of signatures is at most 2. - """ - adata_base = ad.AnnData(X=np.zeros((self.mdata.n_obs, 1))) - adata_base.obsm["embeddings"] = self.mdata.obsm["embeddings"] - adata_base.obsm[f"X_{method}"] = _get_basis_obsm(self.mdata, method) - adatas = list(self.asignatures.values()) + [adata_base] - plot_adata = ad.concat(adatas) - if self.dim_embeddings <= 2: warnings.warn( f"The embedding dimension is {self.dim_embeddings}. " @@ -681,33 +675,27 @@ def _get_embedding_plot_adata( else: basis = method - return plot_adata, basis - - def _get_default_embedding_plot_annotations(self) -> list[str]: - """ - The embedding plot defaults to annotating the signature embeddings. - """ - return sum(self.signature_names.values(), []) + if color is None: + color = "color_embedding" + for asigs in self.asignatures.values(): + asigs.obs[color] = asigs.n_obs * ["black"] + self.mdata.obs[color] = self.mdata.n_obs * ["#1f77b4"] # default blue - def plot_embeddings( - self, - method: _Dim_reduction_methods = "umap", - n_components: int = 2, - dimensions: tuple[int, int] = (0, 1), - annotations: Iterable[str] | None = None, - outfile: str | None = None, - **kwargs, - ) -> Axes: - self.reduce_dimension_embeddings(method=method, n_components=n_components) - adata, basis = self._get_embedding_plot_adata(method=method) + if zorder is None: + zorder = "zorder_embedding" + for asigs in self.asignatures.values(): + asigs.obs[zorder] = asigs.n_obs * [2] + self.mdata.obs[zorder] = self.mdata.n_obs * [1] if annotations is None: - annotations = self._get_default_embedding_plot_annotations() + annotations = sum(self.signature_names.values(), []) - ax = pl.embedding( - adata=adata, + ax = pl.embedding_multiple( + adatas=adatas, basis=basis, dimensions=dimensions, + color=color, + zorder=zorder, annotations=annotations, **kwargs, ) diff --git a/src/salamander/models/signature_nmf.py b/src/salamander/models/signature_nmf.py index 3bde302..84abb9f 100644 --- a/src/salamander/models/signature_nmf.py +++ b/src/salamander/models/signature_nmf.py @@ -559,36 +559,13 @@ def plot_correlation( return clustergrid @abstractmethod - def reduce_dimension_embeddings( - self, method: _Dim_reduction_methods = "umap", **kwargs - ) -> None: - """ - Reduce the dimension of the embeddings. - Usually, the embeddings are the signature exposures of the samples. - But in correlated NMF, they are the signature and sample embeddings - in a shared embedding space. - """ - - @abstractmethod - def _get_embedding_plot_adata( - self, method: _Dim_reduction_methods = "umap" - ) -> tuple[AnnData, str]: - """ - Get the anndata object containing all embeddings - and the name of the embedding basis. - """ - - @abstractmethod - def _get_default_embedding_plot_annotations(self) -> Iterable[str] | None: - """ - Get the default annotations of the data points in the embedding plot. - """ - def plot_embeddings( self, method: _Dim_reduction_methods = "umap", n_components: int = 2, dimensions: tuple[int, int] = (0, 1), + color: str | None = None, + zorder: str | None = None, annotations: Iterable[str] | None = None, outfile: str | None = None, **kwargs, @@ -613,6 +590,12 @@ def plot_embeddings( dimensions: tuple[int, int], default=(0,1) The indices of the dimensions to plot. + color: str, default=None + Optional annotation key to use for the colors. + + zorder: str, default=None + Optional annotation key to use for the drawing order. + annotations : Iterable[str], optional, default=None Annotations per data point, e.g. the sample names. If None, the algorithm-specific default annotations are used. @@ -632,20 +615,3 @@ def plot_embeddings( ax : matplotlib.axes.Axes The matplotlib axes containing the plot. """ - self.reduce_dimension_embeddings(method=method, n_components=n_components) - adata, basis = self._get_embedding_plot_adata(method=method) - - if annotations is None: - annotations = self._get_default_embedding_plot_annotations() - - ax = pl.embedding( - adata=adata, - basis=basis, - dimensions=dimensions, - annotations=annotations, - **kwargs, - ) - if outfile is not None: - plt.savefig(outfile, bbox_inches="tight") - - return ax diff --git a/src/salamander/models/standard_nmf.py b/src/salamander/models/standard_nmf.py index d3da79e..6c32e94 100644 --- a/src/salamander/models/standard_nmf.py +++ b/src/salamander/models/standard_nmf.py @@ -1,15 +1,18 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Iterable -import anndata as ad +import matplotlib.pyplot as plt +from .. import plot as pl from .. import tools as tl from ..initialization.initialize import initialize_standard_nmf from .signature_nmf import SignatureNMF if TYPE_CHECKING: + from matplotlib.axes import Axes + from .signature_nmf import _Dim_reduction_methods @@ -54,35 +57,43 @@ def _initialize( **init_kwargs, ) - def reduce_dimension_embeddings( - self, method: _Dim_reduction_methods = "umap", n_components: int = 2, **kwargs - ) -> None: + def plot_embeddings( + self, + method: _Dim_reduction_methods = "umap", + n_components: int = 2, + dimensions: tuple[int, int] = (0, 1), + color: str | None = None, + zorder: str | None = None, + annotations: Iterable[str] | None = None, + outfile: str | None = None, + **kwargs, + ) -> Axes: tl.reduce_dimension( self.adata, basis="exposures", method=method, n_components=n_components, - **kwargs, ) - - def _get_embedding_plot_adata( - self, method: _Dim_reduction_methods = "umap" - ) -> tuple[ad.AnnData, str]: - """ - Plot the exposures directly if the number of signatures is at most 2. - """ if self.n_signatures <= 2: warnings.warn( f"There are only {self.n_signatures} many signatures. " "The exposures are plotted directly.", UserWarning, ) - return self.adata, "exposures" + basis = "exposures" + else: + basis = method - return self.adata, method + ax = pl.embedding( + adata=self.adata, + basis=basis, + dimensions=dimensions, + color=color, + zorder=zorder, + annotations=annotations, + **kwargs, + ) + if outfile is not None: + plt.savefig(outfile, bbox_inches="tight") - def _get_default_embedding_plot_annotations(self) -> None: - """ - The embedding plot defaults to no annotations. - """ - return + return ax From 5368b843b73d6b5347ccec48c341555a3570a943 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 30 Apr 2024 17:15:08 -0400 Subject: [PATCH 099/103] improve indentation --- src/salamander/models/mmcorrnmf.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/salamander/models/mmcorrnmf.py b/src/salamander/models/mmcorrnmf.py index de5d52b..33cb89c 100644 --- a/src/salamander/models/mmcorrnmf.py +++ b/src/salamander/models/mmcorrnmf.py @@ -374,14 +374,13 @@ def update_sample_embeddings(self, auxs: dict[str, np.ndarray]) -> None: for d, aux_col in enumerate(aux.T): embedding_init = self.mdata.obsm["embeddings"][d, :] - scalings = np.concatenate( - [ - np.repeat(adata.obs["scalings"][d], n_signatures) - for adata, n_signatures in zip( - self.mdata.mod.values(), self.ns_signatures - ) - ] - ) + scalings = [ + np.repeat(adata.obs["scalings"][d], n_signatures) + for adata, n_signatures in zip( + self.mdata.mod.values(), self.ns_signatures + ) + ] + scalings = np.concatenate(scalings) self.mdata.obsm["embeddings"][d, :] = _utils_corrnmf.update_embedding( embedding_init, sig_embeddings, From 16641f0f98798f8813fb1989037d0411471ca7c6 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 30 Apr 2024 17:51:28 -0400 Subject: [PATCH 100/103] improve structure Implemented modality-specific updates to improve the indentation and readability. --- src/salamander/models/mmcorrnmf.py | 123 ++++++++++++++++++----------- 1 file changed, 76 insertions(+), 47 deletions(-) diff --git a/src/salamander/models/mmcorrnmf.py b/src/salamander/models/mmcorrnmf.py index 33cb89c..7071658 100644 --- a/src/salamander/models/mmcorrnmf.py +++ b/src/salamander/models/mmcorrnmf.py @@ -240,6 +240,19 @@ def _compute_auxs(self) -> dict[str, np.ndarray]: ) return auxs + def update_sample_scalings_mod( + self, mod_name: str, given_parameters_mod: dict[str, Any] + ) -> None: + if "sample_scalings" not in given_parameters_mod: + adata = self.mdata[mod_name] + asigs = self.asignatures[mod_name] + adata.obs["scalings"] = _utils_corrnmf.update_sample_scalings( + adata.X, + asigs.obs["scalings"].values, + asigs.obsm["embeddings"], + self.mdata.obsm["embeddings"], + ) + def update_sample_scalings( self, given_parameters: dict[str, Any] | None = None, @@ -247,20 +260,24 @@ def update_sample_scalings( if given_parameters is None: given_parameters = {} - for mod_name, adata in self.mdata.mod.items(): + for mod_name in self.mod_names: if mod_name in given_parameters: given_parameters_mod = given_parameters[mod_name] else: given_parameters_mod = {} + self.update_sample_scalings_mod(mod_name, given_parameters_mod) - if "sample_scalings" not in given_parameters_mod: - asigs = self.asignatures[mod_name] - adata.obs["scalings"] = _utils_corrnmf.update_sample_scalings( - adata.X, - asigs.obs["scalings"].values, - asigs.obsm["embeddings"], - self.mdata.obsm["embeddings"], - ) + def update_signature_scalings_mod( + self, mod_name: str, aux: np.ndarray, given_parameters_mod: dict[str, Any] + ) -> None: + if "signature_scalings" not in given_parameters_mod: + asigs = self.asignatures[mod_name] + asigs.obs["scalings"] = _utils_corrnmf.update_signature_scalings( + aux, + self.mdata[mod_name].obs["scalings"].values, + asigs.obsm["embeddings"], + self.mdata.obsm["embeddings"], + ) def update_signature_scalings( self, @@ -270,19 +287,14 @@ def update_signature_scalings( if given_parameters is None: given_parameters = {} - for mod_name, asigs in self.asignatures.items(): + for mod_name in self.mod_names: if mod_name in given_parameters: given_parameters_mod = given_parameters[mod_name] else: given_parameters_mod = {} - - if "signature_scalings" not in given_parameters_mod: - asigs.obs["scalings"] = _utils_corrnmf.update_signature_scalings( - auxs[mod_name], - self.mdata[mod_name].obs["scalings"].values, - asigs.obsm["embeddings"], - self.mdata.obsm["embeddings"], - ) + self.update_signature_scalings_mod( + mod_name, auxs[mod_name], given_parameters_mod + ) def update_variance(self, given_parameters: dict[str, Any] | None = None) -> None: if given_parameters is None: @@ -298,28 +310,54 @@ def update_variance(self, given_parameters: dict[str, Any] | None = None) -> Non variance = np.mean(embeddings**2) self.variance = np.clip(variance, EPSILON, None) + def update_signatures_mod( + self, mod_name: str, given_parameters_mod: dict[str, Any] + ) -> None: + if "asignatures" in given_parameters_mod: + n_given_signatures = given_parameters_mod["asignatures"].n_obs + else: + n_given_signatures = 0 + + asigs = self.asignatures[mod_name] + W = update_W( + self.mdata[mod_name].X.T, + asigs.X.T, + self.mdata[mod_name].obsm["exposures"].T, + n_given_signatures=n_given_signatures, + ) + asigs.X = W.T + def update_signatures(self, given_parameters: dict[str, Any] | None = None) -> None: if given_parameters is None: given_parameters = {} - for mod_name, asigs in self.asignatures.items(): + for mod_name in self.mod_names: if mod_name in given_parameters: given_parameters_mod = given_parameters[mod_name] else: given_parameters_mod = {} + self.update_signatures_mod(mod_name, given_parameters_mod) - if "asignatures" in given_parameters_mod: - n_given_signatures = given_parameters_mod["asignatures"].n_obs - else: - n_given_signatures = 0 - - W = update_W( - self.mdata[mod_name].X.T, - asigs.X.T, - self.mdata[mod_name].obsm["exposures"].T, - n_given_signatures=n_given_signatures, - ) - asigs.X = W.T + def update_signature_embeddings_mod( + self, + mod_name: str, + aux: np.ndarray, + outer_prods_sample_embeddings: np.ndarray, + given_parameters_mod: dict[str, Any], + ) -> None: + if "signature_embeddings" not in given_parameters_mod: + asigs = self.asignatures[mod_name] + for k, aux_row in enumerate(aux): + embedding_init = asigs.obsm["embeddings"][k, :] + asigs.obsm["embeddings"][k, :] = _utils_corrnmf.update_embedding( + embedding_init, + self.mdata.obsm["embeddings"], + asigs.obs["scalings"][k], + self.mdata[mod_name].obs["scalings"].values, + self.variance, + aux_row, + outer_prods_sample_embeddings, + ) def update_signature_embeddings( self, @@ -339,26 +377,17 @@ def update_signature_embeddings( self.mdata.obsm["embeddings"], self.mdata.obsm["embeddings"], ) - - for mod_name, asigs in self.asignatures.items(): + for mod_name in self.mod_names: if mod_name in given_parameters: given_parameters_mod = given_parameters[mod_name] else: given_parameters_mod = {} - - if "signature_embeddings" not in given_parameters_mod: - aux = auxs[mod_name] - for k, aux_row in enumerate(aux): - embedding_init = asigs.obsm["embeddings"][k, :] - asigs.obsm["embeddings"][k, :] = _utils_corrnmf.update_embedding( - embedding_init, - self.mdata.obsm["embeddings"], - asigs.obs["scalings"][k], - self.mdata[mod_name].obs["scalings"].values, - self.variance, - aux_row, - outer_prods_sample_embeddings, - ) + self.update_signature_embeddings_mod( + mod_name, + auxs[mod_name], + outer_prods_sample_embeddings, + given_parameters_mod, + ) def update_sample_embeddings(self, auxs: dict[str, np.ndarray]) -> None: sig_embeddings = np.concatenate( From 4c22f59fa6c18eddd5b27eb54057afc6ef573454 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 30 Apr 2024 23:11:22 -0400 Subject: [PATCH 101/103] improve reconstruction error --- src/salamander/models/mmcorrnmf.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/salamander/models/mmcorrnmf.py b/src/salamander/models/mmcorrnmf.py index 7071658..85a0980 100644 --- a/src/salamander/models/mmcorrnmf.py +++ b/src/salamander/models/mmcorrnmf.py @@ -150,6 +150,12 @@ def compute_reconstruction_errors(self) -> None: @property def reconstruction_errors(self) -> dict[str, float]: + if any( + "reconstruction_error" not in self.mdata[mod_name].obs + for mod_name in self.mod_names + ): + self.compute_reconstruction_errors() + return { mod_name: np.sum(adata.obs["reconstruction_error"]) for mod_name, adata in self.mdata.mod.items() From 465042c712a7e91438b117d3c9490efeaf3f6556 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Tue, 30 Apr 2024 23:11:35 -0400 Subject: [PATCH 102/103] improve names --- src/salamander/models/corrnmf.py | 4 ++-- src/salamander/models/mmcorrnmf.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/salamander/models/corrnmf.py b/src/salamander/models/corrnmf.py index f0ddebd..744587f 100644 --- a/src/salamander/models/corrnmf.py +++ b/src/salamander/models/corrnmf.py @@ -208,12 +208,12 @@ def plot_embeddings( basis = method if color is None: - color = "color_embedding" + color = "color_embeddings" self.asignatures.obs[color] = self.n_signatures * ["black"] self.adata.obs[color] = self.adata.n_obs * ["#1f77b4"] # default blue if zorder is None: - zorder = "zorder_embedding" + zorder = "zorder_embeddings" self.asignatures.obs[zorder] = self.n_signatures * [2] self.adata.obs[zorder] = self.adata.n_obs * [1] diff --git a/src/salamander/models/mmcorrnmf.py b/src/salamander/models/mmcorrnmf.py index 85a0980..b21716b 100644 --- a/src/salamander/models/mmcorrnmf.py +++ b/src/salamander/models/mmcorrnmf.py @@ -710,13 +710,13 @@ def plot_embeddings( basis = method if color is None: - color = "color_embedding" + color = "color_embeddings" for asigs in self.asignatures.values(): asigs.obs[color] = asigs.n_obs * ["black"] self.mdata.obs[color] = self.mdata.n_obs * ["#1f77b4"] # default blue if zorder is None: - zorder = "zorder_embedding" + zorder = "zorder_embeddings" for asigs in self.asignatures.values(): asigs.obs[zorder] = asigs.n_obs * [2] self.mdata.obs[zorder] = self.mdata.n_obs * [1] From db078e9d446d6a336b051a35b18f1d0be7cdf267 Mon Sep 17 00:00:00 2001 From: BeGeiger Date: Wed, 1 May 2024 00:07:38 -0400 Subject: [PATCH 103/103] bump version --- pyproject.toml | 2 +- src/salamander/__init__.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index adaba4c..630d26b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "salamander-learn" -version = "0.3.2" +version = "0.4.0" description = "Salamander is a non-negative matrix factorization framework for signature analysis" license = "MIT" authors = ["Benedikt Geiger"] diff --git a/src/salamander/__init__.py b/src/salamander/__init__.py index bad5934..35d6efa 100644 --- a/src/salamander/__init__.py +++ b/src/salamander/__init__.py @@ -7,11 +7,12 @@ from . import plot as pl from . import tools as tl -__version__ = "0.3.2" +__version__ = "0.4.0" pl.set_salamander_style() __all__ = [ + "__version__", "models", "pl", "tl",