Background - volcano plots are incredible useful visuals for taking large amount of genomic data and visualizing patterns and differences between genes. To get a feel for how we create and interpret these plots, please follow the tutorial found here: https://bioconductor.org/packages/release/bioc/vignettes/EnhancedVolcano/inst/doc/EnhancedVolcano.html
This tutorial utilizes bioconductor tools. NOTE: I had trouble getting some of these packages to load in my Desktop version of R/R Studio - I was getting an error message that the packages were not available for my version of the software. I was able to complete the tutorial using the Posit Cloud R Studio. If you run into this issue, I suggest you update your R software and/or try it on the cloud.
You can also force R to download a package using the following command:
install.packages(“your_package”, force = T)
if (!requireNamespace('BiocManager', quietly = TRUE))
install.packages('BiocManager')
BiocManager::install('EnhancedVolcano')
devtools::install_github('kevinblighe/EnhancedVolcano')
Loading Package
library(EnhancedVolcano)
## Loading required package: ggplot2
## Loading required package: ggrepel
library(airway) # older package, use: BiocManager::install("airway")
## Loading required package: SummarizedExperiment
## Loading required package: MatrixGenerics
## Loading required package: matrixStats
##
## Attaching package: 'MatrixGenerics'
## The following objects are masked from 'package:matrixStats':
##
## colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse,
## colCounts, colCummaxs, colCummins, colCumprods, colCumsums,
## colDiffs, colIQRDiffs, colIQRs, colLogSumExps, colMadDiffs,
## colMads, colMaxs, colMeans2, colMedians, colMins, colOrderStats,
## colProds, colQuantiles, colRanges, colRanks, colSdDiffs, colSds,
## colSums2, colTabulates, colVarDiffs, colVars, colWeightedMads,
## colWeightedMeans, colWeightedMedians, colWeightedSds,
## colWeightedVars, rowAlls, rowAnyNAs, rowAnys, rowAvgsPerColSet,
## rowCollapse, rowCounts, rowCummaxs, rowCummins, rowCumprods,
## rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps,
## rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians, rowMins,
## rowOrderStats, rowProds, rowQuantiles, rowRanges, rowRanks,
## rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs, rowVars,
## rowWeightedMads, rowWeightedMeans, rowWeightedMedians,
## rowWeightedSds, rowWeightedVars
## Loading required package: GenomicRanges
## Loading required package: stats4
## Loading required package: BiocGenerics
## Loading required package: generics
##
## Attaching package: 'generics'
## The following objects are masked from 'package:base':
##
## as.difftime, as.factor, as.ordered, intersect, is.element, setdiff,
## setequal, union
##
## Attaching package: 'BiocGenerics'
## The following objects are masked from 'package:stats':
##
## IQR, mad, sd, var, xtabs
## The following objects are masked from 'package:base':
##
## anyDuplicated, aperm, append, as.data.frame, basename, cbind,
## colnames, dirname, do.call, duplicated, eval, evalq, Filter, Find,
## get, grep, grepl, is.unsorted, lapply, Map, mapply, match, mget,
## order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank,
## rbind, Reduce, rownames, sapply, saveRDS, table, tapply, unique,
## unsplit, which.max, which.min
## Loading required package: S4Vectors
##
## Attaching package: 'S4Vectors'
## The following object is masked from 'package:utils':
##
## findMatches
## The following objects are masked from 'package:base':
##
## expand.grid, I, unname
## Loading required package: IRanges
##
## Attaching package: 'IRanges'
## The following object is masked from 'package:grDevices':
##
## windows
## Loading required package: Seqinfo
## Loading required package: Biobase
## Welcome to Bioconductor
##
## Vignettes contain introductory material; view with
## 'browseVignettes()'. To cite Bioconductor, see
## 'citation("Biobase")', and for packages 'citation("pkgname")'.
##
## Attaching package: 'Biobase'
## The following object is masked from 'package:MatrixGenerics':
##
## rowMedians
## The following objects are masked from 'package:matrixStats':
##
## anyMissing, rowMedians
library(magrittr)
##
## Attaching package: 'magrittr'
## The following object is masked from 'package:GenomicRanges':
##
## subtract
Loading in data and reordering the trt group
data('airway')
airway$dex %>%
relevel('untrt')
## [1] untrt trt untrt trt untrt trt untrt trt
## Levels: untrt trt
Annotate Ensembl Gene IDs to gene symbols:
ens <- rownames(airway)
library(org.Hs.eg.db) # use: if (!require("BiocManager", quietly = TRUE))
## Loading required package: AnnotationDbi
##
# install.packages("BiocManager")
# BiocManager::install("org.Hs.eg.db")
symbols <- mapIds(org.Hs.eg.db, keys = ens,
column = c('SYMBOL'), keytype = 'ENSEMBL')
## 'select()' returned 1:many mapping between keys and columns
symbols <- symbols[!is.na(symbols)]
symbols <- symbols[match(rownames(airway), names(symbols))]
rownames(airway) <- symbols
keep <- !is.na(rownames(airway))
airway <- airway[keep,]
Conducting differential expression using DESeq2 to create 2 sets of results
library('DESeq2')
dds <- DESeqDataSet(airway, design = ~ cell + dex)
## Warning in DESeqDataSet(airway, design = ~cell + dex): 1345 duplicate rownames
## were renamed by adding numbers
dds <- DESeq(dds, betaPrior = FALSE)
## estimating size factors
## estimating dispersions
## gene-wise dispersion estimates
## mean-dispersion relationship
## final dispersion estimates
## fitting model and testing
res <- results(dds,
contrast = c('dex','trt','untrt'))
res <- lfcShrink(dds,
contrast = c('dex','trt','untrt'), res=res, type = 'normal')
## using 'normal' for LFC shrinkage, the Normal prior from Love et al (2014).
##
## Note that type='apeglm' and type='ashr' have shown to have less bias than type='normal'.
## See ?lfcShrink for more details on shrinkage type, and the DESeq2 vignette.
## Reference: https://doi.org/10.1093/bioinformatics/bty895
3.1 Plotting a basic volcano plot. Only a single dataframe, matrix, or tibble is needed that contains point labels, log2FC, and (un)adjusted p-values. Default log2FC cutoff is >|2| and p-value default cutoff is 10e-6
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue')
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## ℹ The deprecated feature was likely used in the EnhancedVolcano package.
## Please report the issue to the authors.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: The `size` argument of `element_line()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## ℹ The deprecated feature was likely used in the EnhancedVolcano package.
## Please report the issue to the authors.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
4 Getting into advanced features:
4.1 Modifying cut-offs for log2FC and P value, specifying titles, adjusting point size and label size
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
title = 'N0601011 versus N61311',
pCutoff = 10e-32,
FCcutoff = 0.5,
pointSize = 3.0,
labSize = 6.0)
4.2 Adjusting color and alpha for point shaping:
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
title = 'N0601011 versus N61311',
pCutoff = 10e-32,
FCcutoff = 0.5,
pointSize = 3.0,
labSize = 6.0,
col = c('black', 'black', 'black', 'red3'), # everything over cutoffs will be black, 'significant' genes are red
colAlpha = 1) # controls opacity
4.3 Adjusting the shape:
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
title = 'N0601011 versus N61311',
pCutoff = 10e-16,
FCcutoff = 1.5,
pointSize = 3.0,
labSize = 6.0,
shape = 8,
colAlpha = 1)
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
title = 'N0601011 versus N61311',
pCutoff = 10e-16,
FCcutoff = 1.5,
pointSize = 3.0,
labSize = 6.0,
shape = c(1, 4, 23, 25),
colAlpha = 1)
## Warning: The `guide` argument in `scale_*()` cannot be `FALSE`. This was deprecated in
## ggplot2 3.3.4.
## ℹ Please use "none" instead.
## ℹ The deprecated feature was likely used in the EnhancedVolcano package.
## Please report the issue to the authors.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
4.4 Adjusting cut-off lines and adding extra threshold lines:
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
xlim = c(-6, 6),
title = 'N0601011 versus N61311',
pCutoff = 10e-16,
FCcutoff = 1.5,
pointSize = 3.0,
labSize = 6.0,
shape = c(1, 4, 23, 25),
colAlpha = 1,
cutoffLineType = 'blank',
cutoffLineCol = 'black',
cutoffLineWidth = 0.8,
hline = c(10e-20,
10e-20 * 10e-30,
10e-20 * 10e-60,
10e-20 * 10e-90),
hlineCol = c('pink', 'hotpink', 'purple', 'black'),
hlineType = c ('solid', 'longdash', 'dotdash', 'dotted'),
hlineWidth = c(1.0, 1.5, 2.0, 2.5),
gridlines.major = FALSE,
gridlines.minor = FALSE)
4.5 Adjust legend position, size, and text
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
title = 'N0601011 versus N61311',
pCutoff = 10e-16,
FCcutoff = 1.5,
pointSize = 3.0,
labSize = 6.0,
shape = c(1, 4, 23, 25),
colAlpha = 1,
cutoffLineType = 'twodash',
cutoffLineWidth = 0.8,
legendLabels = c('Not sig.', 'Log (base 2)', 'p-value',
'p-value & Log (base 2) FC'), # use = 'none' to make legens invisible
legendPosition = 'right',
legendLabSize = 16,
legendIconSize = 5.0)
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
xlab = bquote(~Log[2]~ 'fold change'),
pCutoff = 10e-32,
FCcutoff = 2.0,
pointSize = 4.0,
labSize = 6.0,
colAlpha = 1,
legendPosition = 'right',
legendLabSize = 12,
legendIconSize = 4.0,
drawConnectors = TRUE,
widthConnectors = 0.75)
4.7 Labelling only key variables:
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
selectLab = c('TMEM176B','ADH1A'),
xlab = bquote(~Log[2]~ 'fold change'),
pCutoff = 10e-14,
FCcutoff = 2.0,
pointSize = 4.0,
labSize = 6.0,
shape = c(4, 35, 17, 18),
colAlpha = 1,
legendPosition = 'right',
legendLabSize = 14,
legendIconSize = 5.0)
4.8 Draw labels in boxes:
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
selectLab = c('VCAM1','KCTD12','ADAM12',
'CXCL12','CACNB2','SPARCL1','DUSP1','SAMHD1','MAOA'),
xlab = bquote(~Log[2]~ 'fold change'),
pCutoff = 10e-14,
FCcutoff = 2.0,
pointSize = 4.0,
labSize = 6.0,
labCol = 'black',
labFace = 'bold',
boxedLabels = TRUE,
colAlpha = 4/5,
legendPosition = 'right',
legendLabSize = 14,
legendIconSize = 4.0,
drawConnectors = TRUE,
widthConnectors = 1.0,
colConnectors = 'black')
4.9 Italicizing labels and flipping volcano horizontally:
lab_italics <- paste0("italic('", rownames(res), "')")
selectLab_italics = paste0(
"italic('",
c('VCAM1','KCTD12','ADAM12', 'CXCL12','CACNB2','SPARCL1','DUSP1','SAMHD1','MAOA'),
"')")
EnhancedVolcano(res,
lab = lab_italics,
x = 'log2FoldChange',
y = 'pvalue',
selectLab = selectLab_italics,
xlab = bquote(~Log[2]~ 'fold change'),
pCutoff = 10e-14,
FCcutoff = 1.0,
pointSize = 3.0,
labSize = 6.0,
labCol = 'black',
labFace = 'bold',
boxedLabels = TRUE,
parseLabels = TRUE,
col = c('black', 'pink', 'purple', 'red3'),
colAlpha = 4/5,
legendPosition = 'bottom',
legendLabSize = 14,
legendIconSize = 4.0,
drawConnectors = TRUE,
widthConnectors = 1.0,
colConnectors = 'black') + coord_flip()
## Coordinate system already present.
## ℹ Adding new coordinate system, which will replace the existing one.
4.10 Overriding coloring scheme with custom key-value pairs:
# create custom key-value pairs for 'high', 'low', 'mid' expression by fold-change
# this can be achieved with nested ifelse statements
keyvals <- ifelse(
res$log2FoldChange < -2.5, 'royalblue',
ifelse(res$log2FoldChange > 2.5, 'gold',
'black'))
keyvals[is.na(keyvals)] <- 'black'
names(keyvals)[keyvals == 'gold'] <- 'high'
names(keyvals)[keyvals == 'black'] <- 'mid'
names(keyvals)[keyvals == 'royalblue'] <- 'low'
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
selectLab = rownames(res)[which(names(keyvals) %in% c('high', 'low'))],
xlab = bquote(~Log[2]~ 'fold change'),
title = 'Custom colour over-ride',
pCutoff = 10e-14,
FCcutoff = 1.0,
pointSize = 3.5,
labSize = 4.5,
shape = c(6, 4, 2, 11),
colCustom = keyvals,
colAlpha = 1,
legendPosition = 'left',
legendLabSize = 15,
legendIconSize = 5.0,
drawConnectors = TRUE,
widthConnectors = 1.0,
colConnectors = 'black',
arrowheads = FALSE,
gridlines.major = TRUE,
gridlines.minor = FALSE,
border = 'partial',
borderWidth = 1.5,
borderColour = 'black')
4.11 Do the same but with shapes:
#running into problems in the end where the plots were too big for the display. By adjusting fig.width = and fig.height = in the {r} header, you can predetermine plot size
# define different cell-types that will be shaded
celltype1 <- c('VCAM1','KCTD12','ADAM12','CXCL12')
celltype2 <- c('CACNB2','SPARCL1','DUSP1','SAMHD1','MAOA')
# create custom key-value pairs for different cell-types
# this can be achieved with nested ifelse statements
keyvals.shape <- ifelse(
rownames(res) %in% celltype1, 17,
ifelse(rownames(res) %in% celltype2, 64,
3))
keyvals.shape[is.na(keyvals.shape)] <- 3
names(keyvals.shape)[keyvals.shape == 3] <- 'PBMC'
names(keyvals.shape)[keyvals.shape == 17] <- 'Cell-type 1'
names(keyvals.shape)[keyvals.shape == 64] <- 'Cell-type 2'
p1 <- EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
selectLab = rownames(res)[which(names(keyvals) %in% c('high', 'low'))],
xlab = bquote(~Log[2]~ 'fold change'),
title = 'Custom shape over-ride',
pCutoff = 10e-14,
FCcutoff = 1.0,
pointSize = 4.5,
labSize = 4.5,
shapeCustom = keyvals.shape,
colCustom = NULL,
colAlpha = 1,
legendLabSize = 15,
legendPosition = 'left',
legendIconSize = 5.0,
drawConnectors = TRUE,
widthConnectors = 0.5,
colConnectors = 'grey50',
gridlines.major = TRUE,
gridlines.minor = FALSE,
border = 'partial',
borderWidth = 1.5,
borderColour = 'black')
# create custom key-value pairs for 'high', 'low', 'mid' expression by fold-change
# this can be achieved with nested ifelse statements
keyvals.colour <- ifelse(
res$log2FoldChange < -2.5, 'royalblue',
ifelse(res$log2FoldChange > 2.5, 'gold',
'black'))
keyvals.colour[is.na(keyvals.colour)] <- 'black'
names(keyvals.colour)[keyvals.colour == 'gold'] <- 'high'
names(keyvals.colour)[keyvals.colour == 'black'] <- 'mid'
names(keyvals.colour)[keyvals.colour == 'royalblue'] <- 'low'
p2 <- EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
selectLab = rownames(res)[which(names(keyvals) %in% c('high', 'low'))],
xlab = bquote(~Log[2]~ 'fold change'),
title = 'Custom shape & colour over-ride',
pCutoff = 10e-14,
FCcutoff = 1.0,
pointSize = 5.5,
labSize = 0.0,
shapeCustom = keyvals.shape,
colCustom = keyvals.colour,
colAlpha = 1,
legendPosition = 'right',
legendLabSize = 15,
legendIconSize = 5.0,
drawConnectors = TRUE,
widthConnectors = 0.5,
colConnectors = 'grey50',
gridlines.major = TRUE,
gridlines.minor = FALSE,
border = 'full',
borderWidth = 1.0,
borderColour = 'black')
## Warning: The `size` argument of `element_rect()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## ℹ The deprecated feature was likely used in the EnhancedVolcano package.
## Please report the issue to the authors.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
library(gridExtra)
##
## Attaching package: 'gridExtra'
## The following object is masked from 'package:Biobase':
##
## combine
## The following object is masked from 'package:BiocGenerics':
##
## combine
library(grid)
grid.arrange(p1, p2,
ncol=2,
top = textGrob('EnhancedVolcano',
just = c('center'),
gp = gpar(fontsize = 32)))
4.12 Encircle/highlight certain variables
# define different cell-types that will be shaded
celltype1 <- c('VCAM1','CXCL12')
celltype2 <- c('SORT1', 'KLF15')
has_ggalt <- ! is(try(find.package("ggalt")), "try-error")
EnhancedVolcano(res,
lab = rownames(res),
x = 'log2FoldChange',
y = 'pvalue',
selectLab = c(if (has_ggalt) celltype1 else NULL, celltype2),
xlab = bquote(~Log[2]~ 'fold change'),
title = 'Shading cell-type 1|2',
pCutoff = 10e-14,
FCcutoff = 1.0,
pointSize = 8.0,
labSize = 6.0,
labCol = 'black',
labFace = 'bold',
boxedLabels = TRUE,
shape = 42,
colCustom = keyvals,
colAlpha = 1,
legendPosition = 'right',
legendLabSize = 20,
legendIconSize = 20.0,
# encircle
encircle = if (has_ggalt) celltype1 else NULL,
encircleCol = 'black',
encircleSize = 2.5,
encircleFill = 'pink',
encircleAlpha = 1/2,
# shade
shade = celltype2,
shadeAlpha = 1/2,
shadeFill = 'skyblue',
shadeSize = 1,
shadeBins = 5,
drawConnectors = TRUE,
widthConnectors = 2.0,
gridlines.major = TRUE,
gridlines.minor = FALSE,
border = 'full',
borderWidth = 5,
borderColour = 'black')
4.12 Highlighting key variables via custom point sizes
library("pasilla")
## Loading required package: DEXSeq
## Loading required package: BiocParallel
pasCts <- system.file("extdata", "pasilla_gene_counts.tsv",
package="pasilla", mustWork=TRUE)
pasAnno <- system.file("extdata", "pasilla_sample_annotation.csv",
package="pasilla", mustWork=TRUE)
cts <- as.matrix(read.csv(pasCts,sep="\t",row.names="gene_id"))
coldata <- read.csv(pasAnno, row.names=1)
coldata <- coldata[,c("condition","type")]
rownames(coldata) <- sub("fb", "", rownames(coldata))
cts <- cts[, rownames(coldata)]
library("DESeq2")
dds <- DESeqDataSetFromMatrix(countData = cts,
colData = coldata,
design = ~ condition)
## Warning in DESeqDataSet(se, design = design, ignoreRank): some variables in
## design formula are characters, converting to factors
featureData <- data.frame(gene=rownames(cts))
mcols(dds) <- DataFrame(mcols(dds), featureData)
dds <- DESeq(dds)
## estimating size factors
## estimating dispersions
## gene-wise dispersion estimates
## mean-dispersion relationship
## final dispersion estimates
## fitting model and testing
res <- results(dds)
p1 <- EnhancedVolcano(res,
lab = rownames(res),
x = "log2FoldChange",
y = "pvalue",
pCutoff = 10e-4,
FCcutoff = 2,
ylim = c(0, -log10(10e-12)),
pointSize = c(ifelse(res$log2FoldChange>2, 8, 1)),
labSize = 6.0,
shape = c(6, 6, 19, 16),
title = "DESeq2 results",
subtitle = "Differential expression",
caption = bquote(~Log[2]~ "fold change cutoff, 2; p-value cutoff, 10e-4"),
legendPosition = "right",
legendLabSize = 14,
col = c("grey30", "forestgreen", "royalblue", "red2"),
colAlpha = 0.9,
drawConnectors = TRUE,
hline = c(10e-8),
widthConnectors = 0.5)
p1
Questions: 1. Of all of the plots you made, which one do YOU think is easiest to interpret/tells the best story? - Honestly I think one of the earlier plots is the best for this scenario being the one made on step 4.3.