# DegNorm: an R package for degradation normalization for RNA-seq data

#### Bin Xiong, Ji-Ping Wang

#### 2025-12-08

Maintainer: Ji-Ping Wang, <jzwang@northwestern.edu>

Reference: Xiong, B., Yang, Y., Fineis, F. Wang, J.-P., DegNorm: normalization of generalized transcript degradation improves accuracy in RNA-seq analysis, Genome Biology, 2019,20:75

## What is DegNorm?

DegNorm, short for Degradation Normalization, is a bioinformatics pipeline designed to correct for bias due to the heterogeneous patterns of transcript degradation in RNA-seq data. DegNorm helps improve the accuracy of the differential expression analysis by accounting for this degradation.

In practice, RNA samples are often more-or-less degraded, and the degradation severity is not only sample-specific, but gene-specific as well. It is known that longer genes tend to degrade faster than shorter ones. As such, commonplace global degradation normalization approaches that impose a single normalization factor on all genes within a sample can be ineffective in correcting for RNA degradation bias.

## DegNorm pipline available formats

We’ve developed an R package and an indepedent Python package (download), both of which allow to run the entire pipeline from the RNA-seq alignment (.bam) files. For most-updated version, we recommend to use R package from bioconductor.

## DegNorm version 1.3.4 updates

In version 1.3.1, we made following updates: 1. In `plot_coverage` funciton, an `samples` options is provided to allow user to select which samples to plot for coverage curves. 2. We fixed a bug in `DegNorm` funciton. In earlier version, we used genes with maximum `rho<0.1` from initial SVD to determine the scaling factor before running core algorithm. For some data with many samples and large degradation, it may return `NA` to cause issues.

## Install DegNorm R package

    if(!requireNamespace("BiocManager", quietly = TRUE))
        install.packages("BiocManager")
    BiocManager::install("DegNorm")

## DegNorm main features

DegNorm R package contains two major functions: (1) processing the RNA-seq alignment file (.bam) to calculate the coverage; and (2) using a core algorithm written in RcppArmadillo to perform rank-one over-approximation on converage matrices for each gene to estimate the degramation index (DI) score for each gene within each sample.

DegNorm outputs DI scores together with degradation-normalized read counts (based on DI scores). It also provides supplementary functions for visualization of degradation at both gene and sample level. The following diagram illustrates the flow of DegNorm pipeline.

A diagram of DegNorm.

The following vignette is intended to provide example codes for running DegNorm R package. It presumes that you have successfully installed DegNorm package. We illustrate below how to: 1) calculate the read coverage curves for all genes within all samples, and 2) perform degradation normalization on coverage curves. Either step is computing intensive. Dependent upon the number of samples and the sequencing depth, the total computing time may last a few hours. DegNorm utilizes the parallel computing functionality of R and automatically detects the number of cores on your computer to run jobs in parallel. Due to the large size of bam file and limited computing power of personal computer, we recommend users to run it in servers or computing clusters.

### 1. Compute coverage score based on alignment .bam files

##### Set up input file: .bam and .gtf files.

    ## specify bam_files from RNA-seq, you should replace it by your own bam files
    bam_file_list=list.files(path=system.file("extdata",package="DegNorm"),
                            pattern=".bam$",full.names=TRUE)

The three bam files were subsetted from a specific region of chorosome 21 from the origianl bam for package size limitation. Original files can be found from the included reference above.

    ## gtf_file you used for RNA-seq alignment, replace it by your own gtf file
    gtf_file=list.files(path=system.file("extdata",package="DegNorm"),
                        pattern=".gtf$",full.names=TRUE)

##### Run main function to create read coverage matrix and read counts

    ## calculate the read coverage score for all genes of all samples
    coverage_res_chr21_sub=read_coverage_batch(bam_file_list, gtf_file,cores=2)
    #> Parse gtf file...done
    #> Index SRR873822_chr21_9900000-10000000.bam
    #> Computing coverage for SRR873822_chr21_9900000-10000000.bam ...
    #>    SRR873822_chr21_9900000-10000000.bam is a paired-end bam file
    #> SRR873822_chr21_9900000-10000000.bam is done!
    #>
    #> Index SRR873834_chr21_9900000-10000000.bam
    #> Computing coverage for SRR873834_chr21_9900000-10000000.bam ...
    #>    SRR873834_chr21_9900000-10000000.bam is a paired-end bam file
    #> SRR873834_chr21_9900000-10000000.bam is done!
    #>
    #> Index SRR873838_chr21_9900000-10000000.bam
    #> Computing coverage for SRR873838_chr21_9900000-10000000.bam ...
    #>    SRR873838_chr21_9900000-10000000.bam is a paired-end bam file
    #> SRR873838_chr21_9900000-10000000.bam is done!

`cores` argument specifies the number of cores to use. Users should try to use as many as possible cores to maximize the computing efficiency.

    ## save the coverage results
    save(coverage_res_chr21_sub,file="coverage_res_chr21_sub.Rda")

Function `read_coverage_batch` returns the coverage matrices as a list, one per gene, and a dataframe for read counts, each row for one gene and each column for one sample.

    data("coverage_res_chr21")
    ## summarize the coverage results
    summary_CoverageClass(coverage_res_chr21)
    #> CoverageClass from read_coverage_batch function
    #> $coverage    : list of length 339
    #> $counts  : data.frame of dimension 339 by 3
    #>
    #> Samples:      SRR873822_chr21.bam SRR873834_chr21.bam SRR873838_chr21.bam
    #> Total number genes:   339

    ## extract coverage scores and counts from coverage_res
    coverage_matrix=coverage_res_chr21$coverage
    counts=coverage_res_chr21$counts

### 2. DegNorm core algorithm

Run degnorm core algorithm for degradation normalization. DegNorm purpose is for differential expression analysis. Thus genes with extremely low read counts from all samples are filtered out. The current filtering criterion is that if more than half of the samples have less than 5 read count, that gene will not be considered in the degnorm algorithm. In the following example, I am using downsamling to save time below (default). Alternatively you can set down_sampling = 0, which takes longer time. If `down_samplin= 1`, read coverage scores are binned with size by `grid_size` for baseline selection to achieve better efficiency. The default `grid_size` is 10 bp. We recommend to use a `grid_size` less than 50 bp. `iteration` specifies the big loop in DegNorm algorithm and 5 is usually sufficient. `loop` specifies the iteration number in the matrix factorization over-approximation.

    res_DegNorm_chr21 = degnorm(read_coverage = coverage_res_chr21[[1]],
                        counts = coverage_res_chr21[[2]],
                        iteration = 5,
                        down_sampling = 1,
                        grid_size=10,
                        loop = 100,
                        cores=2)
    #> Filtering out genes with low read counts (x<5)..
    #> Initial estimation of count normalization factors...
    #> Initial degradation normalization without base-line selection...
    #> DegNorm core algorithm starts...
    #>    DegNorm iteration 1
    #>    DegNorm iteration 2
    #>    DegNorm iteration 3
    #>    DegNorm iteration 4
    #>    DegNorm iteration 5
    #> DegNorm core algorithm done!

If `down_sampling= 0`, then the argument `grid_size` is ignored.

    ## save the DegNorm results
    save(res_DegNorm_chr21,file="res_DegNorm_chr21.Rda")

Function `degnorm` returns a list of multiple objects. counts_normed is the one with degradation normalized read counts for you to input DeSeq or EdgeR for DE analysis.

    data("res_DegNorm_chr21")

    ## summary of the DegNorm output
    summary_DegNormClass(res_DegNorm_chr21)
    #> DegNormClass from DegNorm function:
    #> $counts  : data.frame of dimension 132 by 3
    #> $counts_normed   : data.frame of dimension 132 by 3
    #> $DI      : matrix array of dimension 132 by 3
    #> $K       : matrix array of dimension 132 by 3
    #> $convergence     : integer of length 132
    #> $envelop     : list of length 132
    #>
    #> Samples:      SRR873822_chr21.bam SRR873834_chr21.bam SRR873838_chr21.bam
    #> Total number genes:   132

The difference of number of genes between `res_DegNorm` and `coverage_res` is 207 (339-132). The 207 genes were filtered out from `degnorm` degradation normalization because less than half of the samples (3) have more than 5 read count.

    ## extrac normalized read counts
    counts_normed=res_DegNorm_chr21$counts_normed

### 3. Plot functions in DegNorm

DegNorm provides four plot functions for visualization of degradation and sample quality diagnosis.

- plot_coverage
- plot_corr
- plot_heatmap
- plot_boxplot

##### – Plot the before-/after-degradation coverage curves

    ##gene named "SOD1"
    plot_coverage(gene_name="SOD1", coverage_output=coverage_res_chr21,
                degnorm_output=res_DegNorm_chr21,group=c(0,1,1))

In version 1.3.1, a new argument \(samples\) was added so the user can specifiy which samples to plot.

    ##gene named "SOD1"
    plot_coverage(gene_name="SOD1", coverage_output=coverage_res_chr21,
                degnorm_output=res_DegNorm_chr21,group=c(0,1),
                samples=c("SRR873822_chr21.bam", "SRR873834_chr21.bam"))

##### – Boxplot of the degradation index(DI) scores

    plot_boxplot(DI=res_DegNorm_chr21$DI)

##### – Heatmap plot of the degradation index(DI) scores

    plot_heatmap(DI=res_DegNorm_chr21$DI)

##### – Correlation matrix plot of degradation index(DI) scores

    plot_corr(DI=res_DegNorm_chr21$DI)

## Session info

    sessionInfo()
    #> R Under development (unstable) (2025-11-04 r88984)
    #> Platform: aarch64-apple-darwin20
    #> Running under: macOS Ventura 13.7.8
    #>
    #> Matrix products: default
    #> BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
    #> LAPACK: /Library/Frameworks/R.framework/Versions/4.6-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
    #>
    #> locale:
    #> [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
    #>
    #> time zone: America/New_York
    #> tzcode source: internal
    #>
    #> attached base packages:
    #> [1] stats     graphics  grDevices utils     datasets  methods   base
    #>
    #> other attached packages:
    #> [1] knitr_1.50     DegNorm_1.21.0
    #>
    #> loaded via a namespace (and not attached):
    #>   [1] DBI_1.2.3                   bitops_1.0-9
    #>   [3] gridExtra_2.3               httr2_1.2.2
    #>   [5] biomaRt_2.67.0              rlang_1.1.6
    #>   [7] magrittr_2.0.4              otel_0.2.0
    #>   [9] matrixStats_1.5.0           compiler_4.6.0
    #>  [11] RSQLite_2.4.5               GenomicFeatures_1.63.1
    #>  [13] reshape2_1.4.5              png_0.1-8
    #>  [15] vctrs_0.6.5                 txdbmaker_1.7.2
    #>  [17] stringr_1.6.0               pkgconfig_2.0.3
    #>  [19] crayon_1.5.3                fastmap_1.2.0
    #>  [21] dbplyr_2.5.1                XVector_0.51.0
    #>  [23] labeling_0.4.3              ca_0.71.1
    #>  [25] Rsamtools_2.27.0            rmarkdown_2.30
    #>  [27] UCSC.utils_1.7.0            purrr_1.2.0
    #>  [29] bit_4.6.0                   xfun_0.54
    #>  [31] cachem_1.1.0                cigarillo_1.1.0
    #>  [33] GenomeInfoDb_1.47.2         jsonlite_2.0.0
    #>  [35] progress_1.2.3              blob_1.2.4
    #>  [37] DelayedArray_0.37.0         BiocParallel_1.45.0
    #>  [39] parallel_4.6.0              prettyunits_1.2.0
    #>  [41] R6_2.6.1                    bslib_0.9.0
    #>  [43] stringi_1.8.7               RColorBrewer_1.1-3
    #>  [45] rtracklayer_1.71.0          GenomicRanges_1.63.1
    #>  [47] jquerylib_0.1.4             iterators_1.0.14
    #>  [49] Rcpp_1.1.0.8.1              Seqinfo_1.1.0
    #>  [51] assertthat_0.2.1            SummarizedExperiment_1.41.0
    #>  [53] IRanges_2.45.0              Matrix_1.7-4
    #>  [55] tidyselect_1.2.1            dichromat_2.0-0.1
    #>  [57] abind_1.4-8                 yaml_2.3.11
    #>  [59] viridis_0.6.5               TSP_1.2.6
    #>  [61] doParallel_1.0.17           codetools_0.2-20
    #>  [63] curl_7.0.0                  plyr_1.8.9
    #>  [65] lattice_0.22-7              tibble_3.3.0
    #>  [67] withr_3.0.2                 Biobase_2.71.0
    #>  [69] KEGGREST_1.51.1             S7_0.2.1
    #>  [71] evaluate_1.0.5              heatmaply_1.6.0
    #>  [73] BiocFileCache_3.1.0         Biostrings_2.79.2
    #>  [75] pillar_1.11.1               filelock_1.0.3
    #>  [77] MatrixGenerics_1.23.0       foreach_1.5.2
    #>  [79] stats4_4.6.0                plotly_4.11.0
    #>  [81] generics_0.1.4              RCurl_1.98-1.17
    #>  [83] S4Vectors_0.49.0            hms_1.1.4
    #>  [85] ggplot2_4.0.1               scales_1.4.0
    #>  [87] glue_1.8.0                  lazyeval_0.2.2
    #>  [89] tools_4.6.0                 dendextend_1.19.1
    #>  [91] BiocIO_1.21.0               data.table_1.17.8
    #>  [93] webshot_0.5.5               GenomicAlignments_1.47.0
    #>  [95] registry_0.5-1              XML_3.99-0.20
    #>  [97] grid_4.6.0                  tidyr_1.3.1
    #>  [99] crosstalk_1.2.2             seriation_1.5.8
    #> [101] AnnotationDbi_1.73.0        restfulr_0.0.16
    #> [103] cli_3.6.5                   rappdirs_0.3.3
    #> [105] S4Arrays_1.11.1             viridisLite_0.4.2
    #> [107] dplyr_1.1.4                 gtable_0.3.6
    #> [109] sass_0.4.10                 digest_0.6.39
    #> [111] BiocGenerics_0.57.0         SparseArray_1.11.8
    #> [113] rjson_0.2.23                htmlwidgets_1.6.4
    #> [115] farver_2.1.2                memoise_2.0.1
    #> [117] htmltools_0.5.9             lifecycle_1.0.4
    #> [119] httr_1.4.7                  bit64_4.6.0-1