PBMC UMAP panels

Compare six marker genes, total counts, and cell types on one embedding.

The first six panels show marker expression: darker purple means higher expression. They use the same color scale. The last two panels show total counts (n_counts) and cell types, with separate legends.

Zoom with the mouse wheel and drag to pan; all eight panels move together. Hover over a dot for the cell and its annotation or expression value. Compare with the cell-type UMAP.

Data use and provenance

2,638 cells from Scanpy’s processed 10x Genomics PBMC3k dataset (CC BY 4.0), retaining its saved UMAP coordinates, log-transformed counts, and cell-type labels.

Inspired by Scanpy’s UMAP examples.

Code

"""PBMC UMAP panels.

Compare six marker genes, total counts, and cell types on one embedding.
"""

import genome_spy as gs
from genome_spy.anndata import from_anndata
from genome_spy.datasets import _load_table_bundle as load_data
from genome_spy.datasets._pbmc import pbmc_markers


adata = pbmc_markers(umap_genes=True)
data = load_data("pbmc_markers", ("groups",))
genes = list(adata.var_names)
cells = from_anndata(
    adata,
    obs=["cell", "cell_type", "n_counts"],
    genes=genes,
    obsm_keys=[("X_umap", 0), ("X_umap", 1)],
)
zoom = gs.Expression("zoomLevel")
points = (
    gs.Chart()
    .mark_point(size=gs.expr(gs.expr.min(8 * gs.expr.pow(zoom, 0.6), 40)), opacity=0.9)
    .encode(
        x=gs.X("X_umap-0:Q")
        .scale(
            name="pbmc-umap-x",
            domain=data["umap_domains"]["x"],
            zero=False,
            nice=False,
            zoom=True,
        )
        .axis(None),
        y=gs.Y("X_umap-1:Q")
        .scale(
            name="pbmc-umap-y",
            domain=data["umap_domains"]["y"],
            zero=False,
            nice=False,
            zoom=True,
        )
        .axis(None),
    )
    .properties(width=gs.SizeDef(grow=1), height=210)
)
panels = [
    points.transform_collect(sort={"field": gene})
    .encode(
        color=gs.Color(f"{gene}:Q")
        .scale(
            domain=[0, data["umap_expression_limit"]],
            range=["#e8edf1", "#c9b2df", "#9976c4", "#70469c", "#48226f"],
        )
        .legend(
            gs.Legend(title="log1p(counts)", orient="bottom", gradientLength=180)
            if i == 0
            else None
        ),
        tooltip=["cell:N", "cell_type:N", gs.Tooltip(f"{gene}:Q", format=".3f")],
    )
    .properties(title=gs.title(gene, fontSize=13))
    for i, gene in enumerate(genes)
]
counts = (
    points.transform_collect(sort={"field": "n_counts"})
    .encode(
        color=gs.Color("n_counts:Q")
        .scale(domain=[0, data["n_counts_limit"]], scheme="blues")
        .legend(title="Total counts", orient="bottom", gradientLength=140),
        tooltip=["cell:N", "cell_type:N", gs.Tooltip("n_counts:Q", format=",.0f")],
    )
    .properties(title=gs.title("n_counts", fontSize=13))
)
labels = points.encode(
    color=gs.Color("cell_type:N")
    .scale(
        domain=data["groups"]["cell_type"].tolist(),
        range=data["groups"]["color"].tolist(),
    )
    .legend(title="Cell type", orient="bottom", columns=2),
    tooltip=["cell:N", "cell_type:N"],
).properties(title=gs.title("cell_type", fontSize=13))
chart = (
    gs.concat(*panels, counts, labels, columns=4, spacing=16)
    .properties(data=cells, title="PBMC markers and cell annotations")
    .resolve_scale(x="shared", y="shared", color="independent")
    .resolve_legend(default="collected")
    .configure_legend(direction="horizontal", titleOrient="top", columnPadding=20)
)