From 21a4365245a64e072458f8ac68e19d748e444617 Mon Sep 17 00:00:00 2001 From: Philippine Louail <127301965+philouail@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:37:36 +0200 Subject: [PATCH] fix DockerFile --- Dockerfile | 8 +- .../a-end-to-end-untargeted-metabolomics.qmd | 3147 +++++++++++++++++ vignettes/images/sitcker_draft.xcf | Bin 0 -> 219777 bytes vignettes/install_v0.qmd | 60 - 4 files changed, 3151 insertions(+), 64 deletions(-) create mode 100644 vignettes/a-end-to-end-untargeted-metabolomics.qmd create mode 100644 vignettes/images/sitcker_draft.xcf delete mode 100644 vignettes/install_v0.qmd diff --git a/Dockerfile b/Dockerfile index 26d4402..6f8aa72 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,11 +25,11 @@ RUN Rscript -e "BiocManager::install(c('RCurl', 'xcms', 'MsExperiment', 'Summari ## Install the current package with vignettes RUN Rscript -e "devtools::install('.', dependencies = TRUE, type = 'source', build_vignettes = FALSE)" -## USER rstudio +USER rstudio ## build article for end-to-end:test -##RUN Rscript -e "quarto::quarto_render('vignettes/a-end-to-end-untargeted-metabolomics.qmd', quiet = FALSE)" +RUN Rscript -e "quarto::quarto_render('vignettes/a-end-to-end-untargeted-metabolomics.qmd', quiet = FALSE)" -##USER root +USER root -##RUN find vignettes/ -name "*.html" -type f -delete && find vignettes/ -name "*_files" -type d -exec rm -r {} + +RUN find vignettes/ -name "*.html" -type f -delete && find vignettes/ -name "*_files" -type d -exec rm -r {} + diff --git a/vignettes/a-end-to-end-untargeted-metabolomics.qmd b/vignettes/a-end-to-end-untargeted-metabolomics.qmd new file mode 100644 index 0000000..853277a --- /dev/null +++ b/vignettes/a-end-to-end-untargeted-metabolomics.qmd @@ -0,0 +1,3147 @@ +--- +title: "Complete end-to-end LC-MS/MS Metabolomic Data analysis" +format: html +tbl-cap-location: bottom +editor: visual +minimal: true +date: 'Compiled: `r format(Sys.Date(), "%B %d, %Y")`' +vignette: > + %\VignetteIndexEntry{Complete end-to-end LC-MS/MS Metabolomic Data analysis} + %\VignetteEngine{quarto::html} + %\VignetteEncoding{UTF-8} +--- + +```{r setup, include=FALSE} +library(knitr) +library(quarto) +knitr::opts_knit$set(root.dir = './') +``` + +# Introduction + +The present workflow describes all steps for the analysis of an LC-MS/MS +experiment, which includes the preprocessing of the raw data to generate the +*abundance* matrix for the *features* in the various samples, followed by data +normalization, differential abundance analysis and finally the annotation of +features to metabolites. Note that also alternative analysis options and R +packages could be used for different steps and some examples are mentioned +throughout the workflow. + +![Steps of the end-to-end workflow and possible +alternatives](images/workflow.png) + +# Data description + +See the [data +description](https://rformassspectrometry.github.io/metabonaut/articles/dataset-investigation.html) +vignette for detailed explanation of the dataset we go through in this workflow +and general tips on what should be done when you first get your data. + +# Packages needed + +Our workflow is therefore based on the following dependencies: + +```{r packages used, message=FALSE, warning=FALSE} +## General bioconductor package +library(BiocStyle) +library(knitr) + +## Data Import and handling +library(readxl) +library(MsExperiment) +library(MsIO) +library(MsBackendMetaboLights) +library(SummarizedExperiment) + +## Preprocessing of LC-MS data +library(xcms) +library(Spectra) +library(MetaboCoreUtils) + +## Statistical analysis +library(limma) # Differential abundance +library(matrixStats) # Summaries over matrices + +## Visualisation +library(pander) +library(RColorBrewer) +library(pheatmap) +library(vioplot) +library(ggfortify) # Plot PCA +library(gridExtra) # To arrange multiple ggplots into single plots + +## Annotation +library(AnnotationHub) # Annotation resources +library(CompoundDb) # Access small compound annotation data. +library(MetaboAnnotation) # Functionality for metabolite annotation. +``` + +# Data import + +Note that different equipment will generate various file extensions, so a +conversion step might be needed beforehand, though it does not apply to this +dataset. The Spectra package supports a variety of ways to store and retrieve MS +data, including mzML, mzXML, CDF files, simple flat files, or database systems. +If necessary, several tools, such as ProteoWizard's MSConvert, can be used to +convert files to the .mzML format [@chambers_cross-platform_2012]. + +Below we will show how to extract our dataset from the MetaboLigths database and +load it as an `MsExperiment` object. For more information on how to load your +data from the MetaboLights database, refer to the +[vignette](https://rformassspectrometry.github.io/MsIO/articles/MsIO.html#loading-data-from-metabolights). +For other type of data loading, check out this xcms +[vignette](https://jorainer.github.io/xcmsTutorials/articles/xcms-preprocessing.html#data-import-and-exploration) +A more specific vignette will be created for data import soon. + +```{r import, message=FALSE, warning=FALSE} +param <- MetaboLightsParam(mtblsId = "MTBLS8735", + assayName = paste0("a_MTBLS8735_LC-MS_positive_", + "hilic_metabolite_profiling.txt"), + filePattern = ".mzML") + +lcms1 <- readMsObject(MsExperiment(), + param, + keepOntology = FALSE, + keepProtocol = FALSE, + simplify = TRUE) +``` + +We next configure the parallel processing setup. Most functions from the *xcms* +package allow per-sample parallel processing, which can improve the performance +of the analysis, especially for large data sets. *xcms* and all packages from +the *RforMassSpectrometry* package ecosystem use the parallel processing setup +configured through the `r Biocpkg("BiocParallel")` Bioconductor package. With +the code below we use a *fork-based* parallel processing on unix system, and a +*socket-based* parallel processing on the Windows operating system. + +```{r par-process, eval = FALSE} +register(SerialParam()) +#' Set up parallel processing using 2 cores +## if (.Platform$OS.type == "unix") { +## register(MulticoreParam(2)) +## } else { +## register(SnowParam(2)) +## } + +``` + + +# Data organisation + +The experimental data is now represented by a `MsExperiment` object from the +`r Biocpkg("MsExperiment")` package. The `MsExperiment` object is a container +for metadata and spectral data that provides and manages also the linkage +between samples and spectra. + +```{r} +lcms1 +``` + +Below we provide a brief overview of the data structure and content. The +`sampleData()` function extracts sample information from the object. We next +extract this data and use the *pander* package to render and show this +information in Table 1 below. Throughout the document we use the R pipe operator +(`|>`) to avoid nested function calls and hence improve code readability. + +The `sampleData()` output from *MetaboLights* is not always ideal for direct and +easy access to the data. We therefore rename it and transform it in a more +user-friendly way. The user can add, transform and remove any column they want +using base R functionalities. + +```{r phenodata} +#| tbl-cap: "Table 1. Samples from the data set." +sampleData(lcms1)[, c("Derived_Spectral_Data_File", + "Characteristics[Sample type]", + "Factor Value[Phenotype]", + "Sample Name", + "Factor Value[Age]")] |> + kable(format = "pipe") +``` + +```{r update-phenodata} +#| tbl-cap: "Table 2. Simplified sample data." +# Let's rename the column for easier access +colnames(sampleData(lcms1)) <- c("sample_name", "derived_spectra_data_file", + "metabolite_asssignment_file", + "source_name", + "organism", + "blood_sample_type", + "sample_type", "age", "unit", "phenotype") + +# Add "QC" to the phenotype of the QC samples +sampleData(lcms1)$phenotype[sampleData(lcms1)$sample_name == "POOL"] <- "QC" +sampleData(lcms1)$sample_name[sampleData(lcms1)$sample_name == "POOL" ] <- c("POOL1", "POOL2", "POOL3", "POOL4") + +# Add injection index column +sampleData(lcms1)$injection_index <- seq_len(nrow(sampleData(lcms1))) + +#let's look at the updated sample data +sampleData(lcms1)[, c("derived_spectra_data_file", + "phenotype", "sample_name", "age", + "injection_index")] |> + kable(format = "pipe") +``` + +There are `r length(sampleData(lcms1))` samples in this data set. Below are +abbreviations essential for proper interpretation of this metadata information: + +- *injection_index*: An index representing the order (position) in which an + individual sample was measured (injected) within the LC-MS measurement run + of the experiment. +- *phenotype*: The sample groups of the experiment: + - `"QC"`: Quality control sample (pool of serum samples from an external, + large cohort). + - `"CVD"`: Sample from an individual with a cardiovascular disease. + - `"CTR"`: Sample from a presumably healthy control. +- *sample_name*: An arbitrary name/identifier of the sample. +- *age*: The (rounded) age of the individuals. + +We will define colors for each of the sample groups based on their sample group +using the *RColorBrewer* package: + +```{r define-colors, include=FALSE} +#' Define colors for the different phenotypes +col_phenotype <- brewer.pal(9, name = "Set1")[c(9, 5, 4)] +names(col_phenotype) <- c("QC", # grey + "CVD", # orange + "CTR") # purple +col_sample <- col_phenotype[sampleData(lcms1)$phenotype] +``` + +The MS data of this experiment is stored as a `Spectra` object (from the +`r Biocpkg("Spectra")` Bioconductor package) within the `MsExperiment` object +and can be accessed using `spectra()` function. Each element in this object is a +spectrum - they are organised linearly and are all combined in the same +`Spectra` object one after the other (ordered by retention time and samples). + +Below we access the dataset's `Spectra` object which will summarize its +available information and provide, among other things, the total number of +spectra of the data set. + +```{r} +#' Access Spectra Object +spectra(lcms1) +``` + +We can also summarize the number of spectra and their respective MS level +(extracted with the `msLevel()` function). The `fromFile()` function returns for +each spectrum the index of its sample (data file) and can thus be used to split +the information (MS level in this case) by sample to further summarize using the +base R `table()` function and combine the result into a matrix. Note that this +is the number of spectra acquired at each run, and not the number of spectral +features in each sample. + +```{r} +#' Count the number of spectra with a specific MS level per file. +spectra(lcms1) |> + msLevel() |> + split(fromFile(lcms1)) |> + lapply(table) |> + do.call(what = cbind) +``` + +The present data set thus contains only MS1 data, which is ideal for +quantification of the signal. A second (LC-MS/MS) data set also with fragment +(MS2) spectra of the same samples will be used later on in the workflow. + +Note that users should not restrict themselves to data evaluation examples shown +here or in other tutorials. The *Spectra* package enables user-friendly access +to the full MS data and its functionality should be extensively used to explore, +visualize and summarize the data. + +As another example, we below determine the retention time range for the entire +data set. + +```{r} +#' Retention time range for entire dataset +spectra(lcms1) |> + rtime() |> + range() +``` + +Data obtained from LC-MS experiments are typically analyzed along the retention +time axis, while MS data is organized by spectrum, orthogonal to the retention +time axis. + +# Data visualization and general quality assessment + +Effective visualization is paramount for inspecting and assessing the quality of +MS data. For a general overview of our LC-MS data, we can: + +- Combine all mass peaks from all (MS1) spectra of a sample into a single + spectrum in which each mass peak then represents the maximum signal of all + mass peaks with a similar *m/z*. This spectrum might then be called Base + Peak Spectrum (BPS), providing information on the most abundant ions of a + sample. +- Aggregate mass peak intensities for each spectrum, resulting in the Base + Peak Chromatogram (BPC). The BPC shows the highest measured intensity for + each distinct retention time (hence spectrum) and is thus orthogonal to the + BPS. +- Sum the mass peak intensities for each spectrum to create a Total Ion + Chromatogram (TIC). +- Compare the BPS of all samples in an experiment to evaluate similarity of + their ion content. +- Compare the BPC of all samples in an experiment to identify samples with + similar or dissimilar chromatographic signal. + +In addition to such general data evaluation and visualization, it is also +crucial to investigate specific signal of e.g. internal standards or +compounds/ions known to be present in the samples. By providing a reliable +reference, internal standards help achieve consistent and accurate analytical +results. + +## Spectra Data Visualization: BPS + +The BPS collapses data in the retention time dimension and reveals the most +prevalent ions present in each of the samples, creation of such BPS is however +not straightforward. Mass peaks, even if representing signals from the same ion, +will never have identical *m/z* values in consecutive spectra due to the +measurement error/resolution of the instrument. + +Below we use the `combineSpectra` function to combine all spectra from one file +(defined using parameter `f = fromFile(data)`) into a single spectrum. All mass +peaks with a difference in *m/z* value smaller than 3 parts-per-million (ppm) +are combined into one mass peak, with an intensity representing the maximum of +all such grouped mass peaks. To reduce memory requirement, we in addition first +*bin* each spectrum combining all mass peaks within a spectrum, aggregating mass +peaks into bins with 0.01 *m/z* width. In case of large datasets, it is also +recommended to set the `processingChunkSize()` parameter of the `MsExperiment` +object to a finite value (default is `Inf`) causing the data to be processed +(and loaded into memory) in chunks of `processingChunkSize()` spectra. This can +reduce memory demand and speed up the process. + +```{r} +#' Setting the chunksize +chunksize <- 1000 +processingChunkSize(spectra(lcms1)) <- chunksize +``` + +We can now generate BPS for each sample and `plot()` them. + +```{r bps, eval=FALSE} +#| fig-cap: "Figure 1. BPS of all samples." +#' Combining all spectra per file into a single spectrum +bps <- spectra(lcms1) |> + bin(binSize = 0.01) |> + combineSpectra(f = fromFile(lcms1), intensityFun = max, ppm = 3) + +#' Plot the base peak spectra +par(mar = c(2, 1, 1, 1)) +plotSpectra(bps, main= "") +``` + +Here, there is observable overlap in ion content between the files, particularly +around 300 *m/z* and 700 *m/z*. There are however also differences between sets +of samples. In particular, BPS 1, 4, 7 and 10 (counting row-wise from left to +right) seem different than the others. In fact, these four BPS are from QC +samples, and the remaining six from the study samples. The observed differences +might be explained by the fact that the QC samples are pools of serum samples +from a different cohort, while the study samples represent plasma samples, from +a different sample collection. + +Next to the visual inspection above, we can also calculate and express the +similarity between the BPS with a heatmap. Below we use the `compareSpectra()` +function to calculate pairwise similarities between all BPS and use then the +`pheatmap()` function from the *pheatmap* package to cluster and visualize this +result. + +```{r compare-spectra, eval=FALSE} +#| fig-cap: "Figure 2. Heatmap of MS signals similarities." +#' Calculate similarities between BPS +sim_matrix <- compareSpectra(bps) + +#' Add sample names as rownames and colnames +rownames(sim_matrix) <- colnames(sim_matrix) <- sampleData(lcms1)$sample_name +ann <- data.frame(phenotype = sampleData(lcms1)[, "phenotype"]) +rownames(ann) <- rownames(sim_matrix) + +#' Plot the heatmap +pheatmap(sim_matrix, annotation_col = ann, + annotation_colors = list(phenotype = col_phenotype)) +``` + +We get a first glance at how our different samples distribute in terms of +similarity. The heatmap confirms the observations made with the BPS, showing +distinct clusters for the QCs and the study samples, owing to the different +matrices and sample collections. + +It is also strongly recommended to delve deeper into the data by exploring it in +more detail. This can be accomplished by carefully assessing our data and +extracting spectra or regions of interest for further examination. In the next +chunk, we will look at how to extract information for a specific spectrum from +distinct samples. + +```{r} +#| fig-cap: "Figure 3. Intensity and m/z values of the 125th spectrum of two CTR samples." +#| out.width: 90% +#| fig-align: center +#' Accessing a single spectrum - comparing with QC +par(mfrow = c(1,2), mar = c(2, 2, 2, 2)) +spec1 <- spectra(lcms1[1])[125] +spec2 <- spectra(lcms1[3])[125] +plotSpectra(spec1, main = "QC sample") +plotSpectra(spec2, main = "CTR sample") +``` + +The significant dissimilarities in peak distribution and intensity confirm the +difference in composition between QCs and study samples. We next compare a full +MS1 spectrum from a CVD and a CTR sample. + +```{r} +#| fig-cap: "Figure 4. Intensity and m/z values of the 125th spectrum of one CVD and one CTR sample." +#| out.width: 90% +#| fig-align: center +#' Accessing a single spectrum - comparing CVD and CTR +par(mfrow = c(1,2), mar = c(2, 2, 2, 2)) +spec1 <- spectra(lcms1[2])[125] +spec2 <- spectra(lcms1[3])[125] +plotSpectra(spec1, main = "CVD sample") +plotSpectra(spec2, main = "CTR sample") +``` + +Above, we can observe that the spectra between CVD and CTR samples are not +entirely similar, but they do exhibit similar main peaks between 200 and 600 m/z +with a general higher intensity in control samples. However the peak +distribution (or at least intensity) seems to vary the most between an *m/z* of +10 to 210 and after an *m/z* of 600. + +The CTR spectrum above exhibits significant peaks around an *m/z* of 150 - 200 +that have a much lower intensity in the CVD sample. To delve into more details +about this specific spectrum, a wide range of functions can be employed: + +```{r} +#| tbl-cap: "Table 3. Intensity and m/z values of the 125th spectrum of one CTR sample." +#| tbl-style: "border: 1px solid black; width: 50%; border-collapse: collapse;" +#' Checking its intensity +intensity(spec2) + +#' Checking its rtime +rtime(spec2) + +#' Checking its m/z +mz(spec2) + +#' Filtering for a specific m/z range and viewing in a tabular format +filt_spec <- filterMzRange(spec2,c(50,200)) + +data.frame(intensity = unlist(intensity(filt_spec)), + mz = unlist(mz(filt_spec))) |> + head() |> kable(format = "markdown") +``` + +## Chromatographic Data Visualization: BPC and TIC + +The `chromatogram()` function facilitates the extraction of intensities along +the retention time. However, access to chromatographic information is currently +not as efficient and seamless as it is for spectral information. Work is +underway to develop/improve the infrastructure for chromatographic data through +a new `Chromatograms` object aimed to be as flexible and user-friendly as the +`Spectra` object. + +For visualizing LC-MS data, a BPC or TIC serves as a valuable tool to assess the +performance of liquid chromatography across various samples in an experiment. In +our case, we extract the BPC from our data to create such a plot. The BPC +captures the maximum peak signal from each spectrum in a data file and plots +this information against the retention time for that spectrum on the y-axis. The +BPC can be extracted using the `chromatogram` function. + +By setting the parameter `aggregationFun = "max"`, we instruct the function to +report the maximum signal per spectrum. Conversely, when setting +`aggregationFun = "sum"`, it sums up all intensities of a spectrum, thereby +creating a TIC. + +```{r bpc1, echo=TRUE} +#| fig-cap: "Figure 5. BPC of all samples colored by phenotype." +#' Extract and plot BPC for full data +bpc <- chromatogram(lcms1, aggregationFun = "max") + +plot(bpc, col = paste0(col_sample, 80), main = "BPC", lwd = 1.5) +grid() +legend("topright", col = col_phenotype, + legend = names(col_phenotype), lty = 1, lwd = 2, horiz = TRUE, + bty = "n") +``` + +After about 240 seconds no signal seems to be measured. Thus, we filter the data +removing that part as well as the first 10 seconds measured in the LC run. + +```{r filter rt} +#| fig-cap: "Figure 6. BPC after filtering retention time." +#' Filter the data based on retention time +lcms1 <- filterRt(lcms1, c(10, 240)) +bpc <- chromatogram(lcms1, aggregationFun = "max") + +#' Plot after filtering +plot(bpc, col = paste0(col_sample, 80), + main = "BPC after filtering retention time", lwd = 1.5) +grid() +legend("topright", col = col_phenotype, + legend = names(col_phenotype), lty = 1, lwd = 2, horiz = TRUE, bty = "n") +``` + +Initially, we examined the entire BPC and subsequently filtered it based on the +desired retention times. This not only results in a smaller file size but also +facilitates a more straightforward interpretation of the BPC. + +The final plot illustrates the BPC for each sample colored by phenotype, +providing insights on the signal measured along the retention times of each +sample. It reveals the points at which compounds eluted from the LC column. In +essence, a BPC condenses the three-dimensional LC-MS data (*m/z* by retention +time by intensity) into two dimensions (retention time by intensity). + +We can also here compare similarities of the BPCs in a heatmap. The retention +times will however not be identical between different samples. Thus we *bin()* +the chromatographic signal per sample along the retention time axis into bins of +two seconds resulting in data with the same number of bins/data points. We can +then calculate pairwise similarities between these data vectors using the +`cor()` function and visualize the result using `pheatmap()`. + +```{r heatmap1} +#| fig-cap: "Figure 7. Heatmap of BPC similarities." +#' Total ion chromatogram +tic <- chromatogram(lcms1, aggregationFun = "sum") |> + bin(binSize = 2) + +#' Calculate similarity (Pearson correlation) between BPCs +ticmap <- do.call(cbind, lapply(tic, intensity)) |> + cor() + +rownames(ticmap) <- colnames(ticmap) <- sampleData(lcms1)$sample_name +ann <- data.frame(phenotype = sampleData(lcms1)[, "phenotype"]) +rownames(ann) <- rownames(ticmap) + +#' Plot heatmap +pheatmap(ticmap, annotation_col = ann, + annotation_colors = list(phenotype = col_phenotype)) +``` + +The heatmap above reinforces what our exploration of spectra data showed, which +is a strong separation between the QC and study samples. This is important to +bear in mind for later analyses. + +Additionally, study samples group into two clusters, cluster *I* containing +samples *C* and *F* and cluster *II* with all other samples. Below we plot the +TIC of all samples, using a different color for each cluster. + +```{r} +#| fig-cap: "Figure 8. Example of TIC of unusual signal." +cluster_I_idx <- sampleData(lcms1)$sample_name %in% c("F", "C") +cluster_II_idx <- sampleData(lcms1)$sample_name %in% c("A", "B", "D", "E") + +temp_col <- c("grey", "red") +names(temp_col) <- c("Cluster II", "Cluster I") +col <- rep(temp_col[1], length(lcms1)) +col[cluster_I_idx] <- temp_col[2] +col[sampleData(lcms1)$phenotype == "QC"] <- NA + +lcms1 |> + chromatogram(aggregationFun = "sum") |> + plot( col = col, + main = "TIC after filtering retention time", lwd = 1.5) +grid() +legend("topright", col = temp_col, + legend = names(temp_col), lty = 1, lwd = 2, + horiz = TRUE, bty = "n") +``` + +While the TIC of all samples look similar, the samples from cluster I show a +different signal in the retention time range from about 40 to 160 seconds. +Whether, and how strong this difference will impact the following analysis +remains to be determined. + +### Known compounds + +Throughout the entire process, it is crucial to have reference points within the +dataset, such as well-known ions. Most experiments nowadays include internal +standards (IS), and it was the case here. We strongly recommend using them for +visualization throughout the entire analysis. For this experiment, a set of 15 +IS was spiked to all samples. After reviewing signal of all of them, we selected +two to guide this analysis process. However, we also advise to plot and evaluate +all the ions after each steps. + +To illustrate this, we generate Extracted Ion Chromatograms (EIC) for these +selected *test ions*. By restricting the MS data to intensities within a +restricted, small *m/z* range and a selected retention time window, EICs are +expected to contain only signal from a single type of ion. The expected *m/z* +and retention times for our set of IS was determined in a different experiment. +Additionally, in cases where internal standards are not available, commonly +present ions in the sample matrix can serve as suitable alternatives. Ideally, +these compounds should be distributed across the entire retention time range of +the experiment. + +```{r EIC extract for internal standard} +#| tbl-cap: "Table 4. Internal standard list with respective m/z and expected retention time [s]." +#| tbl-style: "font-size: 0.8em" +#| tbl-colwidths: [20, 20, 20] +#' Load our list of standard +intern_standard <- read.delim("intern_standard_list.txt") + +# Extract EICs for the list +eic_is <- chromatogram( + lcms1, + rt = as.matrix(intern_standard[, c("rtmin", "rtmax")]), + mz = as.matrix(intern_standard[, c("mzmin", "mzmax")])) + +#' Add internal standard metadata +fData(eic_is)$mz <- intern_standard$mz +fData(eic_is)$rt <- intern_standard$RT +fData(eic_is)$name <- intern_standard$name +fData(eic_is)$abbreviation <- intern_standard$abbreviation +rownames(fData(eic_is)) <- intern_standard$abbreviation + +#' Summary of IS information +fData(eic_is)[, c("name", "mz", "rt")] |> + kable(format = "pipe") +``` + +We below plot the EICs for isotope labeled cystine and methionine. + +```{r} +#| fig-cap: "Figure 9. EIC of cystine and methionine." +#| fig-align: center +#' Extract the two IS from the chromatogram object. +eic_cystine <- eic_is["cystine_13C_15N"] +eic_met <- eic_is["methionine_13C_15N"] + +#' plot both EIC +par(mfrow = c(1, 2), mar = c(4, 2, 2, 0.5)) +plot(eic_cystine, main = fData(eic_cystine)$name, cex.axis = 0.8, + cex.main = 0.8, + col = paste0(col_sample, 80)) +grid() +abline(v = fData(eic_cystine)$rt, col = "red", lty = 3) + +plot(eic_met, main = fData(eic_met)$name, cex.axis = 0.8, cex.main = 0.8, + col = paste0(col_sample, 80)) +grid() +abline(v = fData(eic_met)$rt, col = "red", lty = 3) +legend("topright", col = col_phenotype, legend = names(col_phenotype), lty = 1, + bty = "n") +``` + +We can observe a clear concentration difference between QCs and study samples +for the isotope labeled cystine ion. Meanwhile, the labeled methionine internal +standard exhibits a discernible signal amidst some noise and a noticeable +retention time shift between samples. + +While the artificially isotope labeled compounds were spiked to the individual +samples, there should also be the signal from the endogenous compounds in serum +(or plasma) samples. Thus, we calculate next the mass and *m/z* of an *\[M+H\]+* +ion of the endogenous cystine from its chemical formula and extract also the EIC +from this ion. For calculation of the exact mass and the *m/z* of the selected +ion adduct we use the `calculateMass()` and `mass2mz()` functions from the +`r Biocpkg("MetaboCoreUtils")` package. + +```{r} +#| fig-cap: "Figure 10. EIC of endogenous cystine vs spiked." +#' extract endogenous cystine mass and EIC and plot. +cysmass <- calculateMass("C6H12N2O4S2") +cys_endo <- mass2mz(cysmass, adduct = "[M+H]+")[, 1] + +#' Plot versus spiked +par(mfrow = c(1, 2)) +chromatogram(lcms1, mz = cys_endo + c(-0.005, 0.005), + rt = unlist(fData(eic_cystine)[, c("rtmin", "rtmax")]), + aggregationFun = "max") |> + plot(col = paste0(col_sample, 80)) |> + grid() + +plot(eic_cystine, col = paste0(col_sample, 80)) +grid() +legend("topright", col = col_phenotype, legend = names(col_phenotype), lty = 1, + bty = "n") +``` + +The two cystine EICs above look highly similar (the endogenous shown left, the +isotope labeled right in the plot above), if not for the shift in m/z, which +arises from the artificial labeling. This shift allows us to discriminate +between the endogenous and non-endogenous compound. + +# Data preprocessing + +Preprocessing stands as the inaugural step in the analysis of untargeted LC-MS. +It is characterized by 3 main stages: chromatographic peak detection, retention +time shift correction (alignment) and correspondence which results in features +defined. The primary objective of preprocessing is the quantification of signals +from ions measured in a sample, addressing any potential retention time drifts +between samples, and ensuring alignment of quantified signals across samples +within an experiment. The final result of LC-MS data preprocessing is a numeric +matrix with abundances of quantified entities in the samples of the experiment. + +## Chromatographic peak detection + +The initial preprocessing step involves detecting intensity peaks along the +retention time axis, the so called *chromatographic peaks*. To achieve this, we +employ the `findChromPeaks()` function within *xcms*. This function supports +various algorithms for peak detection, which can be selected and configured with +their respective parameter objects. + +The preferred algorithm in this case, *CentWave*, utilizes continuous wavelet +transformation (CWT)-based peak detection [@tautenhahn_highly_2008]. This method +is known for its effectiveness in handling non-Gaussian shaped chromatographic +peaks or peaks with varying retention time widths, which are commonly +encountered in HILIC separations. + +Below we apply the CentWave algorithm with its default settings on the extracted +ion chromatogram for cystine and methionine ions and evaluate its results. + +```{r Default centwave param test} +#' Use default Centwave parameter +param <- CentWaveParam() + +#' Look at the default parameters +param + +#' Evaluate for Cystine +cystine_test <- findChromPeaks(eic_cystine, param = param) +chromPeaks(cystine_test) + +#' Evaluate for Methionine +met_test <- findChromPeaks(eic_met, param = param) +chromPeaks(met_test) +``` + +While *CentWave* is a highly performant algorithm, it requires to be costumized +to each dataset. This implies that the parameters should be fine-tuned based on +the user's data. The example above serves as a clear motivation for users to +familiarize themselves with the various parameters and the need to adapt them to +a data set. We will discuss the main parameters that can be easily adjusted to +suit the user's dataset: + +- `peakwidth`: Specifies the minimal and maximal expected width of the peaks + in the retention time dimension. Highly dependent on the chromatographic + settings used. + +- `ppm`: The maximal allowed difference of mass peaks' *m/z* values (in + parts-per-million) in consecutive scans to consider them representing signal + from the same ion. + +- `integrate`: This parameter defines the integration method. Here, we + primarily use `integrate = 2` because it integrates also signal of a + chromatographic peak's tail and is considered more accurate by the + developers. + +To determine `peakwidth`, we recommend that users refer to previous EICs and +estimate the range of peak widths they observe in their dataset. Ideally, +examining multiple EICs should be the goal. For this dataset, the peak widths +appear to be around 2 to 10 seconds. We do not advise choosing a range that is +too wide or too narrow with the `peakwidth` parameter as it can lead to false +positives or negatives. + +To determine the `ppm`, a deeper analysis of the dataset is needed. It is +clarified above that `ppm` depends on the instrument, but users should not +necessarily input the vendor-advertised ppm. Here's how to determine it as +accurately as possible: + +The following steps involve generating a highly restricted MS area with a single +mass peak per spectrum, representing the cystine ion. The *m/z* of these peaks +is then extracted, their absolute difference calculated and finally expressed in +ppm. + +```{r ppm parameter1 } +#' Restrict the data to signal from cystine in the first sample +cst <- lcms1[1L] |> + spectra() |> + filterRt(rt = c(208, 218)) |> + filterMzRange(mz = fData(eic_cystine)["cystine_13C_15N", c("mzmin", "mzmax")]) + +#' Show the number of peaks per m/z filtered spectra +lengths(cst) + +#' Calculate the difference in m/z values between scans +mz_diff <- cst |> + mz() |> + unlist() |> + diff() |> + abs() + +#' Express differences in ppm +range(mz_diff * 1e6 / mean(unlist(mz(cst)))) +``` + +We therefore, choose a value close to the maximum within this range for +parameter `ppm`, i.e., 15 ppm. + +We can now perform the chromatographic peak detection with the adapted settings +on our EICs. It is important to note that, to properly estimate background +noise, sufficient data points outside the chromatographic peak need to be +present. This is generally no problem if peak detection is performed on the full +LC-MS data set, but for peak detection on EICs the retention time range of the +EIC needs to be sufficiently wide. If the function fails to find a peak in an +EIC, the initial troubleshooting step should be to increase this range. +Additionally, the signal-to-noise threshold `snthresh` should be reduced for +peak detection in EICs, because within the small retention time range, not +enough signal is present to properly estimate the background noise. Finally, in +case of too few MS1 data points per peaks, setting CentWave's advanced parameter +`extendLengthMSW` to `TRUE` can help with peak detection. + +```{r} +#' Parameters adapted for chromatographic peak detection on EICs. +param <- CentWaveParam(peakwidth = c(1, 8), ppm = 15, integrate = 2, + snthresh = 2) + +#' Evaluate on the cystine ion +cystine_test <- findChromPeaks(eic_cystine, param = param) +chromPeaks(cystine_test) + +#' Evaluate on the methionine ion +met_test <- findChromPeaks(eic_met, param = param) +chromPeaks(met_test) +``` + +With the customized parameters, a chromatographic peak was detected in each +sample. Below, we use the `plot()` function on the EICs to visualize these +results. + +```{r echo=FALSE} +#| fig-cap: "Figure 11. Chromatographic peak detection on EICs." +#' Plot test chromatogram +par(mfrow = c(1, 2)) +plot(cystine_test, main = fData(cystine_test)$name, + col = paste0(col_sample, 80), + peakBg = paste0(col_sample, 40)[chromPeaks(cystine_test)[, "column"]], + cex.main = 0.8, cex.axis = 0.8) +grid() + +plot(met_test, main = fData(met_test)$name, + col = paste0(col_sample, 80), + peakBg = paste0(col_sample, 40)[chromPeaks(met_test)[, "column"]], + cex.main = 0.8, cex.axis = 0.8) +grid() +legend("topright", col = col_phenotype, + legend = names(col_phenotype), lty = 1, bty = "n") +``` + +We can see a peak seems ot be detected in each sample for both ions. This +indicates that our custom settings seem thus to be suitable for our dataset. We +now proceed and apply them to the entire dataset, extracting EICs again for the +same ions to evaluate and confirm that chromatographic peak detection worked as +expected. Note: + +- We revert the value for parameter `snthresh` to its default, because, as + mentioned above, background noise estimation is more reliable when performed + on the full data set. + +- Parameter `chunkSize` of `findChromPeaks()` defines the number of data files + that are loaded into memory and processed simultaneously. This parameter + thus allows to fine-tune the memory demand as well as performance of the + chromatographic peak detection step. + +```{r run find chrompeak on entire dataset} +#' Using the same settings, but with default snthresh +param <- CentWaveParam(peakwidth = c(1, 8), ppm = 15, integrate = 2) +lcms1 <- findChromPeaks(lcms1, param = param, chunkSize = 5) + +#' Update EIC internal standard object +eics_is_noprocess <- eic_is +eic_is <- chromatogram(lcms1,, + rt = as.matrix(intern_standard[, c("rtmin", "rtmax")]), + mz = as.matrix(intern_standard[, c("mzmin", "mzmax")])) +fData(eic_is) <- fData(eics_is_noprocess) +``` + +Below we plot the EICs of the two selected internal standards to evaluate the +chromatographic peak detection results. + +```{r plot new eics, echo=FALSE} +#| fig-cap: "Figure 12. Chromatographic peak detection on EICs after processing." +#' Test if we find peaks for Cystine and Methionine again +eic_cystine <- eic_is["cystine_13C_15N", ] +eic_met <- eic_is["methionine_13C_15N", ] + +#' Plot +par(mfrow = c(1, 2)) +plot(eic_cystine, main = "L-Cystine (13C6, 99%; 15N2, 99%)", + col = paste0(col_sample, 80), + peakBg = paste0(col_sample[chromPeaks(eic_cystine)[, "sample"]], 40)) +grid() + +plot(eic_met, main = "L-Methionine (13C5, 99%; 15N, 99%)", + col = paste0(col_sample, 80), + peakBg = paste0(col_sample[chromPeaks(eic_met)[, "sample"]], 40)) +grid() +legend("topright", col = col_phenotype, + legend = names(col_phenotype), lty = 1) +``` + +Peaks seem to have been detected properly in all samples for both ions. This +indicates that the peak detection process over the entire dataset was +successful. + +### Refine identified chromatographic peaks + +The identification of chromatographic peaks using the *CentWave* algorithm can +sometimes result in artifacts, such as overlapping or split peaks. To address +this issue, the `refineChromPeaks()` function is utilized, in conjunction with +`MergeNeighboringPeaksParam`, which aims at merging such split peaks. + +Below we show some examples of *CentWave* peak detection artifacts. These +examples are pre-selected to illustrate the necessity of the next step: + +```{r echo=FALSE} +#| fig-cap: "Figure 13. Examples of CentWave peak detection artifacts." +#' Extract m/z-rt regions for selected peaks +mz_rt <- cbind(rtmin = c(155.2120, 181.71800), + rtmax = c(201.0710, 228.13500), + mzmin = c(191.0288, 53.50964), + mzmax = c(191.0527, 53.53183)) + +#' Extract the EICs +eics <- chromatogram(lcms1[3], rt = mz_rt[, c("rtmin", "rtmax")], + mz = mz_rt[, c("mzmin", "mzmax")]) +#' Plot the EICs +plot(eics) +``` + +In both cases the signal presumably from a single type of ion was split into two +separate chromatographic peaks (indicated by the vertical line). The +`MergeNeigboringPeaksParam` allows to combine such split peaks. The parameters +for this algorithm are defined below: + +- `expandMz`and `expandRt`: Define which chromatographic peaks should be + evaluated for merging. + - `expandMz`: Suggested to be kept relatively small (here at 0.0015) to + prevent the merging of isotopes. + - `expandRt`: Usually set to approximately half the size of the average + retention time width used for chromatographic peak detection (in this + case, 2.5 seconds). +- `minProp`: Used to determine whether candidates will be merged. + Chromatographic peaks with overlapping *m/z* ranges (expanded on each side + by `expandMz`) and with a tail-to-head distance in the retention time + dimension that is less than `2 * expandRt`, and for which the signal between + them is higher than `minProp` of the apex intensity of the chromatographic + peak with the lower intensity, are merged. Values for this parameter should + not be too small to avoid merging closely co-eluting ions, such as isomers. + +We test these settings below on the EICs with the split peaks. + +```{r test merging} +#| fig-cap: "Figure 14. Examples of CentWave peak detection artifacts after merging." +#' set up the parameter +param <- MergeNeighboringPeaksParam(expandRt = 2.5, expandMz = 0.0015, + minProp = 0.75) + +#' Perform the peak refinement on the EICs +eics <- refineChromPeaks(eics, param = param) +plot(eics) +``` + +We can observe that the artificially split peaks have been appropriately merged. +Therefore, we next apply these settings to the entire dataset. After peak +merging, column `"merged"` in the result object's `chromPeakData()` data frame +can be used to evaluate which chromatographic peaks in the result represent +signal from merged, or from the originally identified chromatographic peaks. + +```{r} +#' Apply on whole dataset +lcms1 <- refineChromPeaks(lcms1, param = param, chunkSize = 5) +chromPeakData(lcms1)$merged |> + table() +``` + +Before proceeding with the next preprocessing step it is generally suggested to +evaluate the results of the chromatographic peak detection on EICs of e.g. +internal standards or other compounds/ions known to be present in the samples. + +```{r} +eics_is_chrompeaks <- eic_is + +eic_is <- chromatogram(lcms1, + rt = as.matrix(intern_standard[, c("rtmin", "rtmax")]), + mz = as.matrix(intern_standard[, c("mzmin", "mzmax")])) +fData(eic_is) <- fData(eics_is_chrompeaks) + +eic_cystine <- eic_is["cystine_13C_15N", ] +eic_met <- eic_is["methionine_13C_15N", ] +``` + +Additionally, evaluating and comparing the number of identified chromatographic +peaks in all samples of a data set can help spotting potentially problematic +samples. Below we count the number of chromatographic peaks per sample and show +these numbers in a table. + +```{r} +#| tbl-cap: "Table 5. Samples and number of identified chromatographic peaks." +#' Count the number of peaks per sample and summarize them in a table. +data.frame(sample_name = sampleData(lcms1)$sample_name, + peak_count = as.integer(table(chromPeaks(lcms1)[, "sample"]))) |> + kable(format = "pipe") +``` + +A similar number of chromatographic peaks was identified within the various +samples of the data set. + +Additional options to evaluate the results of the chromatographic peak detection +can be implemented using the `plotChromPeaks()` function or by summarizing the +results using base R commands. + +## Retention time alignment + +Despite using the same chromatographic settings and conditions retention time +shifts are unavoidable. Indeed, the performance of the instrument can change +over time, for example due to small variations in environmental conditions, such +as temperature and pressure. These shifts will be generally small if samples are +measured within the same batch/measurement run, but can be considerable if data +of an experiment was acquired across a longer time period. + +To evaluate the presence of a shift we extract and plot below the BPC from the +QC samples. + +```{r} +#| fig-cap: "Figure 15. BPC of QC samples." +#' Get QC samples +QC_samples <- sampleData(lcms1)$phenotype == "QC" + +#' extract BPC +lcms1[QC_samples] |> + chromatogram(aggregationFun = "max", chromPeaks = "none") |> + plot(col = col_phenotype["QC"], main = "BPC of QC samples") |> + grid() +``` + +These QC samples representing the same sample (pool) were measured at regular +intervals during the measurement run of the experiment and were all measured on +the same day. Still, small shifts can be observed, especially in the region +between 100 and 150 seconds. To facilitate proper correspondence of signals +across samples (and hence definition of the LC-MS features), it is essential to +minimize these differences in retention times. + +Theoretically, we proceed in two steps: first we select only the QC samples of +our dataset and do a first alignment on these, using the so-called *anchor +peaks*. In this way we can assume a linear shift in time, since we are always +measuring the same sample in different regular time intervals. Despite having +external QCs in our data set, we still use the subset-based alignment assuming +retention time shifts to be independent of the different sample matrix (human +serum or plasma) and instead are mostly instrument-dependent. Note that it would +also be possible to manually specify anchor peaks, respectively their retention +times or to align a data set against an external, reference, data set. More +information is provided in the vignettes of the `r Biocpkg("xcms")` package. + +After calculating how much to adjust the retention time in these samples, we +apply this shift also on the study samples. + +In *xcms* retention time alignment can be performed using the `adjustRtime()` +function with an alignment algorithm. For this example we use the *PeakGroups* +method [@smith_xcms_2006] that performs the alignment by minimizing differences +in retention times of a set of *anchor peaks* in the different samples. This +method requires an initial correspondence analysis to match/group +chromatographic peaks across samples from which the algorithm then selects the +anchor peaks for the alignment. + +For the initial correspondence, we use the *PeakDensity* approach +[@smith_xcms_2006] that groups chromatographic peaks with similar *m/z* and +retention time into LC-MS features. The parameters for this algorithm, that can +be configured using the `PeakDensityParam` object, are `sampleGroups`, +`minFraction`, `binSize`, `ppm` and `bw`. + +`binSize`, `ppm` and `bw` allow to specify how similar the chromatographic +peaks' *m/z* and retention time values need to be to consider them for grouping +into a feature. + +- `binSize` and `ppm` define the required similarity of *m/z* values. Within + each *m/z* bin (defined by `binSize` and `ppm`) areas along the retention + time axis with a high chromatographic peak density (considering all peaks in + all samples) are identified, and chromatographic peaks within these regions + are considered for grouping into a feature. + +- High density areas are identified using the base R `density()` function, for + which `bw` is a parameter: higher values define wider retention time areas, + lower values require chromatographic peaks to have more similar retention + times. This parameter can be seen as the black line on the plot below, + corresponding to the smoothness of the density curve. + +Whether such candidate peaks get grouped into a feature depends also on +parameters `sampleGroups` and `minFraction`: + +- `sampleGroups` should provide, for each sample, the sample group it belongs + to. + +- `minFraction` is expected to be a value between 0 and 1 defining the + proportion of samples within at least one of the sample groups (defined with + `sampleGroups`) in which a chromatographic peaks was detected to group them + into a feature. + +For the initial correspondence, parameters don't need to be fully optimized. +Selection of dataset-specific parameter values is described in more detail in +the next section. For our dataset, we use small values for `binSize` and `ppm` +and, importantly, also for parameter `bw`, since for our data set an ultra high +performance (UHP) LC setup was used. For `minFraction` we use a high value (0.9) +to ensure only features are defined for chromatographic peaks present in almost +all samples of one sample group (which can then be used as anchor peaks for the +actual alignment). We will base the alignment later on QC samples only and hence +define for `sampleGroups` a binary variable grouping samples either into a +study, or QC group. + +```{r} +#| fig-cap: "Figure 16. Initial correspondence analysis." +# Initial correspondence analysis +param <- PeakDensityParam(sampleGroups = sampleData(lcms1)$phenotype == "QC", + minFraction = 0.9, + binSize = 0.01, ppm = 10, + bw = 2) +lcms1 <- groupChromPeaks(lcms1, param = param) + +plotChromPeakDensity( + eic_cystine, param = param, + col = paste0(col_sample, "80"), + peakCol = col_sample[chromPeaks(eic_cystine)[, "sample"]], + peakBg = paste0(col_sample[chromPeaks(eic_cystine)[, "sample"]], 20), + peakPch = 16) +``` + +*PeakGroups*-based alignment can next be performed using the `adjustRtime()` +function with a `PeakGroupsParam` parameter object. The parameters for this +algorithm are: + +- `subsetAdjust` and `subset`: Allows for subset alignment. Here we base the + retention time alignment on the QC samples, i.e., retention time shifts will + be estimated based on these repeatedly measured samples. The resulting + adjustment is then applied to the entire data. For data sets in which QC + samples (e.g. sample pools) are measured repeatedly, we strongly suggest to + use this method. Note also that for subset-based alignment the samples + should be ordered by injection index (i.e., in the order in which they were + measured during the measurement run). + +- `minFraction`: A value between 0 and 1 defining the proportion of samples + (of the full data set, or the data subset defined with `subset`) in which a + chromatographic peak has to be identified to use it as *anchor peak*. This + is in contrast to the `PeakDensityParam` where this parameter was used to + define a proportion within a sample group. + +- `span`: The *PeakGroups* method allows, depending on the data, to adjust + regions along the retention time axis differently. To enable such local + alignments the *LOESS* function is used and this parameter defines the + degree of smoothing of this function. Generally, values between 0.4 and 0.6 + are used, however, it is suggested to evaluate alignment results and + eventually adapt parameters if the result was not satisfactory. + +Below we perform the alignment of our data set based on retention times of +anchor peaks defined in the subset of QC samples. + +```{r} +#' Define parameters of choice +subset <- which(sampleData(lcms1)$phenotype == "QC") +param <- PeakGroupsParam(minFraction = 0.9, extraPeaks = 50, span = 0.5, + subsetAdjust = "average", + subset = subset) + +#' Perform the alignment +lcms1 <- adjustRtime(lcms1, param = param) +``` + +Alignment adjusted the retention times of all spectra in the data set, as well +as the retention times of all identified chromatographic peaks. + +Once the alignment has been performed, the user should evaluate the results +using the `plotAdjustedRtime()` function. This function visualizes the +difference between adjusted and raw retention time for each sample on the y-axis +along the adjusted retention time on the x-axis. Dot points represent the +position of the used anchor peak along the retention time axis. For optimal +alignment in all areas along the retention time axis, these anchor peaks should +be scattered all over the retention time dimension. + +```{r} +#| fig-cap: "Figure 17. Retention time alignment results." +#' Visualize alignment results +plotAdjustedRtime(lcms1, col = paste0(col_sample, 80), peakGroupsPch = 1) +grid() +legend("topright", col = col_phenotype, + legend = names(col_phenotype), lty = 1, bty = "n") +``` + +All samples from the present data set were measured within the same measurement +run, resulting in small retention time shifts. Therefore, only little +adjustments needed to be performed (shifts of at maximum 1 second as can be seen +in the plot above). Generally, the magnitude of adjustment seen in such plots +should match the expectation from the analyst. + +We can also compare the BPC before and after alignment. To get the original +data, i.e. the raw retention times, we can use the `dropAdjustedRtime()` +function: + +```{r} +#' Get data before alignment +lcms1_raw <- dropAdjustedRtime(lcms1) + +#' Apply the adjusted retention time to our dataset +lcms1 <- applyAdjustedRtime(lcms1) +``` + +```{r bpc before and after1} +#| fig-cap: "Figure 18. BPC before and after alignment." +#' Plot the BPC before and after alignment +par(mfrow = c(2, 1), mar = c(2, 1, 1, 0.5)) +chromatogram(lcms1_raw, aggregationFun = "max", chromPeaks = "none") |> + plot(main = "BPC before alignment", col = paste0(col_sample, 80)) +grid() +legend("topright", col = col_phenotype, + legend = names(col_phenotype), lty = 1, bty = "n", horiz = TRUE) + +chromatogram(lcms1, aggregationFun = "max", chromPeaks = "none") |> + plot(main = "BPC after alignment", + col = paste0(col_sample, 80)) +grid() +legend("topright", col = col_phenotype, + legend = names(col_phenotype), lty = 1, bty = "n", horiz = TRUE) +``` + +The largest shift can be observed in the retention time range from 120 to 130s. +Apart from that retention time range, only little changes can be observed. + +We next evaluate the impact of the alignment on the EICs of the selected +internal standards. We thus below first extract the ion chromatograms after +alignment. + +```{r} +#' Store the EICs before alignment +eics_is_refined <- eic_is + +#' Update the EICs +eic_is <- chromatogram(lcms1, + rt = as.matrix(intern_standard[, c("rtmin", "rtmax")]), + mz = as.matrix(intern_standard[, c("mzmin", "mzmax")])) +fData(eic_is) <- fData(eics_is_refined) + +#' Extract the EICs for the test ions +eic_cystine <- eic_is["cystine_13C_15N"] +eic_met <- eic_is["methionine_13C_15N"] +``` + +We can now evaluate the alignment effect in our test ions. We will plot the EICs +before and after alignment for both the isotope labeled cystine and methionine. + +```{r specific ion before and after1} +#| fig-cap: "Figure 19. EICs of cystine and methionine before and after alignment." +par(mfrow = c(2, 2), mar = c(4, 4.5, 2, 1)) + +old_eic_cystine <- eics_is_refined["cystine_13C_15N"] +plot(old_eic_cystine, main = "Cystine before alignment", peakType = "none", + col = paste0(col_sample, 80)) +grid() +abline(v = intern_standard["cystine_13C_15N", "RT"], col = "red", lty = 3) + +old_eic_met <- eics_is_refined["methionine_13C_15N"] +plot(old_eic_met, main = "Methionine before alignment", + peakType = "none", col = paste0(col_sample, 80)) +grid() +abline(v = intern_standard["methionine_13C_15N", "RT"], col = "red", lty = 3) + +plot(eic_cystine, main = "Cystine after alignment", peakType = "none", + col = paste0(col_sample, 80)) +grid() +abline(v = intern_standard["cystine_13C_15N", "RT"], col = "red", lty = 3) +legend("topright", col = col_phenotype, + legend = names(col_phenotype), lty = 1, bty = "n") + +plot(eic_met, main = "Methionine after alignment", + peakType = "none", col = paste0(col_sample, 80)) +grid() +abline(v = intern_standard["methionine_13C_15N", "RT"], col = "red", lty = 3) +``` + +The non-endogenous cystine ion was already well aligned so the difference is +minimal. The methionine ion, however, shows an improvement in alignment. + +In addition to a visual inspection of the results, we also evaluate the impact +of the alignment by comparing the variance in retention times for internal +standards before and after alignment. To this end, we first need to identify +chromatographic peaks in each sample with an *m/z* and retention time close to +the expected values for each internal standard. For this we use the +`matchValues()` function from the `r Biocpkg("MetaboAnnotation")` package +[@rainer_modular_2022] using the `MzRtParam` method to identify all +chromatographic peaks with similar *m/z* (+/- 50 ppm) and retention time (+/- 10 +seconds) to the internal standard's values. With parameters `mzColname` and +`rtColname` we specify the column names in the query (our IS) and target +(chromatographic peaks) that contain the *m/z* and retention time values on +which to match entities. We perform this matching separately for each sample. +For each internal standard in every sample, we use the `filterMatches()` +function and the `SingleMatchParam()` parameter to select the chromatographic +peak with the highest intensity. + +```{r} +#' Extract the matrix with all chromatographic peaks and add a column +#' with the ID of the chromatographic peak +chrom_peaks <- chromPeaks(lcms1) |> as.data.frame() +chrom_peaks$peak_id <- rownames(chrom_peaks) + +#' Define the parameters for the matching and filtering of the matches +p_1 <- MzRtParam(ppm = 50, toleranceRt = 10) +p_2 <- SingleMatchParam(duplicates = "top_ranked", column = "target_maxo", + decreasing = TRUE) + +#' Iterate over samples and identify for each the chromatographic peaks +#' with similar m/z and retention time than the onse from the internal +#' standard, and extract among them the ID of the peaks with the +#' highest intensity. +intern_standard_peaks <- lapply(seq_along(lcms1), function(i) { + tmp <- chrom_peaks[chrom_peaks[, "sample"] == i, , drop = FALSE] + mtch <- matchValues(intern_standard, tmp, + mzColname = c("mz", "mz"), + rtColname = c("RT", "rt"), + param = p_1) + mtch <- filterMatches(mtch, p_2) + mtch$target_peak_id +}) |> + do.call(what = cbind) +``` + +We have now for each internal standard the ID of the chromatographic peak in +each sample that most likely represents signal from that ion. We can now extract +the retention times for these chromatographic peaks before and after alignment. + +```{r} +#' Define the index of the selected chromatographic peaks in the +#' full chromPeaks matrix +idx <- match(intern_standard_peaks, rownames(chromPeaks(lcms1))) + +#' Extract the raw retention times for these +rt_raw <- chromPeaks(lcms1_raw)[idx, "rt"] |> + matrix(ncol = length(lcms1_raw)) + +#' Extract the adjusted retention times for these +rt_adj <- chromPeaks(lcms1)[idx, "rt"] |> + matrix(ncol = length(lcms1_raw)) + +``` + +We can now evaluate the impact of the alignment on the retention times of +internal standards across the full data set: + +```{r} +#| fig-cap: "Figure 20. Retention time variation of internal standards before and after alignment." +list(all_raw = rowSds(rt_raw, na.rm = TRUE), + all_adj = rowSds(rt_adj, na.rm = TRUE) + ) |> + vioplot(ylab = "sd(retention time)") +grid() +``` + +On average, the variation in retention times of internal standards across +samples was *very* slightly reduced by the alignment. + +## Correspondence + +We briefly touched on the subject of correspondence before to determine anchor +peaks for alignment. Generally, the goal of the correspondence analysis is to +identify chromatographic peaks that originate from the same types of ions in all +samples of an experiment and to group them into LC-MS *features*. At this point, +proper configuration of parameter `bw` is crucial. Here we illustrate how +sensible choices for this parameter's value can be made. We use below the +`plotChromPeakDensity()` function to *simulate* a correspondence analysis with +the default values for *PeakGroups* on the extracted ion chromatograms of our +two selected isotope labeled ions. This plot shows the EIC in the top panel, and +the apex position of chromatographic peaks in the different samples (y-axis), +along retention time (x-axis) in the lower panel. + +```{r} +#| fig-cap: "Figure 21. Initial correspondence analysis, Cystine." +#' Default parameter for the grouping and apply them to the test ions BPC +param <- PeakDensityParam(sampleGroups = sampleData(lcms1)$phenotype, bw = 30) + +param + +plotChromPeakDensity( + eic_cystine, param = param, + col = paste0(col_sample, "80"), + peakCol = col_sample[chromPeaks(eic_cystine)[, "sample"]], + peakBg = paste0(col_sample[chromPeaks(eic_cystine)[, "sample"]], 20), + peakPch = 16) +``` + +```{r} +#| fig-cap: "Figure 22. Initial correspondence analysis, Methionine." +plotChromPeakDensity(eic_met, param = param, + col = paste0(col_sample, "80"), + peakCol = col_sample[chromPeaks(eic_met)[, "sample"]], + peakBg = paste0(col_sample[chromPeaks(eic_met)[, "sample"]], 20), + peakPch = 16) +``` + +Grouping of peaks depends on the smoothness of the previousl mentionned density +curve that can be configured with the parameter `bw`. As seen above, the +smoothness is too high to properly group our features. When looking at the +default parameters, we can observe that indeed, the `bw` parameter is set to +`bw = 30`, which is too high for modern UHPLC-MS setups. We reduce the value of +this parameter to 1.8 and evaluate its impact. + +```{r} +#| fig-cap: "Figure 23. Correspondence analysis with optimized parameters, Cystine." +#' Updating parameters +param <- PeakDensityParam(sampleGroups = sampleData(lcms1)$phenotype, bw = 1.8) + +plotChromPeakDensity( + eic_cystine, param = param, + col = paste0(col_sample, "80"), + peakCol = col_sample[chromPeaks(eic_cystine)[, "sample"]], + peakBg = paste0(col_sample[chromPeaks(eic_cystine)[, "sample"]], 20), + peakPch = 16) +``` + +```{r} +#| fig-cap: "Figure 24. Correspondence analysis with optimized parameters, Methionine." +plotChromPeakDensity(eic_met, param = param, + col = paste0(col_sample, "80"), + peakCol = col_sample[chromPeaks(eic_met)[, "sample"]], + peakBg = paste0(col_sample[chromPeaks(eic_met)[, "sample"]], 20), + peakPch = 16) +``` + +We can observe that the peaks are now grouped more accurately into a single +feature for each test ion. The other important parameters optimized here are: + +- `binsize`: Our data was generated on a high resolution MS instrument, thus + we select a low value for this paramete. + +- `ppm`: For TOF instruments, it is suggested to use a value for `ppm` larger + than 0 to accommodate the higher measurement error of the instrument for + larger *m/z* values. + +- `minFraction`: We set it to `minFraction = 0.75`, hence defining features + only if a chromatographic peak was identified in at least 75% of the samples + of one of the sample groups. + +- `sampleGroups`: We use the information available in our `sampleData`'s + `"phenotype"` column. + +```{r} +#' Define the settings for the param +param <- PeakDensityParam(sampleGroups = sampleData(lcms1)$phenotype, + minFraction = 0.75, binSize = 0.01, ppm = 10, + bw = 1.8) + +#' Apply to whole data +lcms1 <- groupChromPeaks(lcms1, param = param) +``` + +After correspondence analysis it is suggested to evaluate the results again for +selected EICs. Below we extract signal for an *m/z* similar to that of the +isotope labeled methionine for a larger retention time range. Importantly, to +show the actual correspondence results, we set `simulate = FALSE` for the +`plotChromPeakDensity()` function. + +```{r} +#| fig-cap: "Figure 25. Correspondence analysis results, Methionine." +#' Extract chromatogram for an m/z similar to the one of the labeled methionine +chr_test <- chromatogram(lcms1, + mz = as.matrix(intern_standard["methionine_13C_15N", + c("mzmin", "mzmax")]), + rt = c(145, 200), + aggregationFun = "max") +plotChromPeakDensity( + chr_test, simulate = FALSE, + col = paste0(col_sample, "80"), + peakCol = col_sample[chromPeaks(chr_test)[, "sample"]], + peakBg = paste0(col_sample[chromPeaks(chr_test)[, "sample"]], 20), + peakPch = 16) +``` + +As hoped, signal from the two different ions are now grouped into separate +features. Generally, correspondence results should be evaluated on more such +extracted chromatograms. + +Another interesting information to look a the the distribution of these features +along the retention time axis. + +```{r} +#| tbl-cap: "Table 5. Distribution of features along the retention time axis." +# Bin features per RT slices +vc <- featureDefinitions(lcms1)$rtmed +breaks <- seq(0, max(vc, na.rm = TRUE) + 1, length.out = 15) |> + round(0) +cuts <- cut(vc, breaks = breaks, include.lowest = TRUE) + +table(cuts) |> + kable(format = "pipe") +``` + +The results from the correspondence analysis are now stored, along with the +results from the other preprocessing steps, within our `XcmsExperiment` result +object. The correspondence results, i.e., the definition of the LC-MS features, +can be extracted using the `featureDefinitions()` function. + +```{r} +#' Definition of the features +featureDefinitions(lcms1) |> + head() +``` + +This data frame provides the average *m/z* and retention time (in columns +`"mzmed"` and `"rtmed"`) that characterize a LC-MS feature. Column, `"peakidx"` +contains the indices of all chromatographic peaks that were assigned to that +feature. The actual abundances for these features, which represent also the +final preprocessing results, can be extracted with the `featureValues()` +function: + +```{r} +#' Extract feature abundances +featureValues(lcms1, method = "sum") |> + head() +``` + +We can note that some features (e.g. F0003 and F0006) have missing values in +some samples. This is expected to a certain degree as not in all samples +features, respectively their ions, need to be present. We will address this in +the next section. + +## Gap filling + +The previously observed missing values (*NA*) could be attributed to various +reasons. Although they might represent a genuinely missing value, indicating +that an ion (feature) was truly not present in a particular sample, they could +also be a result of a failure in the preceding chromatographic peak detection +step. It is crucial to be able to recover missing values of the latter category +as much as possible to reduce the eventual need for data imputation. We next +examine how prevalent missing values are in our present dataset: + +```{r} +#' Percentage of missing values +sum(is.na(featureValues(lcms1))) / + length(featureValues(lcms1)) * 100 +``` + +We can observe a substantial number of missing values values in our dataset. +Let's therefore delve into the process of *gap-filling*. We first evaluate some +example features for which a chromatographic peak was only detected in some +samples: + +```{r} +#| fig-cap: "Figure 26. Examples of chromatographic peaks with missing values." +ftidx <- which(is.na(rowSums(featureValues(lcms1)))) +fts <- rownames(featureDefinitions(lcms1))[ftidx] +farea <- featureArea(lcms1, features = fts[1:2]) + +chromatogram(lcms1[c(2, 3)], + rt = farea[, c("rtmin", "rtmax")], + mz = farea[, c("mzmin", "mzmax")]) |> + plot(col = c("red", "blue"), lwd = 2) +``` + +In both instances, a chromatographic peak was only identified in one of the two +selected samples (red line), hence a missing value is reported for this feature +in those particular samples (blue line). However, in both cases, signal was +measured in both samples, thus, reporting a missing value would not be correct +in this example. The signal for this feature is very low, which is the most +likely reason peak detection failed. To rescue signal in such cases, the +`fillChromPeaks()` function can be used with the `ChromPeakAreaParam` approach. +This method defines an *m/z*-retention time area for each feature based on +detected peaks, where the signal for the respective ion is expected. It then +integrates all intensities within this area in samples that have missing values +for that feature. This is then reported as feature abundance. Below we apply +this method using the default parameters. + +```{r} +#' Fill in the missing values in the whole dataset +lcms1 <- fillChromPeaks(lcms1, param = ChromPeakAreaParam(), chunkSize = 5) + +#' Percentage of missing values after gap-filling +sum(is.na(featureValues(lcms1))) / + length(featureValues(lcms1)) * 100 +``` + +With `fillChromPeaks()` we could thus rescue most of the missing data in the +data set. Note that, even if in a sample no ion would be present, in the worst +case noise would be integrated, which is expected to be much lower than actual +chromatographic peak signal. Let's look at our previously missing values again: + +```{r echo=FALSE} +#| fig-cap: "Figure 27. Examples of chromatographic peaks with missing values after gap-filling." +#' Extract EICs again and plot them +chromatogram(lcms1[c(2, 3)], + mz = farea[, c("mzmin", "mzmax")], + rt = farea[, c("rtmin", "rtmax")]) |> + plot(col = c("red", "blue"), lwd = 2) +``` + +After gap-filling, also in the blue colored sample a chromatographic peak is +present and its peak area would be reported as feature abundance for that +sample. + +To further assess the effectiveness of the gap-filling method for rescuing +signals, we can also plot the average signal of features with at least one +missing value against the average of filled-in signal. It is advisable to +perform this analysis on repeatedly measured samples; in this case, our QC +samples will be used. + +For this, we extract: + +- Feature values from detected chromatographic peaks by setting + `filled = FALSE` in the `featuresValues()` call. + +- The filled-in signal by first extracting both detected and gap-filled + abundances and then replace the values for detected chromatographic peaks + with `NA`. + +Then, we calculate the row averages of both of these matrices and plot them +against each other. + +```{r Detected vs filled signal1} +#' Get only detected signal in QC samples +vals_detect <- featureValues(lcms1, filled = FALSE)[, QC_samples] + +#' Get detected and filled-in signal +vals_filled <- featureValues(lcms1)[, QC_samples] + +#' Replace detected signal with NA +vals_filled[!is.na(vals_detect)] <- NA + +#' Identify features with at least one filled peak +has_filled <- is.na(rowSums(vals_detect)) + +#' Calculate row averages for features with missing values +avg_detect <- rowMeans(vals_detect[has_filled, ], na.rm = TRUE) +avg_filled <- rowMeans(vals_filled[has_filled, ], na.rm = TRUE) + +#' Plot the values against each other (in log2 scale) +plot(log2(avg_detect), log2(avg_filled), + xlim = range(log2(c(avg_detect, avg_filled)), na.rm = TRUE), + ylim = range(log2(c(avg_detect, avg_filled)), na.rm = TRUE), + pch = 21, bg = "#00000020", col = "#00000080") +grid() +abline(0, 1) +``` + +The detected (x-axis) and gap-filled (y-axis) values for QC samples are highly +correlated. Especially for higher abundances, the agreement is very high, while +for low intensities, as can be expected, differences are higher and trending to +below the correlation line. Below we, in addition, fit a linear regression line +to the data and summarize its results + +```{r} +#' fit a linear regression line to the data +l <- lm(log2(avg_filled) ~ log2(avg_detect)) +summary(l) +``` + +The linear regression line has a slope of `r round(coef(l)[2], 2)` and an +intercept of `r round(coef(l)[1], 2)`. This indicates that the filled-in signal +is on average `r round(coef(l)[2], 2)` times higher than the detected signal. + +## Preprocessing results + +The final results of the LC-MS data preprocessing are stored within the +`XcmsExperiment` object. This includes the identified chromatographic peaks, the +alignment results, as well as the correspondence results. In addition, to +guarantee reproducibility, this result object keeps track of all performed +processing steps, including the individual parameter objects used to configure +these. The `processHistory()` function returns a list of the various applied +processing steps in chronological order. Below, we extract the information for +the first step of the performed preprocessing. + +```{r Process history} +#' Check first step of the process history +processHistory(lcms1)[[1]] +``` + +The `processParam()` function could then be used to extract the actual parameter +class used to configure this processing step. + +The final result of the whole LC-MS data preprocessing is a two-dimensional +matrix with abundances of the so-called LC-MS features in all samples. Note that +at this stage of the analysis features are only characterized by their *m/z* and +retention time and we don't have yet any information which metabolite a feature +could represent. + +As we have seen before, such feature matrix can be extracted with the +`featureValues()` function and the corresponding feature characteristics (i.e., +their *m/z* and retention time values) using the `featureDefinitions()` +function. Thus, these two arrays could be extracted from the *xcms* result +object and used/imported in other analysis packages for further processing. They +could for example also be exported to tab delimited text files, and used in an +external tool, or used, if also MS2 spectra were available, for feature-based +molecular networking in the GNPS analysis environment +[@nothias_feature-based_2020]. + +For further processing in R, if no reference or link to the raw MS data is +required, it is suggested to extract the *xcms* preprocessing result using the +`quantify()` function as a `SummarizedExperiment` object, Bioconductor's default +container for data from biological assays/experiments. This simplifies +integration with other Bioconductor analysis packages. The `quantify()` function +takes the same parameters than the `featureValues()` function, thus, with the +call below we extract a `SummarizedExperiment` with only the detected, but not +gap-filled, feature abundances: + +```{r} +#' Extract results as a SummarizedExperiment +res <- quantify(lcms1, method = "sum", filled = FALSE) +res +``` + +Sample identifications from the *xcms* result's `sampleData()` are now available +as `colData()` (column, sample annotations) and the `featureDefinitions()` as +`rowData()` (row, feature annotations). The feature values have been added as +the first `assay()` in the `SummarizedExperiment` and even the processing +history is available in the object's `metadata()`. A `SummarizedExperiment` +supports multiple assays, all being numeric matrices with the same dimensions. +Below we thus add the detected and gap-filled feature abundances as an +additional assay to the `SummarizedExperiment`. + +```{r} +assays(res)$raw_filled <- featureValues(lcms1, method = "sum", + filled = TRUE ) + +#' Different assay in the SummarizedExperiment object +assayNames(res) +``` + +Feature abundances can be extracted with the `assay()` function. Below we +extract the first 6 lines of the detected and gap-filled feature abundances: + +```{r} +assay(res, "raw_filled") |> head() +``` + +An advantage, in addition to being a container for the full preprocessing +results is also the possibility of an easy and intuitive creation of data +subsets ensuring data integrity. It would for example be very easy to subset the +full data to a selection of features and/or samples: + +```{r} +res[1:14, 3:8] +``` + +Before moving to the next step of the analysis, it is advisable to save the +preprocessing results. We have multiple format options to save into, and they +can be found in the `MsIO` package +[documentation](https://rformassspectrometry.github.io/MsIO/articles/MsIO.html). +Below we will save our `XcmsExperiment` object into a file format handled by the +[alabster framework](https://github.com/ArtifactDB/alabaster.base), which +ensures that our object can be easily read from other languages like Python and +Javascript as well as loaded easily back into R. + +```{r} +#' Save the preprocessing results +# d <- file.path(tempdir(), "objects/lcms1") +# saveMsObject(lcms1, AlabasterParam(path = d)) +#for now let's do R object because the previous method is not implemented yet. +save(lcms1, file = "preprocessed_lcms1.RData") +``` + +# Data normalization + +After preprocessing, data normalization or scaling might need to be applied to +remove any technical variances from the data. While simple approaches like +median scaling can be implemented with a few lines of R code, more advanced +normalization algorithms are available in packages such as Bioconductor's +`r Biocpkg("preprocessCore")`. The comprehensive workflow "Notame" also propose +a very interesting normalization approach adaptable and scalable to the user +dataset [@klavus_notame_2020]. + +Generally, for LC-MS data, bias can be categorized into three main +groups[@broadhurst_guidelines_2018]: + +- Variances introduced by sample collection and initial processing, which can + include differences in sample amounts. This type of bias is expected to be + sample-specific and affect all signals in a sample in the same way. Methods + like median scaling, LOESS or quantiles normalization can adjust this bias. + +- Signal drifts along measurement of samples from an experiment. Reasons for + such drifts can be related to aging of the instrumentation used (columns, + detector), but also to changes in metabolite abundances and characteristics + due to reactions and modifications, such as oxidation. These changes are + expected to affect more the samples measured later in a run rather than the + ones measured at the beginning. For this reason, this bias can play a major + role in large experiments bias can play a major role in large experiments + measured over a long time range and is usually considered to affect + individual metabolites (or metabolite groups) differently. For adjustment, + moving average or linear regression-based approaches can be used. The latter + can for example be performed using the `adjust_lm()` function from the + `r Biocpkg("MetaboCoreUtils")` package. + +- Batch-related biases. These comprise any noise that is specific to a larger + set of samples, which can be the set of samples measured in one LC-MS + measurement run (i.e. from one analysis plate) or samples measured using a + specific batch of reagents. This noise is assumed to affect all samples in + one batch in the same way and linear modeling-based approaches can be used + to adjust for this. + +Unwanted variation can arise from various sources and is highly dependent on the +experiment. Therefore, data normalization should be chosen carefully based on +experimental design, statistical aims, and the balance of accuracy and precision +achieved through the use of auxiliary information. + +Sample preparation biases can be evaluated using internal standards, depending +however also on when they were added to the sample mixes during sample +processing. Repeated measurements of QC samples on the other hand allows to +estimate and correct for LC-MS specific biases. Also, proper planning of an +experiment, such as measurement of study samples in random order, can largely +avoid biases introduced by most of the above mentioned sources of variance. + +In this workflow we present some tools to assess data quality and evaluate need +for normalization as well as options for normalization. For space reasons we are +not able to provide solutions to adjust for all possible sources of variation. + +## Initial quality assessment + +A principal component analysis (PCA) is a very helpful tool for an initial, +unsupervised, visualization of the data that also provides insights into +potential quality issues in the data. In order to apply a PCA to the measured +feature abundances, we need however to impute (the still present) missing +values. We assume that most of these missing values (after the gap-filling step) +represent signal which is below detection limit. In such cases, missing values +can be replaced with random values sampled from a uniform distribution, ranging +from half of the smallest measured value to the smallest measured value for a +specific feature. The uniform distribution is defined with two parameters +(minimum and maximum) and all values between them have an equal probability of +being selected. + +Below we impute missing values with this approach and add the resulting data +matrix as a new *assay* to our result object. + +```{r} +#' Load preprocessing results +## load("SumExp.RData") +## loadResults(RDataParam("data.RData")) + +#' Impute missing values using an uniform distribution +na_unidis <- function(z) { + na <- is.na(z) + if (any(na)) { + min = min(z, na.rm = TRUE) + z[na] <- runif(sum(na), min = min/2, max = min) + } + z +} + +#' Row-wise impute missing values and add the data as a new assay +tmp <- apply(assay(res, "raw_filled"), MARGIN = 1, na_unidis) +assays(res)$raw_filled_imputed <- t(tmp) +``` + +### Principal Component Analysis + +PCA is a powerful tool for detecting biases in data. As a dimensionality +reduction technique, it enables visualization of data in a lower-dimensional +space. In the context of LC-MS data, PCA can be used to identify overall biases +in batch, sample, injection index, etc. However, it is important to note that +PCA is a linear method and may not be able to detect all biases in the data. + +Before plotting the PCA, we apply a log2 transform, center and scale the data. +The log2 transformation is applied to stabilize the variance while centering to +remove dependency on absolute abundances. + +```{r unsupervised checks1} +#| fig-cap: "Figure 28. PCA of the data." +#' Log2 transform and scale data +vals <- assay(res, "raw_filled_imputed") |> + log2() |> + t() |> + scale(center = TRUE, scale = TRUE) + +#' Perform the PCA +pca_res <- prcomp(vals, scale = FALSE, center = FALSE) + +#' Plot the results +vals_st <- cbind(vals, phenotype = res$phenotype) +pca_12 <- autoplot(pca_res, data = vals_st , colour = 'phenotype', scale = 0) + + scale_color_manual(values = col_phenotype) +pca_34 <- autoplot(pca_res, data = vals_st, colour = 'phenotype', + x = 3, y = 4, scale = 0) + + scale_color_manual(values = col_phenotype) +grid.arrange(pca_12, pca_34, ncol = 2) +``` + +The PCA above shows a clear separation of the study samples (plasma) from the QC +samples (serum) on the first principal component (PC1). The separation based on +phenotype is visible on the third principal component (PC3). + +In some cases, it can be a better option to remove the imputed values and +evaluate the PCA again. This is especially true if the imputed values are +replacing a large proportion of the data. + +### Intensity evaluation + +Global differences in feature abundances between samples (e.g. due to +sample-specific biases) can be evaluated by plotting the distribution of the +log2 transformed feature abundances using boxplots of violin plots. Below we +show the number of detected chromatographic peaks per sample and the +distribution of the log2 transformed feature abundances. + +```{r counts1} +#| fig-cap: "Figure 29. Number of detected peaks and feature abundances." +layout(mat = matrix(1:3, ncol = 1), height = c(0.2, 0.2, 0.8)) + +par(mar = c(0.2, 4.5, 0.2, 3)) +barplot(apply(assay(res, "raw"), MARGIN = 2, function(x) sum(!is.na(x))), + col = paste0(col_sample, 80), border = col_sample, + ylab = "# detected peaks", xaxt = "n", space = 0.012) +grid(nx = NA, ny = NULL) +barplot(apply(assay(res, "raw_filled"), MARGIN = 2, function(x) sum(!is.na(x))), + col = paste0(col_sample, 80), border = col_sample, + ylab = "# detected + filled peaks", xaxt = "n", + space = 0.012) +grid(nx = NA, ny = NULL) +vioplot(log2(assay(res, "raw_filled")), xaxt = "n", + ylab = expression(log[2]~feature~abundance), + col = paste0(col_sample, 80), border = col_sample) +points(colMedians(log2(assay(res, "raw_filled")), na.rm = TRUE), type = "b", + pch = 1) +grid(nx = NA, ny = NULL) +legend("topright", col = col_phenotype, + legend = names(col_phenotype), lty=1, lwd = 2, xpd = TRUE, ncol = 3, + cex = 0.8, bty = "n") +``` + +The upper part of the plot show that the gap filling steps allowed to rescue a +substantial number of *NAs* and allowed us to have a more consistent number of +feature values per sample. This consistency aligns with our asspumption that +every sample should have a similar amount of features detected. Additionally we +observe that, on average, the signal distribution from the individual samples is +very similar. + +An alternative way to evaluate differences in abundances between samples are +*relative log abundance* (RLA) plots [@de_livera_normalizing_2012]. An RLA value +is the abundance of a feature in a sample relative to the median abundance of +the same feature across multiple samples. We can discriminate between *within +group* and *across group* RLAs, depending whether the abundance is compared +against samples within the same sample group or across all samples. Within group +RLA plots assess the tightness of replicates within groups and should have a +median close to zero and low variation around it. When used across groups, they +allow to compare behavior between groups. Generally, between-sample differences +can be easily spotted using RLA plots. Below we calculate and visualize within +group RLA values using the `rowRla()` function from the +`r Biocpkg("MsCoreUtils")` package defining with parameter `f` the sample +groups. + +```{r rla-plot raw and filled1} +#| fig-cap: "Figure 30. RLA plot for the raw data and filled data." +par(mfrow = c(1, 1), mar = c(3.5, 4.5, 2.5, 1)) +boxplot(MsCoreUtils::rowRla(assay(res, "raw_filled"), + f = res$phenotype, transform = "log2"), + cex = 0.5, pch = 16, + col = paste0(col_sample, 80), ylab = "RLA", + border = col_sample, boxwex = 1, + outline = FALSE, xaxt = "n", main = "Relative log abundance", + cex.main = 1) +axis(side = 1, at = seq_len(ncol(res)), labels = colData(res)$sample_name) +grid(nx = NA, ny = NULL) +abline(h = 0, lty=3, lwd = 1, col = "black") +legend("topright", col = col_phenotype, + legend = names(col_phenotype), lty=1, lwd = 2, xpd = TRUE, ncol = 3, + cex = 0.8, bty = "n") +``` + +On the RLA plot above, we can observe that the medians for most samples are +indeed centered around 0. Exception are two of the *CVD* samples. Thus, while +the distribution of signals across samples is comparable, some differences seem +to be present that require a between sample normalization. + +### Internal standards + +Depending when IS are added to the sample mixes, they allow evaluation of +variances introduced by the subsequent processing and analysis steps. For the +present experiment, IS were added to the original plasma samples before sample +extraction which included also protein and lipid removal steps. They can +therefore be used to evaluate variances introduced by sample extraction and any +subsequent steps, can however not be used to infer conclusions on performance or +differences on the original sample collection (blood drawing, storage, plasma +creation). + +Here we again use the `matchValues()` function to identify features representing +signal from the IS. We then filter these matches to only keep IS that match with +a single feature using the `filterMatches()` function in combination with +`SingleMatchParam`. + +```{r} +# Do we keep IS in normalisation ? Does not give much info... Would simplify a bit +#' Creating a column within our IS table +intern_standard$feature_id <- NA_character_ + +#' Identify features matching m/z and RT of internal standards. +fdef <- featureDefinitions(lcms1) +fdef$feature_id <- rownames(fdef) +match_intern_standard <- matchValues( + query = intern_standard, + target = fdef, + mzColname = c("mz", "mzmed"), + rtColname = c("RT", "rtmed"), + param = MzRtParam(ppm = 50, toleranceRt = 10)) + +#' Keep only matches with a 1:1 mapping standard to feature. +param <- SingleMatchParam(duplicates = "remove", column = "score_rt", + decreasing = TRUE) +match_intern_standard <- filterMatches(match_intern_standard, param) + +intern_standard$feature_id <- match_intern_standard$target_feature_id +intern_standard <- intern_standard[!is.na(intern_standard$feature_id), ] +``` + +These internal standards play a crucial role in guiding the normalization +process. Given the assumption that the samples were artificially spiked, we +possess a known ground truth—that the abundance or intensity of the internal +standard should be consistent. Any difference is expected to be due to technical +differences/variance. Consequently, normalization aims to minimize variation +between samples for the internal standard, reinforcing the reliability of our +analyses. + +## Between sample normalisation + +The previous RLA plot showed that the data had some biases that need to be +corrected. Therefore, we will implement between-sample normalization using +filled-in features. This process effectively mitigates variations influenced by +technical issues, such as differences in sample preparation, processing and +injection methods. In this instance, we will employ a commonly used technique +known as median scaling [@de_livera_normalizing_2012]. + +### Median scaling + +This method involves computing the median for each sample, followed by +determining the median of these individual sample medians. This ensures +consistent median values for each sample throughout the entire data set. +Maintaining uniformity in the average total metabolite abundance across all +samples is crucial for effective implementation. + +This process aims to establish a shared baseline for the central tendency of +metabolite abundance, mitigating the impact of sample-specific technical +variations. This approach fosters a more robust and comparable analysis of the +top features across the data set. The assumption is that normalizing based on +the median, known for its lower sensitivity to extreme values, enhances the +comparability of top features and ensures a consistent average abundance across +samples. + +```{r} +#' Compute median and generate normalization factor +mdns <- apply(assay(res, "raw_filled"), MARGIN = 2, + median, na.rm = TRUE ) +nf_mdn <- mdns / median(mdns) + +#' divide dataset by median of median and create a new assay. +assays(res)$norm <- sweep(assay(res, "raw_filled"), MARGIN = 2, nf_mdn, '/') +assays(res)$norm_imputed <- sweep(assay(res, "raw_filled_imputed"), MARGIN = 2, + nf_mdn, '/') +``` + +The median scaling is calculated for both imputed and non-imputed data, with +each set stored separately within the `SummarizedExperiment` object. This +approach facilitates testing various normalization strategies while maintaining +a record of all processing steps undertaken, enabling easy regression to +previous stages if necessary. + +## Assessing overall effectiveness of the normalization approach + +It is crucial to evaluate the effectiveness of the normalization process. This +can be achieved by comparing the distribution of the log2 transformed feature +abundances before and after normalization. Additionally, the RLA plots can be +used to assess the tightness of replicates within groups and compare the +behavior between groups. + +### Principal Component Analysis + +```{r} +#| fig-cap: "Figure 31. PC1 and PC2 of the data before and after normalization." +#' Data before normalization +vals_st <- cbind(vals, phenotype = res$phenotype) +pca_raw <- autoplot(pca_res, data = vals_st, + colour = 'phenotype', scale = 0) + + scale_color_manual(values = col_phenotype) + +#' Data after normalization +vals_norm <- apply(assay(res, "norm"), MARGIN = 1, na_unidis) |> + log2() |> + scale(center = TRUE, scale = TRUE) + +pca_res_norm <- prcomp(vals_norm, scale = FALSE, center = FALSE) +vals_st_norm <- cbind(vals_norm, phenotype = res$phenotype) +pca_adj <- autoplot(pca_res_norm, data = vals_st_norm, + colour = 'phenotype', scale = 0) + + scale_color_manual(values = col_phenotype) + +grid.arrange(pca_raw, pca_adj, ncol = 2) +``` + +Normalization did not have a large impact on PC1 or PC2, but the separation of +the study groups on PC3 seems to be better and difference between QC samples +lower after normalization (see below). + +```{r} +#| fig-cap: "Figure 32. PC3 and PC4 of the data before and after normalization." +pca_raw <- autoplot(pca_res, data = vals_st , + colour = 'phenotype', x = 3, y = 4, scale = 0) + + scale_color_manual(values = col_phenotype) +pca_adj <- autoplot(pca_res_norm, data = vals_st_norm, + colour = 'phenotype', x = 3, y = 4, scale = 0) + + scale_color_manual(values = col_phenotype) + +grid.arrange(pca_raw, pca_adj, ncol = 2) +``` + +The PCA plots above show that the normalization process has not changed the +overall structure of the data. The separation between the study and QC samples +remains the same. This is an expected results as normalization should not +correct for biological variance and only technical. + +### Intensity evaluation + +We compare the RLA plots before and after between-sample normalization to +evaluate its impact on the data. + +```{r rla-plot after norm2} +#| fig-cap: "Figure 33. RLA plot before and after normalization." +par(mfrow = c(2, 1), mar = c(3.5, 4.5, 2.5, 1)) + +boxplot(rowRla(assay(res, "raw_filled"), group = res$phenotype), + cex = 0.5, pch = 16, ylab = "RLA", border = col_sample, + col = paste0(col_sample, 80), cex.main = 1, outline = FALSE, + xaxt = "n", main = "Raw data", boxwex = 1) +grid(nx = NA, ny = NULL) +legend("topright", inset = c(0, -0.2), col = col_phenotype, + legend = names(col_phenotype), lty=1, lwd = 2, xpd = TRUE, + ncol = 3, cex = 0.7, bty = "n") +abline(h = 0, lty=3, lwd = 1, col = "black") + +boxplot(rowRla(assay(res, "norm"), group = res$phenotype), + cex = 0.5, pch = 16, ylab = "RLA", border = col_sample, + col = paste0(col_sample, 80), boxwex = 1, outline = FALSE, + xaxt = "n", main = "Normallized data", cex.main = 1) +axis(side = 1, at = seq_len(ncol(res)), labels = res$sample_name) +grid(nx = NA, ny = NULL) +abline(h = 0, lty = 3, lwd = 1, col = "black") +``` + +The normalization process has effectively centered the data around the median +and medians for all samples are now closer to zero. + +### Coefficient of variation + +We next evaluate the coefficient of variation (CV, also referred to as *relative +standard deviation* RSD) for features across samples from either QC or study +samples. In QC samples, the CV would represent technical noise, while in study +samples it would include also expected biological differences. Thus, +normalization should reduce the CV in QC samples, while only slightly reducing +the CV in study samples. The CV is calculated below using the `rowRsd()` +function from the `r Biocpkg("MetaboCoreUtils")` package. By setting +`mad = TRUE` we use a more robust calculation using the median absolute +deviation instead of the standard deviation. + +```{r include=TRUE, results = "asis"} +#| tbl-cap: "Table 6. Distribution of CV values across samples for the raw and normalized data." +#' Calculate the CV values +index_study <- res$phenotype %in% c("CTR", "CVD") +index_QC <- res$phenotype == "QC" + +sample_res <- cbind( + QC_raw = rowRsd(assay(res, "raw_filled")[, index_QC], + na.rm = TRUE, mad = TRUE), + QC_norm = rowRsd(assay(res, "norm")[, index_QC], + na.rm = TRUE, mad = TRUE), + Study_raw = rowRsd(assay(res, "raw_filled")[, index_study], + na.rm = TRUE, mad = TRUE), + Study_norm = rowRsd(assay(res, "norm")[, index_study], + na.rm = TRUE, mad = TRUE) +) + +#' Summarize the values across features +res_df <- data.frame( + QC_raw = quantile(sample_res[, "QC_raw"], na.rm = TRUE), + QC_norm = quantile(sample_res[, "QC_norm"], na.rm = TRUE), + Study_raw = quantile(sample_res[, "Study_raw"], na.rm = TRUE), + Study_norm = quantile(sample_res[, "Study_norm"], na.rm = TRUE) +) + +kable(res_df, format = "pipe") +``` + +The table above shows the distribution of the CV for both raw and normalized +data. The first column highlights the % of the data which is below a given CV +value, e.g. 25% of the data has a CV equal or lower than 0.04557 at the *QC_raw* +data. As anticipated, the CV values for the QCs, which reflect technical +variance, are lower compared to those for the study samples, which include both +technical and biological variance. Overall, minimal disparity exists between the +raw and normalized data, which is a positive indication that the normalization +process has not introduced bias into the dataset, but also reflects the little +differences in average abundances between sample in the raw data. + +### Conclusion on normalization + +The overall conclusion of the normalization process is that very little variance +was present from the beginning, normalization was however able to center the +data around the median (as shown by the RLA plot). Given the simplicity and +limited size of our example dataset, we will conclude the normalization process +at this stage. For more intricate datasets with diverse biases, a tailored +approach would be devised. This could include also approaches to adjust for +signal drifts or batch effects. One possible option would be to use a +linear-model based approach such as can for example be applied with the +`adjust_lm()` function from the `r Biocpkg("MetaboCoreUtils")` package. + +# Quality control: Feature prefiltering + +After normalizing our data we can now pre-filter to clean the data before +performing any statistical analysis. In general, pre-filtering of samples and +features is performed to remove outliers. + +Below we copy the original result object to also keep the unfiltered data for +later comparisons. + +```{r} +#' Number of features before filtering +nrow(res) + +#' keep unfiltered object +res_unfilt <- res +``` + +Here we will eliminate features that exhibit high variability in our dataset. +Repeatedly measured QC samples typically serve as a robust basis for cleansing +datasets allowing to identify features with excessively high noise. As in our +data set external QC samples were used, i.e. pooled samples from a different +collection and using a slightly different sample matrix, their utility for +filtering is somewhat limited. For a comprehensive description and guidelines +for data filtering in untargeted metabolomic studies, please refer to +[@broadhurst_guidelines_2018]. + +We first restrict the data set to features for which a chromatographic peak was +detected in at least 2/3 of samples of at least one of the study samples groups. +This ensures the statistical tests carried out later on the study samples are +being performed on *reliable* signal. Also, with this filter we remove features +that were mostly detected in QC samples, but not the study samples. Such filter +can be performed with `filterFeatures()` function from the `r Biocpkg("xcms")` +package with the `PercentMissingFilter` setting. The parameters of this filer: + +- `threshold`: defines the maximal acceptable percentage of samples with + missing value(s) in at least one of the sample groups defined by parameter + `f`. +- `f`: a factor defining the sample groups. By replacing the `"QC"` sample + group with `NA` in parameter `f` we exclude the QC samples from the + evaluation and consider only the study samples. With `threshold = 40` we + keep only features for which a peak was detected in 2 out of the 3 samples + in one of the sample groups. + +To consider detected chromatographic peaks per sample, we apply the filter on +the`"raw"` assay of our result object, that contains abundance values only for +detected chromatographic peaks (prior gap-filling). + +```{r} +#' Limit features to those with at least two detected peaks in one study group. +#' Setting the value for QC samples to NA excludes QC samples from the +#' calculation. +f <- res$phenotype +f[f == "QC"] <- NA +f <- as.factor(f) +res <- filterFeatures(res, PercentMissingFilter(f = f, threshold = 40), + assay = "raw") +``` + +Following the guidelines stated above we decided to still use the QC samples for +pre-filtering, on the basis that they represent similar bio-fluids to our study +samples, and thus, we anticipate observing relatively similar metabolites +affected by similar measurement biases. + +We therefore evaluate the *dispersion ratio* (Dratio) +[@broadhurst_guidelines_2018] for all features in the data set. We accomplish +this task using the same function as above but this time with the `DratioFilter` +parameter. More filters exist for this function and we invite the user to +explore them as to decide what is best for their dataset. + +```{r} +#' Compute and filter based on the Dratio +filter_dratio <- DratioFilter(threshold = 0.4, + qcIndex = res$phenotype == "QC", + studyIndex = res$phenotype != "QC", + mad = TRUE) +res <- filterFeatures(res, filter = filter_dratio, assay = "norm_imputed") +``` + +The Dratio filter is a powerful tool to identify features that exhibit high +variability in the data, relating the variance observed in QC samples with that +in study samples. By setting a threshold of 0.4, we remove features that have a +high degree of variability between the QC and study samples. In this example, +any feature in which the deviation at the QC is higher than 40% +(`threshold = 0.4`)of the deviation on the study samples is removed. This +filtering step ensures that only features are retained that have considerably +lower technical than biological variance. + +Note that the `rowDratio()` and `rowRsd()` functions from the +`r Biocpkg("MetaboCoreUtils")` package could be used to calculate the actual +numeric values for the estimates used for filtering, to e.g. to evaluate their +distribution in the whole data set or identify data set-dependent threshold +values. + +Finally, we evaluate the number of features left after the filtering steps and +calculate the percentage of features that were removed. + +```{r} +#' Number of features after analysis +nrow(res) + +#' Percentage left: end/beginning +nrow(res)/nrow(res_unfilt) * 100 +``` + +The dataset has been reduced from `r nrow(res_unfilt)` to `r nrow(res)` +features. We did remove a considerable amount of features but this is expected +as we want to focus on the most reliable features for our analysis. For the rest +of our analysis we need to separate the QC samples from the study samples. We +will store the QC samples in a separate object for later use. + +```{r} +res_qc <- res[, res$phenotype == "QC"] +res <- res[, res$phenotype != "QC"] +``` + +We in addition calculate the CV of the QC samples and add that as an additional +column to the `rowData()` of our result object. These could be used later to +prioritize identified *significant* features with e.g. low technical noise. + +```{r} +#' Calculate the QC's CV and add as feature variable to the data set +rowData(res)$qc_cv <- assay(res_qc, "norm") |> + rowRsd() +``` + +Now that our data set has been preprocessed, normalized and filtered, we can +start to evaluate the distribution of the data and estimate the variation due to +biology. + +# Differential abundance analysis + +After normalization and quality control, the next step is to identify features +that are differentially abundant between the study groups. This crucial step +allows us to identify potential biomarkers or metabolites that are associated +with the study groups. There are various approaches and methods available for +the identification of such features of interest. In this workflow we use a +multiple linear regression analysis to identify features with significantly +difference in abundances between the CVD or CTR study group. + +Before performing the tests we evaluate similarities between study samples using +a PCA (after excluding the QC samples to avoid them influencing the results). + +```{r} +#| fig-cap: "Figure 34. PCA of the data after normalization and quality control." +#' Define the colors for the plot +col_sample <- col_phenotype[res$phenotype] + +#' Log transform and scale the data for PCA analysis +vals <- assay(res, "norm_imputed") |> + t() |> + log2() |> + scale(center = TRUE, scale = TRUE) +pca_res <- prcomp(vals, scale = FALSE, center = FALSE) + +vals_st <- cbind(vals, phenotype = res$phenotype) +autoplot(pca_res, data = vals_st , colour = 'phenotype', scale = 0) + + scale_color_manual(values = col_phenotype) +``` + +The samples clearly separate by study group on PCA indicating that there are +differences in the metabolite profiles between the two groups. However, what +drives the separation on PC1 is not clear. Below we evaluate whether this could +be explained by the other available variable of our study, i.e., age: + +```{r} +#| fig-cap: "Figure 35. PCA colored by age of the data after normalization and quality control." +#' Add age to the PCA plot +vals_st <- cbind(vals, age = res$age) +autoplot(pca_res, data = vals_st , colour = 'age', scale = 0) +``` + +According to the PCA above, PC1 does not seem to be related to age. + +Even if there is some variance in the data set we can't explain at this stage, +we proceed with the (supervised) statistical tests to identify features of +interest. We compute linear models for each metabolite explaining the observed +feature abundance by the available study variables. While we could also use the +base R function `lm()`, we utilize the `R Biocpkg("limma")` package to conduct +the differential abundance analysis: the *moderated* test statistics +[@smyth_linear_2004] provided by this package are specifically well suited for +experiments with a limited number of replicates. For our tests we use a linear +model `~ phenotype + age`, hence explaining the abundances of one metabolite +accounting for the study group assignment and age of each individual. The +analysis might benefit from inclusion of a study covariate associated with PC2 +or explaining the variance seen in that principal component, but for the present +analysis only the participant's age and disease association was provided. + +Below we define the design of the study with the `model.matrix()` function and +fit feature-wise linear models to the log2-transformed abundances using the +`lmFit()` function. P-values for significance of association are then calculated +using the `eBayes()` function, that also performs the empirical Bayes-based +robust estimation of the standard errors. See also the excellent vignette/user +guide of the `r Biocpkg("limma")` package for examples and details on the linear +model procedure. + +```{r} +#' Define the linear model to be applied to the data +p.cut <- 0.05 # cut-off for significance. +m.cut <- 0.5 # cut-off for log2 fold change + +age <- res$age +phenotype <- factor(res$phenotype) +design <- model.matrix(~ phenotype + age) + +#' Fit the linear model to the data, explaining metabolite +#' concentrations by phenotype and age. +fit <- lmFit(log2(assay(res, "norm_imputed")), design = design) +fit <- eBayes(fit) +``` + +After the linear models have been fitted, we can now proceed to extract the +results. We create a data frame containing the coefficients, raw and adjusted +p-values (applying a Benjamini-Hochberg correction, i.e., `method = "BH"` for +improved control of the false discovery rate), the average intensity of signals +in CVD and CTR samples, and an indication of whether a feature is deemed +significant or not. We consider all metabolites with an adjusted p-value smaller +than 0.05 to be significant, but we could also include the (absolute) difference +in abundances in the cut-off criteria. At last, we add the differential +abundance results to the result object's `rowData()`. + +```{r} +#' Compile a result data frame +tmp <- data.frame( + coef.CVD = fit$coefficients[, "phenotypeCVD"], + pvalue.CVD = fit$p.value[, "phenotypeCVD"], + adjp.CVD = p.adjust(fit$p.value[, "phenotypeCVD"], method = "BH"), + avg.CVD = rowMeans( + log2(assay(res, "norm_imputed")[, res$phenotype == "CVD"])), + avg.CTR = rowMeans( + log2(assay(res, "norm_imputed")[, res$phenotype == "CTR"])) +) +tmp$significant.CVD <- tmp$adjp.CVD < 0.05 +#' Add the results to the object's rowData +rowData(res) <- cbind(rowData(res), tmp) +``` + +We can now proceed to visualize the distribution of the raw and adjusted +p-values. + +```{r histogram} +#| fig-cap: "Figure 36. Distribution of raw (left) and adjusted p-values (right)." +#' Plot the distribution of p-values +par(mfrow = c(1, 2)) +hist(rowData(res)$pvalue.CVD, breaks = 64, xlab = "p value", + main = "Distribution of raw p-values", + cex.main = 1, cex.lab = 1, cex.axis = 1) +hist(rowData(res)$adjp.CVD, breaks = 64, xlab = expression(p[BH]~value), + main = "Distribution of adjusted p-values", + cex.main = 1, cex.lab = 1, cex.axis = 1) +``` + +The histograms above show the distribution of raw and adjusted p-values. Except +for an enrichment of small p-values, the raw p-values are (more or less) +uniformly distributed, which indicates the absence of any strong systematic +biases in the data. The adjusted p-values are more conservative and account for +multiple testing; this is important here as we fit a linear model to each +feature and therefore perform a large number of tests which leads to a high +chance of false positive findings. We do see that some features have very low +p-values, indicating that they are likely to be significantly different between +the two study groups. + +Below we plot the adjusted p-values against the log2 fold change of (average) +abundances. This volcano plot will allow us to visualize the features that are +significantly different between the two study groups. These are highlighted with +a blue color in the plot below. + +```{r volcano} +#| fig-cap: "Figure 37. Volcano plot showing the analysis results." +#' Plot volcano plot of the statistical results +par(mfrow = c(1, 1), mar = c(5, 5, 5, 1)) +plot(rowData(res)$coef.CVD, -log10(rowData(res)$adjp.CVD), + xlab = expression(log[2]~difference), + ylab = expression(-log[10]~p[BH]), pch = 16, col = "#00000060", + cex.main = 1.5, cex.lab = 1.5, cex.axis = 1.3) +grid() +abline(h = -log10(0.05), col = "#0000ffcc") +if (any(rowData(res)$significant.CVD)) { + points(rowData(res)$coef.CVD[rowData(res)$significant.CVD], + -log10(rowData(res)$adjp.CVD[rowData(res)$significant.CVD]), + col = "#0000ffcc") +} +``` + +The most interesting features are in the top corners in the volcano plot (i.e., +features with a large difference in abundance between the groups and a small +p-value). All significant features have a negative coefficient (log2 fold change +value) indicating that their abundance is lower in CVD samples compared to the +CTR samples. These features are listed, along with their average difference in +(log2) abundance between the compared groups, their adjusted p-values, their +average (log2) abundance in each sample group and their RSD (CV) in QC samples +in the table below. + +```{r result-table} +#| tbl-cap: "Table 7. Features with significant differences in abundances." +# Table of significant features +tab <- rowData(res)[rowData(res)$significant.CVD, + c("mzmed", "rtmed", "coef.CVD", "adjp.CVD", + "avg.CTR", "avg.CVD", "qc_cv")] |> + as.data.frame() +tab <- tab[order(abs(tab$coef.CVD), decreasing = TRUE), ] +kable(tab, format = "pipe") +``` + +Below we visualize the EICs of the significant features to evaluate their (raw) +signal. We restrict the MS data set to study samples. Parameters + +- `keepFeatures = TRUE`: ensures that identified features are retained in the + \`subset object. +- `peakBg`: defines the (background) color for each individual chromatographic + peak in the EIC object. + +```{r} +#| fig-cap: "Figure 38. Extracted ion chromatograms of the significant features." +#' Restrict the raw data to study samples. +lcms1_study <- lcms1[sampleData(lcms1)$phenotype != "QC", keepFeatures = TRUE] +#' Extract EICs for the significant features +eic_sign <- featureChromatograms( + lcms1_study, features = rownames(tab), expandRt = 5, filled = TRUE) + +#' Plot the EICs. +plot(eic_sign, col = col_sample, + peakBg = paste0(col_sample[chromPeaks(eic_sign)[, "sample"]], 40)) +legend("topright", col = col_phenotype, legend = names(col_phenotype), lty = 1) +``` + +The EICs for all significant features show a clear and single peak. Their +intensities are (as already observed above) much larger in the CTR than in the +CVD samples. With the exception of the second feature (second EIC in the top +row), the intensities for all significant features are however generally low. +This might make it challenging to identify them using an LC-MS/MS setup. + +# Annotation + +We have now identified the features with significant differences in abundances +between the two study groups. These provide information on metabolic pathways +that differentiate affected from healthy individuals and might hence also serve +as biomarkers. However, at this stage of the analysis we do not know what +compounds/metabolites they actually represent. We thus need now to annotate +these signals. Annotation can be performed at different level of confidence +[@sumner_proposed_2007,@schymanski_identifying_2014]. The lowest level of +annotation, with the highest rate of false positive hits, bases on the features +*m/z* ratios. Higher levels of annotations employ fragment spectra (MS2 spectra) +of ions of interest requiring this however acquisition of additional data. In +this section, we will demonstrate multiple ways to annotate our significant +features using functionality provided through Bioconductor packages. Alternative +approaches and external software tools, which may be better suited, will also be +discussed later in the section. + +## MS1-based annotation + +Our data set was acquired using an LC-MS setup and our features are thus only +characterized by their *m/z* and retention times. The retention time is +LC-setup-specific and, without prior data/knowledge provide only little +information on the features' identity. Modern MS instruments have a high +accuracy and the *m/z* values should therefore be reliable estimates of the +compound ion's mass-to-charge ratio. As first approach, we use the features' +*m/z* values and match them against reference values, i.e., exact masses of +chemical compounds that are provided by a reference database, in our case the +MassBank database. The full MassBank data is re-distributed through +Bioconductor's `r Biocpkg("AnnotationHub")` resource, which simplifies their +integration into reproducible R-based analysis workflows. + +Below we load the resource, list all available MassBank data sets/releases and +load one of them. + +```{r} +#' load reference data +ah <- AnnotationHub() +#' List available MassBank data sets +query(ah, "MassBank") +#' Load one MAssBank release +mb <- ah[["AH116166"]] +``` + +The MassBank data is provided as a self-contained SQLite database and data can +be queried and accessed through the `r Biocpkg("CompoundDb")` Bioconductor +package. Below we use the `compounds()` function to extract small compound +annotations from the database. + +```{r} +#| tbl-cap: "Table 8. MassBank database extract." +#' Extract compound annotations +cmps <- compounds(mb, columns = c("compound_id", "name", "formula", + "exactmass", "inchikey")) +head(cmps) +``` + +MassBank (as most other small compound annotation databases) provides the +(exact) molecular mass of each compound. Since almost all small compounds are +neutral in their natural state, they need to be first converted to *m/z* values +to allow matching against the feature's *m/z*. To calculate an *m/z* from a +neutral mass, we need to assume which ion (adduct) might be generated from the +measured metabolites by the employed electro-spray ionization. In positive +polarity, for human serum samples, the most common ions will be protonated +(*\[M+H\]+*), or bear the addition of sodium (*\[M+Na\]+*) or ammonium +(*\[M+H-NH3\]+*) ions. To match the observed *m/z* values against reference +values from these potential ions we use again the `matchValues()` function with +the `Mass2MzParam` approach, that allows to specify the types of expected ions +with its `adducts` parameter and the maximal allowed difference between the +compared values using the `tolerance` and `ppm` parameters. Below we first +prepare the data.frame with the significant features, set up the parameters for +the matching and perform the comparison of the query features against the +reference database. + +```{r} +#' Prepare query data frame +rowData(res)$feature_id <- rownames(rowData(res)) +res_sig <- res[rowData(res)$significant.CVD, ] + +#' Setup parameters for the matching +param <- Mass2MzParam(adducts = c("[M+H]+", "[M+Na]+", "[M+H-NH3]+"), + tolerance = 0, ppm = 5) + +#' Perform the matching. +mtch <- matchValues(res_sig, cmps, param = param, mzColname = "mzmed") +mtch +``` + +The resulting `Matched` object shows that 4 of our 6 significant features could +be matched to ions of compounds from the MassBank database. Below we extract the +full result from the `Matched` object. + +```{r} +#' Extracting the results +mtch_res <- matchedData(mtch, c("feature_id", "mzmed", "rtmed", + "adduct", "ppm_error", + "target_formula", "target_name", + "target_inchikey")) +mtch_res +``` + +Thus, in total `r sum(!is.na(mtch_res$ppm_error))` ions of compounds in MassBank +were matched to our significant features based on the specified tolerance +settings. Many compounds, while having a different structure and thus +function/chemical property, have an identical chemical formula and thus mass. +Matching exclusively on the *m/z* of features will hence result in many +potentially false positive hits and is thus considered to provide only low +confidence annotation. An additional complication is that some annotation +resources, like MassBank, being community maintained, contain a large amount of +redundant information. To reduce redundancy in the result table we below iterate +over the hits of a feature and keep only matches to unique compounds (identified +by their INCHIKEY). The INCHI or INCHIKEY combine information from compound's +chemical formula and structure, so while different compounds can share the same +chemical formula, they should have a different structure and thus INCHI. + +```{r} +#| tbl-cap: "Table 9. MS1 annotation results." +rownames(mtch_res) <- NULL + +#' Keep only info on features that machted - create a utility function for that +mtch_res <- split(mtch_res, mtch_res$feature_id) |> + lapply(function(x) { + lapply(split(x, x$target_inchikey), function(z) { + z[which.min(z$ppm_error), ] + }) + }) |> + unlist(recursive = FALSE) |> + do.call(what = rbind) + +#' Display the results +kable(mtch_res, format = "pipe") +``` + +The table above shows the results of the MS1-based annotation process. We can +see that four of our significant features were matched. The matches seem to be +pretty accurate with low ppm errors. The deduplication performed above +considerably reduced the number of hits for each feature, but the first still +matches ions of a large number of compounds (all with the same chemical +formula). + +Considering both features' *m/z* and retention times in the MS1-based annotation +will increase the annotation confidence, but requires additional data, such as +recording of the retention time of thepure standard for a compound on the same +LC setup. An alternative approach which might provide better inside on +annotations and help to choose between different annotations for a same feature +is to evaluate certain chemical properties of the possible matches. For +instance, the LogP value, available in several databases such HMDB, provides an +insight on a given compound's polarity. As this property highly affects the +interaction of the analyte with the column, it usually also directly affects +separation. Therefore, a comparison between an analyte's retention time and +polarity can help to rule out some possible misidentifications. + +While being of low confidence, MS1-based annotation can provide first candidate +annotations that could be confirmed or rejected by additional analyses. + +## MS2-based annotation + +MS1 annotation is a fast and efficient method to annotate features and therefore +give a first insight into the compounds that are significantly different between +the two study groups. However, it is not always the most accurate. MS2 data can +provide a higher level of confidence in the annotation process as it provides, +through the observed fragmentation pattern, information on the structure of the +compound. + +MS2 data can be generated through LC-MS/MS measurement in which MS2 spectra are +recorded for ions either in a data dependent acquisition (DDA) or data +independent acquisition (DIA) mode. Generally, it is advised to include some +LC-MS/MS runs of QC samples or randomly selected study samples already during +the acquisition of the MS1 data that is used for quantification of the signals. +As an alternative, or in addition, a post-hoc LC-MS/MS acquisition can be +performed to generate the MS2 data needed for annotation. For the present +experiment, a separate LC-MS/MS measurement was conducted on QC samples and +selected study samples to generate such data using an *inclusion list* of +pre-selected ions. These represent features found to be significantly different +between CVD and CTR samples in an initial analysis of the full experiment. We +use a subset of this second LC-MS/MS data set to show how such data can be used +for MS2-based annotation. In our differential abundance analysis we found +features with significantly higher abundances in CTR samples. Consequently, we +will utilize the MS2 data obtained from the CTR samples to annotate these +significant features. + +Below we load the LC-MS/MS data from the experiment and restrict it to data +acquired from the CTR sample. + +```{r, message=FALSE, warning=FALSE} +#' Load form the MetaboLights Database +param <- MetaboLightsParam(mtblsId = "MTBLS8735", + assayName = paste0("a_MTBLS8735_LC-MSMS_positive_", + "hilic_metabolite_profiling.txt"), + filePattern = ".mzML") + +lcms2 <- readMsObject(MsExperiment(), + param, + keepOntology = FALSE, + keepProtocol = FALSE, + simplify = TRUE) +``` + +```{r} +#| tbl-cap: "Table 10. Samples from the LC-MS/MS data set." +#adjust sampleData +colnames(sampleData(lcms2)) <- c("sample_name", "derived_spectra_data_file", + "metabolite_asssignment_file", + "source_name", + "organism", + "blood_sample_type", + "sample_type", "age", "unit", "phenotype") + +# filter samples to keep MSMS data from CTR samples: +sampleData(lcms2) <- sampleData(lcms2)[sampleData(lcms2)$phenotype == "CTR", ] + +sampleData(lcms2) <- sampleData(lcms2)[grepl("MSMS", sampleData(lcms2)$derived_spectra_data_file), ] + +# Add fragmentation data information (from filenames) +sampleData(lcms2)$fragmentation_mode <- c("CE20", "CE30", "CES") + +#let's look at the updated sample data +sampleData(lcms2)[, c("derived_spectra_data_file", + "phenotype", "sample_name", "age")] |> + kable(format = "pipe") + +#' Filter the data to the same RT range as the LC-MS run +lcms2 <- filterRt(lcms2, c(10, 240)) +``` + +In total we have `r length(lcms2)` LC-MS/MS data files for the control samples, +each with a different collision energy to fragment the ions. Below we show the +number of MS1 and MS2 spectra for each of the files. + +```{r} +#' check the number of spectra per ms level +spectra(lcms2) |> + msLevel() |> + split(spectraSampleIndex(lcms2)) |> + lapply(table) |> + do.call(what = cbind) +``` + +Compared to the number of MS2 spectra, far less MS1 spectra were acquired. The +configuration of the MS instrument was set to ensure that the ions specified in +the *inclusion list* were selected for fragmentation, even if their intensity +might be very low. With this setting, however, most of the recorded MS2 spectra +will represent only noise. The plot below shows the location of precursor ions +in the *m/z* - retention time plane for the three files. + +```{r echo=TRUE} +plotPrecursorIons(lcms2) +``` + +We can see MS2 spectra being recorded for the *m/z* of interest along the full +retention time range, even if the actual ions will only be eluting within +certain retention time windows. + +We next extract the `Spectra` object with the MS data from the data object and +assign a new spectra variable with the employed collision energy, which we +extract from the data object `sampleData`. + +```{r} +ms2_ctr <- spectra(lcms2) +ms2_ctr$collision_energy <- + sampleData(lcms2)$fragmentation_mode[spectraSampleIndex(lcms2)] +``` + +We next filter the MS data by first restricting to MS2 spectra and then removing +mass peaks from each spectrum with an intensity that is lower than 5% of the +highest intensity for that spectrum, assuming that these very low intensity +peaks represent background signal. + +```{r} +#' Remove low intensity peaks +low_int <- function(x, ...) { + x > max(x, na.rm = TRUE) * 0.05 +} + +ms2_ctr <- filterMsLevel(ms2_ctr, 2L) |> + filterIntensity(intensity = low_int) +``` + +We next remove also mass peaks with an *m/z* value greater or equal to the +precursor *m/z* of the ion. This puts, for the later matching against reference +spectra, more weight on the fragmentation pattern of ions and avoids hits based +only on the precursor *m/z* peak (and hence a similar mass of the compared +compounds). At last, we restrict the data to spectra with at least two fragment +peaks and scale their intensities such that their sum is 1 for each spectrum. +While similarity calculations are not affected by this scaling, it makes visual +comparison of fragment spectra easier to read. + +```{r} +#' Remove precursor peaks and restrict to spectra with a minimum +#' number of peaks +ms2_ctr <- filterPrecursorPeaks(ms2_ctr, ppm = 50, mz = ">=") +ms2_ctr <- ms2_ctr[lengths(ms2_ctr) > 1] |> + scalePeaks() +``` + +Finally, also to speed up the later comparison of the spectra against the +reference database, we load the full MS data into memory (by changing the +*backend* to `MsBackendMemory`) and *apply* all processing steps performed on +this data so far. Keeping the MS data in memory has performance benefits, but is +generally not suggested for large data sets. To evaluate the impact for the +present data set we print in addition the size of the data object before and +after changing the backend. + +```{r} +#' Size of the object before loading into memory +print(object.size(ms2_ctr), units = "MB") + +#' Load the MS data subset into memory +ms2_ctr <- setBackend(ms2_ctr, MsBackendMemory()) +ms2_ctr <- applyProcessing(ms2_ctr) + +#' Size of the object after loading into memory +print(object.size(ms2_ctr), units = "MB") +``` + +There is thus only a moderate increase in memory demand after loading the MS +data into memory (also because we filtered and cleaned the MS2 data). + +While we could proceed and match all these experimental MS2 spectra against +reference fragment spectra, for our workflow we aim to annotate the features +found significant in the differential abundance analysis. The goal is thus to +identify the MS2 spectra from the second (LC-MS/MS) run that could represent +fragments of the ions of features from the data in the first (LC-MS) run. Our +approach is to match MS2 spectra against the significant features determined +earlier based on their precursor *m/z* and retention time (given an acceptable +tolerance) to the feature's *m/z* and retention time. This can be easily done +using the `featureArea()` function that effectively considers the actual *m/z* +and retention time ranges of the features' chromatographic peaks and therefore +increase the chance of finding a correct match. This however also assumes that +retention times between the first and second run don't differ by much. +Alternatively, we would need to align the retention times of the second LC-MS/MS +data set to those of the first. + +Below we first extract the *feature area*, i.e., the *m/z* and retention time +ranges, for the significant features. + +```{r} +#' Define the m/z and retention time ranges for the significant features +target <- featureArea(lcms1)[rownames(res_sig), ] +target +``` + +We next identify the fragment spectra with their precursor *m/z* and retention +times within these ranges. We use the `filterRanges()` function that allows to +filter a `Spectra` object using multiple ranges simultaneously. We apply this +function separately to each feature (row in the above matrix) to extract the MS2 +spectra representing fragmentation information of the presumed feature's ions. + +```{r} +#' Identify for each feature MS2 spectra with their precursor m/z and +#' retention time within the feature's m/z and retention time range +ms2_ctr_fts <- apply(target[, c("rtmin", "rtmax", "mzmin", "mzmax")], + MARGIN = 1, FUN = filterRanges, object = ms2_ctr, + spectraVariables = c("rtime", "precursorMz")) +lengths(ms2_ctr_fts) +``` + +The result from this `apply()` call is a `list` of `Spectra`, each element +representing the result for one feature. With the exception of the last feature, +multiple MS2 spectra could be identified. We next combine the `list` of +`Spectra` into a single `Spectra` object using the `concatenateSpectra()` +function and add an additional spectra variable containing the respective +feature identifier. + +```{r} +l <- lengths(ms2_ctr_fts) +#' Combine the individual Spectra objects +ms2_ctr_fts <- concatenateSpectra(ms2_ctr_fts) +#' Assign the feature identifier to each MS2 spectrum +ms2_ctr_fts$feature_id <- rep(rownames(res_sig), l) +``` + +We have now a `Spectra` object with fragment spectra for the significant +features from our differential expression analysis. + +We next build our reference data which we need to process the same way as our +*query* spectra. We extract all fragment spectra from the MassBank database, +restrict to positive polarity data (since our experiment was acquired in +positive polarity) and perform the same processing to the fragment spectra from +the MassBank database. + +```{r prep spectra object} +ms2_ref <- Spectra(mb) |> + filterPolarity(1L) |> + filterIntensity(intensity = low_int) |> + filterPrecursorPeaks(ppm = 50, mz = ">=") +ms2_ref <- ms2_ref[lengths(ms2_ref) > 1] |> + scalePeaks() +``` + +Note that here we could switch to a `MsBackendMemory` backend hence loading the +full data from the reference database into memory. This could have a positive +impact on the performance of the subsequent spectra matching, however it would +also increase the memory demand of the present analysis. + +Now that both the `Spectra` object for the second run and the database spectra +have been prepared, we can proceed with the matching process. We use the +`matchSpectra()` function from the `r Biocpkg("MetaboAnnotation")` package with +the `CompareSpectraParam` to define the settings for the matching. With the +following parameters: + +- `requirePrecursor = TRUE`: Limits spectra similarity calculations to + fragment spectra with a similar precursor *m/z*. +- `tolerance` and `ppm`: Defines the acceptable difference between compared + *m/z* values. These relaxed tolerance settings ensure that we find matches + even if reference spectra were acquired on instruments with a lower + accuracy. +- `THRESHFUN`: Defines which matches to report. Here, we keep all matches + resulting in a spectra similarity score (calculated with the normalized dot + product [@stein_optimization_1994], the default similarity function) larger + than 0.6. + +```{r} +register(SerialParam()) +#' Define the settings for the spectra matching. +prm <- CompareSpectraParam(ppm = 40, tolerance = 0.05, + requirePrecursor = TRUE, + THRESHFUN = function(x) which(x >= 0.6)) + +ms2_mtch <- matchSpectra(ms2_ctr_fts, ms2_ref, param = prm) +ms2_mtch +``` + +Thus, from the total `r length(ms2_ctr_fts)` query MS2 spectra, only +`r length(whichQuery(ms2_mtch))` could be matched to (at least) one reference +fragment spectrum. + +Below we restrict the results to these matching spectra and extract all metadata +of the query and target spectra as well as the similarity score (the complete +list of available metadata information can be listed with the `colnames()` +function). + +```{r} +#' Keep only query spectra with matching reference spectra +ms2_mtch <- ms2_mtch[whichQuery(ms2_mtch)] + +#' Extract the results +ms2_mtch_res <- matchedData(ms2_mtch) +nrow(ms2_mtch_res) +``` + +Now, we have query-target pairs with a spectra similarity higher than 0.6. +Similar to the MS1-based annotation also this result table contains redundant +information: we have multiple fragment spectra per feature and also MassBank +contains several fragment spectra for each compound, measured using differing +collision energies or MS instruments, by different laboratories. Below we thus +iterate over the feature-compound pairs and select for each the one with the +highest score. As an identifier for a compound, we use its fragment spectra's +INCHI-key, since compound names in MassBank do not have accepted +consensus/controlled vocabularies. + +```{r results='asis'} +#' - split the result per feature +#' - select for each feature the best matching result for each compound +#' - combine the result again into a data frame +ms2_mtch_res <- + ms2_mtch_res |> + split(f = paste(ms2_mtch_res$feature_id, ms2_mtch_res$target_inchikey)) |> + lapply(function(z) { + z[which.max(z$score), ] + }) |> + do.call(what = rbind) |> + as.data.frame() + +#' List the best matching feature-compound pair +pandoc.table(ms2_mtch_res[, c("feature_id", "target_name", "score", + "target_inchikey")], + style = "rmarkdown", + caption = "Table 9.MS2 annotation results.", + split.table = Inf) +``` + +Thus, from the `r length(res_sig)` significant features, only one could be +annotated to a compound based on the MS2-based approach. There could be many +reasons for the failure to find matches for the other features. Although MS2 +spectra were selected for each feature, most appear to only represent noise, as +for most features, in the LC-MS/MS run, no or only very low MS1 signal was +recorded, indicating that in that selected sample the original compound might +not (or no longer) be present. Also, most reference databases contain +predominantly fragment spectra for protonated (*\[M+H\]+*) ions of compounds, +while some of the features might represent signal from other types of ions which +would result in a different fragmentation pattern. Finally, fragment spectra of +compounds of interest might also simply not be present in the used reference +database. + +Thus, combining the information from the MS1- and MS2 based annotation we can +only annotate one feature with a considerable confidence. The feature with the +*m/z* of 195.0879 and a retention time of 32 seconds seems to be an ion of +caffeine. While the result is somewhat disappointing it also clearly shows the +importance of proper experimental planning and the need to control for all +potential confounding factors. For the present experiment, no disease-specific +biomarker could be identified, but a life-style property of individuals +suffering from this disease: coffee consumption was probably contraindicated to +patients of the CVD group to reduce the risk of heart arrhythmia. + +We below plot the EIC for this feature highlighting the retention time at which +the highest scoring MS2 spectra was recorded and create a mirror plot comparing +this MS2 spectra to the reference fragment spectra for caffeine. + +```{r fig.height=5, fig.width=5} +par(mfrow = c(1, 2)) + +col_sample <- col_phenotype[sampleData(lcms1)$phenotype] +#' Extract and plot EIC for the annotated feature +eic <- featureChromatograms(lcms1, features = ms2_mtch_res$feature_id[1]) +plot(eic, col = col_sample, peakCol = col_sample[chromPeaks(eic)[, "sample"]], + peakBg = paste0(col_sample[chromPeaks(eic)[, "sample"]], 20)) +legend("topright", col = col_phenotype, legend = names(col_phenotype), lty = 1) + +#' Identify the best matching query-target spectra pair +idx <- which.max(ms2_mtch_res$score) + +#' Indicate the retention time of the MS2 spectrum in the EIC plot +abline(v = ms2_mtch_res$rtime[idx]) + +#' Get the index of the MS2 spectrum in the query object +query_idx <- which(query(ms2_mtch)$.original_query_index == + ms2_mtch_res$.original_query_index[idx]) +query_ms2 <- query(ms2_mtch)[query_idx] +#' Get the index of the MS2 spectrum in the target object +target_idx <- which(target(ms2_mtch)$spectrum_id == + ms2_mtch_res$target_spectrum_id[idx]) +target_ms2 <- target(ms2_mtch)[target_idx] + +#' Create a mirror plot comparing the two best matching spectra +plotSpectraMirror(query_ms2, target_ms2) +legend("topleft", + legend = paste0("precursor m/z: ", format(precursorMz(query_ms2), 3))) +``` + +The plot above clearly shows the higher signal for this feature in CTR compared +to CVD samples. The QC samples exhibit a lower but highly consistent signal, +suggesting the absence of strong technical noise or biases in the raw data of +this experiment. The vertical line indicates the retention time of the fragment +spectrum with the best match against a reference spectrum. It has to be noted +that, since the fragment spectra were measured in a separate LC-MS/MS +experiment, this should only be considered as an indication of the approximate +retention time in which ions were fragmented in the second experiment. The +fragment spectrum for this feature, shown in the upper panel of the right plot +above is highly similar to the reference spectrum for caffeine from MassBank +(shown in the lower panel). In addition to their matching precursor *m/z*, the +same two fragments (same *m/z* and intensity) are present in both spectra. + +We can also extract additional metadata for the matching reference spectrum, +such as the used collision energy, fragmentation mode, instrument type, +instrument as well as the ion (adduct) that was fragmented. + +```{r} +spectraData(target_ms2, c("collisionEnergy_text", "fragmentation_mode", + "instrument_type", "instrument", "adduct")) |> + as.data.frame() +``` + +## External tools or alternative annotation approaches + +The present workflow highlights how annotation could be performed within R using +packages from the Bioconductor project, but there are also other excellent +external softwares that could be used as an alternative, such as SIRIUS +[@duhrkop_sirius_2019], mummichog [@li_predicting_2013] or GNPS +[@nothias_feature-based_2020] among others. To use these, the data would need to +be exported in a format supported by these. For MS2 spectra, the data could +easily be exported in the required MGF file format using the +`r Biocpkg("MsBackendMgf")` Bioconductor package. Integration of *xcms* into +feature-based molecular networking with GNPS is described in the [GNPS +documentation](https://ccms-ucsd.github.io/GNPSDocumentation/featurebasedmolecularnetworking-with-xcms3/). + +In alternative, or in addition, evidence for the potential matching chemical +formula of a feature could be derived by evaluating the isotope pattern of its +full MS1 scan. This could provide information on the isotope composition. Also +for this, various functions such as the `isotopologues()` from the or +`r Biocpkg("MetaboCoreUtils")` package or the functionality of the *envipat* R +package [@loos_accelerated_2015] could be used. + +# Summary + +In this tutorial, we describe an end-to-end workflow for LC-MS-based untargeted +metabolomics experiments, conducted entirely within R using packages from the +Bioconductor project or base R functionality. While other excellent software +exists to perform similar analyses, the power of an R-based workflow lies in its +adaptability to individual data sets or research questions and its ability to +build reproducible workflows and documentation. + +Due to space restrictions we don't provide a comprehensive listing of +methodologies for the individual analysis steps. More advanced options or +approaches would be available, e.g., for normalization of the data, which will +however also be heavily dependent on the size and properties of the analyzed +data set, as well as for annotation of the features. + +As a result, we found in the present analysis a set of features with significant +abundance differences between the compared groups. From these we could however +only reliably annotate a single feature, that related to the lifestyle of +individuals rather then to the pathological properties of the investigated +disease. This low proportion of annotated signals is however not uncommon for +untargeted metabolomics experiments and reflects the need for more comprehensive +and reliable reference annotation libraries. + +# Session information + +```{r} +sessionInfo() +``` + +# References + +# Appendix + +## Aknowledgment + +Thanks to Steffen Neumann for his continuous work to develop and maintain the +xcms software. ... + +## Alignment using manually selected anchor peaks + +- align the data set using internal standards. +- suggested to eventually *enrich* the anchor peaks with signal from other + ions in retention time regions not covered by the internal standards. + +# Additional informations + +```{r eval=FALSE, include=FALSE} +plot(eics_is_noprocess) + +#' Notes on the EICs: +#' - Alanine 13C 15N: signal very low, maybe other ion? +#' - Arginine: very nice signal, RT a bit off. +#' - Aspartic acid: very nice signal. +#' - Carnitine: nothing there, maybe other ion? +#' - Creatinine: nothing there, maybe other ion? +#' - Cystine: very nice signal. +#' - Glucose: nice signal. +#' - Glutamic acid: very nice signal. +#' - Glycine: signal low but ~ OK. +#' - cystine: very nice signal, RT a bit off. +#' - Isoleucine: multiple peaks, most likely both leucine and isoleucine. +#' - Leucine: same as Isoleucine. +#' - Methionine: nice signal, but some other peaks close by. +#' - Phenylalanine: nice signal, but some other peaks close by. +#' - Proline: signal low but ~ OK (maybe other ion?) +#' - Serine: nice signal. +#' - Threonine: nice signal. +#' - Tyrosine: signal low but ~ OK. +#' - Valine: signal OK. Other peaks close by. +#' + +plot(eics_is_chrompeaks) # show chrompeak detection +plot(eics_is_refined) # show refinement effect +plot(eic_is) # show alignment effect +``` + +```{r} +#possible extra info: +# - +``` diff --git a/vignettes/images/sitcker_draft.xcf b/vignettes/images/sitcker_draft.xcf new file mode 100644 index 0000000000000000000000000000000000000000..1394b73ea2bed7223595916b96ebb289fa8097d6 GIT binary patch literal 219777 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 G!v70ptee3A literal 0 HcmV?d00001 diff --git a/vignettes/install_v0.qmd b/vignettes/install_v0.qmd deleted file mode 100644 index 1066125..0000000 --- a/vignettes/install_v0.qmd +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: "Install" -format: html -editor: visual -minimal: true -date: 'Compiled: `r format(Sys.Date(), "%B %d, %Y")`' -vignette: > - %\VignetteIndexEntry{Install} - %\VignetteEngine{quarto::html} - %\VignetteEncoding{UTF-8} ---- - -For manual installation, an [R](https://www.r-project.org/) version \>= 4.4.0 is -required. - -# Install packages required for metabonaut tutorials - -Notes: only a development version of metabonaut is available as of now but a -stable version will come soon. - -The following packages need to be installed for proper functioning of the -tutorials: - -Run the code as follow: - -```{r eval=FALSE} -install.packages("BiocManager") -BiocManager::install(c('RCurl', 'xcms', 'MsExperiment', 'SummarizedExperiment', - 'Spectra', 'MetaboCoreUtils', 'limma', 'matrixStats', 'pander', - 'RColorBrewer', 'pheatmap', 'vioplot', 'ggfortify', 'gridExtra', - 'AnnotationHub', 'CompoundDb', 'MetaboAnnotation', - 'RforMassSpectrometry/MsIO', 'RforMassSpectrometry/MsBackendMetaboLights')) -``` - -# Docker image - -The vignettes files along with an R runtime environment including all required -packages and the RStudio (Posit) editor are all bundled in a *docker* container. - -After installation, this docker container can be run on the computer and the -code and examples from the vignettes can be evaluated within this environment -(without the need to install any additional packages or files). - -- If you don't already have, install [docker](https://www.docker.com/). Find - installation information [here](https://docs.docker.com/desktop/). -- Get the [docker - image](https://hub.docker.com/r/rformassspectrometry/metabonaut) of this - tutorial e.g. from the command line with - `docker pull rformassspectrometry/metabonaut:latest`. -- Start the docker container, either through the Docker Desktop, or on the - command line with - -``` -docker run -e PASSWORD=bioc -p 8787:8787 rformassspectrometry metabonaut:latest -``` - -- Enter [`http://localhost:8787`](http://localhost:8787) in a web browser and - log in with username `rstudio` and password `bioc`. -- In the RStudio server version: open any of the R-markdown (*.Rmd*) files in - the *vignettes* folder and evaluate the R code blocks in that document.