NADIA 0.99.3
Normalization and imputation are not merely technical details. Normalization changes the scale on which samples are compared, whereas imputation determines which values replace the measurements that were not observed. Both choices can therefore affect the proteins identified as differentially abundant.
There is no method that is best for every dataset. A useful choice should be based on the characteristics of the experiment, the missing-value pattern and diagnostics calculated from the data. This vignette follows that reasoning in four steps:
NADIA exposes 13 normalization labels and 19 imputation strategies. At the level of selectable methods, this gives 13 × 19 = 247 nominal normalization–imputation combinations. Running and interpreting every combination is possible, but it is rarely an efficient first step. The metrics in this vignette provide a staged screen: first rank plausible normalization methods using replicate agreement and condition separation, then compare imputation methods on the selected—or a small number of plausible—normalized assays. The resulting ranks reduce the search space; they do not by themselves prove that the highest-ranked combination is biologically correct.
The value 247 is a catalog-level count rather than 247 fully interchangeable
completed matrices: log2 and log2Norm are numerically redundant, and limpa
and none have special downstream interpretations. The count nevertheless
illustrates why a principled shortlist is preferable to choosing a pipeline by
trial and error.
The examples use nadia_dia, a preprocessed DIA dataset included with NADIA.
The comparison is performed without an external ground truth. If an experiment
contains spike-in proteins with known abundances or expected fold changes, the
best normalization–imputation combination is easier to establish. Such an
experiment supplies an external reference against which the complete pipeline
can be benchmarked, rather than relying only on internal properties of the
processed matrix. Proteins added at known different abundances show whether the
pipeline recovers the expected changes. Proteins kept at the same abundance, by
contrast, show how often the pipeline reports a difference when none is
expected. In that setting, the benchmarking results should take priority over
the internal ranks described here; see vignette("benchmarking").
if (!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install("NADIA")
This vignette also uses impute, imputeLCMD and vegan, which are optional;
BiocManager::install("NADIA", dependencies = TRUE) installs them together
with NADIA.
Only NADIA and SummarizedExperiment need to be attached for the code in this
vignette. Method-specific packages are called internally when required.
library(NADIA)
library(SummarizedExperiment)
data(nadia_dia)
Protein intensities may contain systematic differences caused by sample preparation, injected amount, instrument response or other non-biological sources. Normalization aims to reduce these unwanted differences while preserving the biological variation that the experiment was designed to measure.
An effective method should therefore satisfy two goals at the same time:
Making every sample look identical would reduce technical dispersion, but it could also remove biological signal. For this reason, no single diagnostic is sufficient on its own.
NADIA provides 13 normalization options, which are selected with the
norm_method argument. Regardless of the option chosen, the resulting
protein-abundance matrix is expressed on the log2 scale.
| norm_method | Principle |
|---|---|
log2 |
Applies the log2 transformation and no additional normalization. It is the natural baseline for comparison. |
log2Norm |
Applies the same log2 transformation. It is retained as a named benchmark method for compatibility. |
GlobalMedian |
Divides each sample by its total intensity and rescales the totals to their median before log2 transformation. |
GlobalMean |
Divides each sample by its total intensity and rescales the totals to their mean before log2 transformation. |
eqmedians |
Log2-transforms the data and shifts every sample so that all sample medians are equal. This approach is based on the equal-median normalization used by MSstats. |
medianNorm |
Divides raw intensities by the sample median, rescales to the mean of the sample medians and then applies log2. |
meanNorm |
Divides raw intensities by the sample mean, rescales to the mean of the sample means and then applies log2. |
vsn |
Uses variance-stabilizing normalization from the vsn package to reduce the dependence between variance and mean intensity. |
quantile |
Maps the empirical distribution of every sample to a common quantile distribution using limma. |
quantile.robust |
Uses the median, rather than the mean, across sample quantiles to construct a reference distribution that is less sensitive to extreme samples. |
Rlr |
Fits a robust linear regression between each sample and the protein-wise median profile, then removes the fitted offset and scale. This approach is based on the global robust linear regression normalization implemented in NormalyzerDE. |
MAD |
Aligns both the median and the median absolute deviation of every sample to the corresponding global values. |
cycloess |
Applies cyclic loess normalization with limma, correcting intensity-dependent differences between samples. |
log2 and log2Norm produce the same values. Consequently,
nm_run_normalizations(methods = "all") retains the log2 baseline and skips
the redundant log2Norm assay, leaving 12 distinct assays for comparison.
NADIA combines quantitative measures of within-condition agreement with a measure of between-condition separation. Four of the measures follow the intragroup evaluation used by the PRONE package: PCV, PMAD, PEV and intragroup correlation.
| Rank | What NADIA calculates | Preferred direction |
|---|---|---|
Rank_PCV |
For each protein and condition, the coefficient of variation is calculated as \(100 \times SD / |mean|\). Values are averaged across conditions and summarized by the median across proteins. | Lower |
Rank_PMAD |
The median absolute deviation is calculated within each condition for every protein, averaged across conditions and summarized by the median across proteins. | Lower |
Rank_PEV |
The within-condition variance is calculated for every protein, averaged across conditions and summarized by the median across proteins. | Lower |
Rank_Cor |
Pairwise correlations are calculated between replicate samples within each condition and summarized by their median. | Higher |
Rank_Sep |
PCA is performed on complete protein profiles. A one-way ANOVA F-ratio measures separation of the PC1 sample scores by condition relative to variation within conditions. | Higher |
Rank_Final is the mean of these five ranks. The lowest value is preferred.
This combined criterion rewards agreement among replicates without allowing
dispersion alone to decide the result. That qualification is important: the
PRONE evaluation showed that reducing intragroup variation does not necessarily
identify the method that performs best in downstream analyses.
The ranking should be read together with the diagnostic plots returned by
normalization_metrics():
boxplot and density compare sample intensity distributions;pcv, pmad and pev show within-condition dispersion across proteins;correlation shows agreement between replicate samples;pca and mds show whether replicates cluster and conditions separate;scatter compares two samples directly, and qq assesses one sample against
a normal distribution;metrics displays additional multivariate diagnostics; andfinal_ranking displays the combined ranking.These plots answer different questions. A method should not be selected from a single attractive PCA or a single low dispersion value.
The first step builds a baseline SummarizedExperiment. The second applies the
candidate methods, storing each result as a separate assay. Four methods are
used here to keep the example concise; omitting methods runs all distinct
benchmark assays.
se_norm <- nm_prepare_se(nadia_dia, verbose = FALSE)
se_norm <- nm_run_normalizations(
se_norm,
methods = c("log2Norm", "cycloess", "Rlr", "quantile"),
verbose = FALSE)
assayNames(se_norm)
#> [1] "log2" "cycloess" "Rlr" "quantile"
normalization_metrics() calculates the diagnostics and returns both tables
and plots. output_dir = NULL prevents files from being written to disk.
# Two diagnostics (Hopkins and the PERMANOVA p-value) use random numbers.
set.seed(42)
nm <- normalization_metrics(
se_norm,
output_dir = NULL,
verbose = FALSE)
nm$final_rank
#> Method Rank_PCV Rank_PMAD Rank_PEV Rank_Cor Rank_Sep Rank_Final
#> 1 cycloess 1 1 2 1 2 1.4
#> 2 Rlr 2 2 1 3 3 2.2
#> 3 quantile 3 3 3 4 1 2.8
#> 4 log2 4 4 4 2 4 3.6
The two similarly named elements have different roles but contain the same
ranking. nm$final_rank is the numerical table: its five component columns are
Rank_PCV, Rank_PMAD, Rank_PEV, Rank_Cor and Rank_Sep, and
Rank_Final is their mean. nm$final_ranking is the bar-chart representation
of Rank_Final from that table; it does not calculate an additional ranking.
The separately returned pc1_rank and mds1_rank tables are optional
diagnostics and are not included in Rank_Final.
The first row is the preferred method under the combined criterion. In this
example, cycloess has the lowest Rank_Final: it ranks first for PCV, PMAD and
correlation while retaining good separation between conditions. Rlr is
second, and the unnormalized log2 baseline ranks last.
The final ranking plot provides the same comparison visually. Shorter bars indicate a better mean rank.
nm$final_ranking
The PCA remains useful as a complementary check because it shows the individual samples rather than reducing the comparison to one score.
nm$pca
The selected normalization method can be extracted directly:
norm_winner <- nm$final_rank$Method[1]
norm_winner
#> [1] "cycloess"
Imputation replaces missing entries with estimates so that analyses requiring a complete matrix can be performed. The estimate should reflect a plausible reason for the value being absent. NADIA uses the following practical distinction:
Figure 1: Practical distinction between MAR and MNAR missing values in proteomics
MAR values are reconstructed from observed data structure, whereas MNAR values call for a low-value or detection-aware model.
An individual NA does not reveal why the value is missing. MAR and MNAR are
possible explanations that must be evaluated using the experimental design, the
overall distribution of missing values and their relationship with observed
intensity. Both mechanisms may occur in the same dataset.
By the time the data enter the analysis, an unreported intensity is represented
as NA. How that NA is created depends on the input format. In a long
Spectronaut report, a protein that was not quantified in a particular run is
usually absent from the report; when NADIA constructs the wide protein_quant
table, the corresponding protein–run cell becomes NA. Some wide reports use
zero instead to mark an unreported intensity. Before normalization,
normalize_proteomics() converts these zeros to NA, so downstream functions
handle both situations consistently.
NADIA provides 19 selectable strategies. The grouping below describes their main intent and does not imply that every missing value can be classified unambiguously.
| Group | imp_method | Principle |
|---|---|---|
| Structure-based, generally used for MAR | bpca |
Bayesian principal-component imputation using pcaMethods. |
knn |
Estimates a value from similar proteins using k-nearest neighbors. | |
mice |
Multiple imputation by chained equations; NADIA averages the completed matrices. | |
missForest |
Non-parametric random-forest imputation: each sample is predicted from the others, learned over the proteins (observations = "samples" in method_args reverses it, as NAguideR does). |
|
Impseq |
Sequential model-based imputation from rrcovNA. |
|
Impseqrob |
Robust sequential imputation that reduces sensitivity to outlying profiles. | |
MLE |
Maximum-likelihood imputation under a multivariate normal model. | |
| Low-value, generally used for MNAR | QRILC |
Quantile-regression imputation for left-censored data. |
MinDet |
Uses a low, sample-specific quantile; the default is the first percentile. | |
MinProb |
Draws low values around a sample-specific low quantile. | |
PI |
Perseus-style sampling from a narrowed, down-shifted normal distribution. | |
min |
Replaces every gap with the global observed minimum. | |
halfmin |
Uses half the global minimum on the original scale, equivalent to one unit below the minimum on the log2 scale; this convention is used by DIA-NN. | |
zero |
Replaces every gap with zero on the log2 scale. | |
with |
Replaces every gap with a user-supplied constant. | |
| Hybrid | combo |
Classifies missing cells from condition-wise presence patterns, then applies separate MAR and MNAR methods. Defaults are Impseqrob and min. |
softHybrid |
Blends estimates from MAR and MNAR methods continuously using protein missingness and mean intensity. | |
| Detection model | limpa |
Estimates a detection-probability curve and returns abundance estimates with uncertainty for downstream analysis with de_method = "limpa". |
| No imputation | none |
Leaves missing values unchanged. |
limpa and none require special interpretation. limpa is intended to carry
its estimated uncertainty into a limpa differential abundance model, whereas
none deliberately does not produce a complete matrix. They should not be
treated as ordinary constant-fill methods.
When the true missing values are unknown, NADIA follows the classic computational evaluation framework used by NAguideR:
NADIA provides two ways to decide how many values are hidden and where the artificial gaps are placed:
pattern = "random", the user sets the proportion explicitly through
na_prop. For example, na_prop = 0.20 hides 20% of the values in the
complete-case matrix. The selected cells do not depend on intensity and are
treated as MAR for the benchmark. This option is useful for a controlled
experiment or for comparing performance at several predefined missingness
levels.pattern = "from_data", na_prop is ignored. NADIA estimates the
proportion of proteins affected by missing values and the distribution of
gaps across samples from the original assay, then reproduces those features
when masking the complete-case matrix. The resulting percentage of artificial
gaps is therefore approximately the percentage observed in the experiment;
small differences can occur because cell counts must be rounded.The second option follows the data-derived principle used in the
NAguideR evaluation, in which missing
values were generated in a complete matrix at a proportion similar to that of
the original dataset. For selecting an imputation method for one particular
experiment, pattern = "from_data" is generally the more representative
starting point because it preserves the experiment’s overall missingness burden
and its distribution across samples. In nadia_dia, for example, the original
assay contains approximately 8.0% missing cells and pattern = "from_data"
generates approximately 8.1% artificial gaps, whereas
pattern = "random", na_prop = 0.20 generates 20%.
Both options hide values that were originally observed, so the true values are
known and imputation error can be calculated. Consequently, neither option
directly reproduces MNAR missingness caused by low abundance or a detection
limit. pattern = "from_data" is more representative of the observed NA
frequency and sample distribution, but it is not a direct benchmark of MNAR
imputation.
NADIA calculates four metrics:
| Metric | Interpretation | Preferred direction |
|---|---|---|
NRMSE |
Normalized root mean squared error over the artificially masked cells. | Lower |
SOR |
Sum of per-protein error ranks. A method receives the worst rank for a protein if it fails to impute its artificial gaps. | Lower |
PSS |
Procrustes sum of squares comparing the PCA structures of the true and imputed matrices. | Lower |
ACC_OI |
Pearson correlation between the true and imputed values pooled over the artificially masked cells. | Higher |
Because the four metrics have different scales, NADIA does not average their raw
values. Instead, it orders the methods from best to worst for each metric and
assigns rank 1 to the best method, rank 2 to the second, and so on. Rank_Mean
is the mean of these four ranks, with each metric contributing equally. The
method with the lowest Rank_Mean therefore has the best overall performance
for the simulated gaps.
Occasionally, a metric cannot be calculated for a particular method. In that
case, NADIA calculates Rank_Mean from the available ranks without assigning
the best or worst rank to the missing metric. Therefore, before selecting a
method solely on the basis of Rank_Mean, check whether any of its metric values
are NA. A favourable mean rank based on fewer metrics may not be directly
comparable with one calculated from the complete set of metrics.
ACC_OI compares the true and imputed values only at the cells that NADIA hid
for the simulation. This prevents the many unchanged cells from making the
correlation appear artificially high. Pearson correlation requires variation in
both sets of values. A constant method such as min assigns the same value to
every hidden cell, so its imputed values have no variation and ACC_OI cannot
be calculated. In this situation, ACC_OI = NA means not evaluable; it does
not indicate either good or poor imputation.
Imputation must be evaluated on a normalized assay. The code below uses the
normalization selected above and compares representative structure-based,
low-value and hybrid strategies. It uses a controlled 20% random mask for this
illustration. For an experiment-specific evaluation, use
pattern = "from_data" and omit na_prop from the call.
se_imp <- im_prepare_se(
nadia_dia,
norm_method = norm_winner,
verbose = FALSE)
# The cells masked to build the ground truth are drawn at random.
set.seed(42)
im <- imputation_metrics(
se_imp,
assay_name = norm_winner,
methods = c("Impseqrob", "knn", "MinDet", "QRILC", "min"),
combo_methods = list(
Impseqrob_min = list(
mar_method = "Impseqrob",
mnar_method = "min")),
na_prop = 0.20,
pattern = "random",
output_dir = NULL,
verbose = FALSE)
im$metrics_table[, c(
"Method", "NRMSE", "SOR", "PSS", "ACC_OI", "Rank_Mean")]
#> Method NRMSE SOR PSS ACC_OI Rank_Mean
#> 1 Impseqrob 0.1258250 2668.0 0.008732265 0.99206183 1.250000
#> 2 knn 0.1290966 2802.0 0.004316998 0.99165565 2.000000
#> 3 Impseqrob_min 0.3999569 2701.5 0.009098402 0.92411201 2.750000
#> 4 MinDet 2.0437765 5463.0 0.195061890 0.07321024 4.000000
#> 5 QRILC 2.5574417 6685.0 0.205056432 0.02033958 5.250000
#> 6 min 3.2130875 8009.5 0.199977570 NA 5.666667
Impseqrob has the lowest Rank_Mean in this example. It produces the smallest
NRMSE, the best SOR and the highest correlation with the hidden values. knn
preserves the multivariate structure particularly well, as indicated by its low
PSS, but ranks second overall. The low-value MNAR methods perform poorly because
the simulation hides values at random rather than preferentially hiding low
intensities. That result should not be used to conclude that MNAR imputation is
unnecessary in the original matrix.
The metrics plot shows the values on their original scales:
im$metrics
The heatmap shows one imputation method per row and one evaluation metric per
column. The number printed in each cell is the method’s rank for that metric:
rank 1 is the best result. Lower ranks are shown in blue and higher ranks in
red; a gray cell marked NA indicates that the metric could not be calculated.
The final column, Rank_Mean, is the average of the available metric ranks.
Methods are ordered by this value, so the method in the first row has the lowest
Rank_Mean and the best overall result for the simulated gaps.
im$ranking
The winner can again be extracted from the first row:
imp_winner <- im$metrics_table$Method[1]
imp_winner
#> [1] "Impseqrob"
The selected normalization and imputation methods can now be passed to the main
workflow. For this example the data-driven combination is cycloess followed by
Impseqrob.
res_selected <- process_proteomics(
nadia_dia,
norm_method = norm_winner,
imp_method = imp_winner,
verbose = FALSE)
table(res_selected$DEPs_results$Change)
#>
#> Up Down No Change
#> 964 759 4268
The normalized matrix and the imputed matrix are retained as separate assays, so the processing history can be inspected without overwriting earlier stages.
assayNames(res_selected$se_proc)
#> [1] "raw" "log2" "cycloess" "Impseqrob"
A method-selection metric is a guide, not a guarantee that every downstream result is correct. A useful final check is to repeat the analysis with plausible alternative pipelines and compare their consequences.
variants <- list(
"cycloess + Impseqrob" = list(
norm_method = "cycloess", imp_method = "Impseqrob"),
"cycloess + combo" = list(
norm_method = "cycloess", imp_method = "combo"),
"cycloess + knn" = list(
norm_method = "cycloess", imp_method = "knn"),
"quantile + combo" = list(
norm_method = "quantile", imp_method = "combo"),
"log2 + min" = list(
norm_method = "log2", imp_method = "min"))
# Some imputation methods print progress on stdout; capture.output keeps it out.
invisible(capture.output(
counts <- do.call(rbind, lapply(names(variants), function(label) {
fit <- do.call(
process_proteomics,
c(list(nadia_dia, verbose = FALSE), variants[[label]]))
tab <- table(factor(
fit$DEPs_results$Change,
levels = c("Up", "Down", "No Change")))
data.frame(
Pipeline = label,
Up = tab[["Up"]],
Down = tab[["Down"]],
No_Change = tab[["No Change"]])
}))))
counts
#> Pipeline Up Down No_Change
#> 1 cycloess + Impseqrob 964 759 4268
#> 2 cycloess + combo 985 937 4069
#> 3 cycloess + knn 872 179 4940
#> 4 quantile + combo 902 2825 2264
#> 5 log2 + min 761 778 4452
The input matrix, differential abundance method and decision thresholds are the same in every run, but the numbers called Up and Down change substantially. These are protein–comparison results: a protein tested in several contrasts can contribute more than one row. The comparison does not establish which pipeline is biologically correct. It shows that normalization and imputation are consequential modeling choices and must be evaluated, reported and reproduced.
For a dataset without external ground truth, the following sequence provides a defensible starting point:
When a spike-in or another known reference is available, it should take priority over these proxy criteria because it permits evaluation of the expected biological changes directly.
Arend L, Adamowicz K, Schmidt JR, et al. (2025). Systematic evaluation of normalization approaches in tandem mass tag and label-free protein quantification data using PRONE. Briefings in Bioinformatics, 26(3), bbaf201. PRONE is available from Bioconductor and GitHub.
Wang S, Li W, Hu L, Cheng J, Yang H, Liu Y (2020). NAguideR: performing and prioritizing missing value imputations for consistent bottom-up proteomic analyses. Nucleic Acids Research, 48(14), e83. Source code is available from the NAguideR GitHub repository.
Li M, Cobbold SA, Smyth GK (2025). Quantification and differential analysis
of mass spectrometry proteomics data with probabilistic recovery of
information from missing values.
bioRxiv, 2025.04.28.651125. This work describes the statistical framework
implemented in limpa; the software is available from
Bioconductor and
GitHub.
Shi Y, Davis S, Charles PD, Taylor S, Dombi E, Berridge G, Ebner D, Fischer R
(2026). SoftHybrid: A Hybrid Imputation Algorithm Optimised for Single-Cell
Proteomics Data. bioRxiv,
2026.01.13.699212. This work describes the softHybrid imputation strategy.
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.5 LTS
#>
#> Matrix products: default
#> BLAS: /home/biocbuild/bbs-3.24-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 LC_TIME=en_GB
#> [4] LC_COLLATE=C LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C LC_ADDRESS=C
#> [10] LC_TELEPHONE=C 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 base
#>
#> other attached packages:
#> [1] SummarizedExperiment_1.43.0 Biobase_2.73.2 GenomicRanges_1.65.4
#> [4] Seqinfo_1.3.2 IRanges_2.47.5 S4Vectors_0.51.10
#> [7] BiocGenerics_0.59.13 generics_0.1.4 MatrixGenerics_1.25.0
#> [10] matrixStats_1.5.0 NADIA_0.99.3 BiocStyle_2.41.0
#>
#> loaded via a namespace (and not attached):
#> [1] RColorBrewer_1.1-3 jsonlite_2.0.0 shape_1.4.6.1 magrittr_2.0.5
#> [5] magick_2.9.1 farver_2.1.2 nloptr_2.2.1 rmarkdown_2.32
#> [9] GlobalOptions_0.1.4 vctrs_0.7.3 memoise_2.0.1 Cairo_1.7-0
#> [13] minqa_1.2.8 janitor_2.2.1 BiocBaseUtils_1.15.1 htmltools_0.5.9
#> [17] S4Arrays_1.13.2 curl_8.1.0 broom_1.0.13 pROC_1.19.1
#> [21] SparseArray_1.13.4 TTR_0.24.4 sass_0.4.10 bslib_0.12.0
#> [25] htmlwidgets_1.6.4 sandwich_3.1-3 impute_1.87.0 zoo_1.9-1
#> [29] lubridate_1.9.5 cachem_1.1.0 lifecycle_1.0.5 iterators_1.0.14
#> [33] pkgconfig_2.0.3 Matrix_1.7-6 R6_2.6.1 fastmap_1.2.0
#> [37] rbibutils_2.4.1 snakecase_0.11.1 clue_0.3-68 digest_0.6.39
#> [41] pcaMethods_2.5.0 colorspace_2.1-4 tidyHeatmap_1.13.1 AnnotationDbi_1.75.2
#> [45] patchwork_1.3.2 RSQLite_3.53.3 rrcovNA_0.5-3 vegan_2.7-6
#> [49] labeling_0.4.3 timechange_0.4.0 mgcv_1.9-5 httr_1.4.9
#> [53] abind_1.4-8 compiler_4.6.1 bit64_4.8.6 withr_3.0.3
#> [57] doParallel_1.0.17 S7_0.2.2 backports_1.5.1 BiocParallel_1.47.0
#> [61] DBI_1.3.0 viridis_0.6.5 dendextend_1.19.1 MASS_7.3-66
#> [65] DelayedArray_0.39.8 rjson_0.2.23 permute_0.10-0 tools_4.6.1
#> [69] rrcov_1.7-7 otel_0.3.0 quantmod_0.4.29 glue_1.8.1
#> [73] nlme_3.1-172 grid_4.6.1 cluster_2.1.8.3 sva_3.61.0
#> [77] gtable_0.3.6 tidyr_1.3.2 data.table_1.18.6.1 XVector_0.53.0
#> [81] foreach_1.5.2 pillar_1.11.1 stringr_1.6.0 limma_3.99.4
#> [85] genefilter_1.95.0 gmm_1.9-1 logging_0.10-111 robustbase_0.99-7
#> [89] circlize_0.4.18 splines_4.6.1 dplyr_1.2.1 lattice_0.23-1
#> [93] survival_3.8-12 bit_4.6.0 annotate_1.91.1 tidyselect_1.2.1
#> [97] ComplexHeatmap_2.29.0 Biostrings_2.81.9 knitr_1.52 BERT_1.9.0
#> [101] reformulas_0.4.4 gridExtra_2.3.1 bookdown_0.48 imputeLCMD_2.1
#> [105] edgeR_4.99.12 xfun_0.62 statmod_1.5.2 DEoptimR_1.2-2
#> [109] stringi_1.8.9 yaml_2.3.12 boot_1.3-32 evaluate_1.0.5
#> [113] codetools_0.2-20 MsCoreUtils_1.25.4 tibble_3.3.1 BiocManager_1.30.27
#> [117] cli_3.6.6 xtable_1.8-8 Rdpack_2.6.6 jquerylib_0.1.4
#> [121] dichromat_2.0-1 Rcpp_1.1.2 norm_1.0-11.1 png_0.1-9
#> [125] XML_3.99-0.25 parallel_4.6.1 blob_1.3.0 ggplot2_4.0.3
#> [129] assertthat_0.2.1 lme4_2.1-0 viridisLite_0.4.3 mvtnorm_1.4-2
#> [133] rlist_0.4.6.2 tmvtnorm_1.7 scales_1.4.0 xts_0.14.3
#> [137] pcaPP_2.0-7 purrr_1.2.2 highcharter_0.9.5 crayon_1.5.3
#> [141] GetoptLong_1.1.1 rlang_1.3.0 KEGGREST_1.53.6