Skip to content

Widget object

The live, kernel-linked widget returned by scatterplot(..., interactive=True) (and by every panel inside compose). These methods need a running kernel; a static/exported plot keeps the visual interactions (pan/zoom/lasso/legend/export) but cannot round-trip to Python.

Most methods accept a selection= override; when omitted they act on the current lasso selection (w.selection). Indices are always given and returned in original data order.

Properties

w.selection

Indices of the lasso-selected points (read or assign), always in data order — translated through the draw-order permutation when the plot was z-ordered (sort_order / random_state).

Live (interactive=True) only — on a static plot this stays empty because there is no kernel link.

w.filtered

Original indices of the cells currently passing the in-plot filters (range sliders + legend categories). When no filter is active this is all shown cells (everything passes). Live (interactive=True) only — like selection, but for the filter instead of the lasso.

w.colors

The categorical colour map as {category: '#rrggbb'} (the rendered palette, including any in-plot recolours via the legend colorpicker). None for a continuous / single-colour plot. Save it scanpy-style with e.g. adata.uns['louvain_colors'] = list(w.colors.values()).

Methods

w.subset(selection=None)

The source object subset to the selected cells (adata[w.selection]).

Parameter Type Description Default
selection list[int] | list[str] | bool mask | None Cells to subset. None uses the current lasso selection. None

Returns the same type as the source — adata[sel] for AnnData/MuData, df.iloc[sel] for a DataFrame.

w.annotate(key, label, selection=None)

Write label onto the lasso-selected cells in obs[key] / column key of the source object. Useful for curating cell types interactively.

Parameter Type Description Default
key str obs / DataFrame column to write into (created if missing). required
label str Value to assign to the selected cells. required
selection list[int] | list[str] | bool mask | None Cells to label. None uses the current lasso selection. None

Returns the annotated source object (the column becomes a pandas.Categorical).

w.composition(by, selection=None, normalize=True)

Count + fraction of the selected cells in each category of by — "what is this region made of?".

Parameter Type Description Default
by str Categorical obs / DataFrame column to break the selection down by. required
selection list[int] | list[str] | bool mask | None Cells to summarise. None uses the current lasso selection. None
normalize bool Also add a fraction column (counts ÷ total). True

Returns a pandas.DataFrame indexed by category with a count (and, if normalize, fraction) column.

w.dotplot(genes, groupby=None, *, selection=None, layer=None, use_raw=None, standard_scale=None, expression_cutoff=0.0, swap_axes=False, cmap='Reds', engine='auto', return_frames=False, **kwargs)

A scanpy-style dot plot: per groupby category (rows) and gene (columns), the size of each dot is the fraction of cells expressing the gene and the colour is the mean expression.

Parameter Type Description Default
genes list[str] | dict[str, list[str]] Gene names, or a {label: [genes]} dict to draw labelled gene groups. required
groupby str | None obs column to group rows by. None uses the plot's categorical colour variable. None
selection list[int] | list[str] | bool mask | None Restrict to these cells (or the current lasso when omitted). None
layer / use_raw str / bool Expression matrix to read. use_raw defaults to adata.raw when present (matches scanpy), so means aren't taken over a scaled .X. None
standard_scale 'var' | 'group' | None Min-max scale the colour per gene or per group. None
expression_cutoff float A cell counts as expressing when its value is above this. 0.0
swap_axes bool Put genes on rows and groups on columns. False
cmap str Matplotlib colormap for the mean-expression colour. 'Reds'
engine 'auto' | 'scanpy' | 'gpu' | 'cupy' | 'numpy' Aggregation backend and renderer (see below). 'auto'
return_frames bool Also return the (mean_df, frac_df) DataFrames. False

engine

  • 'auto' (default) — hand off to scanpy's sc.pl.dotplot when scanpy is installed (the authentic figure), else the built-in renderer.
  • 'scanpy' — force sc.pl.dotplot (raises if scanpy is absent).
  • 'gpu' / 'cupy' — aggregate the per-group mean and fraction on the GPU via cupy, then draw with the built-in renderer (the faster path on many cells). 'numpy' runs the same aggregation on the CPU. The dot values match scanpy.

Returns scanpy's axes object (scanpy path) or a small result carrying .figure / .mean_expression / .fraction (built-in path). With return_frames=True it returns (result, mean_df, frac_df).

w.dotplot(["MS4A1", "NKG7", "CST3"], groupby="louvain")

# labelled gene groups + GPU aggregation
markers = {"B": ["MS4A1", "CD79A"], "NK": ["NKG7", "GNLY"], "Mono": ["CST3", "LYZ"]}
w.dotplot(markers, groupby="louvain", engine="gpu")

# just the lassoed cells
w.selection.dotplot(["MS4A1", "NKG7"], groupby="louvain")

w.diff_expression(group_a=None, group_b=None, n=10, layer=None, method='wilcoxon', key_added=None, use_raw=None, engine='auto')

Top differential genes between two cell groups (markers of a lasso vs the rest, or one lasso vs another).

Parameter Type Description Default
group_a list[int] | list[str] | bool mask | None The "test" group. None uses the current lasso selection. None
group_b list[int] | list[str] | bool mask | None The reference group. None means all other cells ("rest"). None
n int Number of top genes to keep per group. 10
layer str | None adata.layers key to test on. None follows scanpy's default (see note). None
method str scanpy test: 'wilcoxon', 't-test', 't-test_overestim_var', 'logreg'. 'wilcoxon'
key_added str | False | None adata.uns key to save under. None'rank_genes_groups'; False → don't save. None
use_raw bool | None Force testing on adata.raw. None uses scanpy's default: adata.raw when it exists, else .X. None
engine str Compute backend / scanpy requirement — see the engine table. 'auto'

When scanpy is installed (and the source is an AnnData) this runs sc.tl.rank_genes_groups on a copy; otherwise it falls back to a Welch t-test. AnnData/MuData only.

Returns the scanpy-native result — the params + rec.array dict (names / scores / logfoldchanges / pvals / pvals_adj), identical to what scanpy stores in adata.uns. The return value and the saved entry are the same object, and a tidy table is one sc.get.rank_genes_groups_df(adata, group="A") away.

Which matrix (scanpy-identical)

With no layer / use_raw pinned, the matrix follows scanpy's own default: adata.raw (the full log-normalised gene set) when it exists, else .X. This is over the full raw, not raw restricted to the HVG var_names, so non-HVG genes (e.g. ribosomal) can still rank — the result is byte-for-byte what a direct sc.tl.rank_genes_groups call returns. The only special case: if .X is scaled/z-scored and there is no raw, logfoldchanges come out NaN (as they would in scanpy) and you get a warning suggesting adata.raw = adata or a log-norm layer=.

w.diff_expression_by(by, group_a=None, group_b=None, selection=None, n=10, layer=None, method='wilcoxon', key_added=None, use_raw=None, min_cells=2)

Differential expression between the levels of an obs column, restricted to the lasso selection — e.g. lasso a region, then compare condition or time within it.

Parameter Type Description Default
by str The obs column whose levels you compare (e.g. "condition", "time"). required
group_a str | None A single level of by. With group_b → A-vs-B; alone → A-vs-rest. None
group_b str | None A second level of by to use as the reference. None
selection list[int] | list[str] | bool mask | None Cells to restrict to. None uses the lasso; empty selection → all cells. None
n int Number of top genes to keep per group. 10
layer str | None adata.layers key to test on. None auto-picks a finite-logFC matrix. None
method str scanpy test: 'wilcoxon', 't-test', 't-test_overestim_var', 'logreg'. 'wilcoxon'
key_added str | False | None adata.uns key to save under. None'rank_genes_groups'; False → don't save. None
use_raw bool | None Force testing on adata.raw. None lets the matrix-picker decide. None
min_cells int In the all-levels mode, skip (with a warning) any level with fewer cells than this in the selection. 2
engine str Compute backend / scanpy requirement — see the engine table. 'auto'

The three modes:

  • group_a and group_b → a single A-vs-B comparison (e.g. group_a="D30", group_b="Y1").
  • group_a only → that level vs the pooled rest of the selection.
  • neither → every present level vs the rest, run as a single scanpy rank_genes_groups call (one-vs-rest, the scanpy idiom).

Returns the scanpy-native result in every mode — same shape as diff_expression. The real by name and real level names appear in params / the rec.array fields (no synthetic _rs_by / _rs_lognorm), so sc.pl.rank_genes_groups(adata) and sc.get.rank_genes_groups_df(adata, group=...) work straight after. The same object is auto-saved to adata.uns (key_added controls the key). Also exposed on a compose grid (grid.diff_expression_by(...), proxied to a shared panel).

import scanpy as sc
w.selection = some_cells                     # or lasso in the UI

# every condition in the lasso vs the rest -> scanpy-native result (== adata.uns):
res = w.diff_expression_by("condition")       # params + rec.arrays, one col per level
res["names"].dtype.names                      # ('D30', 'Y1', 'Y2')
sc.get.rank_genes_groups_df(adata, group="D30")   # tidy table for any level

# one specific pair, saved under a custom uns key:
res = w.diff_expression_by("condition", group_a="D30", group_b="Y1",
                           key_added="D30_vs_Y1")
# one level vs the rest of the lasso:
res = w.diff_expression_by("condition", group_a="Y1")
# also available straight off the selection:
res = w.selection.diff_expression_by("time")

Which engine ran (scanpy / GPU / fallback)

Both DE methods take engine= to pick the backend and to control whether scanpy is required — so a Welch-t-test fallback is never silent:

engine Backend If unavailable
'auto' (default) scanpy (sc.tl.rank_genes_groups) if importable warns, then CPU Welch t-test
'scanpy' scanpy — always raises ImportError (never falls back)
'gpu' (alias 'cupy') GPU Welch t-test via cupy (per-cell reductions on the GPU) raises ImportError
'rapids' GPU logistic-regression DE via rapids-singlecell raises ImportError
'ttest' the built-in CPU Welch t-test

Two ways to be sure scanpy ran:

  1. Require itengine="scanpy" raises instead of falling back silently.
  2. Check the result — scanpy records its correction in params:
res = w.diff_expression(engine="scanpy")
res["params"]["method"]        # -> 'wilcoxon'  (your real test)
res["params"]["corr_method"]   # -> 'benjamini-hochberg'  (scanpy's default)
# both Welch t-test paths (CPU + GPU) instead stamp method='t-test', corr_method='none'

GPU acceleration

sc.tl.rank_genes_groups is CPU-only, so GPU DE uses a different test:

  • engine="gpu" (needs only cupy, pip install cupy-cuda12x) runs a Welch t-test with the heavy O(cells × genes) mean/variance reductions on the GPU. The formula matches scipy's ttest_ind(..., equal_var=False), so GPU and CPU results agree to floating point. On an RTX 2060, DE over 120k cells × 2000 genes ran in ~1.9 s vs ~36 s on the CPU (≈19×). It pays off when a group has many cells; for a few-thousand-cell lasso the CPU path is already fast.
  • engine="rapids" uses rapids-singlecell (full RAPIDS / cuml) for GPU logreg DE — method= is ignored. Heavier install, but returns the same scanpy-native adata.uns dict.

w.highlight(indices, color=None)

Persistently mark points with a crisp ring + size bump (the engine's selection look) — but this is not the selection, so it survives a double-click and a new lasso.

Parameter Type Description Default
indices list[int] | [] | None Original data indices to mark. [] / None clears the highlight. required
color str | None Ring colour (hex / CSS colour). None keeps the default. None

Live (interactive=True) only — needs the kernel link.

Note

With progressive=True the highlight marks the currently-shown cells; it doesn't yet follow new cells streamed in on zoom.

w.morph_to(basis, duration=1200)

Animate the points from their current layout to another embedding — e.g. w.morph_to('spatial') morphs a UMAP into the spatial layout (and back with w.morph_to('umap')). Positions tween; colours and sizes stay.

Parameter Type Description Default
basis str Target obsm embedding to morph into ('umap', 'spatial', …). required
duration int Animation length in milliseconds. 1200

Live (interactive=True) only; not for progressive=True (only a subset is resident). Returns self so calls can chain.

w.to_html(path, title='reglscatterpy plot')

Save this plot as a standalone, offline HTML file (like R's htmlwidgets::saveWidget) — inlines the bundle and data, stays interactive with no kernel.

Parameter Type Description Default
path str | Path Where to write the .html file. required
title str <title> of the saved page. 'reglscatterpy plot'

On a live (interactive=True) widget it captures the current view / selection / filter, so the file opens exactly as you left it.

w.update(spec)

Push a partial spec update to a live widget (e.g. recolour, change point size) without rebuilding it.

Parameter Type Description Default
spec dict Plot properties to merge into the current spec. required

Returns self. Live (interactive=True) only.