1 Introduction

Differential expression analysis is commonly used to study diverse biological datasets. The reproducibility-optimized test statistic (ROTS) (Elo et al., 2008) uses a modified t-statistic to prioritise features that differ between two or more groups. However, the ROTS Bioconductor implementation (Suomi et al., 2017) did not accommodate technical or biological covariates. LimROTS (Anwar et al., 2025) addressed this limitation by combining a reproducibility-optimized test statistic with the limma empirical Bayes approach (Ritchie et al., 2015). This enables the analysis of more complex experimental designs and the incorporation of covariates. These validated solutions were made in Bioconductor development version 3.20 and the subsequent release 3.21. Related but different linear modeling features were subsequently incorporated also into ROTS Bioconductor package, but have not been formally published to our knowledge. Both ROTS and LimROTS added linear model extensions for survival analysis in the Bioconductor devel version 3.23.

2 Citation

If you use LimROTS in your research, please cite:

Anwar, A. M., Jeba, A., Lahti, L., & Coffey, E. (2025). LimROTS: A Hybrid Method Integrating Empirical Bayes and Reproducibility-Optimized Statistics for Robust Differential Expression Analysis. Bioinformatics, btaf570. https://doi.org/10.1093/bioinformatics/btaf570

3 Disclaimer

LimROTS is a standalone package that extends ROTS (Elo et al., 2008; Tomi Suomi et al., 2017) and the limma (Ritchie ME et al., 2015) framework. It is a newly developed, independently implemented method that incorporates covariates in reproducibility-optimized testing. LimROTS is not affiliated with, endorsed by, or maintained by the original ROTS or limma developers. Users are advised to cite the original publications when referencing these methods.

ROTS: Elo LL, Filén S, Lahesmaa R, Aittokallio T. Reproducibility-optimized test statistic for ranking genes in microarray studies. IEEE/ACM Transactions on Computational Biology and Bioinformatics. 2008;5(3):423–31.

Suomi T, Seyednasrollah F, Jaakkola MK, Faux T, Elo LL (2017) ROTS: An R package for reproducibility-optimized statistical testing. PLOS Computational Biology 13(5): e1005562. https://doi.org/10.1371/journal.pcbi.1005562

limma: Ritchie ME, Phipson B, Wu D, Hu Y, Law CW, Shi W, Smyth GK. limma powers differential expression analyses for RNA-sequencing and microarray studies. Nucleic Acids Research. 2015;43(7):e47. https://doi.org/10.1093/nar/gkv007

4 Algorithm Overview

The LimROTS workflow proceeds as follows:

  1. A linear model is fitted to the data using a user-supplied design matrix via limma Ritchie et al. (2015).
  2. Empirical Bayes variance shrinkage is applied to obtain moderated statistics. The adjusted standard error for each feature is: \(SE_{\text{post}} = \sqrt{s^2_{\text{post}}} \times \text{unscaled SD}\), along with the regression coefficient \(\beta_{(p)}\).
  3. A reproducibility-optimization step selects parameters \(\alpha_1\) and \(\alpha_2\) by maximizing the overlap of top-ranked features across group-preserving bootstrap datasets.
  4. The final statistic for each feature is:

\[t_{\alpha(p)} = \frac{\beta_{(p)}}{\alpha_1 + \alpha_2 \times SE_{\text{post}(p)}}\]

where \(t_{\alpha(p)}\) is the optimized statistic, \(\beta_{(p)}\) is the model coefficient, and \(SE_{\text{post}(p)}\) is the adjusted standard error.

P-values are derived from permutation-based null distributions using the qvalue package Storey et al. (2024). FDR is estimated using an internal implementation adapted from ROTS Suomi et al. (2017), and q-values are computed via the bootstrap method for the proportion of true null hypotheses.

5 Computational Considerations

LimROTS runtime depends on four primary factors: the number of samples, features, bootstrap iterations (niter), and the top list size (K). The bootstrapping step is parallelized using BiocParallel Morgan et al. (2024); we recommend using at least 4 workers for routine analyses. The optimization step is sequential and may be time-consuming for large values of K.

6 LimROTS Function Parameters

LimROTS() is the primary interface for end users. For a full description of all parameters, refer to the function documentation:

?LimROTS

7 UPS1 Case Study

7.1 Dataset Description

To illustrate LimROTS in a realistic multi-covariate scenario, we use a DIA proteomics dataset from a UPS1-spiked E. coli protein mixture Gotti et al. (2022). The dataset contains 1,656 proteins measured across 48 samples: 24 processed with Spectronaut and 24 with ScaffoldDIA. Eight UPS1 concentrations (0.1–50 fmol) are grouped into two conditions:

  • Low concentration (0.1–2.5 fmol, Conc. 2): 12 samples per software.
  • High concentration (5–50 fmol, Conc. 1): 12 samples per software.

A synthetic unbalanced batch variable (fake.batch) was assigned across samples with the following distribution:

#>    
#>      1  2
#>   F 18  6
#>   M  6 18

Additionally, a simulated batch effect was introduced: an effect size of 10 was added to 100 randomly selected E. coli proteins log2 values in one of the fake batches. The expected result is that only the 48 UPS1 human proteins are truly differentially expressed; no E. coli protein should show a genuine concentration effect.

This scenario mirrors real-world conditions such as unbalanced batch structures or unequal covariate distributions across groups.

7.2 Loading the Data

library(LimROTS, quietly = TRUE)
library(BiocParallel, quietly = TRUE)
library(ggplot2, quietly = TRUE)
library(SummarizedExperiment, quietly = TRUE)
data("UPS1.Case4")
print(UPS1.Case4)
#> class: SummarizedExperiment 
#> dim: 1656 48 
#> metadata(0):
#> assays(1): norm
#> rownames(1656): O00762_HUMAN O76070_HUMAN ... Q59385_ECOLI Q93K97_ECOLI
#> rowData names(2): GeneID Description
#> colnames(48): UPS1_0_1fmol_inj1_x UPS1_0_1fmol_inj2_x ...
#>   UPS1_50fmol_inj2_y UPS1_50fmol_inj3_y
#> colData names(4): SampleID tool Conc. fake.batch

7.3 Running LimROTS

Set a seed before running LimROTS to ensure reproducibility.

set.seed(1234, kind = "default", sample.kind = "default")

Note: The group column in colData must be a factor with levels defined in the intended contrast order. For example, Conc. 1 (high) vs Conc. 2 (low) reports the fold-change as high − low. Define this explicitly if needed:

colData(UPS1.Case4)$Conc. <- factor(
    colData(UPS1.Case4)$Conc.,
    levels = c("1", "2")
)
# Define analysis parameters
meta.info   <- c("Conc.", "tool", "fake.batch")
niter       <- 100   # bootstrap iterations (use >= 1000 for real analyses)
K           <- 100   # top list size for reproducibility optimization
formula.str <- "~ 0 + Conc. + tool + fake.batch"

# Run LimROTS
UPS1.Case4 <- LimROTS(
    x           = UPS1.Case4,
    niter       = niter,
    K           = K,
    meta.info   = meta.info,
    group       = "Conc.",
    formula.str = formula.str,
    trend       = TRUE,
    robust      = TRUE,
    permutating.group = FALSE
)
#> Data is SummarizedExperiment object
#> Assay: norm will be used
#> Group Level:  1 & Group Level:  2
#> Initiating limma on bootstrapped samples
#> Using MulticoreParam (Unix-like OS) with two workers.
#> Optimizing a1 and a2
#> Computing p-values and FDR

Note: niter = 100 and K = 100 are used here to reduce vignette build time. For production analyses, use niter = 1000 or higher. We also recommend at least 4 parallel workers.

To increase the number of workers, pass a BPPARAM argument:

# Windows
# BPPARAM <- SnowParam(workers = 4)

# Linux / macOS
# BPPARAM <- MulticoreParam(workers = 4)

# Pass to LimROTS via: LimROTS(..., BPPARAM = BPPARAM)

The results are appended directly to the rowData and metadata slots of the input SummarizedExperiment object.

7.4 Volcano Plot

The volcano plot below shows corrected log-fold changes versus \(-\log_{10}\)(q-value). At a 5% q-value threshold, LimROTS recovers the majority of true UPS1 human proteins while flagging very few E. coli proteins.

# Extract results
result_df <- data.frame(
    rowData(UPS1.Case4),
    row.names = rownames(UPS1.Case4)
)

# Annotate proteins
result_df$label <- ifelse(
    grepl("HUMAN", result_df$GeneID),
    "UPS1 (true positive)",
    "E. coli (true negative)"
)

ggplot(result_df, aes(
    x     = corrected.logfc,
    y     = -log10(qvalue),
    color = label
)) +
    geom_point(alpha = 0.7, size = 1.2) +
    geom_hline(
        yintercept = -log10(0.05),
        linetype   = "dashed",
        color      = "steelblue"
    ) +
    scale_color_manual(values = c(
        "E. coli (true negative)" = "grey60",
        "UPS1 (true positive)"    = "firebrick"
    )) +
    labs(
        title = "LimROTS \u2014 UPS1 Case Study",
        x     = "Corrected Log Fold Change",
        y     = expression(-log[10](q~value)),
        color = NULL
    ) +
    theme_bw(base_size = 12) +
    theme(legend.position = "top")

7.5 Quality Control

LimROTS produces permutation-derived p-values and q-values that can be inspected visually. These are the recommended primary inference statistics.

plot(metadata(UPS1.Case4)[["q_values"]])

hist(metadata(UPS1.Case4)[["q_values"]])

print(summary(metadata(UPS1.Case4)[["q_values"]]))
sessionInfo()
#> R version 4.6.0 alpha (2026-04-05 r89794)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.4 LTS
#> 
#> Matrix products: default
#> BLAS:   /home/biocbuild/bbs-3.23-bioc/R/lib/libRblas.so 
#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_GB              LC_COLLATE=C              
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: America/New_York
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats4    stats     graphics  grDevices utils     datasets  methods  
#> [8] base     
#> 
#> other attached packages:
#>  [1] ggplot2_4.0.2               BiocParallel_1.45.0        
#>  [3] LimROTS_1.3.23              SummarizedExperiment_1.41.1
#>  [5] Biobase_2.71.0              GenomicRanges_1.63.2       
#>  [7] Seqinfo_1.1.0               IRanges_2.45.0             
#>  [9] S4Vectors_0.49.1            BiocGenerics_0.57.0        
#> [11] generics_0.1.4              MatrixGenerics_1.23.0      
#> [13] matrixStats_1.5.0           BiocStyle_2.39.0           
#> 
#> loaded via a namespace (and not attached):
#>  [1] gtable_0.3.6        xfun_0.57           bslib_0.10.0       
#>  [4] lattice_0.22-9      vctrs_0.7.2         tools_4.6.0        
#>  [7] parallel_4.6.0      tibble_3.3.1        pkgconfig_2.0.3    
#> [10] Matrix_1.7-5        RColorBrewer_1.1-3  S7_0.2.1           
#> [13] lifecycle_1.0.5     compiler_4.6.0      farver_2.1.2       
#> [16] stringr_1.6.0       tinytex_0.59        statmod_1.5.1      
#> [19] codetools_0.2-20    htmltools_0.5.9     sass_0.4.10        
#> [22] yaml_2.3.12         pillar_1.11.1       jquerylib_0.1.4    
#> [25] DelayedArray_0.37.1 cachem_1.1.0        limma_3.67.0       
#> [28] magick_2.9.1        cmprsk_2.2-12       abind_1.4-8        
#> [31] tidyselect_1.2.1    digest_0.6.39       stringi_1.8.7      
#> [34] dplyr_1.2.1         reshape2_1.4.5      bookdown_0.46      
#> [37] labeling_0.4.3      splines_4.6.0       fastmap_1.2.0      
#> [40] grid_4.6.0          cli_3.6.5           SparseArray_1.11.13
#> [43] magrittr_2.0.5      S4Arrays_1.11.1     dichromat_2.0-0.1  
#> [46] survival_3.8-6      withr_3.0.2         scales_1.4.0       
#> [49] rmarkdown_2.31      XVector_0.51.0      otel_0.2.0         
#> [52] qvalue_2.43.0       evaluate_1.0.5      knitr_1.51         
#> [55] rlang_1.2.0         Rcpp_1.1.1          glue_1.8.0         
#> [58] BiocManager_1.30.27 jsonlite_2.0.0      R6_2.6.1           
#> [61] plyr_1.8.9

References

Gotti, Clarisse, Florence Roux-Dalvai, Charles Joly-Beauparlant, Loïc Mangnier, Mickaël Leclercq, and Arnaud Droit. 2022. “DIA Proteomics Data from a Ups1-Spiked E.coli Protein Mixture Processed with Six Software Tools.” Data in Brief 41: 107829. https://doi.org/https://doi.org/10.1016/j.dib.2022.107829.

Morgan, Martin, Jiefei Wang, Valerie Obenchain, Michel Lang, Ryan Thompson, and Nitesh Turaga. 2024. BiocParallel: Bioconductor Facilities for Parallel Evaluation. https://doi.org/10.18129/B9.bioc.BiocParallel.

Ritchie, Matthew E, Belinda Phipson, Di Wu, Yifang Hu, Charity W Law, Wei Shi, and Gordon K Smyth. 2015. “limma Powers Differential Expression Analyses for RNA-Sequencing and Microarray Studies.” Nucleic Acids Research 43 (7): e47. https://doi.org/10.1093/nar/gkv007.

Storey, John D., Andrew J. Bass, Alan Dabney, and David Robinson. 2024. Qvalue: Q-Value Estimation for False Discovery Rate Control. https://doi.org/10.18129/B9.bioc.qvalue.

Suomi, Tomi, Fatemeh Seyednasrollah, Maria Jaakkola, Thomas Faux, and Laura Elo. 2017. “ROTS: An R Package for Reproducibility-Optimized Statistical Testing.” PLoS Computational Biology 13 (5): e1005562. https://doi.org/10.1371/journal.pcbi.1005562.