| name | bio-single-cell-preprocessing |
| description | Quality control, filtering, and normalization for single-cell RNA-seq using Seurat (R) and Scanpy (Python). Use for calculating QC metrics, filtering cells and genes, normalizing counts, identifying highly variable genes, and scaling data. Use when filtering, normalizing, and selecting features in single-cell data. |
| tool_type | mixed |
| primary_tool | Seurat |
Single-Cell Preprocessing
Quality control, filtering, normalization, and feature selection for scRNA-seq data.
Scanpy (Python)
Required Imports
import scanpy as sc
import numpy as np
Calculate QC Metrics
adata.var['mt'] = adata.var_names.str.startswith('MT-')
sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], percent_top=None, log1p=False, inplace=True)
Visualize QC Metrics
import matplotlib.pyplot as plt
sc.pl.violin(adata, ['n_genes_by_counts', 'total_counts', 'pct_counts_mt'], jitter=0.4, multi_panel=True)
sc.pl.scatter(adata, x='total_counts', y='pct_counts_mt')
sc.pl.scatter(adata, x='total_counts', y='n_genes_by_counts')
Filter Cells and Genes
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.filter_cells(adata, max_genes=5000)
adata = adata[adata.obs['pct_counts_mt'] < 20, :].copy()
sc.pp.filter_genes(adata, min_cells=3)
print(f'After filtering: {adata.n_obs} cells, {adata.n_vars} genes')
Store Raw Counts
adata.raw = adata.copy()
adata.layers['counts'] = adata.X.copy()
Normalization
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
Highly Variable Genes
sc.pp.highly_variable_genes(adata, n_top_genes=2000, flavor='seurat_v3', layer='counts')
sc.pl.highly_variable_genes(adata)
print(f'Highly variable genes: {adata.var.highly_variable.sum()}')
Subset to HVGs (Optional)
adata_hvg = adata[:, adata.var.highly_variable].copy()
Scaling (Z-score)
sc.pp.scale(adata, max_value=10)
Regress Out Confounders
sc.pp.regress_out(adata, ['total_counts', 'pct_counts_mt'])
Complete Preprocessing Pipeline
import scanpy as sc
adata = sc.read_10x_mtx('filtered_feature_bc_matrix/')
adata.var['mt'] = adata.var_names.str.startswith('MT-')
sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], inplace=True)
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.filter_genes(adata, min_cells=3)
adata = adata[adata.obs['pct_counts_mt'] < 20, :].copy()
adata.raw = adata.copy()
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=2000)
adata = adata[:, adata.var.highly_variable].copy()
sc.pp.scale(adata, max_value=10)
Seurat (R)
Required Libraries
library(Seurat)
library(ggplot2)
Calculate QC Metrics
seurat_obj[['percent.mt']] <- PercentageFeatureSet(seurat_obj, pattern = '^MT-')
head(seurat_obj@meta.data)
Visualize QC Metrics
VlnPlot(seurat_obj, features = c('nFeature_RNA', 'nCount_RNA', 'percent.mt'), ncol = 3)
plot1 <- FeatureScatter(seurat_obj, feature1 = 'nCount_RNA', feature2 = 'percent.mt')
plot2 <- FeatureScatter(seurat_obj, feature1 = 'nCount_RNA', feature2 = 'nFeature_RNA')
plot1 + plot2
Filter Cells
seurat_obj <- subset(seurat_obj,
subset = nFeature_RNA > 200 &
nFeature_RNA < 5000 &
percent.mt < 20)
cat('After filtering:', ncol(seurat_obj), 'cells\n')
Normalization (Log Normalization)
seurat_obj <- NormalizeData(seurat_obj, normalization.method = 'LogNormalize', scale.factor = 10000)
Normalization (SCTransform)
seurat_obj <- SCTransform(seurat_obj, vars.to.regress = 'percent.mt', verbose = FALSE)
Find Variable Features
seurat_obj <- FindVariableFeatures(seurat_obj, selection.method = 'vst', nfeatures = 2000)
top10 <- head(VariableFeatures(seurat_obj), 10)
plot1 <- VariableFeaturePlot(seurat_obj)
plot2 <- LabelPoints(plot = plot1, points = top10, repel = TRUE)
plot2
Scaling
all.genes <- rownames(seurat_obj)
seurat_obj <- ScaleData(seurat_obj, features = all.genes)
seurat_obj <- ScaleData(seurat_obj)
Regress Out Confounders
seurat_obj <- ScaleData(seurat_obj, vars.to.regress = c('percent.mt', 'nCount_RNA'))
Complete Preprocessing Pipeline (Log Normalization)
library(Seurat)
counts <- Read10X(data.dir = 'filtered_feature_bc_matrix/')
seurat_obj <- CreateSeuratObject(counts = counts, min.cells = 3, min.features = 200)
seurat_obj[['percent.mt']] <- PercentageFeatureSet(seurat_obj, pattern = '^MT-')
seurat_obj <- subset(seurat_obj,
subset = nFeature_RNA > 200 & nFeature_RNA < 5000 & percent.mt < 20)
seurat_obj <- NormalizeData(seurat_obj)
seurat_obj <- FindVariableFeatures(seurat_obj, nfeatures = 2000)
seurat_obj ScaleDataseurat_obj
Complete Preprocessing Pipeline (SCTransform)
library(Seurat)
counts <- Read10X(data.dir = 'filtered_feature_bc_matrix/')
seurat_obj <- CreateSeuratObject(counts = counts, min.cells = 3, min.features = 200)
seurat_obj[['percent.mt']] <- PercentageFeatureSet(seurat_obj, pattern = '^MT-')
seurat_obj <- subset(seurat_obj,
subset = nFeature_RNA > 200 & nFeature_RNA < 5000 & percent.mt < 20)
seurat_obj <- SCTransform(seurat_obj, vars.to.regress = 'percent.mt', verbose = FALSE)
QC Thresholds Reference
| Metric | Typical Range | Notes |
|---|
| min_genes | 200-500 | Remove empty droplets |
| max_genes | 2500-5000 | Remove doublets |
| max_mt | 5-20% | Remove dying cells (tissue-dependent) |
| min_cells | 3-10 | Remove rarely detected genes |
Method Comparison
| Step | Scanpy | Seurat (Standard) | Seurat (SCTransform) |
|---|
| Normalize | normalize_total + log1p | NormalizeData | SCTransform |
| HVGs | highly_variable_genes | FindVariableFeatures | (included) |
| Scale | scale | ScaleData | (included) |
| Regress | regress_out | ScaleData(vars.to.regress) | SCTransform(vars.to.regress) |
Related Skills
- data-io - Load data before preprocessing
- clustering - PCA and clustering after preprocessing
- markers-annotation - Find markers after clustering