
Bio Workflows Scrnaseq Pipeline
- 5 installs
- 1.1k repo stars
- Updated July 25, 2026
- gptomics/bioskills
Run an end-to-end single-cell RNA-seq workflow from 10X Genomics data to annotated cell types with Seurat.
About
Orchestrates QC, normalization, doublet detection, clustering, marker detection, and cell-type annotation for scRNA-seq. A developer uses it to analyze 10X Genomics single-cell data from raw matrices to annotated cell types.
- Data I/O, preprocessing, doublet detection, and clustering
- Marker detection and cell-type annotation with staged QC gates
Bio Workflows Scrnaseq Pipeline by the numbers
- 5 all-time installs (skills.sh)
- Ranked #1,598 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gptomics/bioskills --skill bio-workflows-scrnaseq-pipelineAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 1.1k |
| Last updated | July 25, 2026 |
| Repository | gptomics/bioskills ↗ |
What it does
Run an end-to-end single-cell RNA-seq workflow from 10X Genomics data to annotated cell types with Seurat.
Files
Version Compatibility
Reference examples tested with: Cell Ranger 8.0+, ggplot2 3.5+, numpy 1.26+, scanpy 1.10+
Before using code patterns, verify installed versions match. If versions differ:
- Python:
pip show <package>thenhelp(module.function)to check signatures - R:
packageVersion('<pkg>')then?function_nameto verify parameters
If code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
Single-Cell RNA-seq Pipeline
"Analyze my single-cell RNA-seq data from counts to cell types" -> Orchestrate QC filtering, normalization (scanpy/Seurat), batch integration (scVI/Harmony), clustering, marker detection, cell type annotation, and trajectory inference.
Complete workflow from 10X Genomics Cell Ranger output to annotated cell types.
Workflow Overview
10X data (filtered_feature_bc_matrix)
|
v
[1. Load Data] ---------> Read10X / read_10x_h5
|
v
[2. QC Filtering] ------> nFeature, percent.mt, doublets
|
v
[3. Normalization] -----> SCTransform or LogNormalize
|
v
[4. HVG Selection] -----> FindVariableFeatures
|
v
[5. Dim Reduction] -----> PCA -> UMAP
|
v
[6. Clustering] --------> FindNeighbors -> FindClusters
|
v
[7. Markers] -----------> FindAllMarkers
|
v
[8. Annotation] --------> Manual or automated
|
v
Annotated Seurat/AnnData objectPrimary Path: Seurat (R)
Step 1: Load 10X Data
library(Seurat)
library(ggplot2)
library(dplyr)
# Load from Cell Ranger output
data_dir <- 'cellranger_output/filtered_feature_bc_matrix'
counts <- Read10X(data.dir = data_dir)
# Create Seurat object
seurat_obj <- CreateSeuratObject(counts = counts, project = 'my_project',
min.cells = 3, min.features = 200)Step 2: Quality Control
# Calculate QC metrics
seurat_obj[['percent.mt']] <- PercentageFeatureSet(seurat_obj, pattern = '^MT-')
seurat_obj[['percent.ribo']] <- PercentageFeatureSet(seurat_obj, pattern = '^RP[SL]')
# Visualize QC metrics
VlnPlot(seurat_obj, features = c('nFeature_RNA', 'nCount_RNA', 'percent.mt'), ncol = 3)
# Filter cells
seurat_obj <- subset(seurat_obj,
nFeature_RNA > 200 &
nFeature_RNA < 5000 &
percent.mt < 20 &
nCount_RNA > 500)
cat('Cells after QC:', ncol(seurat_obj), '\n')QC Checkpoint 1: Review QC plots
- Remove cells with very low/high gene counts
- Remove cells with high mitochondrial content (dying cells)
Step 3: Doublet Detection
library(scDblFinder)
# Convert to SCE for scDblFinder
sce <- as.SingleCellExperiment(seurat_obj)
sce <- scDblFinder(sce)
# Add back to Seurat
seurat_obj$doublet_class <- sce$scDblFinder.class
seurat_obj$doublet_score <- sce$scDblFinder.score
# Remove doublets
seurat_obj <- subset(seurat_obj, doublet_class == 'singlet')
cat('Cells after doublet removal:', ncol(seurat_obj), '\n')Step 4: Normalization with SCTransform
# SCTransform (recommended for most analyses)
seurat_obj <- SCTransform(seurat_obj, vars.to.regress = 'percent.mt', verbose = FALSE)Alternative: Standard normalization
seurat_obj <- NormalizeData(seurat_obj)
seurat_obj <- FindVariableFeatures(seurat_obj, selection.method = 'vst', nfeatures = 2000)
seurat_obj <- ScaleData(seurat_obj, vars.to.regress = 'percent.mt')Step 5: Dimensionality Reduction
# PCA
seurat_obj <- RunPCA(seurat_obj, npcs = 50, verbose = FALSE)
# Determine optimal PCs
ElbowPlot(seurat_obj, ndims = 50)
# UMAP
n_pcs <- 30 # Choose based on elbow plot
seurat_obj <- RunUMAP(seurat_obj, dims = 1:n_pcs, verbose = FALSE)Step 6: Clustering
# Find neighbors
seurat_obj <- FindNeighbors(seurat_obj, dims = 1:n_pcs, verbose = FALSE)
# Find clusters (try multiple resolutions)
seurat_obj <- FindClusters(seurat_obj, resolution = c(0.2, 0.4, 0.6, 0.8, 1.0), verbose = FALSE)
# Visualize
DimPlot(seurat_obj, reduction = 'umap', group.by = 'SCT_snn_res.0.4', label = TRUE)QC Checkpoint 2: Assess clustering
- Clusters should be visually separable on UMAP
- Resolution 0.4-0.8 is often appropriate
Step 7: Find Marker Genes
# Set identity to chosen resolution
Idents(seurat_obj) <- 'SCT_snn_res.0.4'
# Find markers for all clusters
markers <- FindAllMarkers(seurat_obj, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.25)
# Top markers per cluster
top_markers <- markers %>%
group_by(cluster) %>%
slice_max(n = 10, order_by = avg_log2FC)
# Visualize top markers
DoHeatmap(seurat_obj, features = top_markers$gene) + NoLegend()Step 8: Cell Type Annotation
# Manual annotation based on known markers
# Example for PBMC data:
cluster_annotations <- c(
'0' = 'CD4 T cells',
'1' = 'CD14 Monocytes',
'2' = 'B cells',
'3' = 'CD8 T cells',
'4' = 'NK cells',
'5' = 'CD16 Monocytes',
'6' = 'Dendritic cells'
)
seurat_obj$cell_type <- cluster_annotations[as.character(Idents(seurat_obj))]
# Final UMAP
DimPlot(seurat_obj, reduction = 'umap', group.by = 'cell_type', label = TRUE)
# Save object
saveRDS(seurat_obj, 'seurat_annotated.rds')Alternative Path: Scanpy (Python)
import scanpy as sc
import numpy as np
# Load 10X data
adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5')
adata.var_names_make_unique()
# 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)
# Filter
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, :]
# Doublet detection
sc.pp.scrublet(adata)
adata = adata[~adata.obs['predicted_doublet'], :]
# Normalize and HVGs
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=2000)
# PCA, neighbors, UMAP
sc.pp.scale(adata, max_value=10)
sc.tl.pca(adata, n_comps=50)
sc.pp.neighbors(adata, n_neighbors=15, n_pcs=30)
sc.tl.umap(adata)
# Clustering
sc.tl.leiden(adata, resolution=0.5)
# Markers
sc.tl.rank_genes_groups(adata, 'leiden', method='wilcoxon')
sc.pl.rank_genes_groups(adata, n_genes=10, sharey=False)
# Save
adata.write('scanpy_annotated.h5ad')Parameter Recommendations
| Step | Parameter | Recommendation |
|---|---|---|
| QC | min.features | 200-500 |
| QC | max.features | 2500-5000 (depends on data) |
| QC | percent.mt | <10-20% |
| SCTransform | vars.to.regress | percent.mt |
| PCA | npcs | 30-50 |
| UMAP | dims | 15-30 (check elbow plot) |
| Clustering | resolution | 0.4-0.8 (start with 0.5) |
Troubleshooting
| Issue | Likely Cause | Solution |
|---|---|---|
| All cells filtered | QC too strict | Relax thresholds |
| Poor UMAP separation | Too few HVGs or PCs | Increase nfeatures, check n_pcs |
| Too many/few clusters | Wrong resolution | Adjust resolution parameter |
| Unknown cell types | Missing markers | Check known marker genes manually |
Complete R Workflow
library(Seurat)
library(scDblFinder)
library(ggplot2)
library(dplyr)
# Configuration
data_dir <- 'filtered_feature_bc_matrix'
output_dir <- 'results'
dir.create(output_dir, showWarnings = FALSE)
# Load
counts <- Read10X(data.dir = data_dir)
seurat_obj <- CreateSeuratObject(counts = counts, min.cells = 3, min.features = 200)
cat('Initial cells:', ncol(seurat_obj), '\n')
# QC
seurat_obj[['percent.mt']] <- PercentageFeatureSet(seurat_obj, pattern = '^MT-')
seurat_obj <- subset(seurat_obj, nFeature_RNA > 200 & nFeature_RNA < 5000 & percent.mt < 20)
cat('After QC:', ncol(seurat_obj), '\n')
# Doublets
sce <- as.SingleCellExperiment(seurat_obj)
sce <- scDblFinder(sce)
seurat_obj$doublet <- sce$scDblFinder.class
seurat_obj <- subset(seurat_obj, doublet == 'singlet')
cat('After doublet removal:', ncol(seurat_obj), '\n')
# Normalize
seurat_obj <- SCTransform(seurat_obj, vars.to.regress = 'percent.mt', verbose = FALSE)
# Dimension reduction
seurat_obj <- RunPCA(seurat_obj, npcs = 50, verbose = FALSE)
seurat_obj <- RunUMAP(seurat_obj, dims = 1:30, verbose = FALSE)
# Cluster
seurat_obj <- FindNeighbors(seurat_obj, dims = 1:30, verbose = FALSE)
seurat_obj <- FindClusters(seurat_obj, resolution = 0.5, verbose = FALSE)
# Markers
markers <- FindAllMarkers(seurat_obj, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.25)
write.csv(markers, file.path(output_dir, 'markers.csv'))
# Save
saveRDS(seurat_obj, file.path(output_dir, 'seurat_object.rds'))
# Plots
pdf(file.path(output_dir, 'umap.pdf'), width = 10, height = 8)
DimPlot(seurat_obj, reduction = 'umap', label = TRUE)
dev.off()
cat('Pipeline complete. Object saved to:', output_dir, '\n')Related Skills
- database-access/geo-data - Resolve GSE to SRA; detect SuperSeries before processing
- database-access/sra-data - Download 10x records with --include-technical for barcodes/UMIs
- single-cell/data-io - Loading different formats
- single-cell/preprocessing - QC details
- single-cell/doublet-detection - Doublet methods comparison
- single-cell/clustering - Clustering parameters
- single-cell/markers-annotation - Annotation strategies
- single-cell/multimodal-integration - CITE-seq, multiome
# Reference: matplotlib 3.8+, numpy 1.26+, pandas 2.2+, scanpy 1.10+ | Verify API if version differs
# Complete single-cell RNA-seq workflow with Scanpy
import scanpy as sc
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
sc.settings.verbosity = 1
sc.settings.set_figure_params(dpi=100, facecolor='white')
# Public scRNA-seq datasets:
# - 10x Genomics: https://www.10xgenomics.com/datasets (PBMC 3k, 10k datasets)
# - CELLxGENE: https://cellxgene.cziscience.com (curated annotated datasets)
# - GEO: GSE149173 (COVID-19 PBMC), GSE136831 (human lung)
# - Human Cell Atlas: https://data.humancellatlas.org
# - Scanpy built-in: sc.datasets.pbmc3k_processed()
# Configuration
data_path = 'filtered_feature_bc_matrix.h5'
output_dir = 'scrnaseq_results_scanpy'
os.makedirs(output_dir, exist_ok=True)
os.makedirs(f'{output_dir}/plots', exist_ok=True)
# === Step 1: Load Data ===
print('Loading data...')
adata = sc.read_10x_h5(data_path)
adata.var_names_make_unique()
print(f'Initial: {adata.n_obs} cells, {adata.n_vars} genes')
# === Step 2: QC Metrics ===
print('Calculating QC metrics...')
adata.var['mt'] = adata.var_names.str.startswith('MT-')
adata.var['ribo'] = adata.var_names.str.startswith(('RPS', 'RPL'))
sc.pp.calculate_qc_metrics(adata, qc_vars=['mt', 'ribo'], percent_top=None, log1p=False, inplace=True)
# QC plots
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
sc.pl.violin(adata, ['n_genes_by_counts', 'total_counts', 'pct_counts_mt'], jitter=0.4, ax=axes[:3], show=False)
sc.pl.scatter(adata, x='total_counts', y='n_genes_by_counts', color='pct_counts_mt', ax=axes[3], show=False)
plt.savefig(f'{output_dir}/plots/qc_metrics.pdf')
plt.close()
# Filter cells
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, :]
print(f'After QC: {adata.n_obs} cells')
# === Step 3: Doublet Detection ===
print('Detecting doublets...')
sc.pp.scrublet(adata)
doublet_rate = adata.obs['predicted_doublet'].sum() / len(adata)
print(f'Doublet rate: {doublet_rate:.1%}')
adata = adata[~adata.obs['predicted_doublet'], :]
print(f'After doublet removal: {adata.n_obs} cells')
# === Step 4: Normalization ===
print('Normalizing...')
adata.layers['counts'] = adata.X.copy()
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
# HVGs
sc.pp.highly_variable_genes(adata, n_top_genes=2000)
print(f'Highly variable genes: {adata.var.highly_variable.sum()}')
fig, ax = plt.subplots(figsize=(8, 6))
sc.pl.highly_variable_genes(adata, ax=ax, show=False)
plt.savefig(f'{output_dir}/plots/hvgs.pdf')
plt.close()
# === Step 5: Dimensionality Reduction ===
print('Running PCA...')
adata.raw = adata
adata = adata[:, adata.var.highly_variable]
sc.pp.scale(adata, max_value=10)
sc.tl.pca(adata, n_comps=50)
# Elbow plot
fig, ax = plt.subplots(figsize=(6, 4))
sc.pl.pca_variance_ratio(adata, n_pcs=50, ax=ax, show=False)
plt.savefig(f'{output_dir}/plots/elbow.pdf')
plt.close()
print('Computing neighbors and UMAP...')
n_pcs = 30
sc.pp.neighbors(adata, n_neighbors=15, n_pcs=n_pcs)
sc.tl.umap(adata)
# === Step 6: Clustering ===
print('Clustering...')
for res in [0.2, 0.4, 0.6, 0.8, 1.0]:
sc.tl.leiden(adata, resolution=res, key_added=f'leiden_{res}')
# UMAP plots
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
for ax, res in zip(axes.flat, [0.2, 0.4, 0.6, 0.8]):
sc.pl.umap(adata, color=f'leiden_{res}', ax=ax, show=False, title=f'res={res}')
plt.tight_layout()
plt.savefig(f'{output_dir}/plots/umap_resolutions.pdf')
plt.close()
# Set default clustering
adata.obs['leiden'] = adata.obs['leiden_0.5']
print(f'Clusters (res=0.5): {adata.obs["leiden"].nunique()}')
# === Step 7: Find Markers ===
print('Finding marker genes...')
sc.tl.rank_genes_groups(adata, 'leiden', method='wilcoxon')
# Plot markers
fig, ax = plt.subplots(figsize=(12, 8))
sc.pl.rank_genes_groups(adata, n_genes=10, sharey=False, ax=ax, show=False)
plt.savefig(f'{output_dir}/plots/markers.pdf')
plt.close()
# Export markers
markers = sc.get.rank_genes_groups_df(adata, group=None)
markers.to_csv(f'{output_dir}/all_markers.csv', index=False)
# Top markers per cluster
top_markers = markers.groupby('group').head(10)
top_markers.to_csv(f'{output_dir}/top10_markers.csv', index=False)
# Heatmap
top_genes = markers.groupby('group').head(5)['names'].tolist()
fig, ax = plt.subplots(figsize=(12, 10))
sc.pl.heatmap(adata, var_names=top_genes[:50], groupby='leiden', ax=ax, show=False)
plt.savefig(f'{output_dir}/plots/marker_heatmap.pdf')
plt.close()
# === Step 8: Save Results ===
print('Saving results...')
adata.write(f'{output_dir}/adata.h5ad')
# Summary
print('\n=== Pipeline Complete ===')
print(f'Final cells: {adata.n_obs}')
print(f'Clusters: {adata.obs["leiden"].nunique()}')
print(f'Results saved to: {output_dir}')
print('\nTop 3 markers per cluster:')
print(markers.groupby('group').head(3)[['group', 'names', 'logfoldchanges', 'pvals_adj']])
# Reference: Cell Ranger 8.0+, ggplot2 3.5+, numpy 1.26+, scanpy 1.10+ | Verify API if version differs
# Complete single-cell RNA-seq workflow with Seurat
library(Seurat)
library(scDblFinder)
library(ggplot2)
library(dplyr)
# Public scRNA-seq datasets:
# - 10x Genomics: https://www.10xgenomics.com/datasets (PBMC 3k, 10k)
# - SeuratData package: InstallData('pbmc3k') for built-in tutorial data
# - CELLxGENE: https://cellxgene.cziscience.com (curated annotated datasets)
# - GEO: GSE149173 (COVID-19 PBMC), GSE126030 (multi-tissue)
# - Human Cell Atlas: https://data.humancellatlas.org
# Configuration
data_dir <- 'filtered_feature_bc_matrix'
output_dir <- 'scrnaseq_results'
project_name <- 'my_scrnaseq'
dir.create(output_dir, showWarnings = FALSE)
dir.create(file.path(output_dir, 'plots'), showWarnings = FALSE)
# === Step 1: Load Data ===
cat('Loading data...\n')
counts <- Read10X(data.dir = data_dir)
seurat_obj <- CreateSeuratObject(counts = counts, project = project_name,
min.cells = 3, min.features = 200)
cat('Initial cells:', ncol(seurat_obj), 'Genes:', nrow(seurat_obj), '\n')
# === Step 2: QC Metrics ===
cat('Calculating QC metrics...\n')
seurat_obj[['percent.mt']] <- PercentageFeatureSet(seurat_obj, pattern = '^MT-')
seurat_obj[['percent.ribo']] <- PercentageFeatureSet(seurat_obj, pattern = '^RP[SL]')
# QC violin plots
pdf(file.path(output_dir, 'plots', 'qc_violin.pdf'), width = 12, height = 4)
VlnPlot(seurat_obj, features = c('nFeature_RNA', 'nCount_RNA', 'percent.mt'), ncol = 3, pt.size = 0)
dev.off()
# QC scatter plots
pdf(file.path(output_dir, 'plots', 'qc_scatter.pdf'), width = 10, height = 4)
p1 <- FeatureScatter(seurat_obj, feature1 = 'nCount_RNA', feature2 = 'percent.mt')
p2 <- FeatureScatter(seurat_obj, feature1 = 'nCount_RNA', feature2 = 'nFeature_RNA')
p1 + p2
dev.off()
# Filter cells
seurat_obj <- subset(seurat_obj,
nFeature_RNA > 200 &
nFeature_RNA < 5000 &
percent.mt < 20 &
nCount_RNA > 500)
cat('After QC filtering:', ncol(seurat_obj), 'cells\n')
# === Step 3: Doublet Detection ===
cat('Detecting doublets...\n')
sce <- as.SingleCellExperiment(seurat_obj)
set.seed(42)
sce <- scDblFinder(sce)
seurat_obj$doublet_class <- sce$scDblFinder.class
seurat_obj$doublet_score <- sce$scDblFinder.score
doublet_rate <- sum(seurat_obj$doublet_class == 'doublet') / ncol(seurat_obj)
cat('Doublet rate:', round(doublet_rate * 100, 1), '%\n')
seurat_obj <- subset(seurat_obj, doublet_class == 'singlet')
cat('After doublet removal:', ncol(seurat_obj), 'cells\n')
# === Step 4: Normalization ===
cat('Running SCTransform...\n')
seurat_obj <- SCTransform(seurat_obj, vars.to.regress = 'percent.mt', verbose = FALSE)
# === Step 5: Dimensionality Reduction ===
cat('Running PCA...\n')
seurat_obj <- RunPCA(seurat_obj, npcs = 50, verbose = FALSE)
# Elbow plot
pdf(file.path(output_dir, 'plots', 'elbow.pdf'), width = 6, height = 4)
ElbowPlot(seurat_obj, ndims = 50)
dev.off()
# UMAP
n_pcs <- 30
cat('Running UMAP with', n_pcs, 'PCs...\n')
seurat_obj <- RunUMAP(seurat_obj, dims = 1:n_pcs, verbose = FALSE)
# === Step 6: Clustering ===
cat('Finding clusters...\n')
seurat_obj <- FindNeighbors(seurat_obj, dims = 1:n_pcs, verbose = FALSE)
seurat_obj <- FindClusters(seurat_obj, resolution = c(0.2, 0.4, 0.6, 0.8, 1.0), verbose = FALSE)
# UMAP plots at different resolutions
pdf(file.path(output_dir, 'plots', 'umap_resolutions.pdf'), width = 15, height = 10)
p1 <- DimPlot(seurat_obj, group.by = 'SCT_snn_res.0.2', label = TRUE) + ggtitle('res=0.2')
p2 <- DimPlot(seurat_obj, group.by = 'SCT_snn_res.0.4', label = TRUE) + ggtitle('res=0.4')
p3 <- DimPlot(seurat_obj, group.by = 'SCT_snn_res.0.6', label = TRUE) + ggtitle('res=0.6')
p4 <- DimPlot(seurat_obj, group.by = 'SCT_snn_res.0.8', label = TRUE) + ggtitle('res=0.8')
(p1 | p2) / (p3 | p4)
dev.off()
# Set default resolution
Idents(seurat_obj) <- 'SCT_snn_res.0.5'
seurat_obj$seurat_clusters <- Idents(seurat_obj)
# === Step 7: Find Markers ===
cat('Finding marker genes...\n')
markers <- FindAllMarkers(seurat_obj, only.pos = TRUE, min.pct = 0.25,
logfc.threshold = 0.25, verbose = FALSE)
# Top markers per cluster
top_markers <- markers %>%
group_by(cluster) %>%
slice_max(n = 10, order_by = avg_log2FC)
write.csv(markers, file.path(output_dir, 'all_markers.csv'), row.names = FALSE)
write.csv(top_markers, file.path(output_dir, 'top10_markers.csv'), row.names = FALSE)
# Heatmap
pdf(file.path(output_dir, 'plots', 'marker_heatmap.pdf'), width = 12, height = 10)
top5 <- markers %>% group_by(cluster) %>% slice_max(n = 5, order_by = avg_log2FC)
DoHeatmap(seurat_obj, features = top5$gene) + NoLegend()
dev.off()
# === Step 8: Save Results ===
saveRDS(seurat_obj, file.path(output_dir, 'seurat_object.rds'))
# Summary
cat('\n=== Pipeline Complete ===\n')
cat('Final cells:', ncol(seurat_obj), '\n')
cat('Clusters:', length(unique(Idents(seurat_obj))), '\n')
cat('Markers found:', nrow(markers), '\n')
cat('Results saved to:', output_dir, '\n')
cat('\nTop 3 markers per cluster:\n')
print(markers %>% group_by(cluster) %>% slice_max(n = 3, order_by = avg_log2FC) %>%
select(cluster, gene, avg_log2FC, pct.1, pct.2, p_val_adj))
Single-Cell RNA-seq Pipeline - Usage Guide
Overview
This workflow processes single-cell RNA-seq data from 10X Genomics Cell Ranger output to annotated cell types. It supports both Seurat (R) and Scanpy (Python) implementations.
Prerequisites
# R packages
install.packages(c('Seurat', 'ggplot2', 'dplyr'))
BiocManager::install(c('scDblFinder', 'SingleCellExperiment'))# Python packages
pip install scanpy scrublet anndataQuick Start
Tell your AI agent what you want to do:
- "Analyze my 10X single-cell data from start to finish"
- "Run the scRNA-seq pipeline on my PBMC data"
- "Cluster my single-cell data and find marker genes"
Example Prompts
Starting from Cell Ranger output
"I have filtered_feature_bc_matrix from Cell Ranger, analyze it"
"Load my 10X data and perform QC filtering"
"Process my scRNA-seq with Scanpy instead of Seurat"
Analysis steps
"Remove doublets from my single-cell data"
"Find clusters at different resolutions"
"What are the marker genes for cluster 3?"
Annotation
"Help me annotate cell types based on marker genes"
"Use SingleR for automated cell type annotation"
Input Requirements
| Input | Format | Description |
|---|---|---|
| 10X data | filtered_feature_bc_matrix/ | Directory with matrix.mtx, barcodes.tsv, features.tsv |
| 10X H5 | .h5 | Alternatively, the HDF5 format |
What the Workflow Does
1. Load Data - Read 10X matrix into Seurat/AnnData 2. QC Filtering - Remove low-quality cells and doublets 3. Normalization - SCTransform or log-normalization 4. Feature Selection - Find highly variable genes 5. Dimension Reduction - PCA and UMAP 6. Clustering - Graph-based clustering 7. Markers - Find cluster-specific genes 8. Annotation - Assign cell type labels
Seurat vs Scanpy
| Feature | Seurat | Scanpy |
|---|---|---|
| Language | R | Python |
| Speed | Fast | Faster for large data |
| Memory | Moderate | Lower |
| Ecosystem | Bioconductor | Python ML stack |
| Best for | General analysis | Large datasets, integration |
Tips
- Cell numbers: Expect 1,000-20,000 cells from typical 10X run
- Genes per cell: 200-5,000 is typical; very high may be doublets
- Mitochondrial: >20% suggests dying cells
- Resolution: Start at 0.5, adjust based on cluster quality
- Annotation: Check canonical markers for your tissue type