Skip to content

Commit 4221ab4

Browse files
authored
feat(metabolomics): Add new MZMinetoMSstatsFormat converter (#132)
1 parent d7d0fb5 commit 4221ab4

16 files changed

Lines changed: 530 additions & 1 deletion

.Rbuildignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@
88
^pkgdown$
99
^\.positai$
1010
^\.claude$
11+
^doc$
12+
^Meta$

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ inst/doc
1212
.lintr
1313
.vscode
1414
.positai
15+
/doc/
16+
/Meta/

DESCRIPTION

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Suggests:
3434
rmarkdown
3535
LinkingTo: Rcpp
3636
Collate:
37+
'clean_MZMine.R'
3738
'clean_ProteinProspector.R'
3839
'clean_Metamorpheus.R'
3940
'clean_DIANN.R'
@@ -52,6 +53,7 @@ Collate:
5253
'converters_DIANNtoMSstatsFormat.R'
5354
'converters_DIAUmpiretoMSstatsFormat.R'
5455
'converters_FragPipetoMSstatsFormat.R'
56+
'converters_MZMinetoMSstatsFormat.R'
5557
'converters_MaxQtoMSstatsFormat.R'
5658
'converters_MaxQtoMSstatsTMTFormat.R'
5759
'converters_MetamorpheusToMSstatsFormat.R'

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export(MSstatsLogsSettings)
1414
export(MSstatsMakeAnnotation)
1515
export(MSstatsPreprocess)
1616
export(MSstatsSaveSessionInfo)
17+
export(MZMinetoMSstatsFormat)
1718
export(MaxQtoMSstatsFormat)
1819
export(MaxQtoMSstatsTMTFormat)
1920
export(MetamorpheusToMSstatsFormat)

R/MSstatsConvert_core_functions.R

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ setClass("MSstatsMetamorpheusFiles", contains = "MSstatsInputFiles")
7171
#' @rdname MSstatsInputFiles
7272
#' @keywords internal
7373
setClass("MSstatsProteinProspectorFiles", contains = "MSstatsInputFiles")
74+
#' MSstatsMZMineFiles: class for MZMine files.
75+
#' @rdname MSstatsInputFiles
76+
#' @keywords internal
77+
setClass("MSstatsMZMineFiles", contains = "MSstatsInputFiles")
7478

7579

7680
#' Get one of files contained in an instance of `MSstatsInputFiles` class.
@@ -291,8 +295,15 @@ setMethod("MSstatsClean", signature = "MSstatsMetamorpheusFiles",
291295
#' @rdname MSstatsClean
292296
#' @inheritParams .cleanRawProteinProspector
293297
#' @return data.table
294-
setMethod("MSstatsClean", signature = "MSstatsProteinProspectorFiles",
298+
setMethod("MSstatsClean", signature = "MSstatsProteinProspectorFiles",
295299
.cleanRawProteinProspector)
300+
#' Clean MZMine files
301+
#' @include clean_MZMine.R
302+
#' @rdname MSstatsClean
303+
#' @inheritParams .cleanRawMZMine
304+
#' @return data.table
305+
setMethod("MSstatsClean", signature = "MSstatsMZMineFiles",
306+
.cleanRawMZMine)
296307

297308

298309
#' Preprocess outputs from MS signal processing tools for analysis with MSstats

R/clean_MZMine.R

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#' Clean raw MZMine files
2+
#'
3+
#' Operates on the column names produced by MZMine after MSstatsConvert's
4+
#' internal column-name standardization (spaces collapsed and dots removed):
5+
#' "row ID" becomes `rowID`, and each "<sample> Peak area" becomes
6+
#' `<standardized-sample>Peakarea`.
7+
#'
8+
#' @param msstats_object an object of class `MSstatsMZMineFiles`.
9+
#' @param mzmine_annotations `data.frame` of MZMine spectral-library
10+
#' annotations with columns `id`, `compound_name`, `score`. Required;
11+
#' passing `NULL` raises an error. The highest-scoring `compound_name`
12+
#' per feature is used as `ProteinName`, and features in the quant
13+
#' table with no matching annotation row are dropped from the output.
14+
#' These are MSI Level 2 annotations (putative identification via
15+
#' MS/MS spectral matching). See the public `MZMinetoMSstatsFormat`
16+
#' docstring for the full scope discussion.
17+
#' @return data.table
18+
#' @keywords internal
19+
.cleanRawMZMine <- function(msstats_object, mzmine_annotations) {
20+
ProteinName = PeptideSequence = Intensity = Run = NULL
21+
PrecursorCharge = FragmentIon = ProductCharge = NULL
22+
id = score = compound_name = i.compound_name = NULL
23+
24+
mz_input = getInputFile(msstats_object, "input")
25+
mz_input = data.table::as.data.table(mz_input)
26+
27+
peak_area_suffix <- "Peakarea"
28+
peak_area_cols <- grep(paste0(peak_area_suffix, "$"),
29+
colnames(mz_input), value = TRUE)
30+
if (length(peak_area_cols) == 0) {
31+
stop("No 'Peak area' columns found in the input. Expected per-sample ",
32+
"columns named '<run> Peak area' (e.g. 'sampleA.mzML Peak area').")
33+
}
34+
id_col <- "rowID"
35+
required_meta <- id_col
36+
missing_meta <- setdiff(required_meta, colnames(mz_input))
37+
if (length(missing_meta) > 0) {
38+
stop("Missing required MZMine metadata column (expected 'row ID'). ",
39+
"After standardization, looked for: ",
40+
paste(missing_meta, collapse = ", "), ".")
41+
}
42+
43+
if (is.null(mzmine_annotations)) {
44+
stop("mzmine_annotations is required. Pass a data.frame with ",
45+
"columns 'id', 'compound_name', 'score'.")
46+
}
47+
feature_to_compound <- data.table::as.data.table(mzmine_annotations)
48+
required_ann <- c("id", "compound_name", "score")
49+
missing_ann <- setdiff(required_ann, colnames(feature_to_compound))
50+
if (length(missing_ann) > 0) {
51+
stop("mzmine_annotations is missing required column(s): ",
52+
paste(missing_ann, collapse = ", "), ".")
53+
}
54+
feature_to_compound[, score := suppressWarnings(as.numeric(score))]
55+
if (anyNA(feature_to_compound$score)) {
56+
stop("The 'score' column in the mzmine annotations file must contain numeric values.")
57+
}
58+
data.table::setorder(feature_to_compound, id, -score)
59+
feature_to_compound <- unique(feature_to_compound, by = "id")
60+
# Inner-join filter: drop quant rows with no matching annotation.
61+
mz_input[
62+
feature_to_compound,
63+
ProteinName := i.compound_name,
64+
on = setNames("id", id_col)
65+
]
66+
mz_input <- mz_input[!is.na(ProteinName)]
67+
68+
retained_ids <- feature_to_compound$id
69+
retained_msg <- paste0("** MZMine: retained ", length(retained_ids),
70+
" feature(s) after annotation join: ",
71+
paste(retained_ids, collapse = ", "))
72+
getOption("MSstatsLog")("INFO", retained_msg)
73+
getOption("MSstatsMsg")("INFO", retained_msg)
74+
75+
mz_input[, PeptideSequence := as.character(get(id_col))]
76+
77+
long <- data.table::melt(
78+
mz_input,
79+
id.vars = c("ProteinName", "PeptideSequence"),
80+
measure.vars = peak_area_cols,
81+
variable.name = "Run",
82+
value.name = "Intensity",
83+
variable.factor = FALSE)
84+
85+
long[, PrecursorCharge := NA_integer_]
86+
long[, FragmentIon := NA_character_]
87+
long[, ProductCharge := NA_integer_]
88+
long[, Run := sub(paste0(peak_area_suffix, "$"), "", Run)]
89+
90+
final_cols <- c("ProteinName", "PeptideSequence", "PrecursorCharge",
91+
"FragmentIon", "ProductCharge",
92+
"Run", "Intensity")
93+
long <- long[, final_cols, with = FALSE]
94+
95+
.logSuccess("MZMine", "clean")
96+
long
97+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#' Import MZMine files
2+
#'
3+
#' @inheritParams .sharedParametersAmongConverters
4+
#' @param input MZMine feature-quantification table (wide format; one row per
5+
#' feature). Must include the metadata columns `row ID`, `row m/z`,
6+
#' `row retention time`, and per-sample peak-area columns named
7+
#' `"<run> Peak area"` (e.g. `"sampleA.mzML Peak area"`).
8+
#' @param annotation `data.frame` with columns `Run`, `Condition`,
9+
#' `BioReplicate`. `Run` values must match MSstatsConvert-standardized sample
10+
#' names (after column-name normalization removes spaces and dots) with the
11+
#' trailing `"Peakarea"` suffix removed. For example, a quant-file column
12+
#' `"sampleA.mzML Peak area"` becomes `"sampleAmzML"` after standardization,
13+
#' so the corresponding `Run` value must be `sampleAmzML`.
14+
#' @param mzmine_annotations `data.frame` of MZMine spectral-library
15+
#' annotations with columns `id`, `compound_name`, `score`. Required:
16+
#' the highest-scoring `compound_name` per feature is used as
17+
#' `ProteinName`, and features in the quant table with no matching
18+
#' annotation row are dropped from the output.
19+
#'
20+
#' These are MSI Level 2 annotations (putative identification via
21+
#' MS/MS spectral matching against a reference library). Higher-
22+
#' confidence Level 1 identifications require pure reference standards
23+
#' and are out of scope here. Lower-confidence annotations such as
24+
#' Level 3 (SIRIUS, MS2Query) or Level 4 (molecular formula via
25+
#' CANOPUS) are not currently supported -- features without a Level 2
26+
#' annotation row are filtered out.
27+
#'
28+
#' @return data.table in the MSstats required format.
29+
#'
30+
#' @export
31+
#'
32+
#' @examples
33+
#' input_path = system.file("tinytest/raw_data/MZMine/mzmine_input.csv",
34+
#' package = "MSstatsConvert")
35+
#' annot_path = system.file("tinytest/raw_data/MZMine/annotation.csv",
36+
#' package = "MSstatsConvert")
37+
#' lib_path = system.file("tinytest/raw_data/MZMine/mzmine_annotations.csv",
38+
#' package = "MSstatsConvert")
39+
#' input = data.table::fread(input_path)
40+
#' annot = data.table::fread(annot_path)
41+
#' lib = data.table::fread(lib_path)
42+
#' output = MZMinetoMSstatsFormat(input, annotation = annot,
43+
#' mzmine_annotations = lib,
44+
#' use_log_file = FALSE)
45+
#' head(output)
46+
MZMinetoMSstatsFormat = function(
47+
input,
48+
annotation = NULL,
49+
mzmine_annotations,
50+
removeProtein_with1Feature = FALSE,
51+
summaryforMultipleRows = max,
52+
use_log_file = TRUE,
53+
append = FALSE,
54+
verbose = TRUE,
55+
log_file_path = NULL,
56+
...) {
57+
MSstatsConvert::MSstatsLogsSettings(use_log_file, append, verbose,
58+
log_file_path)
59+
60+
if (missing(mzmine_annotations) || is.null(mzmine_annotations)) {
61+
stop("mzmine_annotations is required. Pass a data.frame with ",
62+
"columns 'id', 'compound_name', 'score'.")
63+
}
64+
65+
input = MSstatsConvert::MSstatsImport(list(input = input),
66+
"MSstats", "MZMine", ...)
67+
input = MSstatsConvert::MSstatsClean(
68+
input, mzmine_annotations = mzmine_annotations)
69+
annotation = MSstatsConvert::MSstatsMakeAnnotation(input, annotation)
70+
71+
feature_columns = c("PeptideSequence", "PrecursorCharge", "FragmentIon", "ProductCharge")
72+
73+
input = MSstatsConvert::MSstatsPreprocess(
74+
input,
75+
annotation,
76+
feature_columns,
77+
remove_shared_peptides = FALSE,
78+
remove_single_feature_proteins = removeProtein_with1Feature,
79+
exact_filtering = NULL,
80+
pattern_filtering = NULL,
81+
aggregate_isotopic = FALSE,
82+
feature_cleaning = list(
83+
remove_features_with_few_measurements = FALSE,
84+
summarize_multiple_psms = summaryforMultipleRows),
85+
columns_to_fill = list(Fraction = 1, IsotopeLabelType = "Light"))
86+
input[, Intensity := ifelse(Intensity == 0, NA, Intensity)]
87+
88+
input = MSstatsConvert::MSstatsBalancedDesign(input, feature_columns,
89+
fill_incomplete = TRUE,
90+
handle_fractions = FALSE,
91+
remove_few = FALSE)
92+
93+
msg_final = paste("** Finished preprocessing. The dataset is ready",
94+
"to be processed by the dataProcess function.")
95+
getOption("MSstatsLog")("INFO", msg_final)
96+
getOption("MSstatsMsg")("INFO", msg_final)
97+
getOption("MSstatsLog")("INFO", "\n")
98+
input
99+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Run,Condition,BioReplicate
2+
sampleA.mzML,Control,1
3+
sampleB.mzML,Control,2
4+
sampleC.mzML,Treatment,3
5+
sampleD.mzML,Treatment,4
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
id,compound_name,score,adduct
2+
1,Caffeine,0.95,[M+H]+
3+
2,GlucoseLow,0.72,[M+H]+
4+
2,GlucoseHigh,0.91,[M-H]-
5+
3,Lactate,0.88,[M+H]+
6+
6,Caffeine,0.80,[M+Na]+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
row ID,row m/z,row retention time,sampleA.mzML Peak area,sampleB.mzML Peak area,sampleC.mzML Peak area,sampleD.mzML Peak area
2+
1,123.0560,1.23,1000,1100,1200,1300
3+
2,245.1290,3.45,5000,4800,5200,4900
4+
3,367.2010,5.67,800,0,750,820
5+
4,489.3340,7.89,2000,2100,1900,2050
6+
5,555.4470,9.10,100,0,0,0
7+
6,123.0560,1.45,600,650,700,680

0 commit comments

Comments
 (0)