Install any skill in seconds. Free to start, no credit card required.
Get Started Free →End-to-end multiome workflow for joint scRNA-seq + scATAC-seq analysis. Covers data loading, separate modality processing, and WNN integration with Seurat/Signac. Use when analyzing joint scRNA+scATAC data.
.claude/skills/bio-workflows-multiome-pipeline/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 74% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 39% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 144% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 160% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 87% | 0% |
<!--
#
#
-->
Complete workflow for 10X Multiome (joint scRNA + scATAC) analysis using Seurat and Signac.
10X Multiome data
|
v
[1. Load Data] ---------> Read RNA + ATAC
|
v
[2. RNA Processing] ----> Standard scRNA workflow
|
v
[3. ATAC Processing] ---> Peak calling, LSI
|
v
[4. WNN Integration] ---> Weighted nearest neighbors
|
v
[5. Joint Analysis] ----> Clustering, markers
|
v
[6. Linked Features] ---> Gene-peak links
|
v
Integrated multiome objectrlibrary(Seurat) library(Signac) library(EnsDb.Hsapiens.v86) library(ggplot2) # Load RNA rna_counts <- Read10X_h5('filtered_feature_bc_matrix.h5') # For multiome, this returns a list with 'Gene Expression' and 'Peaks' # Create Seurat object with RNA seurat_obj <- CreateSeuratObject( counts = rna_counts$`Gene Expression`, assay = 'RNA' ) # Load ATAC atac_counts <- rna_counts$Peaks # Or from fragments file frags <- CreateFragmentObject('atac_fragments.tsv.gz', cells = colnames(seurat_obj)) # Create ChromatinAssay atac_assay <- CreateChromatinAssay( counts = atac_counts, sep = c(':', '-'), fragments = frags, annotation = GetGRangesFromEnsDb(ensdb = EnsDb.Hsapiens.v86) ) seurat_obj[['ATAC']] <- atac_assay
r# QC metrics seurat_obj[['percent.mt']] <- PercentageFeatureSet(seurat_obj, pattern = '^MT-') # Filter seurat_obj <- subset(seurat_obj, nCount_RNA > 1000 & nCount_RNA < 25000 & percent.mt < 20 ) # Normalize RNA seurat_obj <- SCTransform(seurat_obj, assay = 'RNA', verbose = FALSE) # PCA seurat_obj <- RunPCA(seurat_obj, assay = 'SCT', verbose = FALSE)
r# ATAC QC metrics DefaultAssay(seurat_obj) <- 'ATAC' seurat_obj <- NucleosomeSignal(seurat_obj) seurat_obj <- TSSEnrichment(seurat_obj) # Visualize VlnPlot(seurat_obj, features = c('nCount_ATAC', 'TSS.enrichment', 'nucleosome_signal'), pt.size = 0, ncol = 3) # Filter ATAC seurat_obj <- subset(seurat_obj, nCount_ATAC > 1000 & nCount_ATAC < 100000 & TSS.enrichment > 2 & nucleosome_signal < 4 ) # Normalize ATAC (TF-IDF + SVD = LSI) seurat_obj <- RunTFIDF(seurat_obj) seurat_obj <- FindTopFeatures(seurat_obj, min.cutoff = 'q0') seurat_obj <- RunSVD(seurat_obj) # Check LSI components (first often correlates with depth) DepthCor(seurat_obj)
r# Build WNN graph using both modalities seurat_obj <- FindMultiModalNeighbors( seurat_obj, reduction.list = list('pca', 'lsi'), dims.list = list(1:30, 2:30), # Skip LSI component 1 if depth-correlated modality.weight.name = 'RNA.weight' ) # UMAP on WNN graph seurat_obj <- RunUMAP(seurat_obj, nn.name = 'weighted.nn', reduction.name = 'wnn.umap', reduction.key = 'wnnUMAP_') # Cluster on WNN seurat_obj <- FindClusters(seurat_obj, graph.name = 'wsnn', algorithm = 3, resolution = 0.5, verbose = FALSE)
r# Compare modality-specific and joint embeddings p1 <- DimPlot(seurat_obj, reduction = 'pca', label = TRUE) + ggtitle('RNA PCA') p2 <- DimPlot(seurat_obj, reduction = 'lsi', label = TRUE) + ggtitle('ATAC LSI') p3 <- DimPlot(seurat_obj, reduction = 'wnn.umap', label = TRUE) + ggtitle('WNN UMAP') p1 + p2 + p3 # Modality weights per cell VlnPlot(seurat_obj, features = 'RNA.weight', group.by = 'seurat_clusters', pt.size = 0) # Find markers (RNA) DefaultAssay(seurat_obj) <- 'SCT' rna_markers <- FindAllMarkers(seurat_obj, only.pos = TRUE, min.pct = 0.25) # Find markers (ATAC - differentially accessible peaks) DefaultAssay(seurat_obj) <- 'ATAC' atac_markers <- FindAllMarkers(seurat_obj, only.pos = TRUE, min.pct = 0.05, test.use = 'LR', latent.vars = 'nCount_ATAC')
r# Link peaks to genes DefaultAssay(seurat_obj) <- 'ATAC' seurat_obj <- RegionStats(seurat_obj, genome = BSgenome.Hsapiens.UCSC.hg38) seurat_obj <- LinkPeaks( seurat_obj, peak.assay = 'ATAC', expression.assay = 'SCT', genes.use = c('CD8A', 'CD4', 'MS4A1', 'CD14') # Example genes ) # Visualize links CoveragePlot(seurat_obj, region = 'CD8A', features = 'CD8A', expression.assay = 'SCT', extend.upstream = 10000, extend.downstream = 10000)
rlibrary(Seurat) library(Signac) library(EnsDb.Hsapiens.v86) library(BSgenome.Hsapiens.UCSC.hg38) library(ggplot2) # Configuration data_dir <- 'multiome_output' output_dir <- 'multiome_results' dir.create(output_dir, showWarnings = FALSE) # === Load Data === cat('Loading data...\n') counts <- Read10X_h5(file.path(data_dir, 'filtered_feature_bc_matrix.h5')) frags <- file.path(data_dir, 'atac_fragments.tsv.gz') seurat_obj <- CreateSeuratObject(counts = counts$`Gene Expression`, assay = 'RNA') seurat_obj[['ATAC']] <- CreateChromatinAssay( counts = counts$Peaks, sep = c(':', '-'), fragments = frags, annotation = GetGRangesFromEnsDb(ensdb = EnsDb.Hsapiens.v86) ) cat('Cells:', ncol(seurat_obj), '\n') # === RNA QC === cat('RNA QC...\n') seurat_obj[['percent.mt']] <- PercentageFeatureSet(seurat_obj, pattern = '^MT-') seurat_obj <- subset(seurat_obj, nCount_RNA > 1000 & nCount_RNA < 25000 & percent.mt < 20) # === ATAC QC === cat('ATAC QC...\n') DefaultAssay(seurat_obj) <- 'ATAC' seurat_obj <- NucleosomeSignal(seurat_obj) seurat_obj <- TSSEnrichment(seurat_obj) seurat_obj <- subset(seurat_obj, nCount_ATAC > 1000 & TSS.enrichment > 2 & nucleosome_signal < 4) cat('After QC:', ncol(seurat_obj), 'cells\n') # === Process RNA === cat('Processing RNA...\n') DefaultAssay(seurat_obj) <- 'RNA' seurat_obj <- SCTransform(seurat_obj, verbose = FALSE) seurat_obj <- RunPCA(seurat_obj, verbose = FALSE) # === Process ATAC === cat('Processing ATAC...\n') DefaultAssay(seurat_obj) <- 'ATAC' seurat_obj <- RunTFIDF(seurat_obj) seurat_obj <- FindTopFeatures(seurat_obj, min.cutoff = 'q0') seurat_obj <- RunSVD(seurat_obj) # === WNN Integration === cat('WNN integration...\n') seurat_obj <- FindMultiModalNeighbors(seurat_obj, reduction.list = list('pca', 'lsi'), dims.list = list(1:30, 2:30), modality.weight.name = 'RNA.weight' ) seurat_obj <- RunUMAP(seurat_obj, nn.name = 'weighted.nn', reduction.name = 'wnn.umap', reduction.key = 'wnnUMAP_') seurat_obj <- FindClusters(seurat_obj, graph.name = 'wsnn', resolution = 0.5, verbose = FALSE) # === Save === saveRDS(seurat_obj, file.path(output_dir, 'multiome_analyzed.rds')) # === Plots === pdf(file.path(output_dir, 'wnn_umap.pdf'), width = 10, height = 8) DimPlot(seurat_obj, reduction = 'wnn.umap', label = TRUE) dev.off() cat('Results saved to:', output_dir, '\n') cat('Clusters:', length(unique(seurat_obj$seurat_clusters)), '\n')
<!-- AUTHOR_SIGNATURE: 9a7f3c2e-MD-BABU-MIA-2026-MSSM-SECURE -->
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-11 | fail→pass | 11,234 | 4,122 | -63% | 1 | 1 | 0% | 2,010 | 3,490 | +74% | 0 | 0 | — |
case-01 | pass→pass | 5,688 | 5,064 | -11% | 1 | 1 | 0% | 955 | 3,518 | +268% | 0 | 0 | — |
case-02 | pass→pass | 5,931 | 5,454 | -8% | 1 | 1 | 0% | 1,061 | 3,610 | +240% | 0 | 0 | — |
case-03 | pass→pass | 5,730 | 5,772 | +1% | 1 | 1 | 0% | 1,047 | 3,632 | +247% | 0 | 0 | — |
case-04 | pass→pass | 4,695 | 2,599 | -45% | 1 | 1 | 0% | 898 | 3,143 | +250% | 0 | 0 | — |
case-05 | pass→pass | 4,416 | 2,554 | -42% | 1 | 1 | 0% | 845 | 3,134 | +271% | 0 | 0 | — |
case-06 | pass→pass | 3,921 | 2,460 | -37% | 1 | 1 | 0% | 802 | 3,119 | +289% | 0 | 0 | — |
case-07 | fail→pass | 13,014 | 4,895 | -62% | 1 | 1 | 0% | 2,496 | 3,475 | +39% | 0 | 0 | — |
case-08 | fail→pass | 16,408 | 8,498 | -48% | 1 | 1 | 0% | 1,696 | 4,133 | +144% | 0 | 0 | — |
case-09 | pass→pass | 3,219 | 2,553 | -21% | 1 | 1 | 0% | 590 | 3,071 | +421% | 0 | 0 | — |
case-10 | pass→pass | 4,492 | 2,597 | -42% | 1 | 1 | 0% | 724 | 3,080 | +325% | 0 | 0 | — |
case-12 | fail→pass | 6,709 | 3,724 | -44% | 1 | 1 | 0% | 1,284 | 3,339 | +160% | 0 | 0 | — |
case-13 | pass→pass | 3,813 | 2,243 | -41% | 1 | 1 | 0% | 555 | 3,026 | +445% | 0 | 0 | — |
case-14 | pass→pass | 6,857 | 5,134 | -25% | 1 | 1 | 0% | 1,350 | 3,577 | +165% | 0 | 0 | — |
case-15 | pass→pass | 4,278 | 3,231 | -24% | 1 | 1 | 0% | 821 | 3,336 | +306% | 0 | 0 | — |
case-16 | pass→pass | 3,706 | 2,668 | -28% | 1 | 1 | 0% | 637 | 3,073 | +382% | 0 | 0 | — |
case-17 | pass→pass | 4,020 | 2,205 | -45% | 1 | 1 | 0% | 728 | 3,066 | +321% | 0 | 0 | — |
case-18 | fail→pass | 9,726 | 3,643 | -63% | 1 | 1 | 0% | 1,766 | 3,301 | +87% | 0 | 0 | — |
case-19 | pass→pass | 4,328 | 2,879 | -33% | 1 | 1 | 0% | 779 | 3,209 | +312% | 0 | 0 | — |
case-20 | pass→pass | 4,838 | 3,153 | -35% | 1 | 1 | 0% | 770 | 3,237 | +320% | 0 | 0 | — |
case-21 | fail→pass | 5,745 | 3,537 | -38% | 1 | 1 | 0% | 1,111 | 3,303 | +197% | 0 | 0 | — |
case-22 | pass→pass | 6,480 | 2,719 | -58% | 1 | 1 | 0% | 1,241 | 3,122 | +152% | 0 | 0 | — |
case-23 | fail→pass | 6,279 | 2,152 | -66% | 1 | 1 | 0% | 1,195 | 3,037 | +154% | 0 | 0 | — |
case-24 | pass→pass | 9,692 | 4,300 | -56% | 1 | 1 | 0% | 1,793 | 3,461 | +93% | 0 | 0 | — |
case-25 | pass→pass | 11,165 | 5,665 | -49% | 1 | 1 | 0% | 2,021 | 3,679 | +82% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +28 percentage points is the difference between those two pass rates over the 25 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/24/2026 | +22% |
Other measured skills in the registry, with their headline benchmark lift.