Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
eecce50
first draft
Intron7 Jan 26, 2026
f279543
add pooch
Intron7 Jan 26, 2026
c724395
Merge branch 'main' into add-harmony
flying-sheep Feb 20, 2026
b552779
improve deprecation
flying-sheep Feb 20, 2026
a59be40
docs
flying-sheep Feb 20, 2026
94710d5
docs
flying-sheep Feb 20, 2026
4b4d827
move API over
flying-sheep Feb 20, 2026
4627d62
faster
flying-sheep Feb 20, 2026
3d53e37
refactor
flying-sheep Feb 20, 2026
96fca7a
more itertools
flying-sheep Feb 20, 2026
4a542a3
fix test and remove phi
Intron7 Mar 2, 2026
a6519b0
Merge branch 'main' into add-harmony
Intron7 Mar 2, 2026
88dca09
add tau remove sparse
Intron7 Mar 2, 2026
fa8e3cd
add harmony 2 and tests
Intron7 Apr 8, 2026
591b951
Merge branch 'main' into add-harmony
flying-sheep Apr 10, 2026
6ec09d8
Merge branch 'main' into add-harmony
Intron7 Apr 20, 2026
fad0fd3
restructure
flying-sheep Apr 20, 2026
4257d8f
rng
flying-sheep Apr 20, 2026
d15aede
docs: use upstream APIs (#4083)
flying-sheep Apr 23, 2026
52cc330
Merge branch 'main' into add-harmony
Intron7 Apr 23, 2026
477081b
add back rng seed
Intron7 Apr 23, 2026
950757c
add citation
Intron7 Apr 23, 2026
d6f2c51
perf: Combat perf improvements (#4070)
ilaykav Apr 24, 2026
44cfc6e
docs: clarify method vs transformer in sc.pp.neighbors (#4079)
CuiweiG Apr 24, 2026
305032e
diff friendly
flying-sheep Apr 24, 2026
8378c78
array type support
flying-sheep Apr 24, 2026
2b60935
fix: limit Numba threads in Wilcoxon path of rank_genes_groups (#4082)
JhonatanFelix Apr 24, 2026
ea8e481
style: add NPY lints to prevent use of legacy `np.random` (#4087)
flying-sheep Apr 24, 2026
6edcc77
Merge branch 'main' into add-harmony
Intron7 Apr 25, 2026
95246c2
Merge branch 'main' into add-harmony
Intron7 Apr 27, 2026
40b32cd
Merge branch 'main' into add-harmony
flying-sheep Apr 27, 2026
e1519e6
fix rng
Intron7 Apr 27, 2026
7a6efb2
use fast correction by default
Intron7 Apr 30, 2026
49bbd92
Merge branch 'main' into add-harmony
Intron7 Apr 30, 2026
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
6 changes: 6 additions & 0 deletions benchmarks/benchmarks/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,9 @@ def time_rank_genes_groups(self) -> None:

def peakmem_rank_genes_groups(self) -> None:
sc.tl.rank_genes_groups(self.adata, "bulk_labels", method="wilcoxon")

def time_combat(self) -> None:
sc.pp.combat(self.adata, key="bulk_labels")

def peakmem_combat(self) -> None:
sc.pp.combat(self.adata, key="bulk_labels")
10 changes: 8 additions & 2 deletions docs/api/preprocessing.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,24 @@ For visual quality control, see {func}`~scanpy.pl.highest_expr_genes` and
pp.recipe_seurat
```

## Batch effect correction
(pp-data-integration)=

Also see {ref}`data-integration`. Note that a simple batch correction method is available via {func}`pp.regress_out`. Checkout {mod}`scanpy.external` for more.
## Data integration

Batch effect correction and other data integration.
Note that a simple batch correction method is available via {func}`pp.regress_out`.

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

pp.combat
pp.harmony_integrate
```

Also see {ref}`data integration tools <data-integration>` and external {ref}`external data integration <external-data-integration>`.

## Doublet detection

```{eval-rst}
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@
"pp.downsample_counts": (["np", "sp[csr]"], []),
"pp.filter_cells": (["np", "sp", "da"], []),
"pp.filter_genes": (["np", "sp", "da"], []),
"pp.harmony_integrate": (["np"], []),
"pp.highly_variable_genes": (["np", "sp", "da"], ["da[sp[csc]]"]),
"pp.log1p": (["np", "sp", "da"], []),
"pp.neighbors": (["np", "sp"], []),
Expand Down
4 changes: 3 additions & 1 deletion docs/extensions/array_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from docutils import nodes
from sphinx.util.docutils import SphinxDirective
from sphinx.util.typing import ExtensionMetadata

from scanpy._utils import _docs

Expand Down Expand Up @@ -179,7 +180,8 @@ def one[T](arg: Collection[T]) -> T | None:
return item


def setup(app: Sphinx) -> None:
def setup(app: Sphinx) -> ExtensionMetadata:
"""App setup hook."""
app.add_directive("array-support", ArraySupport)
app.add_config_value("array_support", {}, "env")
return ExtensionMetadata(parallel_read_safe=True)
5 changes: 4 additions & 1 deletion docs/extensions/autosummary_skip_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from typing import TYPE_CHECKING

from sphinx.util.typing import ExtensionMetadata

if TYPE_CHECKING:
from typing import Literal

Expand All @@ -27,6 +29,7 @@ def skip_deprecated( # noqa: PLR0917
return None


def setup(app: Sphinx) -> None:
def setup(app: Sphinx) -> ExtensionMetadata:
"""App setup hook."""
app.connect("autodoc-skip-member", skip_deprecated)
return ExtensionMetadata(parallel_read_safe=True)
5 changes: 4 additions & 1 deletion docs/extensions/autosummary_skip_inherited.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from traceback import walk_stack
from typing import TYPE_CHECKING

from sphinx.util.typing import ExtensionMetadata

if TYPE_CHECKING:
from typing import Literal

Expand Down Expand Up @@ -54,6 +56,7 @@ def skip_inherited( # noqa: PLR0917
return True


def setup(app: Sphinx) -> None:
def setup(app: Sphinx) -> ExtensionMetadata:
"""App setup hook."""
app.connect("autodoc-skip-member", skip_inherited)
return ExtensionMetadata(parallel_read_safe=True)
4 changes: 3 additions & 1 deletion docs/extensions/canonical_tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import TYPE_CHECKING

from sphinx.util.docutils import SphinxDirective
from sphinx.util.typing import ExtensionMetadata

if TYPE_CHECKING:
from typing import ClassVar
Expand All @@ -22,6 +23,7 @@ def run(self) -> list[nodes.Node]: # noqa: D102
return []


def setup(app: Sphinx) -> None:
def setup(app: Sphinx) -> ExtensionMetadata:
"""App setup hook."""
app.add_directive("canonical-tutorial", CanonicalTutorial)
return ExtensionMetadata(parallel_read_safe=True)
5 changes: 4 additions & 1 deletion docs/extensions/debug_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
from typing import TYPE_CHECKING

import sphinx.ext.napoleon
from sphinx.util.typing import ExtensionMetadata

if TYPE_CHECKING:
from sphinx.application import Sphinx


_pd_orig = sphinx.ext.napoleon._process_docstring


Expand All @@ -21,7 +23,8 @@ def pd_new(app, what, name, obj, options, lines) -> None: # noqa: PLR0917
print(*lines, sep="\n")


def setup(app: Sphinx) -> None:
def setup(app: Sphinx) -> ExtensionMetadata:
"""App setup hook."""
if os.environ.get("DEBUG") is not None:
sphinx.ext.napoleon._process_docstring = pd_new
return ExtensionMetadata(parallel_read_safe=True)
5 changes: 4 additions & 1 deletion docs/extensions/function_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from pathlib import Path
from typing import TYPE_CHECKING

from sphinx.util.typing import ExtensionMetadata

if TYPE_CHECKING:
from typing import Any

Expand All @@ -27,7 +29,8 @@ def insert_function_images( # noqa: PLR0917
]


def setup(app: Sphinx) -> None:
def setup(app: Sphinx) -> ExtensionMetadata:
"""App setup hook."""
app.add_config_value("api_dir", Path(), "env")
app.connect("autodoc-process-docstring", insert_function_images)
return ExtensionMetadata(parallel_read_safe=True)
5 changes: 4 additions & 1 deletion docs/extensions/git_ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from functools import lru_cache
from typing import TYPE_CHECKING

from sphinx.util.typing import ExtensionMetadata

if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.config import Config
Expand Down Expand Up @@ -45,6 +47,7 @@ def set_ref(app: Sphinx, config: Config):
app.config["html_theme_options"]["repository_branch"] = get() or "main"


def setup(app: Sphinx) -> None:
def setup(app: Sphinx) -> ExtensionMetadata:
"""App setup hook."""
app.connect("config-inited", set_ref)
return ExtensionMetadata(parallel_read_safe=True)
83 changes: 83 additions & 0 deletions docs/extensions/myst_ignore_mime_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Extension to patch ignored mime types."""

from __future__ import annotations

import sys
from importlib.abc import MetaPathFinder
from importlib.metadata import Distribution, EntryPoint, EntryPoints
from types import MappingProxyType
from typing import TYPE_CHECKING, override

from myst_nb.core.render import MimeRenderPlugin
from sphinx.util.typing import ExtensionMetadata

if TYPE_CHECKING:
import os
from collections.abc import Iterable, Sequence
from importlib.machinery import ModuleSpec
from importlib.metadata import DistributionFinder, SimplePath
from types import ModuleType

from docutils import nodes
from myst_nb.core.render import MimeData, NbElementRenderer
from sphinx.application import Sphinx


ignore: set[str] = set()


class _Ignore(MimeRenderPlugin):
@override
@staticmethod
def handle_mime(
renderer: NbElementRenderer, data: MimeData, inline: bool
) -> None | list[nodes.Element]:
if data.mime_type in ignore:
return [] # returning a list instead of `None` means “we handled it”
return None


class _IgnoreMimeDist(Distribution):
metadata = MappingProxyType(dict(Name=__name__, Version="0.0.0"))

@override
def read_text(self, filename: str) -> str | None:
return None

@override
def locate_file(self, path: str | os.PathLike[str]) -> SimplePath:
raise RuntimeError

@property
@override
def entry_points(self) -> EntryPoints:
ep = EntryPoint("ignore", f"{__name__}:_Ignore", "myst_nb.mime_renderers")
return EntryPoints([ep])


class _IgnoreMimeFinder(MetaPathFinder):
def find_spec(
self,
fullname: str,
path: Sequence[str] | None,
target: ModuleType | None = None,
) -> ModuleSpec | None:
return None

def find_distributions(
self, context: DistributionFinder.Context | None
) -> Iterable[Distribution]:
"""Find fake distribution."""
yield _IgnoreMimeDist()


def setup(app: Sphinx) -> ExtensionMetadata:
"""App setup hook."""
global ignore # noqa: PLW0603

app.add_config_value("myst_ignore_mime_types", [], "env")
ignore |= set(app.config.myst_ignore_mime_types)

sys.meta_path.append(_IgnoreMimeFinder())

return ExtensionMetadata(parallel_read_safe=True)
5 changes: 4 additions & 1 deletion docs/extensions/param_police.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
from typing import TYPE_CHECKING

from sphinx.ext.napoleon import NumpyDocstring
from sphinx.util.typing import ExtensionMetadata

if TYPE_CHECKING:
from sphinx.application import Sphinx


_format_docutils_params_orig = NumpyDocstring._format_docutils_params
param_warnings = {}

Expand Down Expand Up @@ -46,7 +48,8 @@ def show_param_warnings(app, exception):
raise RuntimeError(msg)


def setup(app: Sphinx):
def setup(app: Sphinx) -> ExtensionMetadata:
"""App setup hook."""
NumpyDocstring._format_docutils_params = scanpy_log_param_types
app.connect("build-finished", show_param_warnings)
return ExtensionMetadata(parallel_read_safe=True)
53 changes: 0 additions & 53 deletions docs/extensions/patch_myst_nb.py

This file was deleted.

8 changes: 5 additions & 3 deletions docs/external/preprocessing.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
.. currentmodule:: scanpy.external
```

Previously found here, but now part of scanpy’s main API:
- {func}`scanpy.pp.harmony_integrate`
- {func}`scanpy.pp.scrublet`
- {func}`scanpy.pp.scrublet_simulate_doublets`

(external-data-integration)=

## Data integration
Expand All @@ -14,10 +19,8 @@
:toctree: ../generated/

pp.bbknn
pp.harmony_integrate
pp.mnn_correct
pp.scanorama_integrate

```

## Sample demultiplexing
Expand All @@ -38,5 +41,4 @@ Note that the fundamental limitations of imputation are still under [debate](htt
:toctree: ../generated/

pp.magic

```
10 changes: 10 additions & 0 deletions docs/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,16 @@ @article{Ntranos2019
pages = {163--166},
}

@article{Patikas2026,
author = {Patikas, Nikolaos and Yao, Hongcheng and Madhu, Roopa and Raychaudhuri, Soumya and Hemberg, Martin and Korsunsky, Ilya},
title = {Integration of large, complex single-cell datasets with Harmony2},
url = {https://doi.org/10.1101/2026.03.16.711825},
doi = {10.1101/2026.03.16.711825},
journal = {bioRxiv},
publisher = {Cold Spring Harbor Laboratory},
year = {2026},
}

@article{Paul2015,
author = {Paul, Franziska and Arkin, Ya’ara and Giladi, Amir and Jaitin, Diego Adhemar and Kenigsberg, Ephraim and Keren-Shaul, Hadas and Winter, Deborah and Lara-Astiaso, David and Gury, Meital and Weiner, Assaf and David, Eyal and Cohen, Nadav and Lauridsen, Felicia Kathrine Bratt and Haas, Simon and Schlitzer, Andreas and Mildner, Alexander and Ginhoux, Florent and Jung, Steffen and Trumpp, Andreas and Porse, Bo Torben and Tanay, Amos and Amit, Ido},
title = {Transcriptional Heterogeneity and Lineage Commitment in Myeloid Progenitors},
Expand Down
2 changes: 1 addition & 1 deletion docs/release-notes/1.10.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Some highlights:
* {func}`scanpy.datasets.blobs` now accepts a `random_state` argument {pr}`2683` {smaller}`E Roellin`
* {func}`scanpy.pp.pca` and {func}`scanpy.pp.regress_out` now accept a layer argument {pr}`2588` {smaller}`S Dicks`
* {func}`scanpy.pp.subsample` with `copy=True` can now be called in backed mode {pr}`2624` {smaller}`E Roellin`
* {func}`scanpy.external.pp.harmony_integrate` now runs with 64 bit floats improving reproducibility {pr}`2655` {smaller}`S Dicks`
* {func}`scanpy.pp.harmony_integrate` now runs with 64 bit floats improving reproducibility {pr}`2655` {smaller}`S Dicks`
* {func}`scanpy.tl.rank_genes_groups` no longer warns that it's default was changed from t-test_overestim_var to t-test {pr}`2798` {smaller}`L Heumos`
* `scanpy.pp.calculate_qc_metrics` now allows `qc_vars` to be passed as a string {pr}`2859` {smaller}`N Teyssier`
* {func}`scanpy.tl.leiden` and {func}`scanpy.tl.louvain` now store clustering parameters in the key provided by the `key_added` parameter instead of always writing to (or overwriting) a default key {pr}`2864` {smaller}`J Fan`
Expand Down
Loading
Loading