Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Quality control and exploration of RNA-seq count matrices before differential expression. Check for outliers, batch effects, and sample relationships. Use when assessing count matrix quality before DE analysis.
.claude/skills/bio-rna-quantification-count-matrix-qc/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-12 | ✗→✓ | ▲ Improved | 6% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 120% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 62% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 69% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 144% | 0% |
<!--
#
#
-->
Quality control and exploratory analysis of count matrices before differential expression.
rlibrary(DESeq2) # From tximport dds <- DESeqDataSetFromTximport(txi, colData = coldata, design = ~ condition) # From count matrix counts <- read.csv('count_matrix.csv', row.names = 1) coldata <- data.frame(condition = factor(c('ctrl', 'ctrl', 'treat', 'treat')), row.names = colnames(counts)) dds <- DESeqDataSetFromMatrix(countData = counts, colData = coldata, design = ~ condition)
pythonimport pandas as pd import numpy as np counts = pd.read_csv('count_matrix.csv', index_col=0) metadata = pd.read_csv('sample_info.csv', index_col=0)
r# Total counts per sample colSums(counts(dds)) # Genes detected per sample colSums(counts(dds) > 0) # Counts summary summary(colSums(counts(dds)))
pythontotal_counts = counts.sum() genes_detected = (counts > 0).sum() print('Total counts per sample:') print(total_counts) print('\nGenes detected:') print(genes_detected)
r# Remove genes with low counts across samples keep <- rowSums(counts(dds)) >= 10 dds <- dds[keep, ] # More stringent: at least N samples with count >= M keep <- rowSums(counts(dds) >= 10) >= 3 dds <- dds[keep, ]
pythonmin_counts = 10 min_samples = 3 gene_filter = (counts >= min_counts).sum(axis=1) >= min_samples counts_filtered = counts[gene_filter]
r# Variance stabilizing transformation vsd <- vst(dds, blind = TRUE) # Or regularized log (slower, better for small n) rld <- rlog(dds, blind = TRUE) # Get transformed values vst_matrix <- assay(vsd)
pythonfrom sklearn.preprocessing import StandardScaler cpm = counts * 1e6 / counts.sum() log_cpm = np.log2(cpm + 1)
rlibrary(pheatmap) # Sample correlation heatmap sample_cor <- cor(assay(vsd)) pheatmap(sample_cor, annotation_col = coldata) # Sample distance heatmap sample_dist <- dist(t(assay(vsd))) pheatmap(as.matrix(sample_dist), annotation_col = coldata)
pythonimport seaborn as sns import matplotlib.pyplot as plt sample_cor = log_cpm.corr() sns.clustermap(sample_cor, annot=True, cmap='RdBu_r', center=0.9, vmin=0.8, vmax=1.0) plt.savefig('sample_correlation.png')
r# PCA plot plotPCA(vsd, intgroup = 'condition') # Custom PCA pca <- prcomp(t(assay(vsd))) pca_df <- data.frame(PC1 = pca$x[,1], PC2 = pca$x[,2], condition = coldata$condition) library(ggplot2) ggplot(pca_df, aes(PC1, PC2, color = condition)) + geom_point(size = 3) + geom_text(aes(label = rownames(pca_df)), vjust = -0.5)
pythonfrom sklearn.decomposition import PCA pca = PCA(n_components=2) pca_result = pca.fit_transform(log_cpm.T) plt.figure(figsize=(8, 6)) for condition in metadata['condition'].unique(): mask = metadata['condition'] == condition plt.scatter(pca_result[mask, 0], pca_result[mask, 1], label=condition) plt.xlabel(f'PC1 ({pca.explained_variance_ratio_[0]:.1%})') plt.ylabel(f'PC2 ({pca.explained_variance_ratio_[1]:.1%})') plt.legend() plt.savefig('pca_plot.png')
r# Cook's distance (after DESeq) dds <- DESeq(dds) W <- results(dds)$cooksd boxplot(W, main = "Cook's Distance") # Identify outlier samples from PCA pca <- prcomp(t(assay(vsd))) outliers <- abs(scale(pca$x[,1])) > 3 | abs(scale(pca$x[,2])) > 3
pythonfrom scipy import stats z_scores = stats.zscore(pca_result, axis=0) outliers = (np.abs(z_scores) > 3).any(axis=1) print('Potential outliers:', counts.columns[outliers].tolist())
r# Color PCA by batch plotPCA(vsd, intgroup = c('condition', 'batch')) # Test for batch effect design(dds) <- ~ batch + condition dds <- DESeq(dds)
python# Color by batch in PCA for batch in metadata['batch'].unique(): mask = metadata['batch'] == batch plt.scatter(pca_result[mask, 0], pca_result[mask, 1], marker=['o', 's', '^'][list(metadata['batch'].unique()).index(batch)], label=f'Batch {batch}')
r# Genes detected vs library size plot(colSums(counts(dds)), colSums(counts(dds) > 0), xlab = 'Library Size', ylab = 'Genes Detected') # Saturation check
pythonplt.scatter(counts.sum(), (counts > 0).sum()) plt.xlabel('Total Counts') plt.ylabel('Genes Detected') plt.savefig('library_complexity.png')
r# Most variable genes rv <- rowVars(assay(vsd)) top_var <- order(rv, decreasing = TRUE)[1:500] # Expression distribution boxplot(log2(counts(dds) + 1), las = 2)
pythongene_var = log_cpm.var(axis=1).sort_values(ascending=False) top_var_genes = gene_var.head(500).index counts[top_var_genes].boxplot(figsize=(12, 6)) plt.xticks(rotation=45) plt.savefig('gene_expression_dist.png')
r# Quick summary cat('Samples:', ncol(dds), '\n') cat('Genes before filter:', nrow(counts), '\n') cat('Genes after filter:', nrow(dds), '\n') cat('Median library size:', median(colSums(counts(dds))), '\n') cat('Median genes detected:', median(colSums(counts(dds) > 0)), '\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-01 | fail→fail | 18,118 | 14,598 | -19% | 1 | 1 | 0% | 4,034 | 5,272 | +31% | 0 | 0 | — |
case-02 | fail→fail | 20,855 | 12,716 | -39% | 1 | 1 | 0% | 4,553 | 4,774 | +5% | 0 | 0 | — |
case-03 | pass→pass | 8,288 | 3,713 | -55% | 1 | 1 | 0% | 1,663 | 2,694 | +62% | 0 | 0 | — |
case-04 | pass→pass | 8,972 | 3,985 | -56% | 1 | 1 | 0% | 1,646 | 2,774 | +69% | 0 | 0 | — |
case-05 | pass→pass | 5,698 | 3,415 | -40% | 1 | 1 | 0% | 1,064 | 2,595 | +144% | 0 | 0 | — |
case-06 | pass→pass | 12,367 | 7,135 | -42% | 1 | 1 | 0% | 1,720 | 3,142 | +83% | 0 | 0 | — |
case-07 | pass→pass | 6,662 | 5,360 | -20% | 1 | 1 | 0% | 1,222 | 2,980 | +144% | 0 | 0 | — |
case-08 | pass→pass | 9,236 | 5,856 | -37% | 1 | 1 | 0% | 1,913 | 3,052 | +60% | 0 | 0 | — |
case-09 | pass→pass | 6,874 | 3,360 | -51% | 1 | 1 | 0% | 1,239 | 2,626 | +112% | 0 | 0 | — |
case-10 | pass→pass | 9,342 | 6,831 | -27% | 1 | 1 | 0% | 1,780 | 3,336 | +87% | 0 | 0 | — |
case-11 | fail→fail | 12,448 | 11,848 | -5% | 1 | 1 | 0% | 2,342 | 4,273 | +82% | 0 | 0 | — |
case-12 | fail→pass | 14,537 | 5,953 | -59% | 1 | 1 | 0% | 2,924 | 3,111 | +6% | 0 | 0 | — |
case-13 | pass→pass | 9,369 | 6,484 | -31% | 1 | 1 | 0% | 1,658 | 3,174 | +91% | 0 | 0 | — |
case-14 | pass→pass | 13,262 | 8,298 | -37% | 1 | 1 | 0% | 2,762 | 3,579 | +30% | 0 | 0 | — |
case-15 | pass→pass | 10,318 | 5,273 | -49% | 1 | 1 | 0% | 2,029 | 3,014 | +49% | 0 | 0 | — |
case-16 | pass→pass | 10,237 | 5,192 | -49% | 1 | 1 | 0% | 2,085 | 3,062 | +47% | 0 | 0 | — |
case-17 | fail→pass | 6,960 | 4,254 | -39% | 1 | 1 | 0% | 1,243 | 2,737 | +120% | 0 | 0 | — |
case-18 | pass→pass | 11,498 | 8,539 | -26% | 1 | 1 | 0% | 2,306 | 3,497 | +52% | 0 | 0 | — |
case-19 | pass→pass | 9,538 | 8,873 | -7% | 1 | 1 | 0% | 2,025 | 3,881 | +92% | 0 | 0 | — |
case-20 | fail→fail | 8,916 | 8,263 | -7% | 1 | 1 | 0% | 1,834 | 3,725 | +103% | 0 | 0 | — |
case-21 | pass→pass | 10,377 | 7,118 | -31% | 1 | 1 | 0% | 1,993 | 3,391 | +70% | 0 | 0 | — |
case-22 | pass→pass | 17,284 | 13,501 | -22% | 1 | 1 | 0% | 3,494 | 4,991 | +43% | 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. 22 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 22 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/26/2026 | +27% |
Other measured skills in the registry, with their headline benchmark lift.