Single-cell data analysis
- Goal
- Identify the cell types in a subsampled human intestinal atlas and produce a clean, batch-integrated, annotated map
- Inputs
- ~15,000 cells x ~33,500 genes (terminal ileum, 4 donors) as .h5ad, plus per-sample technical metadata
- Outputs
- A batch-corrected UMAP with CellTypist labels, cross-checked against marker genes and Leiden clusters
- Tools
- Python, scanpy, scVI, CellTypist
What it does
It takes a subsampled human intestinal atlas from raw counts to an annotated, batch-corrected single-cell map, and then checks that map three independent ways before trusting it.
Why it exists
It was set as an assignment for the Genomics and Transcriptomics Analysis lecture: identify the cell types present in a provided dataset. The interesting part is not running the standard steps, it is deciding which of them are actually correct for this data, and then showing that the answer is not an artefact of the method that produced it.
How it works
The cell-type calls are confirmed by triangulating three independent views. CellTypist assigns labels from a reference model of the adult human intestine; a hand-picked panel of known marker genes is checked against those labels; and unsupervised Leiden clustering is run on the same neighbourhood graph. Where all three agree, the structure is real rather than an artefact of any one method. That cross-check is the point of the notebook.
Getting to that point: genes detected in fewer than three cells are dropped, QC metrics are computed, and doublets are scored with Scrublet per donor. Cells are then filtered on doublet score and total counts. Batch effects across the four donors are removed with scVI, and the neighbour graph is rebuilt from the scVI latent space rather than from PCA, so both clustering and UMAP operate on the batch-corrected representation.
What was hard
Three decisions did most of the work.
The mitochondrial QC was silently doing nothing. This dataset's
var_names are Ensembl IDs, so flagging mitochondrial genes by matching
MT- against them matched zero genes, and every cell reported a
pct_counts_mt of zero. That reads as unusually clean data rather than as a
broken filter, which is what makes it dangerous. Flagging against the gene symbols
instead makes the metric reflect real mitochondrial content.
scVI was being trained on the wrong matrix. Training on the full
~33,500-gene matrix was slow enough that it had to be capped at
max_epochs=150 - which stops training at an arbitrary point rather than at
convergence. Selecting 2,000 highly variable genes from raw counts with
flavor="seurat_v3" and training on that subset lets the run converge on
its own.
Dimensionality reductions do not update themselves. Once cells are filtered, HVG selection, PCA, the neighbour graph and UMAP are all still functions of the old cell set, so all four are recomputed on the cleaned data.
Current state
Complete for the task it was set. The CellTypist reference model is included in the repository, but the JupyterLab environment it ran on was provided by the course and is not, so the notebook is currently easier to read than to re-run.