Work in progress · Unlisted example
This example is shared for feedback. Its analysis, layout, and APIs may change before it joins the documentation.
Tabula Sapiens: the CELLxGENE Atlas UMAP and Cell-Type Signatures¶
CELLxGENE Discover's homepage sends you to Tabula Sapiens - All Cells in its Explorer: 1,136,218 cells from 28 organs of 24 donors, on the authors' UMAP. This notebook shows the same view in Celldega, the UMAP colored by cell type with the Explorer's colors, and adds what the Explorer lacks:
- The atlas UMAP, whose cell-type and gene bars follow the viewport as you zoom in.
- Cell-type signatures: the 180 cell types' expression in a Clustergram, linked to the UMAP and to Enrich, with a slider for the number of markers per cell type.
- Plasma-cell sub-clusters: the 27,349 plasma cells re-clustered on their own, in the same linked layout.
The stores were prepared once, by the pre-process notebook, which streams CELLxGENE's 45 GB h5ad. This notebook reads them from the Celldega Supporting Data on Hugging Face: the Landscapes fetch cells and genes in the browser, one gene at a time, so the 15 GB atlas store is never downloaded whole.
This notebook needs Celldega 0.28.0a2 or later (pip install --pre celldega
until 0.28.0 is released), zarr>=3, and anndata>=0.12,<0.13.
Data and attribution. Tabula Sapiens - All Cells
(Tabula Sapiens 2.0; dataset version 1c88f927-bcbb-4bb1-9881-281842945a2d),
curated and distributed by CZ CELLxGENE Discover in the
Tabula Sapiens collection
under CC BY 4.0.
- The Tabula Sapiens Consortium. Tabula Sapiens reveals transcription factor expression, senescence effects, and sex-specific features in cell types from 28 human organs and tissues. bioRxiv (2024). doi:10.1101/2024.12.03.626516 (the citation the Tabula Sapiens data release policy asks for with the 2.0 data; apparently published as Cell 189, 7212–7227 (2026), doi:10.1016/j.cell.2026.08.010).
- The Tabula Sapiens Consortium. The Tabula Sapiens: A multiple-organ, single-cell transcriptomic atlas of humans. Science 376, eabl4896 (2022). doi:10.1126/science.abl4896
The stores are derived from that data: the expression is normalized to 10,000 counts per cell and log1p-transformed, and the cell-type signatures, marker ranks, and plasma-cell sub-clusters are our analyses, not the Tabula Sapiens Consortium's. The Hugging Face folder's README lists every change and includes the license and the full text of the Tabula Sapiens data release policy, which applies to any redistribution.
import anndata as ad
import scanpy as sc
import celldega as dega
/Users/feni/Documents/celldega/dega/lib/python3.12/site-packages/scanpy/_utils/__init__.py:27: FutureWarning: `__version__` is deprecated, use `importlib.metadata.version('anndata')` instead.
from anndata import __version__ as anndata_version
/Users/feni/Documents/celldega/dega/lib/python3.12/site-packages/scanpy/__init__.py:36: FutureWarning: `__version__` is deprecated, use `importlib.metadata.version('anndata')` instead.
if Version(anndata.__version__) >= Version("0.11.0rc2"):
/Users/feni/Documents/celldega/dega/lib/python3.12/site-packages/scanpy/readwrite.py:15: FutureWarning: `__version__` is deprecated, use `importlib.metadata.version('anndata')` instead.
if Version(anndata.__version__) >= Version("0.11.0rc2"):
# Hugging Face serves each file at its `resolve` URL, like GitHub's raw URLs.
HF_URL = "https://huggingface.co/datasets/broadinstitute/Celldega_Supporting_Data/resolve/main/Tabula_Sapiens"
zarr_url = f"{HF_URL}/tabula_sapiens.zarr"
matrix_url = f"{HF_URL}/tabula_sapiens_cell_type_markers.zarr"
signature_url = f"{HF_URL}/tabula_sapiens_cell_type_signatures.zarr"
plasma_zarr_url = f"{HF_URL}/tabula_sapiens_plasma_cells.zarr"
plasma_matrix_url = f"{HF_URL}/tabula_sapiens_plasma_cell_markers.zarr"
# The cell annotations Landscape reads; the store has more.
CELL_ATTR = ["cell_type", "tissue_in_publication", "compartment"]
1. The atlas, as CELLxGENE shows it¶
The authors' X_umap, colored by cell_type. CELLxGENE Explorer has no author
palette for cell types, so it spreads d3's rainbow over them in their stored
order; the store holds the same colors. The UMAP's second coordinate increases
upward, as in CELLxGENE and Scanpy. Landscape loads the cells, cell types, and UMAP
when it opens, then fetches a gene's expression when you pick one in the gene bar.
Expression is normalized to 10,000 counts per cell and log1p-transformed, as the
Explorer shows it: Tabula Sapiens mixes 10x UMI counts with Smart-seq2 read
counts, which are not comparable unnormalized.
Zoom in and the bars follow the viewport, as they do in a spatial dataset:
- The cell-type bars count the cells in view, and points grow as you zoom.
- The gene bars estimate expression in view by weighting each cell type's mean
expression (
gene_signature_url) by its cells in view; TRX turns purple to mark the estimate. It is blue when every cell is in view and the bars show the whole dataset. - In the current 0.28.0 development build, TRX is an indicator in UMAP/XY; clicking it leaves the estimate unchanged. The saved preview below uses an earlier prerelease and may still offer the former exact-expression action.
To color by organ or compartment instead, pass
cluster_attr="tissue_in_publication" or "compartment": the store holds the
authors' palettes for both, as the Explorer uses them.
landscape = dega.viz.Landscape(
zarr_url=zarr_url,
zarr_layer="log1p_csc",
gene_signature_url=signature_url,
cell_attr=CELL_ATTR,
cluster_attr="cell_type",
dataset_name="Tabula Sapiens · All Cells",
height=750,
ini_zoom=-4.3,
ini_x=5_000,
ini_y=5_000,
)
landscape
2. Cell-type signatures, linked to the UMAP and Enrich¶
Each Clustergram column is a cell type and each row a gene: dot color is the
gene's Z-scored mean log-normalized expression, dot size the fraction of the cell
type's cells expressing it. The column bars show each cell type's color, its
majority compartment, and its number of cells (n_cells): double-click an
attribute's label to order the columns by it, e.g. by cell count.
The RANK slider sets how many top markers per cell type are shown, from 1 to
50 (at most 9,000 genes for 180 cell types) or all, each cell type's top 100
together. Markers are ranked by Welch's t-test, the t-test method of Scanpy's
rank_genes_groups: each cell type against all other cells, computed exactly on
all 1.1M cells.
A second Landscape of the same store sits next to the Clustergram, so each view has its own UMAP.
mat = dega.clust.Matrix.read_zarr(matrix_url)
cgm = dega.viz.Clustergram(matrix=mat, width=550, height=550, viz_mode="dotplot", rank_dim=3)
landscape_linked = dega.viz.Landscape(
zarr_url=zarr_url,
zarr_layer="log1p_csc",
gene_signature_url=signature_url,
cell_attr=CELL_ATTR,
cluster_attr="cell_type",
dataset_name="Tabula Sapiens · All Cells",
height=750,
ini_zoom=-4.6,
ini_x=5_000,
ini_y=5_000,
)
linked = dega.viz.spatial_clustergram(
landscape_linked,
cgm,
width="600px",
height="750px",
enrich=True,
enrich_kwargs={"inst_lib": "CellMarker_2024"},
)
linked
Try these interactions:
- Click a cell-type column to highlight its cells in the UMAP.
- Click a gene row, e.g.
PTPRC,EPCAM, orPECAM1, to color the cells by its expression. - Select a gene dendrogram group or crop rows to send the genes to Enrich (CellMarker 2024), and click a gene in Enrich to color the cells by it.
- Move the RANK slider for more or fewer markers per cell type.
- Double-click the n_cells label to order the cell types by size.
3. Plasma-cell sub-clusters¶
The 27,349 plasma cells, re-clustered on their own: a new UMAP and Leiden sub-clusters from the authors' batch-corrected scVI embedding, so cells group by biology rather than by donor or assay. Each Clustergram column is a sub-cluster, with its majority organ and cell count; markers are Scanpy's Wilcoxon test of each sub-cluster against the other plasma cells.
Things to look for: antibody isotype (IGHA1/IGHA2 with JCHAIN in the gut
against IGHG1–IGHG4 in lymph node and spleen), proliferating plasmablasts
(MKI67), and sub-clusters marked by stress (FOS, JUN) or mitochondrial
genes, which are likely technical rather than new cell states.
Here the viewport signatures are computed on the fly: load the plasma cells'
expression (about 400 MB), average it per sub-cluster with Scanpy, and pass the
result as adata_sig, sent with the widget like adata.
plasma_cells = ad.read_zarr(plasma_zarr_url)
plasma_sig = sc.get.aggregate(plasma_cells, by="leiden", func="mean", layer="log1p_csc")
plasma_sig.layers["log1p"] = plasma_sig.layers.pop("mean")
plasma_sig.uns["viewport_signature"] = {
"groupby": "leiden",
"layer": "log1p",
"units": "mean log1p counts per 10,000",
}
plasma_sig
AnnData object with n_obs × n_vars = 15 × 47549
obs: 'leiden'
var: 'feature_id', 'mean', 'max'
uns: 'viewport_signature'
layers: 'log1p'
plasma_landscape = dega.viz.Landscape(
zarr_url=plasma_zarr_url,
zarr_layer="log1p_csc",
adata_sig=plasma_sig,
cell_attr=["leiden", "tissue_in_publication"],
cluster_attr="leiden",
dataset_name="Tabula Sapiens · plasma cells",
height=750,
ini_zoom=-4.6,
ini_x=5_000,
ini_y=5_000,
)
plasma_cgm = dega.viz.Clustergram(
matrix=dega.clust.Matrix.read_zarr(plasma_matrix_url),
width=470,
height=550,
viz_mode="dotplot",
rank_dim=5,
)
plasma_linked = dega.viz.spatial_clustergram(
plasma_landscape,
plasma_cgm,
width="600px",
height="750px",
enrich=True,
enrich_kwargs={"inst_lib": "CellMarker_2024"},
)
plasma_linked
Selected cells¶
With a running kernel, get_selected_cells() returns the cell IDs of the selected
cell type, Clustergram selection, or drawn gate. The atlas store's expression is
15 GB, so read only its obs table (about 1.1M rows) to look the cells up. The
plasma-cell view uses the same cell IDs, so plasma_landscape.get_selected_cells()
works too:
import zarr
obs = ad.io.read_elem(zarr.open_group(zarr_url, mode="r")["obs"])
selected = landscape_linked.get_selected_cells()
obs.loc[selected, CELL_ATTR].value_counts().head(10)