Skip to content

(feat): presets #3653

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 52 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
5eb83a4
WIP presets
flying-sheep May 28, 2025
fd59bea
Merge branch 'main' into pa/presets
flying-sheep May 28, 2025
df3c3a6
notebooks
flying-sheep Jun 2, 2025
db4c0d3
introduce singleton code
flying-sheep Jun 2, 2025
af89ce5
works, except for role
flying-sheep Jun 3, 2025
513c417
just use `attr` link
flying-sheep Jun 3, 2025
b7d5a17
skip inherited members
flying-sheep Jun 3, 2025
736c752
skip inherited stuff
flying-sheep Jun 3, 2025
368ea84
fix organization
flying-sheep Jun 3, 2025
5c260d4
fix verbosity documentation
flying-sheep Jun 6, 2025
b630891
more docs
flying-sheep Jun 6, 2025
5028415
more verbosity docs
flying-sheep Jun 6, 2025
dcd783a
document all intended settings attrs
flying-sheep Jun 6, 2025
8a626c2
more links
flying-sheep Jun 6, 2025
c9a06dd
document defaults
flying-sheep Jun 6, 2025
3bae7ae
improve other side
flying-sheep Jun 6, 2025
b158545
undo presets
flying-sheep Jun 6, 2025
70d48f3
Merge branch 'main' into pa/settings-overhaul
flying-sheep Jun 6, 2025
26a4ec9
Revert "undo presets"
flying-sheep Jun 6, 2025
28a5ea3
fix broken test
flying-sheep Jun 6, 2025
8335ff9
make into decorator
flying-sheep Jun 6, 2025
c3ce319
Merge branch 'pa/settings-overhaul' into pa/presets
flying-sheep Jun 6, 2025
78fc7c0
Merge branch 'main' into pa/settings-overhaul
flying-sheep Jun 6, 2025
4942123
relnote
flying-sheep Jun 6, 2025
f982a2f
Merge branch 'pa/settings-overhaul' into pa/presets
flying-sheep Jun 6, 2025
3a7d95a
comment on stack magic
flying-sheep Jun 10, 2025
12f7807
simplify `documenting()`
flying-sheep Jun 10, 2025
c8a023c
simpler skipping
flying-sheep Jun 10, 2025
04f96e1
Merge branch 'main' into pa/settings-overhaul
flying-sheep Jun 10, 2025
f9228c6
comment inherited impl
flying-sheep Jun 10, 2025
36d28d2
metaclass docs
flying-sheep Jun 10, 2025
3767c65
Merge branch 'main' into pa/settings-overhaul
flying-sheep Jun 10, 2025
22ed905
merged version of notebooks
flying-sheep Jun 10, 2025
f712fd2
Merge branch 'pa/settings-overhaul' into pa/presets
flying-sheep Jun 10, 2025
b38e93f
Merge branch 'main' into pa/presets
flying-sheep Jun 10, 2025
8fa86c7
override preset
flying-sheep Jun 12, 2025
84b441f
WIP filter_{genes,cells}
flying-sheep Jun 12, 2025
44b14e9
fix docs
flying-sheep Jun 12, 2025
2f4c8a4
param sets
flying-sheep Jun 12, 2025
d0daf38
extract
flying-sheep Jun 13, 2025
370d00c
Merge branch 'main' into pa/presets
flying-sheep Jun 13, 2025
4340838
fix test
flying-sheep Jun 13, 2025
bd34846
relnote and scanpy v2 preset
flying-sheep Jun 13, 2025
67af4e0
add rank_genes_groups preset
flying-sheep Jun 13, 2025
77279a9
WIP RNG
flying-sheep Jun 15, 2025
c2cdf18
undo RNG stuff
flying-sheep Jun 16, 2025
e8678fd
leiden
flying-sheep Jun 16, 2025
6bfc332
oops
flying-sheep Jun 16, 2025
10757fa
add return_df
flying-sheep Jun 16, 2025
6166fec
oops
flying-sheep Jun 16, 2025
76a19c8
revert cutoffs
flying-sheep Jun 17, 2025
80c24a9
mask rank_genes_groups
flying-sheep Jun 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/api/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ An object that allows configuring Scanpy.

Some selected settings are discussed in the following.

Presets allow to set the behavior of many scanpy functions at once:

```{eval-rst}
.. autosummary::
:signatures: none
:toctree: ../generated/

Preset
```

Verbosity controls the amount of logging output:

```{eval-rst}
Expand Down
3 changes: 3 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ def setup(app: Sphinx):
"scanpy.plotting._matrixplot.MatrixPlot": "scanpy.pl.MatrixPlot",
"scanpy.plotting._dotplot.DotPlot": "scanpy.pl.DotPlot",
"scanpy.plotting._stacked_violin.StackedViolin": "scanpy.pl.StackedViolin",
"scanpy._param_sets.HVGFlavor": "tuple",
"scanpy._param_sets.FilterCellsCutoffs": "tuple",
"scanpy._param_sets.FilterGenesCutoffs": "tuple",
"pandas.core.series.Series": "pandas.Series",
"numpy.bool_": "numpy.bool", # Since numpy 2, numpy.bool is the canonical dtype
}
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/3653.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add {attr}`scanpy.settings.preset` setting with two new presets: {attr}`~scanpy.settings.Preset.SeuratV5` and {attr}`~scanpy.settings.Preset.ScanpyV2Preview`. {smaller}`P Angerer`
3 changes: 2 additions & 1 deletion src/scanpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# the actual API
# (start with settings as several tools are using it)

from ._settings import Verbosity, settings
from ._settings import Preset, Verbosity, settings

set_figure_params = settings._set_figure_params

Expand Down Expand Up @@ -62,6 +62,7 @@
__all__ = [
"AnnData",
"Neighbors",
"Preset",
"Verbosity",
"__version__",
"concat",
Expand Down
15 changes: 13 additions & 2 deletions src/scanpy/_settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .._compat import deprecated, old_positionals
from .._singleton import SingletonMeta
from ..logging import _RootLogger, _set_log_file, _set_log_level
from .presets import Preset
from .verbosity import Verbosity

if TYPE_CHECKING:
Expand All @@ -27,7 +28,6 @@
| Literal["raw", "rgba"]
)


S = TypeVar("S")
T = TypeVar("T")
P = ParamSpec("P")
Expand Down Expand Up @@ -67,6 +67,7 @@ def wrapped(self: S, var: T, *args: P.args, **kwargs: P.kwargs) -> R:


class SettingsMeta(SingletonMeta):
_preset: Preset
# logging
_root_logger: _RootLogger
_logfile: TextIO | None
Expand Down Expand Up @@ -99,6 +100,15 @@ class SettingsMeta(SingletonMeta):
_previous_memory_usage: int
"""Stores the previous memory usage."""

@property
def preset(cls) -> Preset:
"""Preset to use."""
return cls._preset

@preset.setter
def preset(cls, preset: Preset | str) -> None:
cls._preset = Preset(preset)

@property
def verbosity(cls) -> Verbosity:
"""Verbosity level (default :attr:`Verbosity.warning`)."""
Expand Down Expand Up @@ -336,7 +346,7 @@ def categories_to_ignore(cls, categories_to_ignore: Iterable[str]) -> None:

@deprecated("Use `scanpy.set_figure_params` instead")
def set_figure_params(cls, *args, **kwargs) -> None:
cls.set_figure_params(*args, **kwargs)
cls._set_figure_params(*args, **kwargs)

@old_positionals(
"scanpy",
Expand Down Expand Up @@ -455,6 +465,7 @@ class settings(metaclass=SettingsMeta):
def __new__(cls) -> type[Self]:
return cls

_preset = Preset.ScanpyV1
# logging
_root_logger: ClassVar = _RootLogger(logging.INFO)
_logfile: ClassVar = None
Expand Down
177 changes: 177 additions & 0 deletions src/scanpy/_settings/presets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
from __future__ import annotations

import inspect
import re
from contextlib import contextmanager
from enum import StrEnum, auto
from functools import cached_property, partial, wraps
from typing import TYPE_CHECKING, Literal, NamedTuple, TypeVar

if TYPE_CHECKING:
from collections.abc import Callable, Generator, Mapping


NT = TypeVar("NT", bound=NamedTuple)

__all__ = [
"DETest",
"HVGFlavor",
"HVGPreset",
"LeidenFlavor",
"LeidenPreset",
"PcaPreset",
"Preset",
"RankGenesGroupsPreset",
]


DETest = Literal["logreg", "t-test", "wilcoxon", "t-test_overestim_var"]
HVGFlavor = Literal["seurat", "cell_ranger", "seurat_v3", "seurat_v3_paper"]
LeidenFlavor = Literal["leidenalg", "igraph"]


class HVGPreset(NamedTuple):
flavor: HVGFlavor
return_df: bool


class PcaPreset(NamedTuple):
key_added: str | None


class RankGenesGroupsPreset(NamedTuple):
method: DETest
mask_var: str | None


class LeidenPreset(NamedTuple):
flavor: LeidenFlavor


preset_postprocessors: list[Callable[[], None]] = []


def named_tuple_non_defaults(
nt: NamedTuple,
) -> Generator[tuple[str, object], None, None]:
cls = type(nt)
for param in cls._fields:
value = getattr(nt, param)
if param not in cls._field_defaults or value != cls._field_defaults[param]:
yield param, value


def postprocess_preset_prop(
prop: cached_property[NT], get_map: Callable[[], Mapping[Preset, NT]]
) -> None:
map = get_map()

map_type = inspect.signature(get_map).return_annotation
m = re.fullmatch(r"Mapping\[Preset, (.*)\]", map_type)
assert m is not None
value_type = m[1]

added_doc = "\n".join(
":attr:`{name}`\n Defaults: {defaults}".format(
name=k.name,
defaults=", ".join(
f"`{param}={default!r}`"
for param, default in named_tuple_non_defaults(params)
)
or "none",
)
for k, params in map.items()
)

prop.__doc__ = f"{prop.__doc__}\n\n{added_doc}"
prop.func.__annotations__["return"] = value_type


def preset_property(get_map: Callable[[], Mapping[Preset, NT]]) -> cached_property[NT]:
@wraps(get_map)
def get(self: Preset) -> NT:
return get_map()[self]

prop = cached_property(get)
preset_postprocessors.append(partial(postprocess_preset_prop, prop, get_map))
return prop


class Preset(StrEnum):
"""Presets for :attr:`scanpy.settings.preset`.

See properties below for details.
"""

ScanpyV1 = auto()
"""Scanpy 1.*’s default settings."""

ScanpyV2Preview = auto()
"""Scanpy 2.*’s feature default settings. (Preview: subject to change!)"""

SeuratV5 = auto()
"""Try to match Seurat 5.* as closely as possible."""

@preset_property
def highly_variable_genes() -> Mapping[Preset, HVGPreset]:
"""Flavor for :func:`~scanpy.pp.highly_variable_genes`."""
return {
Preset.ScanpyV1: HVGPreset(flavor="seurat", return_df=False),
Preset.ScanpyV2Preview: HVGPreset(flavor="seurat_v3_paper", return_df=True),
Preset.SeuratV5: HVGPreset(flavor="seurat_v3_paper", return_df=True),
}

@preset_property
def pca() -> Mapping[Preset, PcaPreset]:
"""Settings for :func:`~scanpy.pp.pca`.""" # noqa: D401
return {
Preset.ScanpyV1: PcaPreset(key_added=None),
Preset.ScanpyV2Preview: PcaPreset(key_added="pca"),
Preset.SeuratV5: PcaPreset(key_added="pca"),
}

@preset_property
def rank_genes_groups() -> Mapping[Preset, RankGenesGroupsPreset]:
"""Correlation method for :func:`~scanpy.tl.rank_genes_groups`."""
return {
Preset.ScanpyV1: RankGenesGroupsPreset(method="t-test", mask_var=None),
Preset.ScanpyV2Preview: RankGenesGroupsPreset(
method="wilcoxon", mask_var=None
),
Preset.SeuratV5: RankGenesGroupsPreset(
method="wilcoxon", mask_var="highly_variable"
),
}

@preset_property
def leiden() -> Mapping[Preset, LeidenPreset]:
"""Flavor for :func:`~scanpy.tl.leiden`."""
return {
Preset.ScanpyV1: LeidenPreset(flavor="leidenalg"),
Preset.ScanpyV2Preview: LeidenPreset(flavor="igraph"),
Preset.SeuratV5: LeidenPreset(flavor="leidenalg"),
}

@contextmanager
def override(self, preset: Preset) -> Generator[Preset, None, None]:
"""Temporarily override :attr:`scanpy.settings.preset`.

>>> import scanpy as sc
>>> sc.settings.preset = sc.Preset.ScanpyV1
>>> with sc.settings.preset.override(sc.Preset.SeuratV5):
... sc.settings.preset
<Preset.SeuratV5: 'seuratv5'>
>>> sc.settings.preset
<Preset.ScanpyV1: 'scanpyv1'>
"""
from scanpy import settings

settings.preset = preset
try:
yield self
finally:
settings.preset = self


for postprocess in preset_postprocessors:
postprocess()
7 changes: 0 additions & 7 deletions src/scanpy/_types.py

This file was deleted.

Loading
Loading