Tutorial: TPM calculation with library size normalization
Published:
This tutorial covers transcripts per million (TPM) calculation with library size normalization using edgeR. It was originally written in December 2019 for the Laboratory of Cell Systems, Institute for Protein Research, Suita, Osaka, Japan.
Setting up
Load the libraries we need.
library(tidyverse)
library(data.table)
library(edgeR)
Read the raw counts table. This example uses a featureCounts output.
counts <- read_csv(file = "D:/lab_rmd/tpm/counts_trimmed.csv")
head(counts)
## # A tibble: 6 x 10
## Geneid Chr Start End Strand Length B00_A01 B00_A02 B00_A03 B00_A04
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 ENSGALG0000005… 1;1;1;1;1;1;1;1;… 5273;5273;55… 5524;5524;59… -;-;-;… 2018 0 0 0 0
## 2 ENSGALG0000005… 1;1;1;1;1 9441;9681;97… 10053;9683;1… +;+;+;… 4754 0 0 0 0
## 3 ENSGALG0000004… 1;1;1;1;1 27209;32230;… 27503;32331;… +;+;+;… 955 105 73 35 42
## 4 ENSGALG0000005… 1;1;1;1;1 31439;32230;… 31484;32331;… +;+;+;… 1050 285 220 124 230
## 5 ENSGALG0000004… 1;1;1;1;1;1;1;1;1 39057;39080;… 39867;39867;… -;-;-;… 2161 224 138 134 178
## 6 ENSGALG0000004… 1 58427 58617 + 191 0 0 0 0
Keeping only what we need
Remove Chr, Start, End and Strand. We only keep the gene ID, the gene length and the sample columns.
counts <- counts[, !colnames(counts) %in% c("Chr", "Start", "End", "Strand")]
head(counts)
## # A tibble: 6 x 6
## Geneid Length B00_A01 B00_A02 B00_A03 B00_A04
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 ENSGALG00000054818 2018 0 0 0 0
## 2 ENSGALG00000053455 4754 0 0 0 0
## 3 ENSGALG00000045540 955 105 73 35 42
## 4 ENSGALG00000051297 1050 285 220 124 230
## 5 ENSGALG00000042023 2161 224 138 134 178
## 6 ENSGALG00000047594 191 0 0 0 0
Normalizing by gene length
Divide each sample’s read count by the gene length, then multiply by 1000. This gives the reads per kilobase (RPK).
counts_tpm <- (counts[, 3:ncol(counts)] / counts$Length) * 1000
# return to a tibble, just because I like tibbles
counts_tpm <- cbind(Geneid = counts$Geneid, counts_tpm) %>% as_tibble()
Getting the normalization factor
Here we use RLE, but you can change the method to whichever edgeR supports.
normfactor <- DGEList(
counts = counts_tpm[, 2:ncol(counts_tpm)],
group = colnames(counts_tpm[, 2:ncol(counts_tpm)])
)
# you can change the normalization method here
normfactor <- calcNormFactors(normfactor, method = "RLE")
normfactor_samples <- normfactor$samples
# multiply normalization factor with the library size
normfactor_samples$normlib <- normfactor_samples$lib.size * normfactor_samples$norm.factors
Final TPM table
Loop over the samples and scale to per-million.
for (i in 1:(dim(counts_tpm)[2] - 1)) {
counts_tpm[, i + 1] <- (counts_tpm[, i + 1] / normfactor_samples$normlib[i]) * 1000000
}
head(counts_tpm)
## # A tibble: 6 x 5
## Geneid B00_A01 B00_A02 B00_A03 B00_A04
## <fct> <dbl> <dbl> <dbl> <dbl>
## 1 ENSGALG00000054818 0 0 0 0
## 2 ENSGALG00000053455 0 0 0 0
## 3 ENSGALG00000045540 54.6 49.4 37.3 33.1
## 4 ENSGALG00000051297 135. 135. 120. 165.
## 5 ENSGALG00000042023 51.5 41.3 63.1 61.9
## 6 ENSGALG00000047594 0 0 0 0
