Raw counts snapshot and library sizes
counts <- assay(airway)
lib_sizes <- colSums(counts)
ggplot(
data.frame(
sample = rownames(meta),
lib_millions = lib_sizes / 1e6,
dex = meta$dex,
cell = meta$cell
),
aes(x = reorder(sample, lib_millions), y = lib_millions, fill = dex)
) +
geom_col(color = "white", width = 0.8) +
coord_flip() +
labs(
x = "Sample",
y = "Library size (millions of reads)",
fill = "Dexamethasone",
title = "Library size per sample"
) +
geom_hline(
yintercept = median(lib_sizes / 1e6),
linetype = "dashed",
color = "gray30"
) +
theme(plot.title = element_text(face = "bold"))

DESeq2 object and normalization
dds <- DESeqDataSetFromMatrix(
countData = counts,
colData = meta,
design = ~ cell + dex
)
dds <- dds[rowSums(counts(dds)) > 1, ]
dds <- DESeq(dds)
vsd <- vst(dds, blind = FALSE)
Sample-level QC
sampleDists <- dist(t(assay(vsd)))
sampleDistMatrix <- as.matrix(sampleDists)
rownames(sampleDistMatrix) <- colnames(vsd)
colnames(sampleDistMatrix) <- colnames(vsd)
ann <- meta[, c("dex", "cell")]
pheatmap(
sampleDistMatrix,
clustering_distance_rows = sampleDists,
clustering_distance_cols = sampleDists,
annotation_col = ann,
main = "Sample-to-sample distances"
)

pca_data <- plotPCA(vsd, intgroup = c("dex", "cell"), returnData = TRUE)
percentVar <- round(100 * attr(pca_data, "percentVar"))
ggplot(pca_data, aes(PC1, PC2, color = dex, shape = cell)) +
geom_point(size = 4, alpha = 0.9) +
labs(
x = paste0("PC1: ", percentVar[1], "%"),
y = paste0("PC2: ", percentVar[2], "%"),
title = "PCA on vst-normalized counts"
) +
theme(plot.title = element_text(face = "bold"))

Differential expression results
coef_name <- tail(resultsNames(dds), 1)
res_raw <- results(dds, name = coef_name)
res <- lfcShrink(dds, coef = coef_name, type = "normal")
res_df <- as.data.frame(res[order(res$padj), ])
knitr::kable(
head(res_df, 10),
digits = 3,
caption = "Top 10 differentially expressed genes (shrunken LFC)"
)
Top 10 differentially expressed genes (shrunken LFC)
| ENSG00000152583 |
997.440 |
-4.079 |
0.162 |
-24.856 |
0 |
0 |
| ENSG00000165995 |
495.093 |
-3.089 |
0.124 |
-24.713 |
0 |
0 |
| ENSG00000120129 |
3409.029 |
-2.796 |
0.115 |
-24.274 |
0 |
0 |
| ENSG00000101347 |
12703.387 |
-3.475 |
0.143 |
-24.235 |
0 |
0 |
| ENSG00000189221 |
2341.767 |
-3.116 |
0.132 |
-23.653 |
0 |
0 |
| ENSG00000211445 |
12285.615 |
-3.389 |
0.151 |
-22.495 |
0 |
0 |
| ENSG00000157214 |
3009.263 |
-1.922 |
0.087 |
-21.968 |
0 |
0 |
| ENSG00000162614 |
5393.102 |
-1.973 |
0.091 |
-21.614 |
0 |
0 |
| ENSG00000125148 |
3656.253 |
-2.125 |
0.102 |
-20.929 |
0 |
0 |
| ENSG00000154734 |
30315.135 |
-2.233 |
0.110 |
-20.254 |
0 |
0 |
summary(res_raw)
##
## out of 29391 with nonzero total read count
## adjusted p-value < 0.1
## LFC > 0 (up) : 2218, 7.5%
## LFC < 0 (down) : 2607, 8.9%
## outliers [1] : 0, 0%
## low counts [2] : 11397, 39%
## (mean count < 5)
## [1] see 'cooksCutoff' argument of ?results
## [2] see 'independentFiltering' argument of ?results
cat("Genes with padj < 0.05:", sum(res_raw$padj < 0.05, na.rm = TRUE), "\n")
## Genes with padj < 0.05: 3994
Global views of differential signal
plotMA(res_raw, ylim = c(-5, 5), main = "MA plot (unshrunken results)")

EnhancedVolcano(
res,
lab = rownames(res),
x = "log2FoldChange",
y = "padj",
xlab = "log2 fold change",
ylab = "-log10(padj)",
title = "Volcano: dex treatment effect",
subtitle = coef_name,
pCutoff = 0.05,
FCcutoff = 1.0,
pointSize = 2.5,
labSize = 3.5,
colAlpha = 0.7,
legendPosition = "right",
legendLabSize = 10
)

Heatmap of strongest changes
top_genes <- rownames(res_df)[1:30]
top_genes <- top_genes[!is.na(top_genes)]
mat <- assay(vsd)[top_genes, ]
mat <- mat - rowMeans(mat)
pheatmap(
mat,
annotation_col = ann,
cluster_rows = TRUE,
cluster_cols = TRUE,
show_rownames = TRUE,
color = colorRampPalette(rev(brewer.pal(9, "RdBu")))(50),
main = "Top 30 DE genes (z-scored per gene)"
)

Spotlight on the top gene
top_gene <- top_genes[1]
gene_df <- data.frame(
expression = assay(vsd)[top_gene, ],
dex = meta$dex,
cell = meta$cell,
sample = rownames(meta)
)
ggplot(gene_df, aes(dex, expression, fill = dex)) +
geom_boxplot(alpha = 0.7, outlier.color = NA) +
geom_jitter(aes(color = cell), width = 0.12, size = 3, alpha = 0.9) +
labs(
title = paste("Expression of", top_gene),
x = "Condition",
y = "vst expression"
) +
scale_fill_brewer(palette = "Set2") +
scale_color_brewer(palette = "Dark2") +
theme(plot.title = element_text(face = "bold"))

Session info
sessionInfo()
## R version 4.3.2 (2023-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 11 x64 (build 26100)
##
## Matrix products: default
##
##
## locale:
## [1] LC_COLLATE=English_United States.utf8
## [2] LC_CTYPE=English_United States.utf8
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C
## [5] LC_TIME=English_United States.utf8
##
## time zone: Asia/Singapore
## tzcode source: internal
##
## attached base packages:
## [1] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] RColorBrewer_1.1-3 pheatmap_1.0.13
## [3] EnhancedVolcano_1.20.0 ggrepel_0.9.6
## [5] ggplot2_4.0.1 DESeq2_1.42.1
## [7] airway_1.22.0 SummarizedExperiment_1.32.0
## [9] Biobase_2.62.0 GenomicRanges_1.54.1
## [11] GenomeInfoDb_1.38.8 IRanges_2.36.0
## [13] S4Vectors_0.40.2 BiocGenerics_0.48.1
## [15] MatrixGenerics_1.14.0 matrixStats_1.5.0
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 xfun_0.52 bslib_0.9.0
## [4] lattice_0.21-9 vctrs_0.6.5 tools_4.3.2
## [7] bitops_1.0-9 generics_0.1.3 parallel_4.3.2
## [10] tibble_3.2.1 fansi_1.0.6 pkgconfig_2.0.3
## [13] Matrix_1.6-1.1 S7_0.2.0 lifecycle_1.0.4
## [16] GenomeInfoDbData_1.2.11 compiler_4.3.2 farver_2.1.1
## [19] codetools_0.2-19 htmltools_0.5.8.1 sass_0.4.9
## [22] RCurl_1.98-1.17 yaml_2.3.10 pillar_1.9.0
## [25] crayon_1.5.2 jquerylib_0.1.4 BiocParallel_1.36.0
## [28] DelayedArray_0.28.0 cachem_1.1.0 abind_1.4-8
## [31] tidyselect_1.2.1 locfit_1.5-9.12 digest_0.6.37
## [34] dplyr_1.1.4 labeling_0.4.3 fastmap_1.2.0
## [37] grid_4.3.2 colorspace_2.1-0 cli_3.6.2
## [40] SparseArray_1.2.4 magrittr_2.0.3 S4Arrays_1.2.1
## [43] utf8_1.2.4 withr_3.0.2 scales_1.4.0
## [46] rmarkdown_2.30 XVector_0.42.0 evaluate_1.0.3
## [49] knitr_1.50 rlang_1.1.3 Rcpp_1.0.12
## [52] glue_1.7.0 rstudioapi_0.17.1 jsonlite_2.0.0
## [55] R6_2.5.1 zlibbioc_1.48.2