--- title: "Getting started with MRManalyzeR" author: - name: Matthew J. Smith affiliation: Karolinska Institutet, Stockholm, Sweden email: mattyjsmith123@gmail.com - name: Antonio Checa affiliation: Karolinska Institutet, Stockholm, Sweden date: "`r Sys.Date()`" package: MRManalyzeR output: BiocStyle::html_document: toc_float: true number_sections: true vignette: > %\VignetteIndexEntry{Getting started with MRManalyzeR} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup-knitr, include = FALSE} knitr::opts_chunk$set(comment = "#>", fig.width = 6.5, fig.height = 4, dpi = 100, message = FALSE, warning = FALSE) library(MRManalyzeR) ``` # Scope MRManalyzeR processes reviewed targeted LC-MS/MS results, assesses data quality and generates reports. A curated workbook and a YAML configuration define the analysis, so a run is described by two files and can be repeated from them. It reads Waters **TargetLynx** exports, **Skyline** exports, and plain sample-by-analyte matrices from anything else. It does not do peak detection, integration or calibration-curve fitting: those decisions stay with you, in the vendor software. Everything is carried in a `r BiocStyle::Biocpkg("struct")` `DatasetExperiment`: the assay matrix, the sample metadata and the feature metadata, kept aligned in one object. Any package that produces or consumes that class can exchange data with this one — see [Working with other packages](#interoperability). # Installation ```{r install, eval = FALSE} if(!requireNamespace("BiocManager", quietly = TRUE)) install.packages("BiocManager") BiocManager::install("MRManalyzeR") ``` Then load it: ```{r library} library(MRManalyzeR) ``` # Example ## Run example The bundled example of oxylipins in mouse lung writes to a directory you choose: ```{r run-example} res <- mrmRunExample(render = FALSE, open = FALSE) list.files(res$output_directory) ``` This is what a successful run produces. `render = FALSE` skips the two HTML reports, which is what makes it quick here; plain `mrmRunExample()` writes those too. ## Data format One workbook with three sheets: | Sheet | One row per | Holds | |---|---|---| | `lcms_data` | see below | the vendor export, as reviewed | | `sample_metadata` | injection | which injections to include, group, batch, amounts | | `feature_metadata` | compound | which compounds to report, class and pathway annotation | The data sheet's layout depends on the data source, and the configuration names which reader to use: | Source | `lcms_data` holds | Configured under | |---|---|---| | Waters TargetLynx | the export as saved: stacked compound blocks, one per compound, each with its own header row | `tl_data:` | | Skyline | one row per molecule, one column per injection | `skyline_data:` | | Anything else | a plain matrix of samples by analytes, or its transpose | `matrix_data:`, with `orientation:` | The two metadata sheets are user curated, MRManalyzeR never writes them. `sample_metadata` needs an injection name matching the export, an inclusion flag, and whatever factors you want to analyse by: ```{r meta-example, echo = FALSE} knitr::kable(data.frame( Name = c("20260505_07", "20260505_08", "20260505_09"), Sample_ID = c("CTRL_1", "CTRL_2", "HDM_1"), Include = c("YES", "YES", "YES"), Sample_type = c("Sample", "Sample", "Sample"), Treatment = c("PBS", "PBS", "HDM"))) ``` Columns beyond those are needed only by the steps that use them, and only when you switch those steps on: injection and vial volumes for `adjust_conc:`, a protein or tissue-weight column for `normalize:`, a batch column for `batch_correction:`, an injection-order column for the drift plots, and a sample-type column wherever QCs or blanks are involved. A run with every processing step off needs only the columns above. Class and pathway annotations in `feature_metadata` group and colour compounds across plots. ## A configuration to edit `newConfig()` writes a skeleton YAML to fill for a project. The short form carries the keys a run cannot do without; `minimal = FALSE` writes the fully commented reference instead, which documents every key the package reads. ```{r new-config} cfg <- file.path(tempdir(), "project_config.yml") newConfig(cfg, source = "targetlynx") ``` ```{r show-config, echo = FALSE} writeLines(readLines(cfg)[8:26]) ``` Open that file and work down this list: 1. **`paths:`** -- `project_dir`, `data_dir`, `result_dir`, and `fn`, the workbook's name without `.xlsx`. 2. **The data-source block** -- `data_tab_names` must name your data sheet, and `datatype:` the value to report (`tl_data:`), or `signal_filter:` the floor to apply (`skyline_data:`). 3. **`sample_meta_tab` and its columns** -- `name_col`, `include_col`, `include_value`. 4. **`feature_meta_tab` and its columns** -- `compound_col`, `processing_name_col`, `report_col`, `report_value`. 5. **The processing steps you want**, each off until you set it. 6. **The report blocks**, where `sample_type_head`, `qc_label` and `sample_labels` have to match your sample-type column. Nothing is guessed from the data, so a key that does not match stops the run with a message naming it -- useful confirmation that you have the list above right, rather than a substitute for working through it. ## The run ```{r run-yaml, eval = FALSE} res <- runMRManalyzeR(cfg) ``` ## Outputs Outputs of MRManalyzeR are stored at `paths.result_dir`, named after the workbook, the datatype and your suffix: | File | Holds | |---|---| | `.xlsx` | processed matrix, both metadata sheets, and the compounds removed with the reason | | `_stats.xlsx` | tests, correlations, linear models, ratios, group summaries, and a `key` sheet defining every column | | `.RDS` | the dataset, for re-rendering reports without reprocessing | | `_measured.RDS` | the same before imputation -- written only when `replace_MVs:` is set | | `.txt` | the parameters as they resolved for this run | | up to two `.html` reports | data quality, and statistics, each written only if its block has `execute: True` | `_stats.xlsx` likewise carries only the analyses that were enabled: a run with `comparisons:` off and `correlations:` on has no `stats` sheet. The term **measured** means *processed but not imputed*: the values as they come out of the filters, batch correction, normalisation and concentration adjustment, with the gaps still gaps. It does not mean the untouched instrument output. In `.xlsx`, the two sheets to read first lead with the answer. After the compound name, `feature_metadata` gives `Lowest_measured`, and `removed_features` gives `Failed_step`, naming what took the compound out (`User exclude`, `Detection filter`, `Signal filter (LOD)`, `Never measured`, and so on). `Lowest_measured` is the smallest value for that compound across the **biological samples** -- QCs and blanks excluded -- taken after all processing but before imputation, in the units the run reports (`Area`, `ng/mL`, and after normalisation or concentration adjustment where those are on). It is rounded to the nearest whole number. The return value holds the same objects: ```{r returned, eval = FALSE} res$datasetExperiment # processed, imputed if replace_MVs was set res$measured # processed, never imputed res$stats_tables # what went into the stats workbook ``` # Reading the configuration Four blocks, in the order they run. ```yaml project: paths: # workbook in, results out PeakMatrixProcessing: # filters, batch correction, normalisation, imputation data_quality_report: # %RSD, drift, normality, QC PCA stats_report: # comparisons, PCA, volcano, heatmaps, ratios ``` Two points decide how a run reads, and both are recorded in the output: **Processing order is fixed.** Therefore, blank filter, then detection filters, then batch correction, then normalisation and concentration adjustment, and imputation last: batch correction works on the instrument signal and imputation on the corrected signal. **Imputed values stay out of the statistics as default.** With `replace_MVs:` set, the exported matrix is filled and the measured values are written beside it. The tests, boxplots and CVs use the measured ones; only the PCA uses the filled matrix, because a PCA cannot take a gap. `use_imputed: True` in a report block changes that for the whole report. A compound reaching the reports carries its own record: how many values were filled in, whether it could be batch-corrected, the fraction of samples it was detected in. The vignette `vignette("processing-details", package = "MRManalyzeR")` goes through each step, what it decides and how to read what it recorded. # The two reports **Data quality** answers whether the data is usable: %RSD for QCs and for samples and their ratio, signal against injection order, per-compound normality, a QC PCA coloured by batch, order and sample type, and every compound that was dropped with the reason. **Statistics** answers what the study found: group comparisons with the test named per comparison, boxplots and summaries, PCA with loadings coloured by pathway, volcano, heatmaps blocked by pathway, feature correlations and metabolite ratios. Both are single self-contained HTML files, and each states which matrix it used and how missing values were handled. # Several data acquisition panels in one study {#combine} When a study runs more than one panel — oxylipins, phospholipids, sphingolipids — each is processed on its own, then merged by sample ID: ```{r combine, eval = FALSE} cfg <- newConfig("combine.yml", template = "combine") res <- runMRManalyzeRCombine("combine.yml") ``` The merge reads each panel's measured values where they were stored, prefixes the compounds by panel so names cannot collide, and runs one statistics report across the lot. Three things decide what the merged dataset contains: * **Samples are intersected.** Only `sample_id_col` values present in *every* panel survive; a sample one panel missed is dropped from the merge rather than carried with gaps. No samples in common causes an error. * **Duplicate sample IDs are an error by default**, naming the offending IDs. `duplicate_samples: first | mean | median` resolves them instead, and a re-injection is better marked `Include: "NO"` before processing. * **Metadata columns are intersected too.** `feature_meta_cols:` and `sample_meta_cols:` default to the columns every panel shares; name them explicitly to keep a particular set. Panels annotated differently therefore merge down to their common columns. # Working with other packages {#interoperability} A `DatasetExperiment` is `r BiocStyle::Biocpkg("struct")`'s container, which enables the MRManalyzeR output to be used in other packages, for instance for statistics. `res$datasetExperiment` is a `DatasetExperiment`, and a `DatasetExperiment` is also a `SummarizedExperiment`, so packages built on either reach the same data. Taking the processed dataset into `r BiocStyle::Biocpkg("structToolbox")` for a PLS-DA. That model is fitted by `pls`, which `structToolbox` suggests rather than requires, so the chunk below runs only where both are installed: ```{r interop, eval = requireNamespace("structToolbox", quietly = TRUE) && requireNamespace("pls", quietly = TRUE)} library(structToolbox) de <- loadDataset(system.file("extdata", "example_synthetic.RDS", package = "MRManalyzeR")) samples <- subsetDataset(de, conditions = list(Sample_type = "Sample")) # structToolbox wants the response as a factor sm <- as.data.frame(samples$sample_meta) sm$Treatment <- factor(sm$Treatment) samples$sample_meta <- sm M <- model_apply(PLSDA(number_components = 2, factor_name = "Treatment"), samples) round(as.data.frame(M$scores$data)[1:3, ], 2) ``` The scores come back as a `DatasetExperiment` too, so the exchange runs both ways: anything holding one -- a `structToolbox` model output, a `r BiocStyle::Biocpkg("pmp")` matrix wrapped in one -- can be handed straight back to any step here, or to `runStats()`. A plain matrix with its two metadata frames goes through `assembleDataset()` first. # Session info ```{r session} sessionInfo() ```