Skip to content

ehrapy.pl wrappers are incompatible with scanpy's scanpy-v2-preview preset (scanpy 1.13.0a1) #1102

Description

@sueoglu

Summary

Scanpy 1.13 alpha 1 introduces an opt-in sc.settings.preset = 'scanpy-v2-preview' that switches
scanpy.plotting to a new accessor-based API. Testing ehrapy's plotting wrappers
(ehrapy/plot/_scanpy_pl_api.py) against this preset shows 0 of 19 tested ep.pl.* functions run
successfully
, every one either raises TypeError (signature changed) or AttributeError
(function not yet ported to v2).

Environment

  • scanpy==1.13.0a1 (installed via pip install --pre "scanpy[scanpy2]==1.13.0a1")
  • ehrapy main branch
  • Reproduced in an isolated venv, not the project's default environment

Repro

import ehrapy as ep          # must import before setting the preset — see Finding 1 below
import ehrdata as ed
import scanpy as sc

sc.settings.preset = "scanpy-v2-preview"

edata = ed.dt.mimic_2()
ed.infer_feature_types(edata, binary_as="numeric")
edata = ep.pp.encode(edata, encodings={"one-hot": ["service_unit", "day_icu_intime"]})
ep.pp.knn_impute(edata, var_names=edata.var_names[edata.var["feature_type"] == "numeric"])
ep.pp.log_norm(edata, offset=1)
ep.pp.neighbors(edata)
ep.pp.pca(edata)
ep.tl.umap(edata)
ep.tl.leiden(edata, resolution=0.5, key_added="leiden_0_5")

ep.pl.scatter(edata, x="age", y="icu_los_day", color="icu_los_day")
# TypeError: scatter() got an unexpected keyword argument 'x'

Findings

Finding 1: import ehrapy fails if the preset is set first

_scanpy_pl_api.py imports DotPlot, MatrixPlot, StackedViolin from scanpy.plotting at module
load time:

from scanpy.plotting import DotPlot, MatrixPlot, StackedViolin

Under scanpy-v2-preview, these class-based names are removed from the top-level scanpy.plotting
namespace, so import ehrapy raises ImportError if the preset is already active. Workaround used
in testing: import ehrapy under the default preset, then switch presets afterward.

Finding 2: every tested ep.pl.* wrapper fails, in two distinct ways

ep.pl.* function Failure Root cause
scatter TypeError: scatter() got an unexpected keyword argument 'x' rewritten signature
pca TypeError: _scatter() got an unexpected keyword argument 'annotate_var_explained' rewritten signature
umap RecursionError: maximum recursion depth exceeded possibly a genuine alpha bug, not just an API change
violin TypeError: violin() got an unexpected keyword argument 'keys' rewritten signature
heatmap TypeError: heatmap() got an unexpected keyword argument 'var_names' rewritten signature
dotplot TypeError: dotplot() got an unexpected keyword argument 'var_names' rewritten signature
matrixplot TypeError: matrixplot() got an unexpected keyword argument 'var_names' rewritten signature
stacked_violin TypeError: stacked_violin() got an unexpected keyword argument 'var_names' rewritten signature
tracksplot TypeError: tracksplot() got an unexpected keyword argument 'var_names' rewritten signature
embedding AttributeError: module 'scanpy.plotting' has no attribute 'embedding' not yet ported to v2
clustermap AttributeError: module 'scanpy.plotting' has no attribute 'clustermap' not yet ported to v2
dendrogram AttributeError: module 'scanpy.plotting' has no attribute 'dendrogram' not yet ported to v2
rank_features_groups AttributeError: module 'scanpy.plotting' has no attribute 'rank_genes_groups' not yet ported to v2
rank_features_groups_violin AttributeError (rank_genes_groups_violin) not yet ported to v2
rank_features_groups_dotplot AttributeError (rank_genes_groups_dotplot) not yet ported to v2
rank_features_groups_heatmap AttributeError (rank_genes_groups_heatmap) not yet ported to v2
rank_features_groups_matrixplot AttributeError (rank_genes_groups_matrixplot) not yet ported to v2
rank_features_groups_stacked_violin AttributeError (rank_genes_groups_stacked_violin) not yet ported to v2
rank_features_groups_tracksplot AttributeError (rank_genes_groups_tracksplot) not yet ported to v2

Rewritten signature functions: under the preset, the top-level sc.pl.<name> now resolves to a new
holoviews-based accessor function (e.g. scatter(adata, kdims, vdims=(), *, color=None) instead of the
old x=/y=/color=/legend_loc=/... matplotlib signature). ep.pl.* binds the old v1 kwargs via
functools.partial(sc.pl.<name>, ...), so the call fails immediately.

Not yet ported functions: these names simply don't exist at the top level of sc.pl under the
preview preset (yet), expected for alpha software, but still breaks ep.pl.* at call time.

Finding 3: the actual v1-compatibility escape hatch — sc.pl.legacy

Scanpy keeps a full submodule, sc.pl.legacy, that preserves the entire old v1 matplotlib API
unchanged, even while scanpy-v2-preview is active, including DotPlot, MatrixPlot, StackedViolin,
and every function above. Confirmed working directly (bypassing ep.pl):

sc.pl.legacy.scatter(edata, x="age", y="icu_los_day", color="icu_los_day", show=False)   
sc.pl.legacy.umap(edata, color=["leiden_0_5", "icu_los_day"], show=False)                
sc.pl.legacy.rank_genes_groups(edata, key="rank_features_groups", show=False)            

This appears to be scanpy's intended migration path for existing code.

Suggested fix

Two options, not mutually exclusive:

Option A: pin to legacy now. Point ehrapy/plot/_scanpy_pl_api.py at scanpy.plotting.legacy
instead of scanpy.plotting (both for the DotPlot/MatrixPlot/StackedViolin import and for every
sc.pl.<name> call inside each wrapper). This restores full compatibility with scanpy-v2-preview
immediately, with no change to ehrapy's own public API or behavior. Simple, low-risk, but ehrapy never
benefits from any v2-only improvements (e.g. new plot defaults) while pinned this way.

Option B: try the v2 accessor, fall back to legacy per function call. A single global
version/preset check isn't actually sufficient here: as Finding 2 shows, even within one scanpy
version/preset there are two independent failure modes: some names exist under sc.pl but with a
changed signature (TypeError), others don't exist there at all yet (AttributeError). A blanket
"if v2 preset, use sc.pl" dispatch would still crash on every not-yet-ported function. Instead the
fallback needs to happen per function, on whichever error actually occurs, e.g.:

def _call_with_fallback(name, edata, v1_kwargs, v2_kwargs=None):
    """Prefer the new v2 accessor API; fall back to sc.pl.legacy if it's not
    available yet (AttributeError) or its signature doesn't match (TypeError)."""
    if v2_kwargs is not None:
        try:
            return getattr(sc.pl, name)(edata, **v2_kwargs)
        except (AttributeError, TypeError):
            pass  # not ported yet, or signature has changed again -- fall back
    return getattr(sc.pl.legacy, name)(edata, **v1_kwargs)

Each ep.pl.<name> wrapper would call _call_with_fallback("scatter", edata, v1_kwargs={...}). Until a
v2_kwargs mapping is added for a given function, it always resolves through sc.pl.legacy, so this
is safe to land today even with zero v2-specific work done yet (equivalent to Option A in the interim).
As v2_kwargs mappings are filled in function-by-function, each one starts preferring the new accessor
API automatically when it's available and correctly shaped, while still degrading gracefully to legacy
for anything not yet ported or if a future alpha changes the accessor signature again. Probably makes sense to land thelegacy-only fallback (no v2_kwargs anywhere yet) first as an immediate compatibility fix, then add
v2_kwargs per function incrementally as the v2 plotting API stabilizes.

Notes

  • This is alpha software (scanpy==1.13.0a1); some of the above (particularly the not yet ported
    and the umap RecursionError) may already be fixed in later alphas/betas.
  • Reproduced in an isolated venv (.venv-scanpy-v2-preview), not the project's default environment.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions