Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Extract, filter, annotate, and export differential expression results from DESeq2 or edgeR. Use for identifying significant genes, applying multiple testing corrections, adding gene annotations, and preparing results for downstream analysis. Use when filtering and exporting DE analysis results.
.claude/skills/bio-de-results/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-03 | ✗→✗ | = Same ✗ | — | — |
| case-11 | ✗→✗ | = Same ✗ | — | — |
| case-18 | ✗→✗ | = Same ✗ | — | — |
Reference examples tested with: DESeq2 1.42+, edgeR 4.0+
Before using code patterns, verify installed versions match. If versions differ:
packageVersion('<pkg>') then ?function_name to verify parametersIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
Extract, filter, and export differential expression results.
rlibrary(DESeq2) # or library(edgeR) library(dplyr) # For data manipulation
Goal: Retrieve DE statistics from a fitted DESeq2 model as a usable data frame.
Approach: Call results() with optional shrinkage, then convert to a data frame with gene identifiers.
r# Basic results res <- results(dds) # With specific alpha (adjusted p-value threshold) res <- results(dds, alpha = 0.05) # With log fold change shrinkage res <- lfcShrink(dds, coef = 'condition_treated_vs_control', type = 'apeglm') # Convert to data frame res_df <- as.data.frame(res) res_df$gene <- rownames(res_df)
Goal: Retrieve DE statistics from a fitted edgeR model as a data frame.
Approach: Use topTags with n=Inf to extract all gene-level results.
r# Get all results results <- topTags(qlf, n = Inf)$table # Add gene column results$gene <- rownames(results)
Goal: Identify genes meeting statistical significance and biological effect size criteria.
Approach: Subset results by adjusted p-value, fold change magnitude, and expression level thresholds.
"Get the significant differentially expressed genes" → Filter DE results by adjusted p-value and fold change cutoffs to produce up- and down-regulated gene lists.
r# DESeq2 sig_genes <- subset(res, padj < 0.05) # edgeR sig_genes <- subset(results, FDR < 0.05) # Using dplyr sig_genes <- res_df %>% filter(padj < 0.05) %>% arrange(padj)
r# Absolute log2 fold change > 1 (2-fold change) sig_genes <- subset(res, padj < 0.05 & abs(log2FoldChange) > 1) # Up-regulated only up_genes <- subset(res, padj < 0.05 & log2FoldChange > 1) # Down-regulated only down_genes <- subset(res, padj < 0.05 & log2FoldChange < -1)
r# Stringent filtering sig_genes <- res_df %>% filter(padj < 0.01, abs(log2FoldChange) > 1, baseMean > 10) %>% arrange(padj)
Goal: Rank DE genes by statistical significance or biological effect size.
Approach: Sort results by adjusted p-value, absolute fold change, or mean expression.
r# By adjusted p-value (most significant first) res_ordered <- res[order(res$padj), ] # By absolute fold change (largest changes first) res_ordered <- res[order(abs(res$log2FoldChange), decreasing = TRUE), ] # By base mean expression res_ordered <- res[order(res$baseMean, decreasing = TRUE), ] # Combined: significant genes ordered by fold change sig_ordered <- res_df %>% filter(padj < 0.05) %>% arrange(desc(abs(log2FoldChange)))
Goal: Quantify the number of up- and down-regulated genes at chosen thresholds.
Approach: Count genes passing significance filters and report directional breakdown.
r# DESeq2 summary summary(res) # Manual counts n_tested <- sum(!is.na(res$padj)) n_sig <- sum(res$padj < 0.05, na.rm = TRUE) n_up <- sum(res$padj < 0.05 & res$log2FoldChange > 0, na.rm = TRUE) n_down <- sum(res$padj < 0.05 & res$log2FoldChange < 0, na.rm = TRUE) cat(sprintf('Tested: %d genes\n', n_tested)) cat(sprintf('Significant (padj < 0.05): %d genes\n', n_sig)) cat(sprintf('Up-regulated: %d genes\n', n_up)) cat(sprintf('Down-regulated: %d genes\n', n_down)) # edgeR summary summary(decideTests(qlf))
Goal: Enrich DE results with gene symbols, descriptions, and cross-database identifiers.
Approach: Map Ensembl or Entrez IDs to human-readable annotations using org.db, biomaRt, or custom files.
"Add gene names to my DE results" → Map gene identifiers to symbols and descriptions using annotation databases, then merge with the results table.
rlibrary(org.Hs.eg.db) # Human; use org.Mm.eg.db for mouse # If gene IDs are Ensembl res_df$symbol <- mapIds(org.Hs.eg.db, keys = rownames(res_df), column = 'SYMBOL', keytype = 'ENSEMBL', multiVals = 'first') res_df$entrez <- mapIds(org.Hs.eg.db, keys = rownames(res_df), column = 'ENTREZID', keytype = 'ENSEMBL', multiVals = 'first') res_df$description <- mapIds(org.Hs.eg.db, keys = rownames(res_df), column = 'GENENAME', keytype = 'ENSEMBL', multiVals = 'first')
rlibrary(biomaRt) mart <- useMart('ensembl', dataset = 'hsapiens_gene_ensembl') annotations <- getBM( attributes = c('ensembl_gene_id', 'external_gene_name', 'description'), filters = 'ensembl_gene_id', values = rownames(res_df), mart = mart ) # Merge with results res_annotated <- merge(res_df, annotations, by.x = 'row.names', by.y = 'ensembl_gene_id', all.x = TRUE)
r# Load annotation file gene_info <- read.csv('gene_annotations.csv') # Merge with results res_annotated <- merge(res_df, gene_info, by = 'gene', all.x = TRUE)
Goal: Save DE results in formats suitable for sharing, publication, or downstream tools.
Approach: Write filtered and annotated results to CSV, Excel workbooks, or ranked gene lists for pathway analysis.
r# All results write.csv(res_df, file = 'deseq2_all_results.csv', row.names = FALSE) # Significant only sig_genes <- res_df %>% filter(padj < 0.05) write.csv(sig_genes, file = 'deseq2_significant.csv', row.names = FALSE)
rlibrary(openxlsx) # Create workbook with multiple sheets wb <- createWorkbook() addWorksheet(wb, 'All Results') writeData(wb, 'All Results', res_df) addWorksheet(wb, 'Significant') writeData(wb, 'Significant', sig_genes) addWorksheet(wb, 'Up-regulated') writeData(wb, 'Up-regulated', up_genes) addWorksheet(wb, 'Down-regulated') writeData(wb, 'Down-regulated', down_genes) saveWorkbook(wb, 'de_results.xlsx', overwrite = TRUE)
r# Just gene IDs for GO/KEGG analysis sig_gene_list <- rownames(subset(res, padj < 0.05)) write.table(sig_gene_list, file = 'significant_genes.txt', quote = FALSE, row.names = FALSE, col.names = FALSE) # With fold changes for GSEA gsea_input <- res_df %>% filter(!is.na(log2FoldChange)) %>% select(gene, log2FoldChange) %>% arrange(desc(log2FoldChange)) write.table(gsea_input, file = 'gsea_input.rnk', sep = '\t', quote = FALSE, row.names = FALSE, col.names = FALSE)
Goal: Assess concordance between DESeq2 and edgeR results to identify robust DE genes.
Approach: Compute set overlaps and visualize with a Venn diagram.
r# Get significant genes from both methods deseq2_sig <- rownames(subset(deseq2_res, padj < 0.05)) edger_sig <- rownames(subset(edger_results, FDR < 0.05)) # Overlap common <- intersect(deseq2_sig, edger_sig) deseq2_only <- setdiff(deseq2_sig, edger_sig) edger_only <- setdiff(edger_sig, deseq2_sig) cat(sprintf('DESeq2 significant: %d\n', length(deseq2_sig))) cat(sprintf('edgeR significant: %d\n', length(edger_sig))) cat(sprintf('Common: %d\n', length(common))) cat(sprintf('DESeq2 only: %d\n', length(deseq2_only))) cat(sprintf('edgeR only: %d\n', length(edger_only))) # Venn diagram library(VennDiagram) venn.diagram( x = list(DESeq2 = deseq2_sig, edgeR = edger_sig), filename = 'de_overlap.png', fill = c('steelblue', 'coral') )
Goal: Apply or compare multiple testing correction methods for DE p-values.
Approach: Use Benjamini-Hochberg (default), Bonferroni, or IHW for adjusted p-values.
r# DESeq2 uses Benjamini-Hochberg by default # To use different methods: # Independent Hypothesis Weighting (more powerful) library(IHW) res_ihw <- results(dds, filterFun = ihw) # Manual p-value adjustment res_df$padj_bonferroni <- p.adjust(res_df$pvalue, method = 'bonferroni') res_df$padj_bh <- p.adjust(res_df$pvalue, method = 'BH') res_df$padj_fdr <- p.adjust(res_df$pvalue, method = 'fdr')
Goal: Understand and handle missing values in DE results caused by filtering or outlier detection.
Approach: Identify the source of NAs (zero counts, independent filtering, outliers) and remove or investigate them.
r# Count NAs sum(is.na(res$padj)) # Remove genes with NA padj res_complete <- res[!is.na(res$padj), ] # Understand why NAs occur # - baseMean = 0: No counts # - NA only in padj: Outlier or low count filtered by independent filtering # Check outliers res[which(is.na(res$pvalue) & res$baseMean > 0), ]
| Column | Description | |--------|-------------| | baseMean | Mean normalized counts | | log2FoldChange | Log2 fold change | | lfcSE | Standard error of LFC | | stat | Wald statistic | | pvalue | Raw p-value | | padj | Adjusted p-value (BH) |
| Column | Description | |--------|-------------| | logFC | Log2 fold change | | logCPM | Average log2 CPM | | F | Quasi-likelihood F-statistic | | PValue | Raw p-value | | FDR | False discovery rate |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
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.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.