omics.co.in logo omics.co.in

Chapter 5: Expression Count Normalization, Library Sizing & Log Transformation

Tested Stacks: GATK 4.6.1 Samtools 1.21 Nextflow 24.10 Seurat v5.1 Python 3.12
Prerequisites: Basic Linux CLI & Sequencing Formats
📚 Chapter 5 of 18 0% Course Completed

1. Why Raw Read Counts Cannot Be Directly Compared

Raw integer read counts output by quantification tools (HTSeq, featureCounts) represent the number of sequencing reads that aligned to each gene. However, raw counts are confounded by two massive technical biases:

  • Sequencing Depth Bias (Library Size): If Sample A is sequenced to 60 million reads and Sample B is sequenced to 20 million reads, every gene in Sample A will exhibit approximately three-fold higher raw counts purely due to sequencing effort.
  • Gene Length Bias: Under uniform expression, an 8,000 bp transcript (such as TTN) will generate 8 times more fragmented cDNA reads than a 1,000 bp transcript, even when both transcripts exist at identical molar concentrations in the cell.

2. Comprehensive Comparison of Normalization Units

MetricFormulaAdjusts for Depth?Adjusts for Gene Length?Valid for Between-Sample Comparison?Primary Research Purpose
Raw Counts\(X_i\)NoNoNoInput for statistical GLMs (DESeq2, edgeR)
CPM (Counts Per Million)\(\frac{X_i}{\sum X} \times 10^6\)YesNoPartial (sensitive to high-expression outliers)Pre-filtering low-expression genes (e.g. CPM > 1)
RPKM / FPKM\(\frac{X_i}{(L_i/10^3) \cdot (\sum X / 10^6)}\)YesYesNo (Sum of FPKMs varies per sample)Obsolete legacy metric; historical papers
TPM (Transcripts Per Million)\(\frac{X_i / L_i}{\sum (X_j / L_j)} \times 10^6\)YesYesYes (Invariant relative molar proportion)Comparing expression within sample & across samples
DESeq2 Median of Ratios\(s_j = \text{median}_i \left( \frac{K_{ij}}{\left(\prod_{j=1}^m K_{ij}\right)^{1/m}} \right)\)Yes (via scaling factor \(s_j\))NoYes (Gold Standard for differential testing)Robust to extreme outlier genes (e.g. globin/albumin)
VST (Variance Stabilizing)Transformation satisfying \(\text{Var}(f(Y)) \approx \text{const}\)YesNoYes (Homoskedastic log-like values)PCA, Euclidean clustering, Machine Learning input

3. Step-by-Step R Normalization Implementation

library(DESeq2)
library(vsn) # for meanSdPlot

# Construct DESeqDataSet
dds <- DESeqDataSetFromMatrix(
    countData = mock_counts,
    colData = clinical_clean,
    design = ~ Condition
)

# 1. Filter lowly expressed genes (require at least 10 reads across at least 3 samples)
keep <- rowSums(counts(dds) >= 10) >= 3
dds <- dds[keep, ]
cat("Genes retained after filtering:", sum(keep), "\n")

# 2. Compute DESeq2 Median of Ratios Size Factors
dds <- estimateSizeFactors(dds)
size_factors <- sizeFactors(dds)
cat("Sample Size Factors:\n")
print(round(size_factors, 3))

# Extract normalized counts matrix
normalized_counts <- counts(dds, normalized = TRUE)

# 3. Apply Variance Stabilizing Transformation (VST)
# VST stabilizes the variance across the entire dynamic range,
# preventing high-count genes from dominating distance metrics.
vsd <- vst(dds, blind = FALSE)
vst_matrix <- assay(vsd)

# 4. Diagnostic Mean-Variance Homoskedasticity Plot
# If properly stabilized, the red running trend line should be approximately flat.
meanSdPlot(vst_matrix)
⚠️

Common Pitfalls & Troubleshooting Checklist

  • Java Heap Exhaustion (GATK / Picard): If processes terminate with java.lang.OutOfMemoryError, set your environment variable explicitly: export _JAVA_OPTIONS="-Xmx16g -Djava.io.tmpdir=/tmp".
  • Index Timestamp Mismatch: If tools complain that a BAM index is missing or out of date, regenerate with samtools index -@ 4 sample.bam.
  • Conda Channel Conflicts: Avoid ABI incompatibilities between bioconda and conda-forge by setting strict priority: conda config --set channel_priority strict.
  • Chromosome Naming Contamination: Always verify whether reference FASTA headers use chr1 or 1; divergent naming causes silent alignment drops.
Was this guide helpful to your research?
Help us maintain 100% accurate, peer-reviewed bioinformatics standards.
📝 Propose Edit on GitHub ↗