{ "cells": [ { "cell_type": "markdown", "id": "cell-0", "metadata": {}, "source": [ "# Tutorial 4 — Inspecting NMF convergence\n", "\n", "This tutorial shows how to use the convergence diagnostics returned by\n", "`multi_sample_nmf` to assess whether the chosen rank range and number of\n", "initialisations are sufficient.\n", "\n", "**Dataset** — Peter Sims lab DMSO vs. panobinostat scRNA-seq cohort." ] }, { "cell_type": "markdown", "id": "cell-1", "metadata": {}, "source": [ "## 0 · Imports and data preparation" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-2", "metadata": {}, "outputs": [], "source": [ "import kaleidocell\n", "import scanpy as sc\n", "import matplotlib.pyplot as plt\n", "\n", "adata = sc.read_h5ad('../data/petersims_example.h5ad')\n", "print(adata)" ] }, { "cell_type": "markdown", "id": "cell-3", "metadata": {}, "source": [ "## 1 · Run NMF and capture the convergence object\n", "\n", "`multi_sample_nmf` returns a tuple `(results_nmf, nmf_convergence)`.\n", "The second element is a dict mapping each sample to a DataFrame with one\n", "row per rank containing the best Frobenius reconstruction error across all\n", "initialisations." ] }, { "cell_type": "code", "execution_count": null, "id": "cell-4", "metadata": {}, "outputs": [], "source": [ "results_nmf, nmf_convergence = kaleidocell.multi_sample_nmf(\n", " adata,\n", " batch_key='Patients',\n", " test_ranks=[4, 5, 6, 7, 8, 9],\n", " n_initializations=10,\n", " seed=42,\n", ")\n", "\n", "# nmf_convergence is a dict {sample_name: DataFrame(rank, error)}\n", "list(nmf_convergence.keys())" ] }, { "cell_type": "markdown", "id": "cell-5", "metadata": {}, "source": [ "## 2 · Inspect reconstruction error per rank\n", "\n", "A large drop in error between consecutive ranks suggests that the higher\n", "rank captures a genuinely new signal. An elbow in the curve is a common\n", "heuristic for rank selection." ] }, { "cell_type": "code", "execution_count": null, "id": "cell-6", "metadata": {}, "outputs": [], "source": [ "fig, axes = plt.subplots(1, len(nmf_convergence), figsize=(4 * len(nmf_convergence), 3), sharey=False)\n", "\n", "for ax, (sample, df) in zip(axes, nmf_convergence.items()):\n", " ax.plot(df['rank'], df['error'], marker='o')\n", " ax.set_title(sample)\n", " ax.set_xlabel('Rank')\n", " ax.set_ylabel('Frobenius error')\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "cell-7", "metadata": {}, "source": [ "## 3 · Derive meta-programs and inspect quality metrics\n", "\n", "After selecting a rank based on the elbow above, proceed with\n", "`derive_nmf_metaprograms` and review the per-MP quality metrics to\n", "confirm the consensus is stable." ] }, { "cell_type": "code", "execution_count": null, "id": "cell-8", "metadata": {}, "outputs": [], "source": [ "results_mp = kaleidocell.derive_nmf_metaprograms(results_nmf)\n", "\n", "# Quality metrics: sampleCoverage, silhouette, meanSimilarity, nPrograms, nGenes\n", "results_mp['metrics']" ] } ], "metadata": { "kernelspec": { "display_name": "kaleidocell_env", "language": "python", "name": "kaleidocell_env" }, "language_info": { "name": "python", "version": "3.11.0" } }, "nbformat": 4, "nbformat_minor": 5 }