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.
Summary
Scanpy 1.13 alpha 1 introduces an opt-in
sc.settings.preset = 'scanpy-v2-preview'that switchesscanpy.plottingto a new accessor-based API. Testing ehrapy's plotting wrappers(
ehrapy/plot/_scanpy_pl_api.py) against this preset shows 0 of 19 testedep.pl.*functions runsuccessfully, every one either raises
TypeError(signature changed) orAttributeError(function not yet ported to v2).
Environment
scanpy==1.13.0a1(installed viapip install --pre "scanpy[scanpy2]==1.13.0a1")Repro
Findings
Finding 1:
import ehrapyfails if the preset is set first_scanpy_pl_api.pyimportsDotPlot,MatrixPlot,StackedViolinfromscanpy.plottingat moduleload time:
Under
scanpy-v2-preview, these class-based names are removed from the top-levelscanpy.plottingnamespace, so
import ehrapyraisesImportErrorif the preset is already active. Workaround usedin testing: import
ehrapyunder the default preset, then switch presets afterward.Finding 2: every tested
ep.pl.*wrapper fails, in two distinct waysep.pl.*functionscatterTypeError: scatter() got an unexpected keyword argument 'x'pcaTypeError: _scatter() got an unexpected keyword argument 'annotate_var_explained'umapRecursionError: maximum recursion depth exceededviolinTypeError: violin() got an unexpected keyword argument 'keys'heatmapTypeError: heatmap() got an unexpected keyword argument 'var_names'dotplotTypeError: dotplot() got an unexpected keyword argument 'var_names'matrixplotTypeError: matrixplot() got an unexpected keyword argument 'var_names'stacked_violinTypeError: stacked_violin() got an unexpected keyword argument 'var_names'tracksplotTypeError: tracksplot() got an unexpected keyword argument 'var_names'embeddingAttributeError: module 'scanpy.plotting' has no attribute 'embedding'clustermapAttributeError: module 'scanpy.plotting' has no attribute 'clustermap'dendrogramAttributeError: module 'scanpy.plotting' has no attribute 'dendrogram'rank_features_groupsAttributeError: module 'scanpy.plotting' has no attribute 'rank_genes_groups'rank_features_groups_violinAttributeError(rank_genes_groups_violin)rank_features_groups_dotplotAttributeError(rank_genes_groups_dotplot)rank_features_groups_heatmapAttributeError(rank_genes_groups_heatmap)rank_features_groups_matrixplotAttributeError(rank_genes_groups_matrixplot)rank_features_groups_stacked_violinAttributeError(rank_genes_groups_stacked_violin)rank_features_groups_tracksplotAttributeError(rank_genes_groups_tracksplot)Rewritten signature functions: under the preset, the top-level
sc.pl.<name>now resolves to a newholoviews-based accessor function (e.g.
scatter(adata, kdims, vdims=(), *, color=None)instead of theold
x=/y=/color=/legend_loc=/... matplotlib signature).ep.pl.*binds the old v1 kwargs viafunctools.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.plunder thepreview preset (yet), expected for alpha software, but still breaks
ep.pl.*at call time.Finding 3: the actual v1-compatibility escape hatch —
sc.pl.legacyScanpy keeps a full submodule,
sc.pl.legacy, that preserves the entire old v1 matplotlib APIunchanged, even while
scanpy-v2-previewis active, includingDotPlot,MatrixPlot,StackedViolin,and every function above. Confirmed working directly (bypassing
ep.pl):This appears to be scanpy's intended migration path for existing code.
Suggested fix
Two options, not mutually exclusive:
Option A: pin to
legacynow. Pointehrapy/plot/_scanpy_pl_api.pyatscanpy.plotting.legacyinstead of
scanpy.plotting(both for theDotPlot/MatrixPlot/StackedViolinimport and for everysc.pl.<name>call inside each wrapper). This restores full compatibility withscanpy-v2-previewimmediately, 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
legacyper function call. A single globalversion/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.plbut with achanged 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 thefallback needs to happen per function, on whichever error actually occurs, e.g.:
Each
ep.pl.<name>wrapper would call_call_with_fallback("scatter", edata, v1_kwargs={...}). Until av2_kwargsmapping is added for a given function, it always resolves throughsc.pl.legacy, so thisis safe to land today even with zero v2-specific work done yet (equivalent to Option A in the interim).
As
v2_kwargsmappings are filled in function-by-function, each one starts preferring the new accessorAPI automatically when it's available and correctly shaped, while still degrading gracefully to
legacyfor anything not yet ported or if a future alpha changes the accessor signature again. Probably makes sense to land the
legacy-only fallback (nov2_kwargsanywhere yet) first as an immediate compatibility fix, then addv2_kwargsper function incrementally as the v2 plotting API stabilizes.Notes
scanpy==1.13.0a1); some of the above (particularly thenot yet portedand the
umapRecursionError) may already be fixed in later alphas/betas..venv-scanpy-v2-preview), not the project's default environment.