forked from carpentries-incubator/bioc-rnaseq
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New translations 04-exploratory-qc.md (French)
- Loading branch information
Showing
1 changed file
with
345 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,345 @@ | ||
--- | ||
source: Rmd | ||
title: Exploratory analysis and quality control | ||
teaching: 120 | ||
exercises: 60 | ||
editor_options: | ||
chunk_output_type: console | ||
--- | ||
|
||
```{r source-download-data, echo=FALSE, purl=FALSE, message=FALSE, warning = FALSE} | ||
source("download_data.R") | ||
``` | ||
|
||
::::::::::::::::::::::::::::::::::::::: objectives | ||
|
||
- Learn how to explore the gene expression matrix and perform common quality control steps. | ||
- Learn how to set up an interactive application for exploratory analysis. | ||
|
||
:::::::::::::::::::::::::::::::::::::::::::::::::: | ||
|
||
:::::::::::::::::::::::::::::::::::::::: questions | ||
|
||
- Why is exploratory analysis an essential part of an RNA-seq analysis? | ||
- How should one preprocess the raw count matrix for exploratory analysis? | ||
- Are two dimensions sufficient to represent your data? | ||
|
||
:::::::::::::::::::::::::::::::::::::::::::::::::: | ||
|
||
## Load packages | ||
|
||
Assuming you just started RStudio again, load some packages we will use in this lesson along with the `SummarizedExperiment` object we created in the last lesson. | ||
|
||
```{r load-pkg} | ||
suppressPackageStartupMessages({ | ||
library(SummarizedExperiment) | ||
library(DESeq2) | ||
library(vsn) | ||
library(ggplot2) | ||
library(ComplexHeatmap) | ||
library(RColorBrewer) | ||
library(hexbin) | ||
library(iSEE) | ||
}) | ||
``` | ||
|
||
```{r read-se} | ||
se <- readRDS("data/GSE96870_se.rds") | ||
``` | ||
|
||
## Remove unexpressed genes | ||
|
||
Exploratory analysis is crucial for quality control and to get to know our data. | ||
It can help us detect quality problems, sample swaps and contamination, as well as give us a sense of the most salient patterns present in the data. | ||
In this episode, we will learn about two common ways of performing exploratory analysis for RNA-seq data; namely clustering and principal component analysis (PCA). | ||
These tools are in no way limited to (or developed for) analysis of RNA-seq data. | ||
However, there are certain characteristics of count assays that need to be taken into account when they are applied to this type of data. First of all, not all mouse genes in the genome will be expressed in our Cerebellum samples. There are many different threshold you could use to say whether a gene's expression was detectable or not; here we are going to use a very minimal one that if a gene does not have more than 5 counts total across all samples, there is simply not enough data to be able to do anything with it anyway. | ||
|
||
```{r filter-lowexpr} | ||
nrow(se) | ||
# Remove genes/rows that do not have > 5 total counts | ||
se <- se[rowSums(assay(se, "counts")) > 5, ] | ||
nrow(se) | ||
``` | ||
|
||
::::::::::::::::::::::::::::::::::::::: challenge | ||
|
||
## Challenge: What kind of genes survived this filtering? | ||
|
||
Last episode we discussed subsetting down to only mRNA genes. Here we subsetted based on a minimal expression level. | ||
|
||
1. How many of each type of gene survived the filtering? | ||
2. Compare the number of genes that survived filtering using different thresholds. | ||
3. What are pros and cons of more aggressive filtering? What are important considerations? | ||
|
||
:::::::::::::::::::::::::::::::::::::::::::::::::: | ||
|
||
::::::::::::::::::::::::::::::::::: solution | ||
|
||
1. | ||
|
||
```{r table-gbkey} | ||
table(rowData(se)$gbkey) | ||
``` | ||
|
||
2. | ||
|
||
```{r filter thresholds} | ||
nrow(se) # represents the number of genes using 5 as filtering threshold | ||
length(which(rowSums(assay(se, "counts")) > 10)) | ||
length(which(rowSums(assay(se, "counts")) > 20)) | ||
``` | ||
|
||
3. | ||
|
||
Cons: Risk of removing interesting information | ||
Pros: | ||
|
||
- Not or lowly expressed genes are unlikely to be biological meaningful. | ||
- Reduces number of statistical tests (multiple testing). | ||
- More reliable estimation of mean-variance relationship | ||
|
||
Potential considerations: | ||
|
||
- Is a gene expressed in both groups? | ||
- How many samples of each group express a gene? | ||
::::::::::::::::::::::::::::::::::: | ||
|
||
## Library size differences | ||
|
||
Differences in the total number of reads assigned to genes between samples typically occur for technical reasons. In practice, it means that we can not simply compare a gene's raw read count directly between samples and conclude that a sample with a higher read count also expresses the gene more strongly - the higher count may be caused by an overall higher number of reads in that sample. | ||
In the rest of this section, we will use the term _library size_ to refer to the total number of reads assigned to genes for a sample. First we should compare the library sizes of all samples. | ||
|
||
```{r lib-size} | ||
# Add in the sum of all counts | ||
se$libSize <- colSums(assay(se)) | ||
# Plot the libSize by using R's native pipe |> | ||
# to extract the colData, turn it into a regular | ||
# data frame then send to ggplot: | ||
colData(se) |> | ||
as.data.frame() |> | ||
ggplot(aes(x = Label, y = libSize / 1e6, fill = Group)) + | ||
geom_bar(stat = "identity") + theme_bw() + | ||
labs(x = "Sample", y = "Total count in millions") + | ||
theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) | ||
``` | ||
|
||
We need to adjust for the differences in library size between samples, to avoid drawing incorrect conclusions. The way this is typically done for RNA-seq data can be described as a two-step procedure. | ||
First, we estimate _size factors_ - sample-specific correction factors such that if the raw counts were to be divided by these factors, the resulting values would be more comparable across samples. | ||
Next, these size factors are incorporated into the statistical analysis of the data. | ||
It is important to pay close attention to how this is done in practice for a given analysis method. | ||
Sometimes the division of the counts by the size factors needs to be done explicitly by the analyst. | ||
Other times (as we will see for the differential expression analysis) it is important that they are provided separately to the analysis tool, which will then use them appropriately in the statistical model. | ||
|
||
With `DESeq2`, size factors are calculated using the `estimateSizeFactors()` function. | ||
The size factors estimated by this function combines an adjustment for differences in library sizes with an adjustment for differences in the RNA composition of the samples. | ||
The latter is important due to the compositional nature of RNA-seq data. | ||
There is a fixed number of reads to distribute between the genes, and if a single (or a few) very highly expressed gene consume a large part of the reads, all other genes will consequently receive very low counts. We now switch our `SummarizedExperiment` object over to a `DESeqDataSet` as it has the internal structure to store these size factors. We also need to tell it our main experiment design, which is sex and time: | ||
|
||
```{r est-size-factors} | ||
dds <- DESeq2::DESeqDataSet(se, design = ~ sex + time) | ||
dds <- estimateSizeFactors(dds) | ||
# Plot the size factors against library size | ||
# and look for any patterns by group: | ||
ggplot(data.frame(libSize = colSums(assay(dds)), | ||
sizeFactor = sizeFactors(dds), | ||
Group = dds$Group), | ||
aes(x = libSize, y = sizeFactor, col = Group)) + | ||
geom_point(size = 5) + theme_bw() + | ||
labs(x = "Library size", y = "Size factor") | ||
``` | ||
|
||
## Transform data | ||
|
||
There is a rich literature on methods for exploratory analysis. | ||
Most of these work best in situations where the variance of the input data (here, each gene) is relatively independent of the average value. | ||
For read count data such as RNA-seq, this is not the case. | ||
In fact, the variance increases with the average read count. | ||
|
||
```{r mean-sd-plot-raw} | ||
meanSdPlot(assay(dds), ranks = FALSE) | ||
``` | ||
|
||
There are two ways around this: either we develop methods specifically adapted to count data, or we adapt (transform) the count data so that the existing methods are applicable. | ||
Both ways have been explored; however, at the moment the second approach is arguably more widely applied in practice. We can transform our data using DESeq2's variance stabilizing transformation and then verify that it has removed the correlation between average read count and variance. | ||
|
||
```{r mean-sd-plot-vst} | ||
vsd <- DESeq2::vst(dds, blind = TRUE) | ||
meanSdPlot(assay(vsd), ranks = FALSE) | ||
``` | ||
|
||
## Heatmaps and clustering | ||
|
||
There are many ways to cluster samples based on their similarity of expression patterns. One simple way is to calculate Euclidean distances between all pairs of samples (longer distance = more different) and then display the results with both a branching dendrogram and a heatmap to visualize the distances in color. From this, we infer that the Day 8 samples are more similar to each other than the rest of the samples, although Day 4 and Day 0 do not separate distinctly. Instead, males and females reliably separate. | ||
|
||
```{r heatmap} | ||
dst <- dist(t(assay(vsd))) | ||
colors <- colorRampPalette(brewer.pal(9, "Blues"))(255) | ||
ComplexHeatmap::Heatmap( | ||
as.matrix(dst), | ||
col = colors, | ||
name = "Euclidean\ndistance", | ||
cluster_rows = hclust(dst), | ||
cluster_columns = hclust(dst), | ||
bottom_annotation = columnAnnotation( | ||
sex = vsd$sex, | ||
time = vsd$time, | ||
col = list(sex = c(Female = "red", Male = "blue"), | ||
time = c(Day0 = "yellow", Day4 = "forestgreen", Day8 = "purple"))) | ||
) | ||
``` | ||
|
||
## PCA | ||
|
||
Principal component analysis is a dimensionality reduction method, which projects the samples into a lower-dimensional space. | ||
This lower-dimensional representation can be used for visualization, or as the input for other analysis methods. | ||
The principal components are defined in such a way that they are orthogonal, and that the projection of the samples into the space they span contains as much variance as possible. | ||
It is an _unsupervised_ method in the sense that no external information about the samples (e.g., the treatment condition) is taken into account. | ||
In the plot below we represent the samples in a two-dimensional principal component space. | ||
For each of the two dimensions, we indicate the fraction of the total variance that is represented by that component. | ||
By definition, the first principal component will always represent more of the variance than the subsequent ones. | ||
The fraction of explained variance is a measure of how much of the 'signal' in the data that is retained when we project the samples from the original, high-dimensional space to the low-dimensional space for visualization. | ||
|
||
```{r pca} | ||
pcaData <- DESeq2::plotPCA(vsd, intgroup = c("sex", "time"), | ||
returnData = TRUE) | ||
percentVar <- round(100 * attr(pcaData, "percentVar")) | ||
ggplot(pcaData, aes(x = PC1, y = PC2)) + | ||
geom_point(aes(color = sex, shape = time), size = 5) + | ||
theme_minimal() + | ||
xlab(paste0("PC1: ", percentVar[1], "% variance")) + | ||
ylab(paste0("PC2: ", percentVar[2], "% variance")) + | ||
coord_fixed() + | ||
scale_color_manual(values = c(Male = "blue", Female = "red")) | ||
``` | ||
|
||
::::::::::::::::::::::::::::::::::::::: challenge | ||
|
||
## Challenge: Discuss the following points with your neighbour | ||
|
||
1. Assume you are mainly interested in expression changes associated with the time after infection (Reminder Day0 -> before infection). What do you need to consider in downstream analysis? | ||
|
||
2. Consider an experimental design where you have multiple samples from the same donor. You are still interested in differences by time and observe the following PCA plot. What does this PCA plot suggest? | ||
|
||
```{r pca-exercise, eval=TRUE, echo=FALSE, include=TRUE} | ||
vsd_ex <- vsd | ||
vsd_ex$sample <- vsd_ex$Group | ||
levels(vsd_ex$sample) <- c("sampleA", "sampleB", "sampleC", | ||
"sampleD", "sampleE", "sampleF") | ||
vsd_ex$time <- sample(vsd_ex$time) | ||
pcaDataEx <- DESeq2::plotPCA(vsd_ex, intgroup = c("sample", "time"), | ||
returnData = TRUE) | ||
percentVar <- round(100 * attr(pcaDataEx, "percentVar")) | ||
ggplot(pcaDataEx, aes(x = PC1, y = PC2)) + | ||
geom_point(aes(color = sample, shape = time), size = 5) + | ||
theme_minimal() + | ||
xlab(paste0("PC1: ", percentVar[1], "% variance")) + | ||
ylab(paste0("PC2: ", percentVar[2], "% variance")) + | ||
coord_fixed() | ||
``` | ||
|
||
:::::::::::::::::::::::::::::::::::::::::::::::::: | ||
|
||
::::::::::::::::::::::::::::::::::: solution | ||
|
||
1. The major signal in this data (37% variance) is associated with sex. As we are not interested in sex-specific changes over time, we need to adjust for this in downstream analysis (see [next episodes](../episodes/05-differential-expression.Rmd)) and keep it in mind for further exploratory downstream analysis. A possible way to do so is to remove genes on sex chromosomes. | ||
|
||
2. | ||
|
||
- A strong donor effect, that needs to be accounted for. | ||
- What does PC1 (37% variance) represent? Looks like 2 donor groups? | ||
- No association of PC1 and PC2 with time --> no or weak transcriptional effect of time | ||
\--> Check association with higher PCs (e.g., PC3,PC4, ..) | ||
|
||
::::::::::::::::::::::::::::::::::: | ||
|
||
::::::::::::::::::::::::::::::::::::::: challenge | ||
|
||
## Challenge: Plot the PCA colored by library sizes. | ||
|
||
Compare before and after variance stabilizing transformation. | ||
|
||
_Hint: The `DESeq2::plotPCA` expect an object of the class `DESeqTransform` as input. You can transform a `SummarizedExperiment` object using `plotPCA(DESeqTransform(se))`_ | ||
|
||
:::::::::::::::::::::::::::::::::::::::::::::::::: | ||
|
||
::::::::::::::::::::::::::::::::::: solution | ||
|
||
```{r pca-lib} | ||
pcaDataVst <- DESeq2::plotPCA(vsd, intgroup = c("libSize"), | ||
returnData = TRUE) | ||
percentVar <- round(100 * attr(pcaDataVst, "percentVar")) | ||
ggplot(pcaDataVst, aes(x = PC1, y = PC2)) + | ||
geom_point(aes(color = libSize / 1e6), size = 5) + | ||
theme_minimal() + | ||
xlab(paste0("PC1: ", percentVar[1], "% variance")) + | ||
ylab(paste0("PC2: ", percentVar[2], "% variance")) + | ||
coord_fixed() + | ||
scale_color_continuous("Total count in millions", type = "viridis") | ||
``` | ||
|
||
```{r pca-lib-vst} | ||
pcaDataCts <- DESeq2::plotPCA(DESeqTransform(se), intgroup = c("libSize"), | ||
returnData = TRUE) | ||
percentVar <- round(100 * attr(pcaDataCts, "percentVar")) | ||
ggplot(pcaDataCts, aes(x = PC1, y = PC2)) + | ||
geom_point(aes(color = libSize / 1e6), size = 5) + | ||
theme_minimal() + | ||
xlab(paste0("PC1: ", percentVar[1], "% variance")) + | ||
ylab(paste0("PC2: ", percentVar[2], "% variance")) + | ||
coord_fixed() + | ||
scale_color_continuous("Total count in millions", type = "viridis") | ||
``` | ||
|
||
::::::::::::::::::::::::::::::::::: | ||
|
||
## Interactive exploratory data analysis | ||
|
||
Often it is useful to look at QC plots in an interactive way to directly explore different experimental factors or get insides from someone without coding experience. | ||
Useful tools for interactive exploratory data analysis for RNA-seq are [Glimma](https://bioconductor.org/packages/release/bioc/html/Glimma.html) and [iSEE](https://bioconductor.org/packages/release/bioc/html/iSEE.html) | ||
|
||
::::::::::::::::::::::::::::::::::::::: challenge | ||
|
||
## Challenge: Interactively explore our data using iSEE | ||
|
||
```{r isee, eval=FALSE} | ||
## Convert DESeqDataSet object to a SingleCellExperiment object, in order to | ||
## be able to store the PCA representation | ||
sce <- as(dds, "SingleCellExperiment") | ||
## Add PCA to the 'reducedDim' slot | ||
stopifnot(rownames(pcaData) == colnames(sce)) | ||
reducedDim(sce, "PCA") <- as.matrix(pcaData[, c("PC1", "PC2")]) | ||
## Add variance-stabilized data as a new assay | ||
stopifnot(colnames(vsd) == colnames(sce)) | ||
assay(sce, "vsd") <- assay(vsd) | ||
app <- iSEE(sce) | ||
shiny::runApp(app) | ||
``` | ||
|
||
:::::::::::::::::::::::::::::::::::::::::::::::::: | ||
|
||
## Session info | ||
|
||
```{r session-info} | ||
sessionInfo() | ||
``` | ||
|
||
:::::::::::::::::::::::::::::::::::::::: keypoints | ||
|
||
- Exploratory analysis is essential for quality control and to detect potential problems with a data set. | ||
- Different classes of exploratory analysis methods expect differently preprocessed data. The most commonly used methods expect counts to be normalized and log-transformed (or similar- more sensitive/sophisticated), to be closer to homoskedastic. Other methods work directly on the raw counts. | ||
|
||
:::::::::::::::::::::::::::::::::::::::::::::::::: |