MRManalyzeR 0.99.8
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 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.
if(!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install("MRManalyzeR")
Then load it:
library(MRManalyzeR)
The bundled example of oxylipins in mouse lung writes to a directory you choose:
res <- mrmRunExample(render = FALSE, open = FALSE)
list.files(res$output_directory)
#> [1] "example_config.yml" "example_data_ng_mL_.RDS"
#> [3] "example_data_ng_mL_.txt" "example_data_ng_mL_.xlsx"
#> [5] "example_data_ng_mL__measured.RDS"
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.
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:
| Name | Sample_ID | Include | Sample_type | Treatment |
|---|---|---|---|---|
| 20260505_07 | CTRL_1 | YES | Sample | PBS |
| 20260505_08 | CTRL_2 | YES | Sample | PBS |
| 20260505_09 | HDM_1 | YES | Sample | 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.
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.
cfg <- file.path(tempdir(), "project_config.yml")
newConfig(cfg, source = "targetlynx")
#> project:
#> name: my_study
#> researcher: ""
#>
#> # Where the workbook is, and where results go. fn is the workbook name
#> # inside data_dir, without the .xlsx.
#> paths:
#> project_dir: &project_dir "C:/path/to/study"
#> data_dir: &data_dir !concat_path [*project_dir, "data"]
#> result_dir: &result_dir !concat_path [*project_dir, "results"]
#> fn: &fn my_workbook
#> suffix: _run1 # appended to every output name
#>
#> PeakMatrixProcessing:
#> execute: True # False = reuse the stored RDS, re-render only
#>
#> # 1. Data source
#> tl_data:
#> enabled: True
Open that file and work down this list:
paths: – project_dir, data_dir, result_dir, and fn, the
workbook’s name without .xlsx.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:).sample_meta_tab and its columns – name_col, include_col,
include_value.feature_meta_tab and its columns – compound_col,
processing_name_col, report_col, report_value.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.
res <- runMRManalyzeR(cfg)
Outputs of MRManalyzeR are stored at paths.result_dir, named after the
workbook, the datatype and your suffix:
| File | Holds |
|---|---|
<name>.xlsx |
processed matrix, both metadata sheets, and the compounds removed with the reason |
<name>_stats.xlsx |
tests, correlations, linear models, ratios, group summaries, and a key sheet defining every column |
<name>.RDS |
the dataset, for re-rendering reports without reprocessing |
<name>_measured.RDS |
the same before imputation – written only when replace_MVs: is set |
<name>.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 |
<name>_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 <name>.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:
res$datasetExperiment # processed, imputed if replace_MVs was set
res$measured # processed, never imputed
res$stats_tables # what went into the stats workbook
Four blocks, in the order they run.
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.
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.
When a study runs more than one panel — oxylipins, phospholipids, sphingolipids — each is processed on its own, then merged by sample ID:
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:
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_samples: first | mean | median resolves them instead, and a
re-injection is better marked Include: "NO" before processing.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.A DatasetExperiment is 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 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:
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)
#> LV1 LV2
#> SYN_002 -12.65 -0.04
#> SYN_003 -20.05 9.41
#> SYN_004 -9.81 15.18
The scores come back as a DatasetExperiment too, so the exchange runs both
ways: anything holding one – a structToolbox model output, a
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.
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.5 LTS
#>
#> Matrix products: default
#> BLAS: /home/biocbuild/bbs-3.24-bioc/R/lib/libRblas.so
#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0 LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_GB LC_COLLATE=C
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: America/New_York
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] structToolbox_1.25.0 MRManalyzeR_0.99.8 struct_1.25.0
#> [4] BiocStyle_2.41.0
#>
#> loaded via a namespace (and not attached):
#> [1] SummarizedExperiment_1.43.0 gtable_0.3.6
#> [3] xfun_0.62 bslib_0.12.0
#> [5] ggplot2_4.0.3 httr2_1.3.0
#> [7] htmlwidgets_1.6.4 Biobase_2.73.2
#> [9] lattice_0.23-1 vctrs_0.7.3
#> [11] tools_4.6.1 generics_0.1.4
#> [13] stats4_4.6.1 tibble_3.3.1
#> [15] pkgconfig_2.0.3 Matrix_1.7-6
#> [17] RColorBrewer_1.1-3 S7_0.2.2
#> [19] S4Vectors_0.51.10 lifecycle_1.0.5
#> [21] compiler_4.6.1 farver_2.1.2
#> [23] stringr_1.6.0 janitor_2.2.1
#> [25] Seqinfo_1.3.2 snakecase_0.11.1
#> [27] htmltools_0.5.9 sass_0.4.10
#> [29] yaml_2.3.12 pillar_1.11.1
#> [31] jquerylib_0.1.4 tidyr_1.3.2
#> [33] DT_0.34.0 DelayedArray_0.39.8
#> [35] cachem_1.1.0 abind_1.4-8
#> [37] pls_2.9-0 tidyselect_1.2.1
#> [39] zip_3.0.2 digest_0.6.39
#> [41] stringi_1.8.9 dplyr_1.2.1
#> [43] purrr_1.2.2 bookdown_0.48
#> [45] ggthemes_7.0.0 fastmap_1.2.0
#> [47] grid_4.6.1 cli_3.6.6
#> [49] SparseArray_1.13.4 magrittr_2.0.5
#> [51] S4Arrays_1.13.2 dichromat_2.0-1
#> [53] withr_3.0.3 scales_1.4.0
#> [55] sp_2.2-4 lubridate_1.9.5
#> [57] timechange_0.4.0 rmarkdown_2.32
#> [59] XVector_0.53.0 matrixStats_1.5.0
#> [61] otel_0.2.0 gridExtra_2.3.1
#> [63] openxlsx_4.2.9 evaluate_1.0.5
#> [65] knitr_1.52 GenomicRanges_1.65.4
#> [67] IRanges_2.47.5 rlang_1.3.0
#> [69] Rcpp_1.1.2 glue_1.8.1
#> [71] BiocManager_1.30.27 BiocGenerics_0.59.12
#> [73] jsonlite_2.0.0 R6_2.6.1
#> [75] MatrixGenerics_1.25.0