diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 306693322c..9f4f44d749 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -72,9 +72,6 @@ jobs: displayName: 'Install dependencies minimum version' condition: eq(variables['DEPENDENCIES_VERSION'], 'minimum-version') - - script: uv pip list - displayName: 'Display installed versions' - - script: pytest displayName: 'PyTest' condition: eq(variables['TEST_TYPE'], 'standard') diff --git a/docs/release-notes/1.10.2.md b/docs/release-notes/1.10.2.md index a030e494d0..9eaa3a6ebc 100644 --- a/docs/release-notes/1.10.2.md +++ b/docs/release-notes/1.10.2.md @@ -17,6 +17,7 @@ * Compatibility with `matplotlib` 3.9 {pr}`2999` {smaller}`I Virshup` * Add clear errors where `backed` mode-like matrices (i.e., from `sparse_dataset`) are not supported {pr}`3048` {smaller}`I gold` +* Fix deprecated use of `.A` with sparse matrices {pr}`3084` {smaller}`P Angerer` ```{rubric} Performance ``` diff --git a/scanpy/external/exporting.py b/scanpy/external/exporting.py index 0b98ba709d..8655a7ce9d 100644 --- a/scanpy/external/exporting.py +++ b/scanpy/external/exporting.py @@ -285,7 +285,7 @@ def write_hdf5_genes(E, gene_list, filename): hf.attrs["ngenes"] = E.shape[1] for iG, g in enumerate(gene_list): - counts = E[:, iG].A.squeeze() + counts = E[:, iG].toarray().squeeze() cell_ix = np.nonzero(counts)[0] counts = counts[cell_ix] counts_group.create_dataset(g, data=counts) @@ -307,7 +307,7 @@ def write_hdf5_cells(E, filename): hf.attrs["ngenes"] = E.shape[1] for iC in range(E.shape[0]): - counts = E[iC, :].A.squeeze() + counts = E[iC, :].toarray().squeeze() gene_ix = np.nonzero(counts)[0] counts = counts[gene_ix] counts_group.create_dataset(str(iC), data=counts) diff --git a/scanpy/plotting/_qc.py b/scanpy/plotting/_qc.py index 2aec45cbf9..e37276f4b6 100644 --- a/scanpy/plotting/_qc.py +++ b/scanpy/plotting/_qc.py @@ -78,7 +78,7 @@ def highest_expr_genes( if issparse(norm_dict["X"]): mean_percent = norm_dict["X"].mean(axis=0).A1 top_idx = np.argsort(mean_percent)[::-1][:n_top] - counts_top_genes = norm_dict["X"][:, top_idx].A + counts_top_genes = norm_dict["X"][:, top_idx].toarray() else: mean_percent = norm_dict["X"].mean(axis=0) top_idx = np.argsort(mean_percent)[::-1][:n_top] diff --git a/scanpy/plotting/_tools/paga.py b/scanpy/plotting/_tools/paga.py index ea3da95f69..188221a060 100644 --- a/scanpy/plotting/_tools/paga.py +++ b/scanpy/plotting/_tools/paga.py @@ -1224,7 +1224,7 @@ def moving_average(a): values = ( adata.obs[key].values if key in adata.obs_keys() else adata_X[:, key].X )[idcs] - x += (values.A if issparse(values) else values).tolist() + x += (values.toarray() if issparse(values) else values).tolist() if ikey == 0: groups += [group] * len(idcs) x_tick_locs.append(len(x)) diff --git a/scanpy/preprocessing/_combat.py b/scanpy/preprocessing/_combat.py index beae208c36..b8487e4e7e 100644 --- a/scanpy/preprocessing/_combat.py +++ b/scanpy/preprocessing/_combat.py @@ -197,7 +197,7 @@ def combat( # only works on dense matrices so far if issparse(adata.X): - X = adata.X.A.T + X = adata.X.toarray().T else: X = adata.X.T data = pd.DataFrame(data=X, index=adata.var_names, columns=adata.obs_names) diff --git a/scanpy/preprocessing/_pca.py b/scanpy/preprocessing/_pca.py index e0b09b7c87..6060bee6f6 100644 --- a/scanpy/preprocessing/_pca.py +++ b/scanpy/preprocessing/_pca.py @@ -415,7 +415,7 @@ def _pca_with_sparse( X = check_array(X, accept_sparse=["csr", "csc"]) if mu is None: - mu = X.mean(0).A.flatten()[None, :] + mu = np.asarray(X.mean(0)).flatten()[None, :] mdot = mu.dot mmat = mdot mhdot = mu.T.dot diff --git a/scanpy/preprocessing/_scrublet/core.py b/scanpy/preprocessing/_scrublet/core.py index 977424f500..b608443de6 100644 --- a/scanpy/preprocessing/_scrublet/core.py +++ b/scanpy/preprocessing/_scrublet/core.py @@ -185,7 +185,7 @@ def __post_init__( ) -> None: self._counts_obs = sparse.csc_matrix(counts_obs) self._total_counts_obs = ( - self._counts_obs.sum(1).A.squeeze() + np.asarray(self._counts_obs.sum(1)).squeeze() if total_counts_obs is None else total_counts_obs ) diff --git a/scanpy/preprocessing/_scrublet/sparse_utils.py b/scanpy/preprocessing/_scrublet/sparse_utils.py index 677c95ecf0..b4ff1a36b0 100644 --- a/scanpy/preprocessing/_scrublet/sparse_utils.py +++ b/scanpy/preprocessing/_scrublet/sparse_utils.py @@ -52,7 +52,7 @@ def subsample_counts( if rate < 1: random_seed = get_random_state(random_seed) E.data = random_seed.binomial(np.round(E.data).astype(int), rate) - current_totals = E.sum(1).A.squeeze() + current_totals = np.asarray(E.sum(1)).squeeze() unsampled_orig_totals = original_totals - current_totals unsampled_downsamp_totals = np.random.binomial( np.round(unsampled_orig_totals).astype(int), rate diff --git a/scanpy/tests/test_highly_variable_genes.py b/scanpy/tests/test_highly_variable_genes.py index 79d01fa54b..e7ee20a798 100644 --- a/scanpy/tests/test_highly_variable_genes.py +++ b/scanpy/tests/test_highly_variable_genes.py @@ -127,7 +127,7 @@ def test_keep_layer(base, flavor): else: assert False - assert np.allclose(X_orig.A, adata.X.A) + assert np.allclose(X_orig.toarray(), adata.X.toarray()) def _check_pearson_hvg_columns(output_df: pd.DataFrame, n_top_genes: int): diff --git a/scanpy/tests/test_score_genes.py b/scanpy/tests/test_score_genes.py index ec2d7d9280..adf7ea763a 100644 --- a/scanpy/tests/test_score_genes.py +++ b/scanpy/tests/test_score_genes.py @@ -2,6 +2,7 @@ import pickle from pathlib import Path +from typing import TYPE_CHECKING import numpy as np import pytest @@ -11,6 +12,9 @@ import scanpy as sc from testing.scanpy._helpers.data import paul15 +if TYPE_CHECKING: + from typing import Literal + HERE = Path(__file__).parent / Path("_data/") @@ -102,16 +106,20 @@ def test_sparse_nanmean(): # sparse matrix, no NaN S = _create_sparse_nan_matrix(R, C, percent_zero=0.3, percent_nan=0) # col/col sum - np.testing.assert_allclose(S.A.mean(0), np.array(_sparse_nanmean(S, 0)).flatten()) - np.testing.assert_allclose(S.A.mean(1), np.array(_sparse_nanmean(S, 1)).flatten()) + np.testing.assert_allclose( + S.toarray().mean(0), np.array(_sparse_nanmean(S, 0)).flatten() + ) + np.testing.assert_allclose( + S.toarray().mean(1), np.array(_sparse_nanmean(S, 1)).flatten() + ) # sparse matrix with nan S = _create_sparse_nan_matrix(R, C, percent_zero=0.3, percent_nan=0.3) np.testing.assert_allclose( - np.nanmean(S.A, 1), np.array(_sparse_nanmean(S, 1)).flatten() + np.nanmean(S.toarray(), 1), np.array(_sparse_nanmean(S, 1)).flatten() ) np.testing.assert_allclose( - np.nanmean(S.A, 0), np.array(_sparse_nanmean(S, 0)).flatten() + np.nanmean(S.toarray(), 0), np.array(_sparse_nanmean(S, 0)).flatten() ) # edge case of only NaNs per row @@ -138,7 +146,7 @@ def test_score_genes_sparse_vs_dense(): adata_sparse = _create_adata(100, 1000, p_zero=0.3, p_nan=0.3) adata_dense = adata_sparse.copy() - adata_dense.X = adata_dense.X.A + adata_dense.X = adata_dense.X.toarray() gene_set = adata_dense.var_names[:10] @@ -161,7 +169,7 @@ def test_score_genes_deplete(): adata_sparse = _create_adata(100, 1000, p_zero=0.3, p_nan=0.3) adata_dense = adata_sparse.copy() - adata_dense.X = adata_dense.X.A + adata_dense.X = adata_dense.X.toarray() # here's an arbitary gene set gene_set = adata_dense.var_names[:10] @@ -194,8 +202,8 @@ def test_npnanmean_vs_sparsemean(monkeypatch): sparse_scores = adata.obs["Test"].values.tolist() # now patch _sparse_nanmean by np.nanmean inside sc.tools - def mock_fn(x, axis): - return np.nanmean(x.A, axis, dtype="float64") + def mock_fn(x: csr_matrix, axis: Literal[0, 1]): + return np.nanmean(x.toarray(), axis, dtype="float64") monkeypatch.setattr(sc.tl._score_genes, "_sparse_nanmean", mock_fn) sc.tl.score_genes(adata, gene_list=gene_set, score_name="Test") diff --git a/scanpy/tests/test_utils.py b/scanpy/tests/test_utils.py index 1f3191668c..3bec055995 100644 --- a/scanpy/tests/test_utils.py +++ b/scanpy/tests/test_utils.py @@ -240,5 +240,5 @@ def test_is_constant_dask(axis, expected, block_type): [0, 0, 0, 0], ] x = da.from_array(np.array(x_data), chunks=2).map_blocks(block_type) - - np.testing.assert_array_equal(expected, is_constant(x, axis=axis)) + result = is_constant(x, axis=axis).compute() + np.testing.assert_array_equal(expected, result) diff --git a/scanpy/tools/_score_genes.py b/scanpy/tools/_score_genes.py index c5be974a99..54206b52ae 100644 --- a/scanpy/tools/_score_genes.py +++ b/scanpy/tools/_score_genes.py @@ -15,13 +15,18 @@ if TYPE_CHECKING: from collections.abc import Sequence + from typing import Literal from anndata import AnnData + from numpy.typing import NDArray + from scipy.sparse import csc_matrix, csr_matrix from .._utils import AnyRandom -def _sparse_nanmean(X, axis): +def _sparse_nanmean( + X: csr_matrix | csc_matrix, axis: Literal[0, 1] +) -> NDArray[np.float64]: """ np.nanmean equivalent for sparse matrices """