Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Create publication-ready volcano plots with custom thresholds, gene labels, and highlighting using ggplot2, EnhancedVolcano, or matplotlib. Use when visualizing differential expression or association results with gene annotations.
.claude/skills/bio-data-visualization-volcano-customization/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 119% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 67% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 63% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 89% | 0% |
<!--
#
#
-->
rlibrary(ggplot2) library(ggrepel) # Add significance category column df$significance <- case_when( df$padj < 0.05 & df$log2FoldChange > 1 ~ 'Up', df$padj < 0.05 & df$log2FoldChange < -1 ~ 'Down', TRUE ~ 'NS' ) ggplot(df, aes(x = log2FoldChange, y = -log10(pvalue))) + geom_point(aes(color = significance), alpha = 0.6, size = 1.5) + scale_color_manual(values = c(Up = '#E64B35', Down = '#4DBBD5', NS = 'gray70')) + geom_hline(yintercept = -log10(0.05), linetype = 'dashed', color = 'gray40') + geom_vline(xintercept = c(-1, 1), linetype = 'dashed', color = 'gray40') + theme_classic() + labs(x = 'log2 Fold Change', y = '-log10(p-value)', color = 'Regulation')
r# Label top significant genes top_genes <- df %>% filter(padj < 0.05, abs(log2FoldChange) > 1) %>% arrange(pvalue) %>% head(20) ggplot(df, aes(x = log2FoldChange, y = -log10(pvalue))) + geom_point(aes(color = significance), alpha = 0.6, size = 1.5) + scale_color_manual(values = c(Up = '#E64B35', Down = '#4DBBD5', NS = 'gray70')) + geom_text_repel( data = top_genes, aes(label = gene), size = 3, max.overlaps = 20, box.padding = 0.5, segment.color = 'gray50' ) + theme_classic() # Label specific genes of interest genes_of_interest <- c('TP53', 'BRCA1', 'MYC', 'EGFR') highlight_df <- df %>% filter(gene %in% genes_of_interest) ggplot(df, aes(x = log2FoldChange, y = -log10(pvalue))) + geom_point(aes(color = significance), alpha = 0.4, size = 1.5) + geom_point(data = highlight_df, color = 'black', size = 3) + geom_text_repel(data = highlight_df, aes(label = gene), fontface = 'bold') + theme_classic()
rlibrary(EnhancedVolcano) # Basic EnhancedVolcano EnhancedVolcano(df, lab = df$gene, x = 'log2FoldChange', y = 'pvalue', pCutoff = 0.05, FCcutoff = 1, title = 'Treatment vs Control', subtitle = 'DE genes highlighted') # Customized EnhancedVolcano EnhancedVolcano(df, lab = df$gene, x = 'log2FoldChange', y = 'pvalue', pCutoff = 0.05, FCcutoff = 1, xlim = c(-5, 5), ylim = c(0, 50), pointSize = 2, labSize = 3, colAlpha = 0.6, col = c('gray70', '#4DBBD5', '#00A087', '#E64B35'), legendLabels = c('NS', 'Log2FC', 'p-value', 'p-value and Log2FC'), legendPosition = 'right', drawConnectors = TRUE, widthConnectors = 0.5, maxoverlapsConnectors = 20, selectLab = genes_of_interest, # Only label specific genes boxedLabels = TRUE)
r# Custom point colors by category keyvals <- ifelse(df$log2FoldChange > 2 & df$padj < 0.01, '#E64B35', ifelse(df$log2FoldChange < -2 & df$padj < 0.01, '#4DBBD5', ifelse(df$padj < 0.05, '#00A087', 'gray70'))) names(keyvals)[keyvals == '#E64B35'] <- 'Highly Up' names(keyvals)[keyvals == '#4DBBD5'] <- 'Highly Down' names(keyvals)[keyvals == '#00A087'] <- 'Moderate' names(keyvals)[keyvals == 'gray70'] <- 'NS' EnhancedVolcano(df, lab = df$gene, x = 'log2FoldChange', y = 'pvalue', colCustom = keyvals, legendPosition = 'right')
pythonimport matplotlib.pyplot as plt import numpy as np fig, ax = plt.subplots(figsize=(8, 6)) # Color by significance colors = np.where((df['padj'] < 0.05) & (df['log2FoldChange'] > 1), '#E64B35', np.where((df['padj'] < 0.05) & (df['log2FoldChange'] < -1), '#4DBBD5', 'gray')) ax.scatter(df['log2FoldChange'], -np.log10(df['pvalue']), c=colors, alpha=0.6, s=20, edgecolors='none') # Threshold lines ax.axhline(-np.log10(0.05), color='gray', linestyle='--', linewidth=1) ax.axvline(-1, color='gray', linestyle='--', linewidth=1) ax.axvline(1, color='gray', linestyle='--', linewidth=1) ax.set_xlabel('log2 Fold Change') ax.set_ylabel('-log10(p-value)') plt.tight_layout()
pythonfrom adjustText import adjust_text # Get top genes to label top_idx = df.nsmallest(15, 'pvalue').index fig, ax = plt.subplots(figsize=(10, 8)) ax.scatter(df['log2FoldChange'], -np.log10(df['pvalue']), c=colors, alpha=0.5, s=15) # Add labels with adjust_text to avoid overlaps texts = [] for idx in top_idx: texts.append(ax.text(df.loc[idx, 'log2FoldChange'], -np.log10(df.loc[idx, 'pvalue']), df.loc[idx, 'gene'], fontsize=8)) adjust_text(texts, arrowprops=dict(arrowstyle='-', color='gray', lw=0.5)) plt.tight_layout()
r# Standard thresholds # FC > 1 (2-fold change): Common for RNA-seq, may miss subtle changes # FC > 0.58 (~1.5-fold): More sensitive, use for subtle effects # padj < 0.05: Standard FDR threshold # padj < 0.01: Stringent, fewer false positives # padj < 0.1: Relaxed, use for exploratory analysis # Adjust thresholds based on your data pval_threshold <- 0.05 fc_threshold <- 1 # log2 scale df$significance <- case_when( df$padj < pval_threshold & df$log2FoldChange > fc_threshold ~ 'Up', df$padj < pval_threshold & df$log2FoldChange < -fc_threshold ~ 'Down', TRUE ~ 'NS' )
r# R - high resolution ggsave('volcano.pdf', width = 8, height = 6) ggsave('volcano.png', width = 8, height = 6, dpi = 300) # EnhancedVolcano returns ggplot object p <- EnhancedVolcano(df, lab = df$gene, x = 'log2FoldChange', y = 'pvalue') ggsave('volcano.pdf', p, width = 10, height = 8)
python# Python plt.savefig('volcano.pdf', bbox_inches='tight') plt.savefig('volcano.png', dpi=300, bbox_inches='tight')
<!-- 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 | 12,562 | 9,891 | -21% | 1 | 1 | 0% | 2,910 | 4,550 | +56% | 0 | 0 | — |
case-02 | fail→fail | 14,827 | 11,962 | -19% | 1 | 1 | 0% | 3,334 | 4,961 | +49% | 0 | 0 | — |
case-03 | fail→fail | 10,528 | 10,236 | -3% | 1 | 1 | 0% | 2,029 | 4,515 | +123% | 0 | 0 | — |
case-04 | fail→pass | 8,459 | 6,970 | -18% | 1 | 1 | 0% | 1,615 | 3,529 | +119% | 0 | 0 | — |
case-05 | fail→fail | 11,277 | 8,447 | -25% | 1 | 1 | 0% | 2,034 | 3,866 | +90% | 0 | 0 | — |
case-06 | fail→fail | 14,926 | 10,438 | -30% | 1 | 1 | 0% | 2,767 | 4,240 | +53% | 0 | 0 | — |
case-07 | fail→pass | 10,397 | 6,038 | -42% | 1 | 1 | 0% | 2,124 | 3,538 | +67% | 0 | 0 | — |
case-08 | pass→pass | 4,899 | 3,162 | -35% | 1 | 1 | 0% | 923 | 2,989 | +224% | 0 | 0 | — |
case-09 | fail→fail | 4,938 | 4,367 | -12% | 1 | 1 | 0% | 967 | 3,130 | +224% | 0 | 0 | — |
case-10 | fail→pass | 11,065 | 5,385 | -51% | 1 | 1 | 0% | 2,099 | 3,428 | +63% | 0 | 0 | — |
case-11 | fail→fail | 14,482 | 10,326 | -29% | 1 | 1 | 0% | 3,117 | 4,587 | +47% | 0 | 0 | — |
case-12 | fail→pass | 11,977 | 7,899 | -34% | 1 | 1 | 0% | 2,548 | 3,977 | +56% | 0 | 0 | — |
case-13 | pass→pass | 9,042 | 6,424 | -29% | 1 | 1 | 0% | 1,923 | 3,694 | +92% | 0 | 0 | — |
case-14 | pass→pass | 11,747 | 6,354 | -46% | 1 | 1 | 0% | 2,142 | 3,583 | +67% | 0 | 0 | — |
case-15 | pass→pass | 15,939 | 12,945 | -19% | 1 | 1 | 0% | 2,842 | 4,588 | +61% | 0 | 0 | — |
case-16 | fail→fail | 15,201 | 8,680 | -43% | 1 | 1 | 0% | 2,704 | 3,872 | +43% | 0 | 0 | — |
case-17 | fail→pass | 10,924 | 6,877 | -37% | 1 | 1 | 0% | 1,876 | 3,554 | +89% | 0 | 0 | — |
case-18 | pass→pass | 6,856 | 4,069 | -41% | 1 | 1 | 0% | 1,372 | 3,040 | +122% | 0 | 0 | — |
case-19 | pass→pass | 7,673 | 5,657 | -26% | 1 | 1 | 0% | 1,477 | 3,133 | +112% | 0 | 0 | — |
case-20 | pass→pass | 11,204 | 10,151 | -9% | 1 | 1 | 0% | 2,306 | 4,291 | +86% | 0 | 0 | — |
case-21 | pass→pass | 13,978 | 11,799 | -16% | 1 | 1 | 0% | 2,828 | 4,447 | +57% | 0 | 0 | — |
case-22 | pass→pass | 13,348 | 10,003 | -25% | 1 | 1 | 0% | 2,858 | 4,267 | +49% | 0 | 0 | — |
case-23 | fail→pass | 11,355 | 4,387 | -61% | 1 | 1 | 0% | 2,312 | 3,283 | +42% | 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. 23 cases were attempted. The headline lift of +26 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
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 | 0% |
Other measured skills in the registry, with their headline benchmark lift.