PBMC cell heatmap

Zoom through individual cells to inspect the same twelve markers within each cell type.

Each row is a cell; each column is a marker gene. Color shows log-transformed counts, and the colored strip identifies cell types. Larger groups occupy more rows.

Use the mouse wheel to zoom and drag to pan. Hover over a tile for the cell, gene, and exact value. The color scale stays fixed while zooming.

For a summary by cell type, see the marker matrix.

Data use and provenance

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

Inspired by Scanpy’s Marsilea examples.

Code

"""PBMC cell heatmap.

Zoom through individual cells to inspect the same twelve markers within each cell type.
"""

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 expression_table, pbmc_markers


adata = pbmc_markers()
data = load_data("pbmc_markers", ("groups", "markers", "group_tree"))
genes = list(adata.var_names)
projection = from_anndata(
    adata, obs=["cell", "cell_type", "cell_order", "cell_end"], genes=genes
)
expression = expression_table(projection, genes)
cell_y = gs.Y("cell_order:Q").axis(None)

tiles = (
    gs.Chart(expression)
    .mark_rect(minHeight=0, minOpacity=1)
    .encode(
        x=gs.X("gene:O")
        .scale(domain=genes, padding=0, zoom=False)
        .axis(title=None, orient="top", labelAngle=-45, labelFontSize=11),
        y=cell_y,
        y2="cell_end:Q",
        color=gs.Color("expression:Q")
        .scale(domain=[0, data["expression_limit"]], scheme="viridis")
        .legend(title="log1p(counts)", orient="bottom", gradientLength=200),
        tooltip=[
            "cell:N",
            "cell_type:N",
            "gene:N",
            gs.Tooltip("expression:Q", format=".3f"),
        ],
    )
    .properties(width=gs.SizeDef(grow=1), height=560)
)
boundaries = (
    gs.Chart(data["groups"])
    .mark_rule(color="white", size=1, tooltip=None)
    .encode(y=gs.Y("start:Q").axis(None))
)
colors = gs.Scale(
    domain=data["groups"]["cell_type"].tolist(), range=data["groups"]["color"].tolist()
)
# Native AnnData inputs expose obs directly for the cell annotation rail.
rail = (
    gs.Chart(adata)
    .mark_rect(minHeight=0, minOpacity=1)
    .encode(
        y=cell_y,
        y2="cell_end:Q",
        color=gs.Color("cell_type:N")
        .scale(colors)
        .legend(title="Cell type", orient="bottom", columns=2),
        tooltip=["cell:N", "cell_type:N"],
    )
    .properties(width=14, height=560)
)
tree = (
    gs.Chart(data["group_tree"])
    .mark_rule(color="#667382", size=1, strokeCap="square", tooltip=None)
    .encode(
        x=gs.X("distance:Q")
        .scale(domain=[0, data["dendrogram"]["distance_limit"]])
        .axis(None),
        x2="distance_end:Q",
        y=gs.Y("cell:Q").axis(None),
        y2="cell_end:Q",
    )
    .properties(width=75, height=560)
)
chart = (
    (
        rail
        | (tiles + boundaries).properties(height=560, width=gs.SizeDef(grow=1))
        | tree
    )
    .properties(
        scales=gs.scales(
            y=gs.Scale(
                name="pbmc-cells",
                type="linear",
                domain=[0, len(adata)],
                reverse=True,
                nice=False,
                zoom=True,
            )
        ),
        title="PBMC marker expression in individual cells",
    )
    .resolve_scale(x="independent", y="shared", color="independent")
    .resolve_legend(default="collected")
    .configure_legend(direction="horizontal", titleOrient="top", columnPadding=20)
)