Overview#

Third of five demonstration notebooks – this one covers dataset overview plots: sample_and_cell_counts_barplot (and its broken-axis variant), cells_per_patient_boxplot, cell_abundance_barplot, compare_cell_abundance_barplot, and compare_cell_abundance_boxplot.

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 Composition for composition plots, Markers & expression for marker-gene and pseudobulk 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', 'umap', 'Level_3_colors', 'UMAP_0.75', 'Is_Core_colors', '_scvi_manager_uuid', 'neighbors', '_scvi_uuid', 'Level_4_colors', 'Level_2_colors', 'Level_4_All_colors', 'Level_1_colors', 'Level_4_Final_colors'
    obsm: 'log_counts', 'X_pca', 'is_outlier_total_counts', 'scanvi_extended_atlas_emb', 'pct_counts_mito', 'bin_edges', 'level0_leiden_subcluster', 'scanvi_L4_emb', 'n_genes_by_counts', '_scvi_batch', 'UMAP_0.85', 'log1p_n_genes_by_counts', 'leiden_0.2_annotation', 'scANVI_cross_species', 'outlier', 'total_counts', 'batch', 'UMAP_0.75', 'log1p_total_counts', 'n_genes', 'total_counts_mito', 'EMT_score_DL', 'leiden', 'Global_Leiden', 'X_umap', 'MALAT1_lognorm', 'UMAP_0.95', 'infercnv_score_malignant', 'log1p_total_counts_mito', 'leiden_0.2', 'cnv_score_abs', 'scANVI_emb_final', 'EMT score', 'leiden_subcluster', 'empty_droplet', 'leiden_0.5', 'mt_frac', 'n_counts', 'infercnv_score_malignant_refined', '_scvi_labels'
    layers: 'raw', 'counts', 'log_norm'
    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")

sample_and_cell_counts_barplot#

Side-by-side bars of samples-per-category and cells-per-category; every call also returns a grayscale twin for print-safe figures.

plotter.sample_and_cell_counts_barplot(adata, level_column="Level_2", sample_column="Sample_ID")
(<Figure size 800x400 with 2 Axes>, <Figure size 800x400 with 2 Axes>)
../_images/31889e961c6366aa5d010ef9001b79223e544504b5a3f994bff94d5a78837995.png ../_images/d86c29b4f1ac2143fb9860259018052ea0e3eeb5eb85582ee884eb45e002d163.png

cells_per_patient_boxplot#

Distribution of cells-per-patient, grouped by category.

plotter.cells_per_patient_boxplot(adata, level_column="Level_2", sample_column="Sample_ID")
(<Figure size 800x480 with 1 Axes>, <Figure size 800x480 with 1 Axes>)
../_images/66c697d9e7c5026982d95ff36d5a3a2ee4d9fb6f0be92678f8b91e79417d7309.png ../_images/888e7bff1521bab9089a5f5deba456975dbfd884a972d48a2f762a07d18960f4.png

cell_abundance_barplot#

Absolute and relative cell-count bars for one column. Two demonstrations: the default neutral grey, and a custom color.

plotter.cell_abundance_barplot(adata, cell_type_column="Level_2", save_name="abundance")
Figure saved to: figures/cell_abundance_barplot/abundance_absolute_counts.png
Figure saved to: figures/cell_abundance_barplot/abundance_relative_abundance.png
(<Figure size 640x400 with 1 Axes>, <Figure size 640x400 with 1 Axes>)
../_images/bc776f5d5240d406df78689d477df4f75f3a0b23afdd93b797b33257e1435793.png ../_images/1c020d1ddff402a9cc3ee77ce928f7a708f3dc1a65e7041f0615c6e61d346324.png
plotter.cell_abundance_barplot(
    adata,
    cell_type_column="Level_2",
    color="#31688e",
    title_suffix=" (custom color)",
    save_name="abundance_custom_color",
)
Figure saved to: figures/cell_abundance_barplot/abundance_custom_color_absolute_counts.png
Figure saved to: figures/cell_abundance_barplot/abundance_custom_color_relative_abundance.png
(<Figure size 640x400 with 1 Axes>, <Figure size 640x400 with 1 Axes>)
../_images/ee05bb7688cdfc37cf029d0a53645f40645e494a7fab7106a39264b25aff6363.png ../_images/0592c1afed370f8ca35315c922212ec8ab9cfab2540d066f5a08e802a90e8366.png

compare_cell_abundance_barplot#

Grouped bars comparing cell-type abundance between two datasets/subsets – here, real treated vs. untreated patients.

untreated = adata[adata.obs["TreatmentStatus"] == "Untreated"].copy()
treated = adata[adata.obs["TreatmentStatus"] == "Treated"].copy()
plotter.compare_cell_abundance_barplot(
    untreated, treated, cell_type_column="Level_2", label1="Untreated", label2="Treated", save_name="compare",
)
Figure saved to: figures/cell_abundance_barplot/compare_absolute_counts_comparison.png
Figure saved to: figures/cell_abundance_barplot/compare_relative_abundance_comparison.png
(<Figure size 960x480 with 1 Axes>, <Figure size 960x480 with 1 Axes>)
../_images/797f0e9d26afd90337b7419886a4a2dabc577ed522d7ccc8a5317ae49d1c5a81.png ../_images/0e7c545b7c8314a3b5d4b501ee126bf8779f22dc43d4f82acf93b492cd669368.png

compare_cell_abundance_boxplot#

Paired per-sample boxplots (the full per-sample distribution rather than one dataset-wide bar), with individual sample dots overlaid. Two demonstrations: the default grayscale with point shape mapped to Sex, and a colored variant restricted to the biggest-difference cell types via high_dif.

plotter.compare_cell_abundance_boxplot(
    untreated,
    treated,
    cell_type_column="Level_2",
    sample_column="Sample_ID",
    label1="Untreated",
    label2="Treated",
    marker_column="Sex",
    figsize=(6, 6),
    save_name="compare_boxplot",
)
Figure saved to: figures/cell_abundance_barplot/compare_boxplot.png
../_images/6852cc8b5b99c145d33311c5523e52a7dd3266d7f3b478dbd48c0d815a563921.png ../_images/6852cc8b5b99c145d33311c5523e52a7dd3266d7f3b478dbd48c0d815a563921.png

colored=True for distinct dataset colors instead of grayscale, restricted to the 3 cell types with the largest mean abundance difference via high_dif.

plotter.compare_cell_abundance_boxplot(
    untreated,
    treated,
    cell_type_column="Level_2",
    sample_column="Sample_ID",
    label1="Untreated",
    label2="Treated",
    colored=True,
    high_dif=3,
    figsize=(6, 6),
    save_name="compare_boxplot_high_dif",
)
Figure saved to: figures/cell_abundance_barplot/compare_boxplot_high_dif.png
../_images/8ef773b2f5ed5d1d59d2f19b9b07e9fc30e88d4511fcba915aaa5d186e51b1d0.png ../_images/8ef773b2f5ed5d1d59d2f19b9b07e9fc30e88d4511fcba915aaa5d186e51b1d0.png

Next steps#

See Markers & expression for markers & expression plots, or API Reference for the full parameter reference of every function used here.