Loading...
Loading...
Scanpy-based single-cell RNA-seq analysis pipeline covering loading (10X, h5ad), QC, normalization, HVG selection, PCA/UMAP, Leiden clustering, differential expression, and batch correction (Harmony, scVI). Use when the user mentions single-cell, scRNA-seq, Scanpy, AnnData, UMAP, clustering, 10X, h5ad, leiden, highly variable genes, Harmony, scVI, cluster cells, find marker genes, filter low-quality cells, gene expression matrix, doublet removal, normalize counts, dimensionality reduction, cell clustering, QC single-cell, filtered_feature_bc_matrix, cellranger output, 10X h5, scrublet, cell ranger, count matrix, find cell types, batch effect, integration, neighborhood graph, or differential expression genes.
npx skill4agent add awslabs/hcls-agent-skills scrna-seq-pipeline.h5adAnnDatapip install "scanpy>=1.10" anndata leidenalg python-igraph harmonypy scvi-toolsimport scanpy as sc
import scanpy.external as sce
sc.settings.verbosity = 2
sc.settings.set_figure_params(dpi=100, facecolor="white")
# 1. Load
adata = sc.read_10x_h5("filtered_feature_bc_matrix.h5")
# Alternatives:
# adata = sc.read_10x_mtx("filtered_feature_bc_matrix/", var_names="gene_symbols", cache=True)
# adata = sc.read_h5ad("input.h5ad")
adata.var_names_make_unique()
# 2. QC
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)
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.filter_genes(adata, min_cells=3)
adata = adata[adata.obs.n_genes_by_counts < 5000, :]
adata = adata[adata.obs.pct_counts_mt < 20, :].copy()
# 3. Normalize + HVG
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
adata.raw = adata # freeze log-normalized full gene set BEFORE HVG subset
sc.pp.highly_variable_genes(adata, n_top_genes=2000, flavor="seurat")
adata = adata[:, adata.var.highly_variable].copy()
# 4. Scale + PCA + neighbors + UMAP + Leiden
sc.pp.scale(adata, max_value=10)
sc.tl.pca(adata, n_comps=50, svd_solver="arpack")
sc.pp.neighbors(adata, n_neighbors=15, n_pcs=30)
sc.tl.umap(adata)
sc.tl.leiden(adata, resolution=0.5)
# 5. Differential expression per cluster
sc.tl.rank_genes_groups(adata, groupby="leiden", method="wilcoxon")
# 6. Save
adata.write("result.h5ad", compression="gzip")# Assumes adata.obs["batch"] exists and PCA is already computed.
sce.pp.harmony_integrate(adata, key="batch") # writes adata.obsm["X_pca_harmony"]
sc.pp.neighbors(adata, n_neighbors=15, n_pcs=30, use_rep="X_pca_harmony")
sc.tl.umap(adata)
sc.tl.leiden(adata, resolution=0.5)import scvi
# adata_raw holds unnormalized integer counts; adata.obs["batch"] is the batch key.
scvi.model.SCVI.setup_anndata(adata_raw, batch_key="batch")
model = scvi.model.SCVI(adata_raw, n_latent=30)
model.train(max_epochs=400, early_stopping=True)
adata.obsm["X_scVI"] = model.get_latent_representation()
sc.pp.neighbors(adata, n_neighbors=15, use_rep="X_scVI")
sc.tl.umap(adata)
sc.tl.leiden(adata, resolution=0.5)adata.Xadata.obsadata.varmtadata.obsmX_pcaX_umapX_scVIadata.uns.rawadata.raw = adatasc.pl.umap(..., color="GENE")sc.pl.rank_genes_groups.rawadatan_genes ∈ [200, 5000]pct_mt < 20sc.pl.violinMT-mt-flavor="seurat"flavor="seurat_v3"n_neighborsn_pcssc.pl.pca_variance_ratioresolution| Input | Call |
|---|---|
| 10X HDF5 | |
| 10X MTX dir | |
| Existing AnnData | |
| Always after load | |
| Parameter | Default | Typical range | Notes |
|---|---|---|---|
| 200 | 100–500 | Drop empty/low-quality droplets |
| 3 | 3–10 | Drop rarely-expressed genes |
| 5000 | 2500–8000 | Upper bound flags doublets |
| 20 | 5–25 | Human tissue dependent |
| | | CP10k; standard |
| 2000 | 1000–5000 | More HVGs → more signal + noise |
| 10 | 10 | Clip extreme z-scores |
| 50 | 30–100 | Pick elbow for downstream |
| 15 | 10–50 | Larger → smoother manifold |
| 30 | 20–50 | ≤ |
| 0.5 | 0.2–1.5 | Higher → more clusters |
sc.tl.rank_genes_groups(adata, groupby="leiden", method="wilcoxon")
sc.pl.rank_genes_groups(adata, n_genes=25, sharey=False)
# Top-N markers per cluster as a DataFrame
import pandas as pd
result = adata.uns["rank_genes_groups"]
groups = result["names"].dtype.names
markers = pd.DataFrame({g: result["names"][g][:20] for g in groups})adata.write("result.h5ad", compression="gzip")
adata = sc.read_h5ad("result.h5ad")adata.raw = adata.rawadata = adata[:, adata.var.highly_variable].copy().rawflavor="seurat_v3"seurat_v3seuratadata.var_names_make_unique()adata.var_names_make_unique()batch_keysetup_anndataMT-mt-MT-mt-pct_counts_mt.copy().copy()adata = adata[mask, :].copy().copy()