Composition#

Second of five demonstration notebooks – this one covers composition plots: stacked_barplots, stacked_barplots_multi_meta, composition_heatmap, and abundance_bubble_grid.

Data: the same PDAC atlas subset used throughout this set of notebooks (Lucarelli et al., https://doi.org/10.64898/2026.03.19.712924). See Embeddings for UMAP-based plots, Overview for dataset overview plots, or API Reference for the complete parameter reference.

import anndata as ad
import scanpy as sc

import scplotkit as spk

sc.settings.verbosity = 1

ATLAS_PATH = "/data/Daniele/atlases/final_versions_topublish/Human_subsampled.zarr"

Load the atlas#

adata = ad.read_zarr(ATLAS_PATH)
adata
AnnData object with n_obs × n_vars = 100000 × 39041
    obs: 'Sample_ID', 'Condition', 'Treatment', 'TreatmentType', 'TreatmentStatus', 'Tissue', 'Sex', 'Dataset', 'Technology', 'Level_1', 'Level_2', 'Level_3', 'Level_4', 'Age', 'Diabetes', 'Is_Core', 'EMT category', 'Dataset_ID', 'Cluster_Names'
    var: 'n_cells', 'ensembl_id', 'start', 'end', 'chromosome', 'gene_name_adata_sc', 'highly_variable_adata_sc', 'means_adata_sc', 'dispersions_adata_sc', 'dispersions_norm_adata_sc', 'highly_variable_nbatches_adata_sc', 'highly_variable_intersection_adata_sc', 'n_cells_by_counts_adata_sc', 'mean_counts_adata_sc', 'log1p_mean_counts_adata_sc', 'pct_dropout_by_counts_adata_sc', 'total_counts_adata_sc', 'log1p_total_counts_adata_sc', 'mito_adata_sc', 'n_cells_by_counts_adata_sn', 'mean_counts_adata_sn', 'log1p_mean_counts_adata_sn', 'pct_dropout_by_counts_adata_sn', 'total_counts_adata_sn', 'log1p_total_counts_adata_sn', 'Manual_Genes', 'mt', 'ribo', 'hb'
    uns: 'UMAP_0.85', 'Condition_colors', 'Level_3_colors', 'Is_Core_colors', 'neighbors', 'UMAP_0.75', '_scvi_uuid', 'Level_4_colors', '_scvi_manager_uuid', 'Level_2_colors', 'Level_4_All_colors', 'Level_1_colors', 'umap', 'Level_4_Final_colors'
    obsm: 'pct_counts_mito', 'X_pca', 'is_outlier_total_counts', 'bin_edges', 'UMAP_0.85', 'log_counts', 'scanvi_extended_atlas_emb', 'scanvi_L4_emb', 'log1p_total_counts', '_scvi_batch', 'scANVI_cross_species', 'level0_leiden_subcluster', 'leiden_0.2_annotation', 'log1p_n_genes_by_counts', 'batch', 'outlier', 'n_genes_by_counts', 'EMT_score_DL', 'n_genes', 'total_counts', 'leiden', 'UMAP_0.75', 'total_counts_mito', 'X_umap', 'MALAT1_lognorm', 'Global_Leiden', 'infercnv_score_malignant', 'leiden_0.5', 'leiden_0.2', 'UMAP_0.95', 'leiden_subcluster', 'EMT score', 'cnv_score_abs', 'log1p_total_counts_mito', 'scANVI_emb_final', 'n_counts', 'mt_frac', 'infercnv_score_malignant_refined', 'empty_droplet', '_scvi_labels'
    layers: 'log_norm', 'counts', 'raw'
    obsp: 'connectivities', 'distances'

Set up the plotter#

config = spk.PlotConfig({
    "palettes": {
        "Level_2": {
            "Lymphoid": "#1f78b4",
            "Malignant Cell": "#e31a1c",
            "Stromal Cell": "#33a02c",
            "Myeloid": "#ff7f00",
            "Endothelial Cell": "#6a3d9a",
            "Exocrine Cell": "#b15928",
            "Endocrine Cell": "#a6cee3",
        },
        "TreatmentStatus": {"Untreated": "#4daf4a", "Treated": "#984ea3"},
    }
})
plotter = spk.ScPlotter(config=config, output_dir="figures")

stacked_barplots#

Stacked bars of cell-type proportion per sample; every call already returns "basic", "clustered", and (if order_by_column is given) "clustered_grouped" variants. Two demonstrations: the full atlas ordered and clustered by treatment status, and a subset to one lineage with a custom palette.

figs = plotter.stacked_barplots(
    adata,
    level_column="Level_2",
    sample_column="Sample_ID",
    order_by_column="TreatmentStatus",
    figsize=(10, 6),
)
figs["clustered_grouped"]
Figure saved to: figures/compositional_plot/composition_basic.png
Figure saved to: figures/compositional_plot/composition_clustered.png
Figure saved to: figures/compositional_plot/composition_clustered_grouped.png
../_images/23bda0deb5ed64895443aa5bbd92c491f9367bc537f89524246f4a10b701ba99.png ../_images/dc3fcb9e9280036cb7bfdf109efa91457b81e6a8eb705c7038320e6d8726146c.png ../_images/770578ca1f651a7798eacc18ee293c750d53f3948462bf354234db91eb4d3033.png ../_images/23bda0deb5ed64895443aa5bbd92c491f9367bc537f89524246f4a10b701ba99.png

Subset to the myeloid compartment only (subset_level/subset_value), at Level_3 resolution, with a custom palette for its four subtypes.

figs = plotter.stacked_barplots(
    adata,
    level_column="Level_3",
    sample_column="Sample_ID",
    subset_level="Level_2",
    subset_value="Myeloid",
    palette={
        "Macrophage": "#d95f02",
        "Dendritic Cell": "#7570b3",
        "Neutrophil": "#e7298a",
        "Monocyte": "#66a61e",
    },
    figsize=(10, 6),
    save_name_prefix="myeloid_composition",
)
figs["clustered"]
Figure saved to: figures/compositional_plot/myeloid_composition_basic.png
Figure saved to: figures/compositional_plot/myeloid_composition_clustered.png
../_images/b9fa7a7fd95e31686fe189e2cf5e3a8e9266db170922fab5726d2936830d055a.png ../_images/e98531c48c1c99e4f70776751cfd8efc17f0f88ca5f67f830d7df8c56009e5f8.png ../_images/b9fa7a7fd95e31686fe189e2cf5e3a8e9266db170922fab5726d2936830d055a.png

stacked_barplots_multi_meta#

Like stacked_barplots, with extra color-coded metadata strips drawn below the bars. Two demonstrations: two strips (treatment status + sex) over the full atlas, and a subset to the lymphoid compartment ordered by treatment status.

plotter.stacked_barplots_multi_meta(
    adata,
    level_column="Level_2",
    sample_column="Sample_ID",
    metadata_columns=["TreatmentStatus", "Sex"],
    figsize=(10, 6),
)
Figure saved to: figures/compositional_plot_multi_meta/composition_multi_meta_basic.png
Figure saved to: figures/compositional_plot_multi_meta/composition_multi_meta_clustered.png
{'basic': <Figure size 800x615.328 with 1 Axes>,
 'clustered': <Figure size 800x615.328 with 1 Axes>}
../_images/9ea02ba77b2b4728eb51d5e7f88f8f9b727ea81adb62b9692532e74915419716.png ../_images/d0f3cb096b09feb468ff696503d1cacda25c510e693f745546be8b326cec280f.png

Subset to the lymphoid compartment at Level_3 resolution, ordered by TreatmentStatus (also drawn as its own metadata strip) with Sex as a second strip.

figs = plotter.stacked_barplots_multi_meta(
    adata,
    level_column="Level_3",
    sample_column="Sample_ID",
    subset_level="Level_2",
    subset_value="Lymphoid",
    order_by_column="TreatmentStatus",
    metadata_columns=["Sex"],
    figsize=(10, 6),
    save_name_prefix="lymphoid_composition_multi_meta",
)
figs["clustered_grouped"]
Figure saved to: figures/compositional_plot_multi_meta/lymphoid_composition_multi_meta_basic.png
Figure saved to: figures/compositional_plot_multi_meta/lymphoid_composition_multi_meta_clustered.png
Figure saved to: figures/compositional_plot_multi_meta/lymphoid_composition_multi_meta_clustered_grouped.png
../_images/de8800de7997af17d81ae629fed02a1e413c398b722998e16307ba3e6b0f9b07.png ../_images/a21557d671b430d82369a27c9f242c42b1d553cf4a47f9dd8f8ac927fedc56a0.png ../_images/b83179b834ba79cf2f57d1ae657edb049cab7b24f1bda56e9824b0cda55c6df7.png ../_images/de8800de7997af17d81ae629fed02a1e413c398b722998e16307ba3e6b0f9b07.png

composition_heatmap#

Clustered heatmap of cell-type proportion per sample. Two demonstrations: the default colormap with a TreatmentStatus color strip, and a different colormap on a lineage subset.

plotter.composition_heatmap(
    adata,
    level_column="Level_3",
    sample_column="Sample_ID",
    order_by_column="TreatmentStatus",
    figsize=(10, 8),
    save_name="composition_heatmap",
)
Figure saved to: figures/compositional_plot/composition_heatmap.png
../_images/e0bad779fe5a70b4923e1841e2340d0b24c44894c4aeee57e6e303e0b41782f0.png ../_images/e0bad779fe5a70b4923e1841e2340d0b24c44894c4aeee57e6e303e0b41782f0.png

cmap="rocket" (a seaborn colormap), subset to the stromal compartment at Level_4.

plotter.composition_heatmap(
    adata,
    level_column="Level_4",
    sample_column="Sample_ID",
    subset_level="Level_2",
    subset_value="Stromal Cell",
    cmap="rocket",
    figsize=(10, 4),
    save_name="composition_heatmap_stromal",
)
Subset to 12349 cells with Level_2 in ['Stromal Cell']
Figure saved to: figures/compositional_plot/composition_heatmap_stromal.png
../_images/4648f7f5fee669d6a1d2852fce338d0de7eaca0f7b6576988352700d1d349a29.png ../_images/4648f7f5fee669d6a1d2852fce338d0de7eaca0f7b6576988352700d1d349a29.png

abundance_bubble_grid#

Dot grid of cell counts per (cell type, sample) pair – dot area encodes count, and gaps are drawn as faint open rings rather than left blank. Two demonstrations: the default styling over the whole atlas, and a subset with a custom dot_color and a larger max_size.

plotter.abundance_bubble_grid(
    adata,
    level_column="Level_2",
    sample_column="Sample_ID",
    save_name="level2_by_sample",
)
Figure saved to: figures/abundance_bubble_grid/level2_by_sample.png
../_images/69c3239e81090e0f6fe4d6666e0dbacf0243024200a370dc1974817a7789fdfa.png ../_images/69c3239e81090e0f6fe4d6666e0dbacf0243024200a370dc1974817a7789fdfa.png

Subset to the stromal compartment at Level_4, with a custom dot_color.

plotter.abundance_bubble_grid(
    adata,
    level_column="Level_4",
    sample_column="Sample_ID",
    subset_level="Level_2",
    subset_value="Stromal Cell",
    dot_color="#8e0152",
    save_name="stromal_abundance_bubble_grid",
)
Figure saved to: figures/abundance_bubble_grid/stromal_abundance_bubble_grid.png
../_images/b798ec81b0495600b864e6944a8b53ff18198dad9a86022a981e129ad11395f2.png ../_images/b798ec81b0495600b864e6944a8b53ff18198dad9a86022a981e129ad11395f2.png

Next steps#

See Overview for overview plots, or API Reference for the full parameter reference of every function used here.