Date: 2025-10-30

# Introduction to TargetDecoy

Elke Debrie1, Adriaan Sticker1, Milan Malfait1 and Lieven Clement1

1Ghent University

#### 30 October 2025

#### Package

TargetDecoy 1.16.0

    library(TargetDecoy)
    library(ggplot2)

# 1 Basics

## 1.1 Installing `TargetDecoy`

TargetDecoy is an `R` package available via the Bioconductor repository for packages. `R` can be installed on any operating system from CRAN after which you can install TargetDecoy by using the following commands in your `R` session:

    if (!requireNamespace("BiocManager", quietly = TRUE)) {
          install.packages("BiocManager")
      }

    BiocManager::install("TargetDecoy")

    ## Check that you have a valid Bioconductor installation
    BiocManager::valid()

The latest development version of `TargetDecoy` can be installed from GitHub by running the following in an `R` session:

    BiocManager::install("statOmics/TargetDecoy")

## 1.2 Citing `TargetDecoy`

We hope that TargetDecoy will be useful for your research. Please use the following information to cite the package and the overall approach. Thank you!

    ## Citation info
    citation("TargetDecoy")
    #> To cite package 'TargetDecoy' in publications use:
    #>
    #>   Debrie E, Clement L, Malfait M (2025). _TargetDecoy: Diagnostic Plots
    #>   to Evaluate the Target Decoy Approach_.
    #>   doi:10.18129/B9.bioc.TargetDecoy
    #>   <https://doi.org/10.18129/B9.bioc.TargetDecoy>, R package version
    #>   1.16.0, <https://bioconductor.org/packages/TargetDecoy>.
    #>
    #> A BibTeX entry for LaTeX users is
    #>
    #>   @Manual{,
    #>     title = {TargetDecoy: Diagnostic Plots to Evaluate the Target Decoy Approach},
    #>     author = {Elke Debrie and Lieven Clement and Milan Malfait},
    #>     year = {2025},
    #>     note = {R package version 1.16.0},
    #>     url = {https://bioconductor.org/packages/TargetDecoy},
    #>     doi = {10.18129/B9.bioc.TargetDecoy},
    #>   }

# 2 Introduction

A first step in the data analysis of Mass Spectrometry (MS) based proteomics data is to identify peptides and proteins. For this, a huge number of experimental mass spectra typically have to be assigned to theoretical peptides derived from a sequence database. This task is carried out by specialised algorithms called search engines, which compare each of the observed spectra to theoretical spectra derived from relevant candidate peptides obtained from a sequence data base and calculate a score for each comparison. The observed spectrum is then assigned to the theoretical peptide with the best score, which is also referred to as a peptide-to-spectrum match (PSM). It is of course crucial for the downstream analysis to evaluate the overall reliability of these matches. Therefore False Discovery Rate (FDR) control is used to return a sufficiently reliable list of PSMs. The FDR calculation, however, requires a good characterisation of the score distribution of PSMs that are matched to an incorrect peptide (bad target hits). In proteomics, the target decoy approach (TDA) is typically used for this purpose. The TDA method matches the observed spectra to a database of real (target) and nonsense (decoy) peptides, with the latter typically generated by reversing protein sequences in the target database. Hence, all PSMs that match to a decoy peptide are known to be bad hits and the distribution of their scores is used to estimate the distribution of the bad scoring target PSMs. A crucial assumption of the TDA is that decoy PSM hits have similar properties as bad target hits so that decoy PSM scores are a good simulation of target PSM scores. Users, however, typically do not evaluate these assumptions. To this end we developed TargetDecoy to generate diagnostic plots to evaluate the quality of the target decoy method, thus allowing users to assess whether the key assumptions underlying the method are met.

# 3 Concepts

## 3.1 Basic Statistical Concepts

We first introduce some notation. With \(x\) we denote the PSM and without loss of generality we assume that larger score values indicate a better match to the theoretical spectrum. The scores will follow a mixture distribution:

\[f(x) = \pi_0f_0(x)+(1-\pi_0)f_1(x),\]

with \(f(x)\) the target PSM score distribution, \(f_0(x)\) the mixture component corresponding to incorrect PSMs, \(f_1(x)\) the mixture component corresponding to the correct PSMs and \(\pi_0\) the fraction of incorrect PSMs. Based on the mixture distribution we can calculate the posterior probability that a PSM with score \(x\) is a bad match:

\[ P[\text{Bad hit} \mid \text{score }x]=\frac{\pi_0 f_0 (x)}{f(x)}, \]

which is also referred to as the posterior error probability (PEP) in mass spectrometry based proteomics. With the mixture model, we can also calculate the posterior probability that a random PSM in the set of all PSMs with scores above a score threshold t is a bad hit:

\[ P[\text{Bad hit} \mid \text{score }x>t]=\pi_0 \frac{\int\limits_{x=t}^{+\infty} f_0(x)dx}{\int\limits_{x=t}^{+\infty} f(x)dx}, \]

with \(\int\limits_{x=t}^{+\infty} f_0(x) dx\) the probability to observe a bad PSM hit above the threshold and, \(\int\limits_{x=t}^{+\infty} f(x) dx\) the probability to observe a target PSM hit above the threshold. The probability \(P[\text{Bad hit} \mid \text{score }x>t]\) is also referred to as the False Discovery Rate for peptide identification (FDR) of the set of PSMs with scores above the threshold \(t\). Hence, the FDR has the interpretation of the expected fraction of bad hits in the set of all target hits that are returned in the final PSM list, so bad PSM hits with scores above the threshold.

In order to estimate the FDR, we thus have to estimate the distribution of the bad hits and of the targets. In proteomics this is typically done by the use of the Target Decoy Approach (TDA).

## 3.2 Target Decoy Approach

A competitive target decoy search involves performing a search on a concatenated target and decoy database (as, for example, obtained from a reversed target database). Typically, one will ensure that there are as many targets as there are decoys. If the decoy hits are a good simulation of the bad target hits, it is equally likely that a bad hit will go to the targets as as it is to go to the decoys. With a competitive target decoy approach, it is therefore assumed that a bad hit matches a bad target equally likely as a decoy.

The distribution of bad target hits (\(f_0(t)\)) and the marginal distribution of all target hits (\(f(t)\)) is empirically estimated using the decoy scores and all target scores respectively. With the TDA, the FDR of the set of returned PSMs with scores above a threshold t, is estimated by dividing the number of decoy hits with a score above t by the number of target PSMs with a score above t.

\[ \widehat{\text{FDR}}(t) = \frac{\#\widehat{\text{ bad hits}} \mid x>t}{\#\text{ targets} \mid x>t} \stackrel{(*)}{=} \frac{\#\text{ decoys} \mid x>t}{\#\text{ targets} \mid x>t} \\ (*) \text{ Assumption TDA}: \text{bad targets} \stackrel{d}{=} \text{decoys} \]

This can be rewritten as:

\[\widehat{\text{FDR}}(t)=\frac{\#decoys}{\#targets} \cdot \frac{\frac{\# decoys \mid x>t}{\#decoys}}{\frac{\#targets \mid x>t}{\#targets}}\]

\[\widehat{\text{FDR}}(t) = {\widehat{\pi}}_0 \frac{\widehat{\int\limits_t^{+\infty} f_0(x) dx}}{\widehat{\int\limits_t^{+\infty} f(x)dx}}\]

Hence, the proportion of bad hits \(\pi_0\) is estimated as the number of decoys divided by the number of targets, since the competitive TDA assumes that it is equally likely that a bad hit matches to a bad target hit or to a decoy. The probability on a (bad) target PSM hit above the threshold is estimated based on the empirical cumulative distribution in the sample, i.e. as the fraction of targets (decoys) that are above the threshold. Hence, a second assumption is that the decoy scores provide a good simulation of the bad target scores.

Figure 1: Illustration of the target and decoy distributions
The green bars are the histogram of the target PSM scores. The orange bars are the histogram of the decoy PSM scores. We see that the decoy PSMs match well with the incorrect target hits that are more likely to occur at low scores.

In summary, two assumptions have to be checked:

- Assumption 1: The decoy PSM hits are a good simulation of the bad target hits and the distributions are equal.

- Assumption 2: When the library size of targets and decoys is the same, it is equally likely that a bad hit matches to a target sequence or to a decoy sequence.

When these two assumptions are met we can replace the number of bad hits by the number of decoys.

These assumptions can be checked using a histogram and a PP-plot.

# 4 Diagnostic plots

## 4.1 Histogram

In the histogram the shape of the score distribution of the decoys (shown in blue in the figure below) should be equal to that of bad target hits (first mode in the target distribution indicated in red). Note that the height of the decoys can be slightly lower than the first mode in the target distribution because some good target hits also have a low score (e.g., because of poor quality observed spectra).

    data("ModSwiss")
    evalTargetDecoysHist(ModSwiss,
        decoy = "isdecoy", score = "ms-gf:specevalue",
        log10 = TRUE, nBins = 50
    )

Figure 2: Histogram of the scores of targets and decoys

## 4.2 PPplot

The figure below shows an example of a PP plot, the main diagnostic plot to evaluate the quality of the decoys.

    evalTargetDecoysPPPlot(ModSwiss,
        decoy = "isdecoy", score = "ms-gf:specevalue",
        log10 = TRUE
    )

Figure 3: PP plot

Deviations from the assumptions of TDA can be best evaluated in the PP-plot. The PP-plot displays the empirical cumulative distribution (ECDF) from the target distribution in function of that of the decoy distribution. PP-plots have the property that they show a straight 45 degree line through the origin if and only if both distributions are equivalent. Any deviation from this straight line indicates that the distributions differ. Note that we expect that these distributions will differ for targets and decoys. Indeed, the target distribution is stochastically larger than the decoy distribution as larger scores indicate more reliable hits and as decoys are believed to behave similarly to bad target hits. Hence, the PP-plot for targets versus decoys will always be expected to lie below the 45 degree line. When the decoys are a good simulation for the bad target hits, however, the lower values in the PP-plot should lie on a straight line through the origin with a slope determined by \(\widehat{\pi}_0\). Indeed, small target PSM scores are most likely bad hits. Hence we will include the \(\widehat{\pi}_0\)-line in our diagnostic PP plot to evaluate the quality of the decoys.

When the assumptions of the TDA approach are violated, the dots in the PP-plot at lower percentiles will deviate from the \(\widehat{\pi}_0\) line. In case the PP-plot is still a straight line at lower percentiles, then the shape of the decoy distribution is correct, but there are less (or more) decoys than expected under the concatenated TDA assumption, which could occur if the decoy database is different in size than the target database or when a bad hit is less likely to match to a decoy than to a bad target hit. This would also be visible in the histograms: the decoy histogram would be considerably lower (higher) than the first mode of the target distribution. When the PP-plot at lower percentiles deviates from a straight line, the distribution of decoys and the bad target PSMs is not equivalent, indicating that the decoys are not a good simulation of bad target hits. Either type of deviation should be of concern as each indicates that the FDR returned by the conventional concatenated TDA is incorrect.

# 5 Supported data formats

TargetDecoy currently supports objects of class `mzID` or `mzRIdent` from the mzID and mzR packages, respectively.

These datasets are typically loaded from an `.mzid` file as follows

    filename <- system.file("extdata/55merge_omssa.mzid", package = "mzID")

    ## mzID
    mzID_object <- mzID::mzID(filename)
    #> reading 55merge_omssa.mzid... DONE!
    class(mzID_object)
    #> [1] "mzID"
    #> attr(,"package")
    #> [1] "mzID"

    ## mzRIdent
    mzR_object <- mzR::openIDfile(filename)
    class(mzR_object)
    #> [1] "mzRident"
    #> attr(,"package")
    #> [1] "mzR"

# 6 Examples

## 6.1 The data

In this vignette we use data from a Pyrococcus furiosis sample run on a LTQ-Orbitrap Velos mass spectrometer. The data can be found in the PRIDE repository with identifier PXD001077. The Pyrococcus furiosis reference proteome fasta files were downloaded from UniProtKB and UniProtKB/Swiss-Prot on April 22, 2016. The Pyrococcus data was searched against all Pyrococcus proteins with multiple search engines using either the reference proteome from UniProtKB or the reference proteome from UniProtKB/Swiss-Prot.

This dataset is available in the TargetDecoy installation and can be loaded with `data("ModSwiss")`.

## 6.2 Example 1: One file and one search engine

### 6.2.1 Example 1.1: MSGF+ search engine

In the next example MS-GF+ was used as the database search engine and SwissProt was used as protein sequence database. The MS-GF+ search provided an mzid file.

    ## Load the example SwissProt dataset
    data("ModSwiss")

We use the function `evalTargetDecoys()`. This function creates two diagnostic plots, i.e. a histogram and a PP plot. It can be used to evaluate the quality of the target decoy approach, i.e. to check the competitive TDA assumptions.

It has the following arguments:

- `object`: mzID object, mzR object or `data.frame`
- `decoy`: Character, name of the decoy variable which consists of a boolean that indicates if the score belongs to a target or a decoy. Name of this variable in the file (typically `"isdecoy"` or `"isDecoy"`).
- `score`: Score variable contains the scores of the search engine, which have to be continuous (larger scores are assumed to be better. E-values are typically -log10(e-value) transformed.) Name of the variable depends on the search engine.
- `log10`: Logical. Should the score be log10 transformed? Typically used when the score is an e-value. The default is `TRUE`.
- `nBins`: Number of bins in the histogram. The default is `50`

`evalTargetDecoys()` will return a figure consisting of 4 plots:

1. A PP plot showing the empirical cumulative distribution of the target distribution in function of that of the decoy distribution
2. A histogram showing the score distributions of the decoys and non-decoys
3. A zoomed PP plot
4. A zoomed histogram

    evalTargetDecoys(ModSwiss,
        decoy = "isdecoy", score = "ms-gf:specevalue",
        log10 = TRUE, nBins = 50
    )

Figure 4: Histogram and PP plot, as well as a zoom for both plots

The four plots can also be generated individually as follows:

    msgfSwiss_ppPlot <- evalTargetDecoysPPPlot(ModSwiss,
        decoy = "isdecoy", score = "ms-gf:specevalue",
        log10 = TRUE
    )
    msgfSwiss_ppPlot_zoomed <- evalTargetDecoysPPPlot(ModSwiss,
        decoy = "isdecoy", score = "ms-gf:specevalue",
        log10 = TRUE, zoom = TRUE
    )
    msgfSwiss_hist <- evalTargetDecoysHist(ModSwiss,
        decoy = "isdecoy", score = "ms-gf:specevalue",
        log10 = TRUE, nBins = 50
    )
    msgfSwiss_hist_zoomed <- evalTargetDecoysHist(ModSwiss,
        decoy = "isdecoy", score = "ms-gf:specevalue",
        log10 = TRUE, nBins = 50, zoom = TRUE
    )

All plots are `ggplot` objects, which means that one can zoom in on any part of the plot.

    msgfSwiss_ppPlot +
        coord_cartesian(xlim = c(NA, 0.25), ylim = c(NA, 0.05))
    #> Coordinate system already present.
    #> ℹ Adding new coordinate system, which will replace the existing one.

Figure 5: Zoom of the first part of the PP plot

### 6.2.2 Interpretation

The histogram shows that the shape of the distribution of the decoys (shown in blue) is approximately equal to that of the bad target hits (first mode in the target distribution indicated in red). The height of the decoys is slightly lower than the first mode in the target distribution, which is due to the fact that some good target hits also have a low score. This indicates that the form is good which is also confirmed in the PP-plot.

Moreover, the first part of the PP-plot is linear with a slope that is about equal to \(\hat{\pi}_0\) line. This indicates that the decoy distribution and the mixture component for incorrect PSMs of the target mixture distribution (= distribution of the bad target hits) coincide more or less. This profile in the PP-plot indicates that the set of decoys from the complete search is representative for the bad target hits. Hence, the assumptions of the concatenated TDA approach are not violated for this Pyrococcus example, assumption 2 is also fulfilled.

Overall we can conclude that in this example the assumptions for the TDA are met.

### 6.2.3 Example 1.2: X!Tandem search engine

In the next example, X!Tandem was used as the database search engine and SwissProt was used as protein sequence database. The X!Tandem search provided an mzid file.

    ## Load X!Tandem example data
    data("ModSwissXT")

We run `evalTargetDecoys()` with the necessary arguments.

    evalTargetDecoys(ModSwissXT,
        decoy = "isdecoy", score = "x!tandem:expect",
        log10 = TRUE, nBins = 50
    )

Figure 6: Histogram and PP plot, each with a zoom level

The shape of the decoy distribution seems to be fine, i.e. the first part of the PP-plot lies on a straight line. But, the histogram and the PP-plot show issues with the estimation of pi0. The total number of decoys does not seem to be a good estimate for the total number of bad target PSM hits. Indeed, it seems to be more likely that a bad match is a target than a decoy! This is due to the two pass search strategy that is performed by X!tandem. In the first phase a rapid search is performed, which does not allow for modifications nor for miss cleavages. In a second phase, a new search is conducted solely against the identified peptides in the first phase, but now by using a more complex strategy that allows for missed cleavages and post translational modifications. Performing the refined search against the smaller population of candidate peptides from the first phase greatly reduces the computational complexity, however, it comes at the cost that the TDA assumptions are violated. Indeed, in the second pass low scoring PSMs can switch to a modified PSM, which seems to be the case for many decoy hits from the first phase. Many of these switched to modified target PSMs, however, remain to have a relative low score and are likely to be bad target PSMs. The number of decoy matches is no longer representative for the number of bad target matches. This example shows that the use of a second pass strategy can be very detrimental for the FDR estimation using the TDA approach.

## 6.3 Example 2: One file and search performed by an engine that combines different search engines

In the following example multiple database search engines where used to process the same mzID file and both Swissprot and Uniprot were used as protein sequence database. Three search engines – MS-GF+, OMSSA and X!Tandem – and a combination of these 3 (by using PeptideShaker) were used to search against both protein sequence databases. `createPPlotScores()` provides the possibility to evaluate each of the sub-engines and the overall itself. Two PP plots are created to evaluate the quality of the TDA.

Provide following arguments in the function. When one are more are missing, also here a GUI pops up.

- `object`: mzID object, mzR object or `data.frame`
- `decoy`: Character, name of the decoy variable which consists of a boolean that indicates if the score belongs to a target or a decoy. Name of this variable in the file (typically `"isdecoy"` or `"isDecoy"`).
- `scores`: Character vector indicating the score variables containing the scores of the search engine. Name of the variable depends on the search engine.
- `log10`: Vector, should the score be log10 transformed? Typically used when the score is an e-value.

We will reuse the `ModSwissXT` object from before.

    ## Run createPPlotScores with necessary arguments
    ## Omitting 'decoy', 'scores' or 'log10' will launch the Shiny app
    createPPlotScores(ModSwissXT,
        decoy = "isdecoy",
        scores = c("omssa:evalue", "ms-gf:specevalue",
                   "x!tandem:expect", "peptideshaker psm score"),
        ## We can choose to log-transform some scores but not others
        log10 = c(TRUE, TRUE, TRUE, FALSE)
    )

Figure 7: PP plot and standardized PP plot

### 6.3.1 Interpretation of the plots

For files with results for multiple search engines, a “standardized” PP-plot is also included. In this plot the estimated \(\pi_0\) is removed from the PP-plot. In the standardized PP-plot deviations in the first part have to be compared to the 0 line, which allows the user to quickly spot issues when the results of multiple search engines is visualised. The standardized plots show that the TDA assumptions hold for both MSGF+ and OMSSA. For X!tandem, however, we again see issues for the Swissprot search and these also seem to affect the combined peptide shaker search engine score that relies on all three searches.

From these plots we can deduce that combined search engines are useful. However, it is crucial to assess the TDA assumptions, otherwise the FDR control can be dramatically compromised and our tool can help data analysts to choose the engines that they include in combined search engines, carefully.

## 6.4 Example 3: Check the assumptions for multiple searches/runs

When you have to evaluate the quality of the TDA for different runs, it can be useful to see at a glance which runs have problems with these assumptions. The function `createPPlotObjects()` generated multiple files at once and two PP plots to check the necessary assumptions. A first plot with the original PP plots and pi0 lines. A second “standardized” plot where the reference \(\pi_O\) line is at 0. An overview in one figure ensures a quick evaluation of a large number of files. Based on this plot a more detailed analysis of the ‘anomalous’ runs can be done with `evalTargetDecoys()`.

The function `createPPlotObjects()` has four arguments:

- `object_list`: a list of mzID, mzR or `data.frame` objects.
- `decoy`: Vector of characters, name of the decoy variable which consists of a boolean that indicates if the score belongs to a target or a decoy. Name of this variable in the file (typically `"isdecoy"` or `"isDecoy"`).
- `score`: Character vector indicating the score variables containing the scores of the search engine. Name of the variable depends on the search engine.
- `log10`: Vector. Should the score be log10 transformed? Typically used when the score is an e-value.

Again, when all of these arguments are given correctly, the function will be executed and the desired plots will be created. However, if the user forgets to provide one of the arguments, or gives an incorrect variable that does not belong to the desired class, a shiny app is launched to select the variables for each input object given in `object_list`.

### 6.4.1 Provide a list with all objects

We will reuse the data sets from the previous examples and store them in a list.

### 6.4.2 Example 3.1: Two data sets with the same search engine

In this example scores come from the same search engine, MS-GF+, and thus are indicated by the same variable name `"ms-gf:specevalue"`.

    ## Create list of input objects, list names will be reused in the plots
    mzObjects <- list(
        ModSwiss_data = ModSwiss,
        pyroSwissprot_data = ModSwissXT
    )

    createPPlotObjects(
        mzObjects, decoy = "isdecoy",
        score = "ms-gf:specevalue"
    )

Figure 8: PP plot and standardized PP plot for two searches (same search engine)

### 6.4.3 Example 3.2: Two objects with different search engines

In this last example, different variable names for score are given as a vector, because two different search engines, MS-GF+ and X!tandem, were used.

    ## Create list of input objects, list names will be reused in the plots
    mzObjects <- list(
        ModSwiss_data = ModSwiss,
        ModSwissXT_data = ModSwissXT
    )

    plotDiffEngine <- createPPlotObjects(mzObjects,
        decoy = "isdecoy",
        score = c("ms-gf:specevalue", "x!tandem:expect")
    )
    plotDiffEngine

Figure 9: PP plot and standardized PP plot for two searches (different search engine)

Also here, it can be useful to zoom in on one specific part of the figure.

    # Zoom in on the relevant or desired part of the plot
    plotZoom <- plotDiffEngine +
        coord_cartesian(xlim = c(NA, 0.25), ylim = c(NA, 0.05))
    #> Coordinate system already present.
    #> ℹ Adding new coordinate system, which will replace the existing one.
    plotZoom

In a final step, we can investigate the anomalous plot in more detail using `evalTargetDecoys()`.

    evalTargetDecoys(mzObjects$ModSwissXT_data,
        decoy = "isdecoy", score = "x!tandem:expect",
        log10 = TRUE, nBins = 50
    )

Figure 10: Histogram and PP plot, each with a zoom level

Similar to example 1.2, the histogram and the PP-plot show issues with the estimation of \(\pi_0\), which is due to the second pass strategy that is performed by X!tandem.

# 7 Shiny gadget

Sometimes the variable names are not known up front. If this is the case, the `⁠evalTargetDecoys*(`)⁠ functions can be called with only an input object. This launches a Shiny gadget that allows selecting the variables interactively. A histogram and PP-plot of the selected variables are created on the fly for previewing, together with a snapshot of the selected data.

When working in RStudio, the gadget opens in the “Viewer” pane by default, but it can also be opened in any browser window.

Figure 11: The Shiny gadget opens after calling `evalTargetDecoys()` with only an input object but no variables specified

Figure 12: A histogram and PP-plot can be previewed in the respective tabs

# 8 Reproducibility

`R` session information.

    #> ─ Session info ───────────────────────────────────────────────────────────────────────────────────────────────────────
    #>  setting  value
    #>  version  R version 4.5.1 Patched (2025-09-10 r88807)
    #>  os       macOS Ventura 13.7.7
    #>  system   aarch64, darwin20
    #>  ui       X11
    #>  language (EN)
    #>  collate  C
    #>  ctype    en_US.UTF-8
    #>  tz       America/New_York
    #>  date     2025-10-30
    #>  pandoc   3.1.12.3 @ /opt/homebrew/bin/ (via rmarkdown)
    #>  quarto   1.7.32 @ /usr/local/bin/quarto
    #>
    #> ─ Packages ───────────────────────────────────────────────────────────────────────────────────────────────────────────
    #>  package      * version   date (UTC) lib source
    #>  abind          1.4-8     2024-09-12 [2] CRAN (R 4.5.0)
    #>  backports      1.5.0     2024-05-23 [2] CRAN (R 4.5.0)
    #>  Biobase        2.70.0    2025-10-29 [2] Bioconductor 3.22 (R 4.5.1)
    #>  BiocGenerics   0.56.0    2025-10-29 [2] Bioconductor 3.22 (R 4.5.1)
    #>  BiocManager    1.30.26   2025-06-05 [2] CRAN (R 4.5.0)
    #>  BiocStyle    * 2.38.0    2025-10-29 [2] Bioconductor 3.22 (R 4.5.1)
    #>  bookdown       0.45      2025-10-03 [2] CRAN (R 4.5.1)
    #>  broom          1.0.10    2025-09-13 [2] CRAN (R 4.5.0)
    #>  bslib          0.9.0     2025-01-30 [2] CRAN (R 4.5.0)
    #>  cachem         1.1.0     2024-05-16 [2] CRAN (R 4.5.0)
    #>  car            3.1-3     2024-09-27 [2] CRAN (R 4.5.0)
    #>  carData        3.0-5     2022-01-06 [2] CRAN (R 4.5.0)
    #>  cli            3.6.5     2025-04-23 [2] CRAN (R 4.5.0)
    #>  codetools      0.2-20    2024-03-31 [2] CRAN (R 4.5.1)
    #>  cowplot        1.2.0     2025-07-07 [2] CRAN (R 4.5.0)
    #>  dichromat      2.0-0.1   2022-05-02 [2] CRAN (R 4.5.0)
    #>  digest         0.6.37    2024-08-19 [2] CRAN (R 4.5.0)
    #>  doParallel     1.0.17    2022-02-07 [2] CRAN (R 4.5.0)
    #>  dplyr          1.1.4     2023-11-17 [2] CRAN (R 4.5.0)
    #>  evaluate       1.0.5     2025-08-27 [2] CRAN (R 4.5.0)
    #>  farver         2.1.2     2024-05-13 [2] CRAN (R 4.5.0)
    #>  fastmap        1.2.0     2024-05-15 [2] CRAN (R 4.5.0)
    #>  foreach        1.5.2     2022-02-02 [2] CRAN (R 4.5.0)
    #>  Formula        1.2-5     2023-02-24 [2] CRAN (R 4.5.0)
    #>  generics       0.1.4     2025-05-09 [2] CRAN (R 4.5.0)
    #>  ggplot2      * 4.0.0     2025-09-11 [2] CRAN (R 4.5.0)
    #>  ggpubr         0.6.2     2025-10-17 [2] CRAN (R 4.5.0)
    #>  ggsignif       0.6.4     2022-10-13 [2] CRAN (R 4.5.0)
    #>  glue           1.8.0     2024-09-30 [2] CRAN (R 4.5.0)
    #>  gtable         0.3.6     2024-10-25 [2] CRAN (R 4.5.0)
    #>  htmltools      0.5.8.1   2024-04-04 [2] CRAN (R 4.5.0)
    #>  httpuv         1.6.16    2025-04-16 [2] CRAN (R 4.5.0)
    #>  iterators      1.0.14    2022-02-05 [2] CRAN (R 4.5.0)
    #>  jquerylib      0.1.4     2021-04-26 [2] CRAN (R 4.5.0)
    #>  jsonlite       2.0.0     2025-03-27 [2] CRAN (R 4.5.0)
    #>  knitr        * 1.50      2025-03-16 [2] CRAN (R 4.5.0)
    #>  labeling       0.4.3     2023-08-29 [2] CRAN (R 4.5.0)
    #>  later          1.4.4     2025-08-27 [2] CRAN (R 4.5.0)
    #>  lifecycle      1.0.4     2023-11-07 [2] CRAN (R 4.5.0)
    #>  magrittr       2.0.4     2025-09-12 [2] CRAN (R 4.5.0)
    #>  mime           0.13      2025-03-17 [2] CRAN (R 4.5.0)
    #>  miniUI         0.1.2     2025-04-17 [2] CRAN (R 4.5.0)
    #>  mzID           1.48.0    2025-10-29 [2] Bioconductor 3.22 (R 4.5.1)
    #>  mzR            2.44.0    2025-10-29 [2] Bioconductor 3.22 (R 4.5.1)
    #>  ncdf4          1.24      2025-03-25 [2] CRAN (R 4.5.0)
    #>  otel           0.2.0     2025-08-29 [2] CRAN (R 4.5.0)
    #>  pillar         1.11.1    2025-09-17 [2] CRAN (R 4.5.0)
    #>  pkgconfig      2.0.3     2019-09-22 [2] CRAN (R 4.5.0)
    #>  plyr           1.8.9     2023-10-02 [2] CRAN (R 4.5.0)
    #>  promises       1.4.0     2025-10-22 [2] CRAN (R 4.5.0)
    #>  ProtGenerics   1.42.0    2025-10-29 [2] Bioconductor 3.22 (R 4.5.1)
    #>  purrr          1.1.0     2025-07-10 [2] CRAN (R 4.5.0)
    #>  R6             2.6.1     2025-02-15 [2] CRAN (R 4.5.0)
    #>  RColorBrewer   1.1-3     2022-04-03 [2] CRAN (R 4.5.0)
    #>  Rcpp           1.1.0     2025-07-02 [2] CRAN (R 4.5.0)
    #>  rlang          1.1.6     2025-04-11 [2] CRAN (R 4.5.0)
    #>  rmarkdown      2.30      2025-09-28 [2] CRAN (R 4.5.1)
    #>  rstatix        0.7.3     2025-10-18 [2] CRAN (R 4.5.0)
    #>  S7             0.2.0     2024-11-07 [2] CRAN (R 4.5.0)
    #>  sass           0.4.10    2025-04-11 [2] CRAN (R 4.5.0)
    #>  scales         1.4.0     2025-04-24 [2] CRAN (R 4.5.0)
    #>  sessioninfo    1.2.3     2025-02-05 [2] CRAN (R 4.5.0)
    #>  shiny          1.11.1    2025-07-03 [2] CRAN (R 4.5.0)
    #>  TargetDecoy  * 1.16.0    2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
    #>  tibble         3.3.0     2025-06-08 [2] CRAN (R 4.5.0)
    #>  tidyr          1.3.1     2024-01-24 [2] CRAN (R 4.5.0)
    #>  tidyselect     1.2.1     2024-03-11 [2] CRAN (R 4.5.0)
    #>  vctrs          0.6.5     2023-12-01 [2] CRAN (R 4.5.0)
    #>  withr          3.0.2     2024-10-28 [2] CRAN (R 4.5.0)
    #>  xfun           0.53      2025-08-19 [2] CRAN (R 4.5.0)
    #>  XML            3.99-0.19 2025-08-22 [2] CRAN (R 4.5.0)
    #>  xtable         1.8-4     2019-04-21 [2] CRAN (R 4.5.0)
    #>  yaml           2.3.10    2024-07-26 [2] CRAN (R 4.5.0)
    #>
    #>  [1] /private/var/folders/r0/l4fjk6cj5xj0j3brt4bplpl40000gt/T/RtmpnV6lJO/Rinst1109e4a870367
    #>  [2] /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library
    #>  * ── Packages attached to the search path.
    #>
    #> ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────