Basic plotting tools

hv resources

  • Gallery: https://holoviews.org/reference/

  • https://holoviews.org/user_guide/Customizing_Plots.html

  • https://holoviews.org/user_guide/Style_Mapping.html

  • https://holoviews.org/user_guide/Colormaps.html

  • https://holoviews.org/user_guide/Plotting_with_Bokeh.html

%load_ext autoreload
%autoreload 2
import holoviews as hv

import hv_anndata
from hv_anndata import A, register
from hv_anndata import scanpy as hv_sc

register()

hv.extension("bokeh")
import numpy as np
import scanpy as sc

rng = np.random.default_rng()
adata = sc.datasets.pbmc68k_reduced()
adata.layers["counts"] = adata.raw.X
del adata.raw
sc.tl.umap(adata)
adata
/home/docs/.local/share/hatch/env/virtual/hv-anndata/STk7F69l/docs/lib/python3.13/site-packages/numba/cpython/hashing.py:477: UserWarning: FNV hashing is not implemented in Numba. See PEP 456 https://www.python.org/dev/peps/pep-0456/ for rationale over not using FNV. Numba will continue to work, but hashes for built in types will be computed using siphash24. This will permit e.g. dictionaries to continue to behave as expected, however anything relying on the value of the hash opposed to hash as a derived property is likely to not work as expected.
  warnings.warn(msg)
AnnData object with n_obs × n_vars = 700 × 765
    obs: 'bulk_labels', 'n_genes', 'percent_mito', 'n_counts', 'S_score', 'G2M_score', 'phase', 'louvain'
    var: 'n_counts', 'means', 'dispersions', 'dispersions_norm', 'highly_variable'
    uns: 'bulk_labels_colors', 'louvain', 'louvain_colors', 'neighbors', 'pca', 'rank_genes_groups', 'umap'
    obsm: 'X_pca', 'X_umap'
    varm: 'PCs'
    obsp: 'connectivities', 'distances'
    layers: None (.X), 'counts'

scanpy.pl.scatter()

missing features:

  • use_raw (deprecated!)

  • na_color (not super important)

  • color=dimension in plotly backend: holoviz/holoviews#5222

missing convenience:

  • basis for easy X&Y

(
    hv_sc.scatter(adata, A.X[:, ["PSAP", "C1QA"]], color=A.obs["bulk_labels"]).opts(
        cmap="tab10", show_legend=False
    )
    + hv_sc.scatter(
        adata, A.layers["counts"][:, ["PSAP", "C1QA"]], color=A.obs["bulk_labels"]
    ).opts(cmap="tab10")
)
# add NAs to check how missing values look
adata_scatter = adata.copy()
adata_scatter.obs.loc[
    (
        (adata_scatter.obs["bulk_labels"] == "Dendritic")
        & rng.choice([True, False], size=len(adata_scatter))
    ),
    "bulk_labels",
] = np.nan

hv_sc.umap(adata_scatter) + hv_sc.umap(adata_scatter, color=A.obs["bulk_labels"]).opts(
    cmap="tab10"
)

scanpy.pl.heatmap()

missing:

  • groupby / TickBar

  • standard_scale (see implemantation in hv_anndata.Dotmap)

  • dendrogram doesn’t work on heatmap (again ndim problem: dendrogram should tread (n, 1) as 1D)

markers = ["C1QA", "PSAP", "CD79A", "CD79B", "CST3", "LYZ"]
(
    hv_sc.heatmap(adata[:, markers], A.X, [A.obs["n_counts"]], add_dendrogram="obs")
    + hv_sc.heatmap(adata[:, markers], A.layers["counts"], [A.obs["n_counts"]])
).cols(1).opts(hv.opts.HeatMap(xticks=0, width=800, height=300)).opts(shared_axes=False)

TODO: support sparse data

hv_sc.heatmap(adata[:40], A.obsp["distances"]).opts(tools=["hover"])

scanpy.pl.dotplot()

missing:

  • var_group_* (highlight groups of var_names by drawing brackets)

markers = ["C1QA", "PSAP", "CD79A", "CD79B", "CST3", "LYZ"]
dm = hv_anndata.Dotmap(adata=adata, marker_genes=markers, groupby="bulk_labels")
dm
WARNING:param.main: JSONEditor was not imported on instantiation and may not render in a notebook. Restart the notebook kernel and ensure you load it as part of the extension using:

pn.extension('jsoneditor')
hv.operation.dendrogram(
    dm.plot(), adjoint_dims=["cluster"], main_dim="mean_expression", invert=True
)

scanpy.pl.tracksplot()

missing:

hv_sc.tracksplot(
    adata,
    A.X[:, ["C1QA", "PSAP", "CD79A", "CD79B", "CST3", "LYZ"]],
    color=A.obs["bulk_labels"],
).opts(hv.opts.Curve(aspect=20))

scanpy.pl.violin()

missing:

  • density_norm

hv_sc.violin(adata, A.obs[["percent_mito", "n_counts", "n_genes"]]).opts(
    hv.opts.Violin(ylim=(0, None))
)
hv_sc.violin(adata, A.obs["S_score"], color=A.obs["bulk_labels"]).opts(
    width=500, xrotation=30
)

scanpy.pl.stacked_violin()

missing:

  • see tracksplot above

  • can’t do .hist() or .operations.dendrogram on GridSpace

  • slow!

markers = ["C1QA", "PSAP", "CD79A", "CD79B", "CST3", "LYZ"]
hv_sc.stacked_violin(adata[:, markers], A.var.index, A.obs["bulk_labels"]).opts(
    hv.opts.Violin(frame_height=50, frame_width=50)
)

scanpy.pl.matrixplot()

missing:

  • aspect="equal" breaks (bokeh tries to divide None/None)

  • hist doesn’t align properly

markers = ["C1QA", "PSAP", "CD79A", "CD79B", "CST3", "LYZ"]
hv_sc.matrixplot(
    adata[:, markers], A.obs["bulk_labels"], data=A.layers["counts"], add_totals=True
).opts(hv.opts.Bars(width=150))

scanpy.pl.clustermap()

missing:

  • TickBar (see above)

hv_anndata.ClusterMap(adata=adata)

scanpy.pl.dendrogram(): holoviews.operation.dendrogram