Skip to content

Commit 900c2bf

Browse files
committed
feat: add additional replacement methods
- Add `mz<-` and `intensity<-` methods.
1 parent 4b6df4c commit 900c2bf

6 files changed

Lines changed: 176 additions & 2 deletions

File tree

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ importFrom(reticulate,py_run_string)
5050
importFrom(reticulate,py_set_attr)
5151
importFrom(reticulate,py_to_r)
5252
importFrom(reticulate,r_to_py)
53+
importMethodsFrom(ProtGenerics,"intensity<-")
54+
importMethodsFrom(ProtGenerics,"mz<-")
5355
importMethodsFrom(ProtGenerics,"peaksData<-")
5456
importMethodsFrom(ProtGenerics,"spectraData<-")
5557
importMethodsFrom(ProtGenerics,acquisitionNum)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
variables that contain only missing values.
1313
- Add `$<-` method for `MsBackendPy`.
1414
- Add `peaksData()<-` for `MsBackendPy`.
15+
- Add `intensity()<-` and `mz()<-` methods for `MsBackendPy`.
1516

1617
## Changes in 0.99.11
1718

R/MsBackendPython.R

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@
5555
#' If for example data was transformed or metadata added or removed in the
5656
#' Python object, it immediately affects the `Spectra`/backend.
5757
#'
58+
#' Any replacement operation uses internally the `spectraData()<-` method,
59+
#' thus replacing/updating values for individual spectra variables or peaks
60+
#' variables will first load the current data from Python to R, update or
61+
#' replace the values and then store the full MS data again to the
62+
#' referenced Python attribute.
63+
#'
5864
#' @section `MsBackendPy` methods:
5965
#'
6066
#' The `MsBackendPy` supports all methods defined by the [Spectra::MsBackend()]
@@ -91,6 +97,28 @@
9197
#' instance of `MsBackendPy`. See examples below for different settings
9298
#' and conversion of spectra variables.
9399
#'
100+
#' - `intensity()`, `intensity()<-`: get or replace the intensity values.
101+
#' `intensity()` returns a `NumericList` of length equal to the number of
102+
#' spectra with each element being the intensity values of the individual
103+
#' mass peaks per spectrum. `intensity()<-` takes the same list-like
104+
#' structure as input parameter. Both the number of spectra and the number of
105+
#' peaks must match the length of the spectra and the number of existing mass
106+
#' peaks. To change the number of peaks use the `peaksData()<-` method
107+
#' instead that replaces the *m/z* and intensity values at the same time.
108+
#' Calling `intensity()<-` will replace the full MS data (spectra variables
109+
#' as well as peaks variables) of the associated Python variable.
110+
#'
111+
#' - `mz()`, `mz()<-`: get or replace the *m/z* values. `mz()` returns a
112+
#' `NumericList` of length equal to the number of spectra with each element
113+
#' being the *m/z* values of the individual mass peaks per spectrum.
114+
#' `mz()<-` takes the same list-like structure as input parameter. Both the
115+
#' number of spectra and the number of peaks must match the length of the
116+
#' spectra and the number of existing mass peaks. To change the number of
117+
#' peaks use the `peaksData()<-` method instead that replaces the *m/z* and
118+
#' intensity values at the same time.
119+
#' Calling `mz()<-` will replace the full MS data (spectra variables
120+
#' as well as peaks variables) of the associated Python variable.
121+
#'
94122
#' - `peaksData()`: extracts the peaks data matrices from the backend. Python
95123
#' code is applied to the data structure in Python to
96124
#' extract the *m/z* and intensity values as a list of (numpy) arrays. These
@@ -628,6 +656,18 @@ setMethod("intensity", "MsBackendPy", function(object) {
628656
NumericList(peaksData(object, "intensity", drop = TRUE), compress = FALSE)
629657
})
630658

659+
#' @importMethodsFrom ProtGenerics intensity<-
660+
#'
661+
#' @rdname MsBackendPy
662+
setReplaceMethod("intensity", "MsBackendPy", function(object, value) {
663+
.check_mz_intensity(value, length(object), lengths(object))
664+
spd <- spectraData(object, union(names(spectraVariableMapping(object)),
665+
peaksVariables(object)))
666+
spd[["intensity"]] <- value
667+
spectraData(object) <- spd
668+
object
669+
})
670+
631671
#' @importMethodsFrom ProtGenerics isolationWindowLowerMz
632672
setMethod("isolationWindowLowerMz", "MsBackendPy", function(object) {
633673
spectraData(object, "isolationWindowLowerMz", drop = TRUE)
@@ -653,6 +693,18 @@ setMethod("mz", "MsBackendPy", function(object) {
653693
NumericList(peaksData(object, "mz", drop = TRUE), compress = FALSE)
654694
})
655695

696+
#' @importMethodsFrom ProtGenerics mz<-
697+
#'
698+
#' @rdname MsBackendPy
699+
setReplaceMethod("mz", "MsBackendPy", function(object, value) {
700+
.check_mz_intensity(value, length(object), lengths(object))
701+
spd <- spectraData(object, union(names(spectraVariableMapping(object)),
702+
peaksVariables(object)))
703+
spd[["mz"]] <- value
704+
spectraData(object) <- spd
705+
object
706+
})
707+
656708
#' @importMethodsFrom ProtGenerics polarity
657709
setMethod("polarity", "MsBackendPy", function(object) {
658710
spectraData(object, "polarity", drop = TRUE)
@@ -877,3 +929,25 @@ reindex <- function(object) {
877929
keep <- c(setdiff(colnames(x), svs), svs[keep])
878930
x[, colnames(x) %in% keep, drop = FALSE]
879931
}
932+
933+
#' helper to check input/validity of intensity or mz: has to be a list-like
934+
#' structure with numeric vectors.
935+
#'
936+
#' @param x `list` or `NumericList`.
937+
#'
938+
#' @param l `integer(1)` with the number of spectra/expected elements in `x`.
939+
#'
940+
#' @param ls `integer` with the number of peaks, i.e., the lengths of the
941+
#' numeric vectors.
942+
#'
943+
#' @noRd
944+
.check_mz_intensity <- function(x, l = length(x), ls = lengths(x)) {
945+
if (!(is.list(x) | inherits(x, "SimpleList")))
946+
stop("'value' has to be a list-like data structure.")
947+
if (length(x) != l)
948+
stop("length of 'value' has to match the number of spectra")
949+
if (!all(lengths(x) == ls))
950+
stop("lengths(value) has to match the number of peaks per spectrum")
951+
if (!all(vapply1l(x, is.numeric)))
952+
stop("elements of 'value' are expected to be numeric vectors.")
953+
}

man/MsBackendPy.Rd

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test_MsBackendPython.R

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,60 @@ test_that("peaksData<-,MsBackendPy works", {
709709
expect_equal(spd, spd_2)
710710
})
711711

712+
test_that(".check_mz_intensity works", {
713+
mzs <- mz(s)
714+
expect_silent(.check_mz_intensity(mzs, length(mzs), lengths(mzs)))
715+
expect_silent(.check_mz_intensity(as.list(mzs), length(mzs), lengths(mzs)))
716+
expect_error(.check_mz_intensity(3, 1, 1), "list-like")
717+
expect_error(.check_mz_intensity(mzs, 1, 1), "number of spectra")
718+
expect_error(.check_mz_intensity(mzs[1:3], 3, c(3, 3, 3)),
719+
"number of peaks")
720+
a <- list(c(1:4), c(1.1, 2.2, 3), c("a", "b"))
721+
expect_error(.check_mz_intensity(a, 4, c(4, 3, 2)), "number of spectra")
722+
expect_error(.check_mz_intensity(a, 3, c(4, 1, 2)), "number of peaks")
723+
expect_error(.check_mz_intensity(a, 3, c(4, 3, 2)), "numeric vectors")
724+
})
725+
726+
test_that("mz<-,MsBackendPy works", {
727+
a <- setBackend(
728+
s, MsBackendPy(), pythonVariableName = "mz_test",
729+
spectraVariableMapping = c(INCHI = "inchi",
730+
defaultSpectraVariableMapping()))@backend
731+
spd <- spectraData(a)
732+
mzs <- mz(a)
733+
expect_equal(spd$mz, mzs)
734+
mzs <- mzs / 3
735+
mz(a) <- mzs
736+
expect_equal(mz(a), mzs)
737+
expect_equal(a$INCHI, spd$INCHI)
738+
mzs <- as.list(mzs)
739+
mzs[[1L]] <- mzs[[1L]] * 2
740+
mz(a) <- mzs
741+
expect_equal(as.list(mz(a)), mzs)
742+
## errors/issues
743+
expect_error(mz(a) <- mzs[1:3], "match the number")
744+
})
745+
746+
test_that("intensity<-,MsBackendPy works", {
747+
a <- setBackend(
748+
s, MsBackendPy(), pythonVariableName = "intensity_test",
749+
spectraVariableMapping = c(INCHI = "inchi",
750+
defaultSpectraVariableMapping()))@backend
751+
spd <- spectraData(a)
752+
ints <- intensity(a)
753+
expect_equal(spd$intensity, ints)
754+
ints <- ints / 3
755+
intensity(a) <- ints
756+
expect_equal(intensity(a), ints)
757+
expect_equal(a$INCHI, spd$INCHI)
758+
ints <- as.list(ints)
759+
ints[[1L]] <- ints[[1L]] * 2
760+
intensity(a) <- ints
761+
expect_equal(as.list(intensity(a)), ints)
762+
## errors/issues
763+
expect_error(intensity(a) <- ints[1:3], "match the number")
764+
})
765+
712766
## Comments, thoughts TODO
713767
## DONE spectraData()<-: replaces the full data and allows adding/removing
714768
## spectra variables. number of spectra has to match.
@@ -718,8 +772,8 @@ test_that("peaksData<-,MsBackendPy works", {
718772
## in @spectraVariableMapping
719773
## DONE $<- : replace the full data?
720774
## DONE peaksData()<-: replace the full data?
721-
## TODO mz()<-
722-
## TODO intensity()<-
775+
## DONE mz()<-
776+
## DONE intensity()<-
723777
## TODO applyProcessing(): replace the full data? is that needed? should
724778
## internally call peaksData()<-
725779
## DONE all other replacement methods.

vignettes/SpectriPy.qmd

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,17 @@ sps$SMILES |> head()
631631
See also the next section for more information on the mapping between
632632
`Spectra`'s spectra variables and *matchms* metadata.
633633

634+
The `MsBackendPy` has full *read/write* support, i.e., it allows to add new
635+
spectra variables or change existing spectra and/or peaks variables through the
636+
available replacement methods `spectraData()<-`, `peaksData()<-`,
637+
`intensity()<-`, `mz()<-` and `$<-`. Currently, this operations change however
638+
the full MS data of the associated Python variable. Thus, even if for example
639+
only the retention times are replaced using `$rtime<-`, the (full!) data is
640+
first loaded from Python to R, the retention time values are then replaced and
641+
finally the full data (spectra and peaks variables) are again stored to the
642+
associated Python variable. Replacement operations are thus quite memory
643+
demanding.
644+
634645
## Conversion of spectra variables
635646

636647
Conversion of the MS peaks data (i.e. the *m/z* and intensity values) is always

0 commit comments

Comments
 (0)