This is just a pile of plotting functions I kept rewriting from scratch for every single-cell project, finally bundled up so I stop copy-pasting them between notebooks. Built on top of scanpy and AnnData — masked/highlighted UMAPs, composition bar plots and heatmaps, pseudobulk boxplots, marker gene panels, Sankeys/sunbursts/treemaps over annotation hierarchies, that kind of thing.
No fancy analysis happening here, just a package to create reproducible and uniformly styled plots for single cell data.
If you've got a plot you always reach for that isn't in here, please open a PR — genuinely happy to have other people's favorite ways of looking at single-cell (and maybe eventually spatial) data in here too. See Contributing below.
- scanpy-native — works directly on
AnnDataobjects, no wrangling in between. - One config, every plot — a single
PlotConfigcontrols fonts, DPI, and color palettes, so figures don't end up looking like they came from six different notebooks. - Explicit over magic — column names, palettes, and grouping variables are always arguments you pass in; if you don't give a palette, a colorblind-friendly one gets generated for you.
- Two ways to call it — plain functions (
scplotkit.composition.stacked_barplots(...)) if you like the scanpy-style API, orscplotkit.ScPlotterif you're making a bunch of figures and don't want to passconfig/output_direvery single time.
Worth being upfront about: masked_umap, masked_umap_highlight, and gene_expression_umap in
scplotkit.embeddings, plus everything in scplotkit.markers, are thin wrappers around scanpy's own
sc.pl.embedding / sc.pl.rank_genes_groups_dotplot / sc.pl.rank_genes_groups_matrixplot /
sc.pl.matrixplot / sc.pl.stacked_violin. scplotkit just layers its masking/highlighting logic, shared
PlotConfig styling, and save-to-disk convention on top — they're here purely for convenience (one
consistent way to call and save every plot), not because scanpy's own plotting needed reinventing.
pip install scplotkit
# optional: Sankey/sunburst/treemap plots (needs plotly + kaleido)
pip install "scplotkit[sankey]"Or from source:
git clone https://github.com/philinscience/scplotkit.git
cd scplotkit
pip install -e ".[dev]"import scanpy as sc
import scplotkit as spk
adata = sc.read_h5ad("my_atlas.h5ad")
plotter = spk.ScPlotter(output_dir="figures")
# Highlight one lineage within the full UMAP
plotter.masked_umap(
adata,
color_by="cell_type",
mask_values=["CD4 T", "CD8 T", "NK"],
figure_name="Lymphocytes",
)
# Cell-type composition per sample, in three orderings (as-is, clustered,
# clustered within a metadata group)
plotter.stacked_barplots(
adata,
level_column="cell_type",
sample_column="sample_id",
order_by_column="condition",
)
# Dataset overview: samples & cells per category
plotter.sample_and_cell_counts_barplot(adata, level_column="cell_type")Prefer a functional style? Every method above is also a plain function:
from scplotkit import composition, embeddings
embeddings.masked_umap(adata, color_by="cell_type", mask_values=[...], figure_name="Lymphocytes")
composition.stacked_barplots(adata, level_column="cell_type", sample_column="sample_id")| Module | Plots |
|---|---|
scplotkit.embeddings |
Masked UMAP, masked-and-highlighted UMAP overlay, gene expression on an embedding, two-gene co-expression color blend, per-group embedding density |
scplotkit.composition |
Stacked composition bar plots (plain / clustered / clustered-within-group), a multi-metadata variant with color strips, a clustered composition heatmap, a cell-count bubble grid |
scplotkit.overview |
Samples/cells-per-category bar plots (incl. broken-axis), cells-per-patient boxplot, cell abundance bar plots, dataset-vs-dataset comparisons (bars and paired boxplots) |
scplotkit.markers |
rank_genes_groups dot/matrix plots, plus matrix and stacked-violin plots for your own marker gene sets |
scplotkit.pseudobulk |
Per-sample pseudobulk boxplot for one gene, or several at once (optionally min-max scaled) |
scplotkit.ridgeline |
Ridgeline (joy) plots of a continuous value across groups, with an optional per-condition overlay |
scplotkit.enrichment |
Dot plot for over-representation analysis (ORA) results, e.g. from Enrichr/gseapy |
scplotkit.sankey |
Sankeys, sunbursts, and treemaps over 1-3 nested annotation levels |
All styling (fonts, DPI, legend sizes) and any custom color palettes live in
a PlotConfig:
from scplotkit import PlotConfig
config = PlotConfig.from_yaml("my_config.yml")
# or inline:
config = PlotConfig({
"general": {"dpi_save": 400, "font_family": "Arial"},
"palettes": {"cell_type": {"CD4 T": "#1f78b4", "CD8 T": "#33a02c"}},
})
plotter = spk.ScPlotter(config=config, output_dir="figures")Categories without an explicit color are filled in automatically with a colorblind-friendly categorical palette, so you never need to enumerate every value up front.
Full API reference and five demonstration notebooks (real-world examples, several parameter variations per
plot) live in docs/ and are published at https://scplotkit.readthedocs.io. To build the docs
locally:
pip install -e ".[docs]"
sphinx-build -b html docs docs/_build/htmlGot a plot you always end up making for single-cell (or spatial) data? Open a
PR. No formal process here — just try to match the existing style (one
function per plot, config/output_dir as the last two args, save via
save_figure so it lands in the same place as everything else) and add it to
__all__ and the table above. Bug reports and "this default looks bad"
complaints are just as welcome as new plots.
pip install -e ".[dev]"
pytest
ruff check .If you find scplotkit useful for your research, I'd appreciate a citation:
@software{Putze_scplotkit,
author = {Putze, Philipp},
title = {{scplotkit}},
url = {https://github.com/philinscience/scplotkit},
version = {0.1.0},
year = {2026}
}See CITATION.cff for the machine-readable version (GitHub also surfaces this via the "Cite
this repository" button on the repo page).
MIT — see LICENSE.
