diff --git a/docs/release-notes/4191.chore.md b/docs/release-notes/4191.chore.md new file mode 100644 index 0000000000..03faf7e975 --- /dev/null +++ b/docs/release-notes/4191.chore.md @@ -0,0 +1 @@ +{mod}`anndata` is lower bounded by `0.11.2` {smaller}`I Gold` diff --git a/pyproject.toml b/pyproject.toml index da05accbf4..c1f77a980d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ classifiers = [ ] dynamic = [ "version" ] dependencies = [ - "anndata>=0.10.8", + "anndata>=0.11.2", "certifi", "fast-array-utils[accel,sparse]>=1.4", "h5py>=3.11", @@ -67,7 +67,7 @@ dependencies = [ "patsy", "pynndescent>=0.5.13", "scikit-learn>=1.6", - "scipy>=1.14", + "scipy>=1.15", "scverse-misc[settings]>=0.1.1", "seaborn>=0.13.2", "session-info2", diff --git a/src/scanpy/_utils/__init__.py b/src/scanpy/_utils/__init__.py index f0d67b550d..14e184aabf 100644 --- a/src/scanpy/_utils/__init__.py +++ b/src/scanpy/_utils/__init__.py @@ -30,10 +30,9 @@ import numpy as np import pandas as pd from anndata._core.sparse_dataset import BaseCompressedSparseDataset -from packaging.version import Version from .. import logging as logg -from .._compat import CSBase, DaskArray, SpBase, _CSArray, pkg_version, warn +from .._compat import CSBase, DaskArray, SpBase, warn from ._numba import _numba_thread_limit if TYPE_CHECKING: @@ -728,21 +727,9 @@ def axis_nnz(x: ArrayLike, /, axis: Literal[0, 1]) -> np.ndarray: return np.count_nonzero(x, axis=axis) -if pkg_version("scipy") >= Version("1.15"): - # newer scipy versions support the `axis` argument for count_nonzero - @axis_nnz.register(CSBase) - def _(x: CSBase, /, axis: Literal[0, 1]) -> np.ndarray: - return x.count_nonzero(axis=axis) - -else: - # older scipy versions don’t have any way to get the nnz of a sparse array - @axis_nnz.register(CSBase) - def _(x: CSBase, /, axis: Literal[0, 1]) -> np.ndarray: - if isinstance(x, _CSArray): - from scipy.sparse import csc_array, csr_array # noqa: TID251 - - x = (csr_array if x.format == "csr" else csc_array)(x) - return x.getnnz(axis=axis) +@axis_nnz.register(CSBase) +def _(x: CSBase, /, axis: Literal[0, 1]) -> np.ndarray: + return x.count_nonzero(axis=axis) @axis_nnz.register(DaskArray) diff --git a/src/testing/scanpy/_helpers/__init__.py b/src/testing/scanpy/_helpers/__init__.py index e45accb601..d399bbbc2e 100644 --- a/src/testing/scanpy/_helpers/__init__.py +++ b/src/testing/scanpy/_helpers/__init__.py @@ -16,13 +16,14 @@ from packaging.version import Version import scanpy as sc -from scanpy._compat import DaskArray, pkg_version if TYPE_CHECKING: from collections.abc import Iterable from numpy.typing import NDArray + from scanpy._compat import DaskArray + # TODO: Report more context on the fields being compared on error # TODO: Allow specifying paths to ignore on comparison @@ -128,14 +129,6 @@ def as_dense_dask_array(*args, **kwargs) -> DaskArray: from anndata.tests.helpers import as_dense_dask_array a = as_dense_dask_array(*args, **kwargs) - # Newer versions of as_dense_dask_array chunk all axes by halve when the input is not a dask array. - if ( - pkg_version("anndata") < Version("0.11") - and not isinstance(args[0], DaskArray) # keep chunksize intact - ): - from anndata.tests.helpers import _half_chunk_size - - a = a.rechunk(_half_chunk_size(a.shape)) return a diff --git a/src/testing/scanpy/_pytest/fixtures/data.py b/src/testing/scanpy/_pytest/fixtures/data.py index b556e4dee5..65c910f8cd 100644 --- a/src/testing/scanpy/_pytest/fixtures/data.py +++ b/src/testing/scanpy/_pytest/fixtures/data.py @@ -61,7 +61,7 @@ def random_csr(rng: np.random.Generator, size: tuple[int, int]) -> CSRBase: @pytest.fixture( - params=[np.random.Generator.standard_normal, random_csr], ids=["sparse", "dense"] + params=[np.random.Generator.standard_normal, random_csr], ids=["dense", "sparse"] ) def backed_adata(request: pytest.FixtureRequest, tmp_path: Path) -> AnnData: rng = np.random.default_rng() diff --git a/src/testing/scanpy/_pytest/params.py b/src/testing/scanpy/_pytest/params.py index ce4094206d..bc5f5bad17 100644 --- a/src/testing/scanpy/_pytest/params.py +++ b/src/testing/scanpy/_pytest/params.py @@ -24,11 +24,6 @@ from ....scanpy._compat import DaskArray -skipif_no_sparray = pytest.mark.skipif( - Version(version("anndata")) < Version("0.11"), - reason="scipy cs{rc}_array not supported in anndata<0.11", -) - anndata_test_utils_supports_typ_kwarg = Version(version("anndata")) >= Version("0.12.6") @@ -84,7 +79,7 @@ def wrapper(a: np.ndarray) -> DaskArray: ("mem", "sparse"): ( pytest.param(sparse.csr_matrix, id="scipy_csr_mat"), # noqa: TID251 pytest.param(sparse.csc_matrix, id="scipy_csc_mat"), # noqa: TID251 - pytest.param(sparse.csr_array, id="scipy_csr_arr", marks=[skipif_no_sparray]), # noqa: TID251 + pytest.param(sparse.csr_array, id="scipy_csr_arr"), # noqa: TID251 ), ("dask", "dense"): tuple( pytest.param( diff --git a/tests/test_aggregated.py b/tests/test_aggregated.py index d9c0b71829..459c6e85df 100644 --- a/tests/test_aggregated.py +++ b/tests/test_aggregated.py @@ -7,11 +7,10 @@ import numpy as np import pandas as pd import pytest -from packaging.version import Version from scipy import sparse import scanpy as sc -from scanpy._compat import DaskArray, pkg_version +from scanpy._compat import DaskArray from scanpy._utils import _resolve_axis, get_literal_vals from scanpy.get._aggregated import AggType from testing.scanpy._helpers import assert_equal @@ -84,19 +83,10 @@ def test_aggregate_vs_pandas( remove_unused_categories: bool, ) -> None: adata = pbmc3k_processed().raw.to_adata() - anndata_has_settings = pkg_version("anndata") >= Version("0.11") cat_col = adata.obs["louvain"] categories = cat_col.cat.categories - if anndata_has_settings: - with ad.settings.override(remove_unused_categories=remove_unused_categories): - adata = adata[cat_col.isin(categories[:5]), :1_000].copy() - else: - del adata.obs["louvain"] - mask = cat_col.isin(categories[:5]) - adata = adata[mask, :1_000].copy() - adata.obs["louvain"] = cat_col[mask] - if remove_unused_categories: - adata.obs["louvain"] = adata.obs["louvain"].cat.remove_unused_categories() + with ad.settings.override(remove_unused_categories=remove_unused_categories): + adata = adata[cat_col.isin(categories[:5]), :1_000].copy() adata.X = array_type(adata.X) if with_na: nas = list(range(0, adata.shape[0], 5))