diff --git a/DESCRIPTION b/DESCRIPTION index b763305..208e2d4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: SpectriPy Title: Enhancing Cross-Language Mass Spectrometry Data Analysis with R and Python -Version: 1.1.4 +Version: 1.1.5 Description: The SpectriPy package allows integration of Python-based MS analysis code with the Spectra package. Spectra objects can be converted into Python MS data structures. In addition, SpectriPy integrates diff --git a/NEWS.md b/NEWS.md index e117646..bf28f92 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # SpectriPy 1.1 +## Changes in 1.1.5 + +- Fix `$<-`, `spectraData()<-` and related replacement methods for + `Spectra`/`MsBackendPy` of length 1. + ## Changes in 1.1.4 - Implement a `setBackend()` method for `MsBackendPy` with a parameter diff --git a/R/MsBackendPython.R b/R/MsBackendPython.R index a7331b1..cd8ea7f 100644 --- a/R/MsBackendPython.R +++ b/R/MsBackendPython.R @@ -29,6 +29,20 @@ #' between R and Python. See the description for `backendInitialize()` and the #' package vignette for details and examples. #' +#' @section Python metadata names *vs* spectra variables: +#' +#' The *matchms* library in Python supports arbitrary metadata assigned to +#' a spectrum, similar to *Spectra*'s spectra variables concept. However, +#' *matchms* and *Spectra* use different names for the same metadata/variables. +#' To support this, *MsBackendPy* implements `spectraVariableMapping` which +#' allows to link spectra variable names to *matchms* metadata names. This +#' mapping can be provided with the `spectraVariableMapping` to the +#' `backendInitialize()` function. Also, be aware that *matchms* automatically +#' **renames** certain metadata fields: a metadata `"NAME"` will be +#' automatically renamed to `"compound_name"`. Also, *matchms* generally +#' supports only lower-case metadata names. +#' See also [setSpectraVariableMapping()] for more information. +#' #' @details #' #' The `MsBackendPy` keeps only a reference to the MS data in Python (i.e. the @@ -633,7 +647,7 @@ setMethod("$", "MsBackendPy", function(x, name) { #' @importMethodsFrom Spectra peaksVariables setReplaceMethod("$", "MsBackendPy", function(x, name, value) { spd <- spectraData( - x, union(names(spectraVariableMapping(x)), peaksVariables(x))) + x, union(spectraVariables(x), peaksVariables(x))) spd[[name]] <- value spectraData(x) <- spd x @@ -678,7 +692,7 @@ setMethod("intensity", "MsBackendPy", function(object) { #' @rdname MsBackendPy setReplaceMethod("intensity", "MsBackendPy", function(object, value) { .check_mz_intensity(value, length(object), lengths(object)) - spd <- spectraData(object, union(names(spectraVariableMapping(object)), + spd <- spectraData(object, union(spectraVariables(object), peaksVariables(object))) spd[["intensity"]] <- value spectraData(object) <- spd diff --git a/R/conversion.R b/R/conversion.R index 3b0cb05..e03e4c5 100644 --- a/R/conversion.R +++ b/R/conversion.R @@ -245,7 +245,8 @@ NULL collisionEnergy = "collision_energy", isolationWindowTargetMz = "isolation_window_target_mz", ## polarity = "ionmode", # Disabling since matchms does not support int. - msLevel = "ms_level" + msLevel = "ms_level", + dataStorage = "data_storage" ) .SPECTRA_2_SPECTRUM_UTILS <- c( @@ -408,7 +409,8 @@ rspec_to_pyspec <- function(x, mapping = spectraVariableMapping(), .rspec_to_matchms_pyspec <- function(x, mapping = spectraVariableMapping()) { if (is(x, "Spectra")) pks <- peaksData(x, c("mz", "intensity"), return.type = "list") - else pks <- mapply(mz = x$mz, intensity = x$intensity, FUN = cbind) + else pks <- mapply(mz = x$mz, intensity = x$intensity, FUN = cbind, + SIMPLIFY = FALSE, USE.NAMES = FALSE) sv <- mapping[!mapping %in% c("mz", "intensity")] if (length(sv)) { if (is(x, "Spectra")) @@ -447,7 +449,8 @@ rspec_to_pyspec <- function(x, mapping = spectraVariableMapping(), function(x, mapping = spectraVariableMapping()) { if (is(x, "Spectra")) pks <- peaksData(x, c("mz", "intensity")) - else pks <- mapply(mz = x$mz, intensity = x$intensity, FUN = cbind) + else pks <- mapply(mz = x$mz, intensity = x$intensity, FUN = cbind, + SIMPLIFY = FALSE, USE.NAMES = FALSE) l <- length(pks) sv <- mapping[!mapping %in% c("mz", "intensity")] svm <- sv[sv %in% .SPECTRA_2_SPECTRUM_UTILS] diff --git a/man/MsBackendPy.Rd b/man/MsBackendPy.Rd index bd958b0..a4b9629 100644 --- a/man/MsBackendPy.Rd +++ b/man/MsBackendPy.Rd @@ -174,6 +174,22 @@ variables will first load the current data from Python to R, update or replace the values and then store the full MS data again to the referenced Python attribute. } +\section{Python metadata names \emph{vs} spectra variables}{ + + +The \emph{matchms} library in Python supports arbitrary metadata assigned to +a spectrum, similar to \emph{Spectra}'s spectra variables concept. However, +\emph{matchms} and \emph{Spectra} use different names for the same metadata/variables. +To support this, \emph{MsBackendPy} implements \code{spectraVariableMapping} which +allows to link spectra variable names to \emph{matchms} metadata names. This +mapping can be provided with the \code{spectraVariableMapping} to the +\code{backendInitialize()} function. Also, be aware that \emph{matchms} automatically +\strong{renames} certain metadata fields: a metadata \code{"NAME"} will be +automatically renamed to \code{"compound_name"}. Also, \emph{matchms} generally +supports only lower-case metadata names. +See also \code{\link[=setSpectraVariableMapping]{setSpectraVariableMapping()}} for more information. +} + \section{\code{MsBackendPy} methods}{ diff --git a/tests/testthat.R b/tests/testthat.R index 5a6142e..0d2f561 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -13,9 +13,9 @@ test_check("SpectriPy") ## TODO: add tests from the Spectra unit test suite! -## ## Run the MsBackend spectra variable test suite -## test_suite <- system.file("test_backends", "test_MsBackend", -## package = "Spectra") +## Run the MsBackend spectra variable test suite +test_suite <- system.file("test_backends", "test_MsBackend", + package = "Spectra") -## ## Run single test file. +## Run single test file. ## res <- test_dir(test_suite, stop_on_failure = TRUE) diff --git a/tests/testthat/test_MsBackendPython.R b/tests/testthat/test_MsBackendPython.R index 0d82dd8..0b57d9d 100644 --- a/tests/testthat/test_MsBackendPython.R +++ b/tests/testthat/test_MsBackendPython.R @@ -124,7 +124,8 @@ test_that("backendInitialize,MsBackendPy works with providing data", { expect_true(all(is.na(res$precursorMz))) ## Only non-NA variables have a mapping expect_equal(res@spectraVariableMapping, - c(precursorCharge = "charge", msLevel = "ms_level")) + c(precursorCharge = "charge", msLevel = "ms_level", + dataStorage = "data_storage")) ## Repeat with spectrum_utils py_del_attr(py, "ttt") @@ -788,6 +789,46 @@ test_that("setBackend,Spectra,MsBackendPy works", { expect_equal(smod_1$compound_name, smod$NAME) }) +test_that("$<- does not change any other spectra variables", { + map <- c(spectraVariableMapping(), INCHI = "inchi", NAME = "NAME", + SMILES = "smiles", spectrum_index = "spectrum_index") + a <- setBackend(s, MsBackendPy(), pythonVariableName = "tmp", + spectraVariableMapping = map)@backend + sd <- spectraData(a) + a$other_variable <- "a" + res <- spectraData(a) + expect_true(all(colnames(sd) %in% colnames(res))) + expect_equal(sd[, intersect(colnames(sd), colnames(res))], + res[, intersect(colnames(sd), colnames(res))]) + expect_true(all(res$other_col == "a")) +}) + +test_that("MsBackendPy of length 1 works", { + map <- c(spectraVariableMapping(), INCHI = "inchi", NAME = "NAME", + SMILES = "smiles") + a <- setBackend(s, MsBackendPy(), pythonVariableName = "tmp", + spectraVariableMapping = map)@backend + ## Subset + a_3 <- a[c(3L, 1L, 5L)] + sd <- spectraData(a_3) + expect_equal(sd$SMILES, a$SMILES[c(3L, 1L, 5L)]) + a_3$other_var <- "a" + res <- spectraData(a_3) + expect_true(all(colnames(sd) %in% colnames(res))) + expect_equal(sd[, intersect(colnames(sd), colnames(res))], + res[, intersect(colnames(sd), colnames(res))]) + expect_true(all(res$other_var == "a")) + + a_1 <- a[3L] + sd <- spectraData(a_1) + a_1$other_var_2 <- "a" + res <- spectraData(a_1) + expect_true(all(colnames(sd) %in% colnames(res))) + expect_equal(sd[, intersect(colnames(sd), colnames(res))], + res[, intersect(colnames(sd), colnames(res))]) + expect_equal(res$other_var_2, "a") +}) + ## Comments, thoughts TODO ## DONE spectraData()<-: replaces the full data and allows adding/removing ## spectra variables. number of spectra has to match.