Tutorial 3 — Visualisation, GSEA, and export#
This tutorial covers:
Similarity heatmap
MP score distributions across experimental groups (violin plots)
MP scores on UMAP
Gene Set Enrichment Analysis (GSEA) using bundled MSigDB gene sets
Exporting the gene signatures to CSV — with and without loading scores
Dataset — Peter Sims lab DMSO vs. panobinostat scRNA-seq cohort.
0 · Imports and data preparation#
[ ]:
import kaleidocell
import scanpy as sc
import pandas as pd
adata = sc.read_h5ad('../data/petersims_example.h5ad')
results_nmf, nmf_convergence = kaleidocell.multi_sample_nmf(
adata,
batch_key='Patients',
test_ranks=[4, 5, 6, 7, 8, 9],
n_initializations=10,
seed=42,
)
results_mp = kaleidocell.derive_nmf_metaprograms(results_nmf, top_n_genes=50)
mp_scores = kaleidocell.compute_mp_scores(results_mp, adata)
1 · Similarity heatmap#
Visualise pairwise cosine similarities between all NMF programs. Diagonal blocks correspond to meta-programs; tight blocks indicate well-defined signatures.
[ ]:
kaleidocell.plot_heatmap(results_mp)
2 · Quality metrics#
[ ]:
results_mp['metrics']
3 · MP score distributions across conditions#
show_distribution_over_obs draws one violin plot per MP, grouped by the specified obs column. This is useful for quickly identifying MPs that are differentially active between conditions or donors.
[ ]:
kaleidocell.show_distribution_over_obs(
mp_scores,
adata,
batch_key='Treatment',
save=False,
)
4 · MP scores on UMAP#
Overlay per-cell MP scores on the UMAP embedding. If no UMAP is present in adata.obsm, it is computed automatically.
Setting weighted=True scales each gene’s expression by its normalised loading weight before summing, giving higher-specificity genes more influence than low-weight genes.
[ ]:
# Standard equal-weight scores
kaleidocell.plot_mp_scores_on_umap(mp_scores, adata, ncols=4)
[ ]:
# Weight-scaled scores — requires results_mp
kaleidocell.plot_mp_scores_on_umap(
mp_scores, adata,
ncols=4,
weighted=True,
results_mp=results_mp,
)
5 · Gene Set Enrichment Analysis#
kaleidocell ships with several MSigDB gene-set files. Use kaleidocell.files to discover what is available.
[ ]:
# List all bundled gene-set files with descriptions
print(kaleidocell.files)
[ ]:
# Run Enrichr-based GSEA against Hallmarks and GO Biological Process
# Pass the short filename — kaleidocell resolves it to the bundled path automatically
gsea_results, gsea_plots = kaleidocell.run_gsea_pipeline(
results_mp,
from_file=[
'h.all.v2026.1.Hs.symbols.gmt',
'c5.go.bp.v2026.1.Hs.symbols.gmt',
],
top_n_plot=6,
plot=False,
save_csv=False,
)
# Significant terms table
gsea_results.head(20)
[ ]:
# Bar plots of top terms per MP
kaleidocell.plot_gsea_results(gsea_plots, ncols=4)
6 · Export gene signatures to CSV#
The gene signatures live in results_mp['mp_dict'] — a dictionary mapping each MP name to a pd.Series of gene names (index) and loading scores (values), sorted descending by score.
Two export formats are shown below.
6a · Gene names only (no scores)#
Long-format table: one row per gene per MP. Useful for downstream tools that expect a plain gene list per program.
[ ]:
genes_only = pd.DataFrame(
[
{"MP": mp_name, "gene": gene}
for mp_name, gene_series in results_mp["mp_dict"].items()
for gene in gene_series.index
]
)
genes_only.to_csv('../results/03/metaprograms_genes.csv', index=False)
print(f"Saved {len(genes_only)} rows. Preview:")
genes_only.head(10)
6b · Gene names with loading scores#
Same long-format table but with an additional score column containing the consensus NMF loading weight. Higher scores indicate greater specificity to this meta-program.
[ ]:
genes_with_scores = pd.DataFrame(
[
{"MP": mp_name, "gene": gene, "score": float(score)}
for mp_name, gene_series in results_mp["mp_dict"].items()
for gene, score in gene_series.items()
]
)
genes_with_scores.to_csv('../results/03/metaprograms_genes_scores.csv', index=False)
print(f"Saved {len(genes_with_scores)} rows. Preview:")
genes_with_scores.head(10)
6c · Wide format — one column per MP#
Useful when you want to open the file in Excel and compare signatures side-by-side.
[ ]:
wide = pd.DataFrame(
{
mp_name: gene_series.index.tolist()
for mp_name, gene_series in results_mp["mp_dict"].items()
}
)
wide.to_csv('../results/03/metaprograms_wide.csv', index=False)
wide.head()