From 97b8d30234028bf7fafe5886926ca051c274b5b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Jul 2026 04:22:53 +0000 Subject: [PATCH 1/5] Add GCIMSDataset methods for getChromatogram()/getSpectrum() getChromatogram()/getSpectrum() are now S4 generics (previously plain functions working only on GCIMSSample). The new GCIMSDataset methods extract one chromatogram/spectrum per sample and wrap them in a new GCIMSChromatogramSet/GCIMSSpectrumSet object, together with a copy of pData(dataset) so plots can use the dataset's annotations. Each sample keeps its own native retention/drift time axis inside the Set (no interpolation onto a shared grid), and the new plot() methods combine them by row-binding rather than requiring a common axis, so mismatched sample axes are never silently distorted. plot(..., color_by = ) lets you color by group instead of just SampleID. The vignette now demonstrates getChromatogram(dataset, ...)/ getSpectrum(dataset, ...) right after smoothing the whole dataset, as a simpler alternative to manually picking one sample to visualize. Regenerated NAMESPACE/man with roxygen2 7.3.1 (matching RoxygenNote), which also picked up previously stale docs for DelayedDatasetBase's dropSolePendingOp() method (added in an earlier PR, never regenerated). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019V3CHRGSzcEu3k6tpUFv56 --- NAMESPACE | 10 ++ NEWS.md | 7 ++ R/aaa-AllGenerics.R | 18 ++++ R/aaa-class-GCIMSChromatogramSet.R | 87 +++++++++++++++++ R/aaa-class-GCIMSSample.R | 8 +- R/aaa-class-GCIMSSpectrumSet.R | 87 +++++++++++++++++ R/getChromatogram_getSpectrum-GCIMSDataset.R | 67 +++++++++++++ R/plot-GCIMSChromatogramSet.R | 50 ++++++++++ R/plot-GCIMSSpectrumSet.R | 50 ++++++++++ man/DelayedDatasetBase.Rd | 26 +++++ man/DelayedDatasetDisk.Rd | 1 + man/DelayedDatasetRAM.Rd | 1 + man/GCIMS-generics.Rd | 16 ++++ man/GCIMSChromatogram-class.Rd | 2 + man/GCIMSChromatogram.Rd | 2 + man/GCIMSChromatogramSet-class.Rd | 90 ++++++++++++++++++ man/GCIMSChromatogramSet.Rd | 33 +++++++ man/GCIMSSpectrumSet-class.Rd | 83 ++++++++++++++++ man/GCIMSSpectrumSet.Rd | 26 +++++ man/dtime-GCIMSChromatogram-method.Rd | 2 + ...timateBaseline-GCIMSChromatogram-method.Rd | 2 + man/findPeaks-GCIMSChromatogram-method.Rd | 2 + man/findPeaks-GCIMSSpectrum-method.Rd | 5 + man/getChromatogram-GCIMSDataset-method.Rd | 39 ++++++++ ... => getChromatogram-GCIMSSample-method.Rd} | 6 +- man/getSpectrum-GCIMSDataset-method.Rd | 39 ++++++++ ...m.Rd => getSpectrum-GCIMSSample-method.Rd} | 6 +- man/intensity-GCIMSChromatogram-method.Rd | 2 + man/rtime-GCIMSChromatogram-method.Rd | 2 + man/smooth-GCIMSChromatogram-method.Rd | 2 + .../test-aaa-class-GCIMSChromatogramSet.R | 48 ++++++++++ .../test-aaa-class-GCIMSSpectrumSet.R | 48 ++++++++++ ...getChromatogram_getSpectrum-GCIMSDataset.R | 95 +++++++++++++++++++ vignettes/introduction-to-gcims.Rmd | 16 ++++ 34 files changed, 968 insertions(+), 10 deletions(-) create mode 100644 R/aaa-class-GCIMSChromatogramSet.R create mode 100644 R/aaa-class-GCIMSSpectrumSet.R create mode 100644 R/getChromatogram_getSpectrum-GCIMSDataset.R create mode 100644 R/plot-GCIMSChromatogramSet.R create mode 100644 R/plot-GCIMSSpectrumSet.R create mode 100644 man/GCIMSChromatogramSet-class.Rd create mode 100644 man/GCIMSChromatogramSet.Rd create mode 100644 man/GCIMSSpectrumSet-class.Rd create mode 100644 man/GCIMSSpectrumSet.Rd create mode 100644 man/getChromatogram-GCIMSDataset-method.Rd rename man/{getChromatogram.Rd => getChromatogram-GCIMSSample-method.Rd} (91%) create mode 100644 man/getSpectrum-GCIMSDataset-method.Rd rename man/{getSpectrum.Rd => getSpectrum-GCIMSSample-method.Rd} (92%) create mode 100644 tests/testthat/test-aaa-class-GCIMSChromatogramSet.R create mode 100644 tests/testthat/test-aaa-class-GCIMSSpectrumSet.R create mode 100644 tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R diff --git a/NAMESPACE b/NAMESPACE index 290fbd65..677e1de9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -14,10 +14,12 @@ export("peaks<-") export("sampleNames<-") export(DelayedOperation) export(GCIMSChromatogram) +export(GCIMSChromatogramSet) export(GCIMSDataset) export(GCIMSDataset_fromList) export(GCIMSSample) export(GCIMSSpectrum) +export(GCIMSSpectrumSet) export(add_peaklist_rect) export(align) export(alignDt) @@ -64,9 +66,12 @@ export(smooth) export(updateObject) exportClasses(DelayedOperation) exportClasses(GCIMSChromatogram) +exportClasses(GCIMSChromatogramSet) exportClasses(GCIMSDataset) exportClasses(GCIMSSample) exportClasses(GCIMSSpectrum) +exportClasses(GCIMSSpectrumSet) +exportMethods("[[") exportMethods("baseline<-") exportMethods("description<-") exportMethods("intensity<-") @@ -80,16 +85,21 @@ exportMethods(estimateBaseline) exportMethods(filterDt) exportMethods(filterRt) exportMethods(findPeaks) +exportMethods(getChromatogram) exportMethods(getRIC) +exportMethods(getSpectrum) exportMethods(getTIS) exportMethods(integratePeaks) exportMethods(intensity) +exportMethods(length) +exportMethods(pData) exportMethods(peaks) exportMethods(plot) exportMethods(plotRIC) exportMethods(plotTIS) exportMethods(prealign) exportMethods(rtime) +exportMethods(sampleNames) exportMethods(smooth) exportMethods(updateObject) import(methods) diff --git a/NEWS.md b/NEWS.md index be0305db..0783a7a7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,12 @@ # GCIMS (development version) +- `getChromatogram()` and `getSpectrum()` are now S4 generics with methods + for `GCIMSDataset`, in addition to the existing `GCIMSSample` method. + Calling them on a dataset returns a new `GCIMSChromatogramSet`/ + `GCIMSSpectrumSet` object: one chromatogram/spectrum per sample (each kept + on its own native axis, no interpolation across samples), together with a + copy of `pData()`. Both new classes have a `plot()` method that can color + by `SampleID` or by any `pData()` column via `color_by`. - `GCIMSSample()` now validates the object right after construction, so passing an intensity matrix in the wrong orientation (drift time/retention time swapped) errors immediately with a clear message instead of diff --git a/R/aaa-AllGenerics.R b/R/aaa-AllGenerics.R index ef475684..9859d476 100644 --- a/R/aaa-AllGenerics.R +++ b/R/aaa-AllGenerics.R @@ -45,6 +45,24 @@ NULL #' setGeneric("dtime", function(object, ...) standardGeneric("dtime")) +#' @describeIn GCIMS-generics Get a chromatogram +#' +#' @return A [GCIMSChromatogram] (one sample) or a [GCIMSChromatogramSet] +#' (several samples, one chromatogram each, with a copy of `pData()`) +#' @param object An object to extract a chromatogram from +#' @param ... Further arguments, possibly used by downstream methods. +#' @export +setGeneric("getChromatogram", function(object, ...) standardGeneric("getChromatogram")) + +#' @describeIn GCIMS-generics Get a spectrum +#' +#' @return A [GCIMSSpectrum] (one sample) or a [GCIMSSpectrumSet] +#' (several samples, one spectrum each, with a copy of `pData()`) +#' @param object An object to extract a spectrum from +#' @param ... Further arguments, possibly used by downstream methods. +#' @export +setGeneric("getSpectrum", function(object, ...) standardGeneric("getSpectrum")) + #' @describeIn GCIMS-generics Get the Total Ion Spectrum #' #' @return The Total Ion Spectrum as a numeric vector or a matrix diff --git a/R/aaa-class-GCIMSChromatogramSet.R b/R/aaa-class-GCIMSChromatogramSet.R new file mode 100644 index 00000000..e292508e --- /dev/null +++ b/R/aaa-class-GCIMSChromatogramSet.R @@ -0,0 +1,87 @@ +#' GCIMSChromatogramSet class +#' +#' @description +#' GCIMSChromatogramSet is an S4 class to store one [GCIMSChromatogram] per +#' sample of a [GCIMSDataset], together with a copy of `pData()` so plots can +#' use the dataset's annotations. +#' +#' Samples are not required to share a common retention time axis: each +#' chromatogram keeps its own, exactly as extracted from its sample. No +#' interpolation is performed. +#' +#' @slot chromatograms A named list of [GCIMSChromatogram] objects, one per +#' sample, named after their `SampleID`. +#' @slot pData A `DataFrame` with the phenotype data, or `NULL`. +#' +#' @export +#' @family GCIMSChromatogram +methods::setClass( + Class = "GCIMSChromatogramSet", + slots = c( + chromatograms = "list", + pData = "DataFrameOrNULL" + ) +) + +methods::setMethod( + "initialize", "GCIMSChromatogramSet", + function(.Object, chromatograms = list(), pData = NULL) { + if (!rlang::is_named(chromatograms) && length(chromatograms) > 0) { + cli_abort("chromatograms should be a named list, with the SampleID of each chromatogram as its name") + } + if (!all(purrr::map_lgl(chromatograms, inherits, "GCIMSChromatogram"))) { + cli_abort("All elements of chromatograms should be GCIMSChromatogram objects") + } + .Object@chromatograms <- chromatograms + .Object@pData <- pData + .Object + } +) + +#' Create a [GCIMSChromatogramSet-class] object +#' +#' @param chromatograms A named list of [GCIMSChromatogram] objects, one per +#' sample, named after their `SampleID`. +#' @param pData A `data.frame`/`DataFrame`/tibble with the phenotype data, or `NULL`. +#' @return A [GCIMSChromatogramSet-class] object +#' @export +#' @family GCIMSChromatogram +GCIMSChromatogramSet <- function(chromatograms = list(), pData = NULL) { + if (!is.null(pData) && !inherits(pData, "DataFrame")) { + pData <- S4Vectors::DataFrame(pData) + } + methods::new("GCIMSChromatogramSet", chromatograms = chromatograms, pData = pData) +} + +#' @describeIn GCIMSChromatogramSet-class Get the sample names +#' @param object A [GCIMSChromatogramSet] object +#' @return A character vector with the sample names +#' @export +setMethod("sampleNames", "GCIMSChromatogramSet", function(object) { + nms <- names(object@chromatograms) + if (is.null(nms)) character(0) else nms +}) + +#' @describeIn GCIMSChromatogramSet-class Get the phenotype data +#' @param object A [GCIMSChromatogramSet] object +#' @return A tibble with the phenotype data, or `NULL` if not set +#' @export +setMethod("pData", "GCIMSChromatogramSet", function(object) { + if (is.null(object@pData)) { + return(NULL) + } + tibble::as_tibble(object@pData) +}) + +#' @describeIn GCIMSChromatogramSet-class Number of chromatograms (samples) in the set +#' @param x A [GCIMSChromatogramSet] object +#' @return An integer with the number of chromatograms +#' @export +setMethod("length", "GCIMSChromatogramSet", function(x) length(x@chromatograms)) + +#' @describeIn GCIMSChromatogramSet-class Extract the chromatogram of a single sample +#' @param x A [GCIMSChromatogramSet] object +#' @param i A number or a string with the sample index or name +#' @return The [GCIMSChromatogram] of the requested sample +#' @export +setMethod("[[", "GCIMSChromatogramSet", function(x, i) x@chromatograms[[i]]) diff --git a/R/aaa-class-GCIMSSample.R b/R/aaa-class-GCIMSSample.R index 9db41b03..d0442711 100644 --- a/R/aaa-class-GCIMSSample.R +++ b/R/aaa-class-GCIMSSample.R @@ -307,7 +307,7 @@ subset.GCIMSSample <- function( #' getChromatogram(x) #' # Take the maximum intensity in the region for each retention time: #' sp1 <- getChromatogram(x, aggregate = function(x) apply(x, 2, max)) -getChromatogram <- function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = colSums) { +setMethod("getChromatogram", "GCIMSSample", function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = colSums) { dt <- dtime(object) rt <- rtime(object) idx <- dt_rt_range_normalization(dt, rt, dt_range, rt_range, dt_idx, rt_idx) @@ -328,7 +328,7 @@ getChromatogram <- function(object, dt_range = NULL, rt_range = NULL, dt_idx = N description = object@description, baseline = basel ) -} +}) #' Get IMS spectrum from a sample #' @@ -348,7 +348,7 @@ getChromatogram <- function(object, dt_range = NULL, rt_range = NULL, dt_idx = N #' #' # Take the maximum intensity in the region for each drift time: #' sp1 <- getSpectrum(x, aggregate = function(x) apply(x, 1, max)) -getSpectrum <- function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = rowSums) { +setMethod("getSpectrum", "GCIMSSample", function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = rowSums) { dt <- dtime(object) rt <- rtime(object) idx <- dt_rt_range_normalization(dt, rt, dt_range, rt_range, dt_idx, rt_idx) @@ -369,5 +369,5 @@ getSpectrum <- function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, description = object@description, baseline = basel ) -} +}) diff --git a/R/aaa-class-GCIMSSpectrumSet.R b/R/aaa-class-GCIMSSpectrumSet.R new file mode 100644 index 00000000..1bb62a23 --- /dev/null +++ b/R/aaa-class-GCIMSSpectrumSet.R @@ -0,0 +1,87 @@ +#' GCIMSSpectrumSet class +#' +#' @description +#' GCIMSSpectrumSet is an S4 class to store one [GCIMSSpectrum] per sample of +#' a [GCIMSDataset], together with a copy of `pData()` so plots can use the +#' dataset's annotations. +#' +#' Samples are not required to share a common drift time axis: each spectrum +#' keeps its own, exactly as extracted from its sample. No interpolation is +#' performed. +#' +#' @slot spectra A named list of [GCIMSSpectrum] objects, one per sample, +#' named after their `SampleID`. +#' @slot pData A `DataFrame` with the phenotype data, or `NULL`. +#' +#' @export +#' @family GCIMSSpectrum +methods::setClass( + Class = "GCIMSSpectrumSet", + slots = c( + spectra = "list", + pData = "DataFrameOrNULL" + ) +) + +methods::setMethod( + "initialize", "GCIMSSpectrumSet", + function(.Object, spectra = list(), pData = NULL) { + if (!rlang::is_named(spectra) && length(spectra) > 0) { + cli_abort("spectra should be a named list, with the SampleID of each spectrum as its name") + } + if (!all(purrr::map_lgl(spectra, inherits, "GCIMSSpectrum"))) { + cli_abort("All elements of spectra should be GCIMSSpectrum objects") + } + .Object@spectra <- spectra + .Object@pData <- pData + .Object + } +) + +#' Create a [GCIMSSpectrumSet-class] object +#' +#' @param spectra A named list of [GCIMSSpectrum] objects, one per sample, +#' named after their `SampleID`. +#' @param pData A `data.frame`/`DataFrame`/tibble with the phenotype data, or `NULL`. +#' @return A [GCIMSSpectrumSet-class] object +#' @export +#' @family GCIMSSpectrum +GCIMSSpectrumSet <- function(spectra = list(), pData = NULL) { + if (!is.null(pData) && !inherits(pData, "DataFrame")) { + pData <- S4Vectors::DataFrame(pData) + } + methods::new("GCIMSSpectrumSet", spectra = spectra, pData = pData) +} + +#' @describeIn GCIMSSpectrumSet-class Get the sample names +#' @param object A [GCIMSSpectrumSet] object +#' @return A character vector with the sample names +#' @export +setMethod("sampleNames", "GCIMSSpectrumSet", function(object) { + nms <- names(object@spectra) + if (is.null(nms)) character(0) else nms +}) + +#' @describeIn GCIMSSpectrumSet-class Get the phenotype data +#' @param object A [GCIMSSpectrumSet] object +#' @return A tibble with the phenotype data, or `NULL` if not set +#' @export +setMethod("pData", "GCIMSSpectrumSet", function(object) { + if (is.null(object@pData)) { + return(NULL) + } + tibble::as_tibble(object@pData) +}) + +#' @describeIn GCIMSSpectrumSet-class Number of spectra (samples) in the set +#' @param x A [GCIMSSpectrumSet] object +#' @return An integer with the number of spectra +#' @export +setMethod("length", "GCIMSSpectrumSet", function(x) length(x@spectra)) + +#' @describeIn GCIMSSpectrumSet-class Extract the spectrum of a single sample +#' @param x A [GCIMSSpectrumSet] object +#' @param i A number or a string with the sample index or name +#' @return The [GCIMSSpectrum] of the requested sample +#' @export +setMethod("[[", "GCIMSSpectrumSet", function(x, i) x@spectra[[i]]) diff --git a/R/getChromatogram_getSpectrum-GCIMSDataset.R b/R/getChromatogram_getSpectrum-GCIMSDataset.R new file mode 100644 index 00000000..bf91d9f1 --- /dev/null +++ b/R/getChromatogram_getSpectrum-GCIMSDataset.R @@ -0,0 +1,67 @@ +#' Get a chromatogram from each sample of a dataset +#' +#' @param object A [GCIMSDataset] object +#' @inheritParams dt_rt_range_normalization +#' @param aggregate Function that takes the subsetted intensity matrix of each +#' sample according to the region of interest and aggregates the drift times, +#' returning a vector representing the chromatogram intensity. `colSums` by +#' default. +#' @return A [GCIMSChromatogramSet], with one [GCIMSChromatogram] per sample +#' (each on its own retention time axis, no interpolation across samples) and +#' a copy of `pData(object)` +#' @export +setMethod( + "getChromatogram", + "GCIMSDataset", + function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = colSums) { + object$realize() + sample_names <- sampleNames(object) + chromatograms <- stats::setNames( + purrr::map(sample_names, function(sample_name) { + sample <- object$getSample(sample_name) + getChromatogram( + sample, + dt_range = dt_range, rt_range = rt_range, + dt_idx = dt_idx, rt_idx = rt_idx, + aggregate = aggregate + ) + }), + sample_names + ) + GCIMSChromatogramSet(chromatograms = chromatograms, pData = pData(object)) + } +) + +#' Get a spectrum from each sample of a dataset +#' +#' @param object A [GCIMSDataset] object +#' @inheritParams dt_rt_range_normalization +#' @param aggregate Function that takes the subsetted intensity matrix of each +#' sample according to the region of interest and aggregates the retention +#' times, returning a vector representing the spectrum intensity. `rowSums` +#' by default. +#' @return A [GCIMSSpectrumSet], with one [GCIMSSpectrum] per sample (each on +#' its own drift time axis, no interpolation across samples) and a copy of +#' `pData(object)` +#' @export +setMethod( + "getSpectrum", + "GCIMSDataset", + function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = rowSums) { + object$realize() + sample_names <- sampleNames(object) + spectra <- stats::setNames( + purrr::map(sample_names, function(sample_name) { + sample <- object$getSample(sample_name) + getSpectrum( + sample, + dt_range = dt_range, rt_range = rt_range, + dt_idx = dt_idx, rt_idx = rt_idx, + aggregate = aggregate + ) + }), + sample_names + ) + GCIMSSpectrumSet(spectra = spectra, pData = pData(object)) + } +) diff --git a/R/plot-GCIMSChromatogramSet.R b/R/plot-GCIMSChromatogramSet.R new file mode 100644 index 00000000..524e53d4 --- /dev/null +++ b/R/plot-GCIMSChromatogramSet.R @@ -0,0 +1,50 @@ +#' @describeIn GCIMSChromatogramSet-class plot method +#' @param x A [GCIMSChromatogramSet] object to plot +#' @param color_by The name of a `pData(x)` column (or `"SampleID"`) used to +#' color the chromatograms +#' @param y From the generic `plot` function, ignored for `GCIMSChromatogramSet` class objects +#' @return A ggplot2 plot object +#' @export +setMethod( + "plot", + "GCIMSChromatogramSet", + function(x, color_by = "SampleID", ...) { + sample_names <- sampleNames(x) + if (length(sample_names) == 0) { + cli_abort("Can't plot an empty GCIMSChromatogramSet") + } + df <- dplyr::bind_rows(purrr::map2( + x@chromatograms, sample_names, + function(chrom, sample_id) { + data.frame( + SampleID = sample_id, + retention_time_s = rtime(chrom), + intensity = unname(intensity(chrom)) + ) + } + )) + + pd <- pData(x) + if (!is.null(pd) && "SampleID" %in% colnames(pd)) { + df <- dplyr::left_join(df, pd, by = "SampleID") + } + if (!color_by %in% colnames(df)) { + cli_abort("{.val {color_by}} is not a column of {.code pData(x)} (or {.val SampleID})") + } + + ggplot2::ggplot(df) + + ggplot2::geom_line( + mapping = ggplot2::aes( + x = .data$retention_time_s, + y = .data$intensity, + color = .data[[color_by]], + group = .data$SampleID + ) + ) + + ggplot2::labs( + x = "Retention time (s)", + y = "Intensity (a.u.)", + color = color_by + ) + } +) diff --git a/R/plot-GCIMSSpectrumSet.R b/R/plot-GCIMSSpectrumSet.R new file mode 100644 index 00000000..6a525729 --- /dev/null +++ b/R/plot-GCIMSSpectrumSet.R @@ -0,0 +1,50 @@ +#' @describeIn GCIMSSpectrumSet-class plot method +#' @param x A [GCIMSSpectrumSet] object to plot +#' @param color_by The name of a `pData(x)` column (or `"SampleID"`) used to +#' color the spectra +#' @param y From the generic `plot` function, ignored for `GCIMSSpectrumSet` class objects +#' @return A ggplot2 plot object +#' @export +setMethod( + "plot", + "GCIMSSpectrumSet", + function(x, color_by = "SampleID", ...) { + sample_names <- sampleNames(x) + if (length(sample_names) == 0) { + cli_abort("Can't plot an empty GCIMSSpectrumSet") + } + df <- dplyr::bind_rows(purrr::map2( + x@spectra, sample_names, + function(spec, sample_id) { + data.frame( + SampleID = sample_id, + drift_time_ms = dtime(spec), + intensity = unname(intensity(spec)) + ) + } + )) + + pd <- pData(x) + if (!is.null(pd) && "SampleID" %in% colnames(pd)) { + df <- dplyr::left_join(df, pd, by = "SampleID") + } + if (!color_by %in% colnames(df)) { + cli_abort("{.val {color_by}} is not a column of {.code pData(x)} (or {.val SampleID})") + } + + ggplot2::ggplot(df) + + ggplot2::geom_line( + mapping = ggplot2::aes( + x = .data$drift_time_ms, + y = .data$intensity, + color = .data[[color_by]], + group = .data$SampleID + ) + ) + + ggplot2::labs( + x = "Drift time (ms)", + y = "Intensity (a.u.)", + color = color_by + ) + } +) diff --git a/man/DelayedDatasetBase.Rd b/man/DelayedDatasetBase.Rd index 4329201d..d1731efe 100644 --- a/man/DelayedDatasetBase.Rd +++ b/man/DelayedDatasetBase.Rd @@ -28,6 +28,7 @@ This class is not exported, but if you want to use it reach us at \item \href{#method-DelayedDatasetBase-registerOptimization}{\code{DelayedDatasetBase$registerOptimization()}} \item \href{#method-DelayedDatasetBase-appendDelayedOp}{\code{DelayedDatasetBase$appendDelayedOp()}} \item \href{#method-DelayedDatasetBase-hasDelayedOps}{\code{DelayedDatasetBase$hasDelayedOps()}} +\item \href{#method-DelayedDatasetBase-dropSolePendingOp}{\code{DelayedDatasetBase$dropSolePendingOp()}} \item \href{#method-DelayedDatasetBase-getSample}{\code{DelayedDatasetBase$getSample()}} \item \href{#method-DelayedDatasetBase-history_as_list}{\code{DelayedDatasetBase$history_as_list()}} \item \href{#method-DelayedDatasetBase-pending_as_list}{\code{DelayedDatasetBase$pending_as_list()}} @@ -142,6 +143,31 @@ Returns \code{TRUE} if the dataset has pending operations, \code{FALSE} otherwis } } \if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-DelayedDatasetBase-dropSolePendingOp}{}}} +\subsection{Method \code{dropSolePendingOp()}}{ +If the \emph{only} pending operation is the one named \code{name}, discard it. +This is narrowly scoped on purpose: it is only safe to replace a +pending operation with a newer, equivalent one when nothing else is +queued alongside it (otherwise the operation could be re-run out of +the order it was originally queued in, relative to whatever else is +pending). +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{DelayedDatasetBase$dropSolePendingOp(name)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{name}}{The operation name to drop, if it is the sole pending one} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +\code{TRUE} if the operation was dropped, \code{FALSE} otherwise +} +} +\if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-DelayedDatasetBase-getSample}{}}} \subsection{Method \code{getSample()}}{ diff --git a/man/DelayedDatasetDisk.Rd b/man/DelayedDatasetDisk.Rd index 9e7297bd..b1e6a4b2 100644 --- a/man/DelayedDatasetDisk.Rd +++ b/man/DelayedDatasetDisk.Rd @@ -41,6 +41,7 @@ This class is not exported, but if you want to use it reach us at
Inherited methods
  • GCIMS::DelayedDatasetBase$appendDelayedOp()
  • +
  • GCIMS::DelayedDatasetBase$dropSolePendingOp()
  • GCIMS::DelayedDatasetBase$hasDelayedOps()
  • GCIMS::DelayedDatasetBase$history_as_list()
  • GCIMS::DelayedDatasetBase$pending_as_list()
  • diff --git a/man/DelayedDatasetRAM.Rd b/man/DelayedDatasetRAM.Rd index 726afabe..09d95983 100644 --- a/man/DelayedDatasetRAM.Rd +++ b/man/DelayedDatasetRAM.Rd @@ -36,6 +36,7 @@ This class is not exported, but if you want to use it reach us at
    Inherited methods
    • GCIMS::DelayedDatasetBase$appendDelayedOp()
    • +
    • GCIMS::DelayedDatasetBase$dropSolePendingOp()
    • GCIMS::DelayedDatasetBase$hasDelayedOps()
    • GCIMS::DelayedDatasetBase$history_as_list()
    • GCIMS::DelayedDatasetBase$pending_as_list()
    • diff --git a/man/GCIMS-generics.Rd b/man/GCIMS-generics.Rd index cd7d4818..0224a63c 100644 --- a/man/GCIMS-generics.Rd +++ b/man/GCIMS-generics.Rd @@ -3,6 +3,8 @@ \name{GCIMS-generics} \alias{GCIMS-generics} \alias{dtime} +\alias{getChromatogram} +\alias{getSpectrum} \alias{getTIS} \alias{getRIC} \alias{plotTIS} @@ -19,6 +21,10 @@ \usage{ dtime(object, ...) +getChromatogram(object, ...) + +getSpectrum(object, ...) + getTIS(object, ...) getRIC(object, ...) @@ -53,6 +59,12 @@ integratePeaks(object, ...) \value{ A numeric vector with the drift time +A \link{GCIMSChromatogram} (one sample) or a \link{GCIMSChromatogramSet} +(several samples, one chromatogram each, with a copy of \code{pData()}) + +A \link{GCIMSSpectrum} (one sample) or a \link{GCIMSSpectrumSet} +(several samples, one spectrum each, with a copy of \code{pData()}) + The Total Ion Spectrum as a numeric vector or a matrix (depending if the object is one sample or several) @@ -87,6 +99,10 @@ to an existing generics-only package if you need so. \itemize{ \item \code{dtime()}: Get drift time vector +\item \code{getChromatogram()}: Get a chromatogram + +\item \code{getSpectrum()}: Get a spectrum + \item \code{getTIS()}: Get the Total Ion Spectrum \item \code{getRIC()}: Get the Reverse Ion Chromatogram diff --git a/man/GCIMSChromatogram-class.Rd b/man/GCIMSChromatogram-class.Rd index 87a348bb..29903b82 100644 --- a/man/GCIMSChromatogram-class.Rd +++ b/man/GCIMSChromatogram-class.Rd @@ -101,6 +101,8 @@ or \code{NULL} if not set. Use \code{\link[=estimateBaseline]{estimateBaseline() \seealso{ Other GCIMSChromatogram: \code{\link{GCIMSChromatogram}}, +\code{\link{GCIMSChromatogramSet}}, +\code{\link{GCIMSChromatogramSet-class}}, \code{\link{dtime,GCIMSChromatogram-method}}, \code{\link{estimateBaseline,GCIMSChromatogram-method}}, \code{\link{findPeaks,GCIMSChromatogram-method}}, diff --git a/man/GCIMSChromatogram.Rd b/man/GCIMSChromatogram.Rd index beecdae5..e8be0545 100644 --- a/man/GCIMSChromatogram.Rd +++ b/man/GCIMSChromatogram.Rd @@ -48,6 +48,8 @@ GCIMSChromatogram( \seealso{ Other GCIMSChromatogram: \code{\link{GCIMSChromatogram-class}}, +\code{\link{GCIMSChromatogramSet}}, +\code{\link{GCIMSChromatogramSet-class}}, \code{\link{dtime,GCIMSChromatogram-method}}, \code{\link{estimateBaseline,GCIMSChromatogram-method}}, \code{\link{findPeaks,GCIMSChromatogram-method}}, diff --git a/man/GCIMSChromatogramSet-class.Rd b/man/GCIMSChromatogramSet-class.Rd new file mode 100644 index 00000000..c6a66ee2 --- /dev/null +++ b/man/GCIMSChromatogramSet-class.Rd @@ -0,0 +1,90 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/aaa-class-GCIMSChromatogramSet.R, +% R/plot-GCIMSChromatogramSet.R +\docType{class} +\name{GCIMSChromatogramSet-class} +\alias{GCIMSChromatogramSet-class} +\alias{sampleNames,GCIMSChromatogramSet-method} +\alias{pData,GCIMSChromatogramSet-method} +\alias{length,GCIMSChromatogramSet-method} +\alias{[[,GCIMSChromatogramSet-method} +\alias{plot,GCIMSChromatogramSet,ANY-method} +\title{GCIMSChromatogramSet class} +\usage{ +\S4method{sampleNames}{GCIMSChromatogramSet}(object) + +\S4method{pData}{GCIMSChromatogramSet}(object) + +\S4method{length}{GCIMSChromatogramSet}(x) + +\S4method{[[}{GCIMSChromatogramSet}(x, i) + +\S4method{plot}{GCIMSChromatogramSet,ANY}(x, color_by = "SampleID", ...) +} +\arguments{ +\item{object}{A \link{GCIMSChromatogramSet} object} + +\item{x}{A \link{GCIMSChromatogramSet} object to plot} + +\item{i}{A number or a string with the sample index or name} + +\item{color_by}{The name of a \code{pData(x)} column (or \code{"SampleID"}) used to +color the chromatograms} + +\item{y}{From the generic \code{plot} function, ignored for \code{GCIMSChromatogramSet} class objects} +} +\value{ +A character vector with the sample names + +A tibble with the phenotype data, or \code{NULL} if not set + +An integer with the number of chromatograms + +The \link{GCIMSChromatogram} of the requested sample + +A ggplot2 plot object +} +\description{ +GCIMSChromatogramSet is an S4 class to store one \link{GCIMSChromatogram} per +sample of a \link{GCIMSDataset}, together with a copy of \code{pData()} so plots can +use the dataset's annotations. + +Samples are not required to share a common retention time axis: each +chromatogram keeps its own, exactly as extracted from its sample. No +interpolation is performed. +} +\section{Functions}{ +\itemize{ +\item \code{sampleNames(GCIMSChromatogramSet)}: Get the sample names + +\item \code{pData(GCIMSChromatogramSet)}: Get the phenotype data + +\item \code{length(GCIMSChromatogramSet)}: Number of chromatograms (samples) in the set + +\item \code{[[}: Extract the chromatogram of a single sample + +\item \code{plot(x = GCIMSChromatogramSet, y = ANY)}: plot method + +}} +\section{Slots}{ + +\describe{ +\item{\code{chromatograms}}{A named list of \link{GCIMSChromatogram} objects, one per +sample, named after their \code{SampleID}.} + +\item{\code{pData}}{A \code{DataFrame} with the phenotype data, or \code{NULL}.} +}} + +\seealso{ +Other GCIMSChromatogram: +\code{\link{GCIMSChromatogram}}, +\code{\link{GCIMSChromatogram-class}}, +\code{\link{GCIMSChromatogramSet}}, +\code{\link{dtime,GCIMSChromatogram-method}}, +\code{\link{estimateBaseline,GCIMSChromatogram-method}}, +\code{\link{findPeaks,GCIMSChromatogram-method}}, +\code{\link{intensity,GCIMSChromatogram-method}}, +\code{\link{rtime,GCIMSChromatogram-method}}, +\code{\link{smooth,GCIMSChromatogram-method}} +} +\concept{GCIMSChromatogram} diff --git a/man/GCIMSChromatogramSet.Rd b/man/GCIMSChromatogramSet.Rd new file mode 100644 index 00000000..5cb15967 --- /dev/null +++ b/man/GCIMSChromatogramSet.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/aaa-class-GCIMSChromatogramSet.R +\name{GCIMSChromatogramSet} +\alias{GCIMSChromatogramSet} +\title{Create a \linkS4class{GCIMSChromatogramSet} object} +\usage{ +GCIMSChromatogramSet(chromatograms = list(), pData = NULL) +} +\arguments{ +\item{chromatograms}{A named list of \link{GCIMSChromatogram} objects, one per +sample, named after their \code{SampleID}.} + +\item{pData}{A \code{data.frame}/\code{DataFrame}/tibble with the phenotype data, or \code{NULL}.} +} +\value{ +A \linkS4class{GCIMSChromatogramSet} object +} +\description{ +Create a \linkS4class{GCIMSChromatogramSet} object +} +\seealso{ +Other GCIMSChromatogram: +\code{\link{GCIMSChromatogram}}, +\code{\link{GCIMSChromatogram-class}}, +\code{\link{GCIMSChromatogramSet-class}}, +\code{\link{dtime,GCIMSChromatogram-method}}, +\code{\link{estimateBaseline,GCIMSChromatogram-method}}, +\code{\link{findPeaks,GCIMSChromatogram-method}}, +\code{\link{intensity,GCIMSChromatogram-method}}, +\code{\link{rtime,GCIMSChromatogram-method}}, +\code{\link{smooth,GCIMSChromatogram-method}} +} +\concept{GCIMSChromatogram} diff --git a/man/GCIMSSpectrumSet-class.Rd b/man/GCIMSSpectrumSet-class.Rd new file mode 100644 index 00000000..22cc7ad5 --- /dev/null +++ b/man/GCIMSSpectrumSet-class.Rd @@ -0,0 +1,83 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/aaa-class-GCIMSSpectrumSet.R, +% R/plot-GCIMSSpectrumSet.R +\docType{class} +\name{GCIMSSpectrumSet-class} +\alias{GCIMSSpectrumSet-class} +\alias{sampleNames,GCIMSSpectrumSet-method} +\alias{pData,GCIMSSpectrumSet-method} +\alias{length,GCIMSSpectrumSet-method} +\alias{[[,GCIMSSpectrumSet-method} +\alias{plot,GCIMSSpectrumSet,ANY-method} +\title{GCIMSSpectrumSet class} +\usage{ +\S4method{sampleNames}{GCIMSSpectrumSet}(object) + +\S4method{pData}{GCIMSSpectrumSet}(object) + +\S4method{length}{GCIMSSpectrumSet}(x) + +\S4method{[[}{GCIMSSpectrumSet}(x, i) + +\S4method{plot}{GCIMSSpectrumSet,ANY}(x, color_by = "SampleID", ...) +} +\arguments{ +\item{object}{A \link{GCIMSSpectrumSet} object} + +\item{x}{A \link{GCIMSSpectrumSet} object to plot} + +\item{i}{A number or a string with the sample index or name} + +\item{color_by}{The name of a \code{pData(x)} column (or \code{"SampleID"}) used to +color the spectra} + +\item{y}{From the generic \code{plot} function, ignored for \code{GCIMSSpectrumSet} class objects} +} +\value{ +A character vector with the sample names + +A tibble with the phenotype data, or \code{NULL} if not set + +An integer with the number of spectra + +The \link{GCIMSSpectrum} of the requested sample + +A ggplot2 plot object +} +\description{ +GCIMSSpectrumSet is an S4 class to store one \link{GCIMSSpectrum} per sample of +a \link{GCIMSDataset}, together with a copy of \code{pData()} so plots can use the +dataset's annotations. + +Samples are not required to share a common drift time axis: each spectrum +keeps its own, exactly as extracted from its sample. No interpolation is +performed. +} +\section{Functions}{ +\itemize{ +\item \code{sampleNames(GCIMSSpectrumSet)}: Get the sample names + +\item \code{pData(GCIMSSpectrumSet)}: Get the phenotype data + +\item \code{length(GCIMSSpectrumSet)}: Number of spectra (samples) in the set + +\item \code{[[}: Extract the spectrum of a single sample + +\item \code{plot(x = GCIMSSpectrumSet, y = ANY)}: plot method + +}} +\section{Slots}{ + +\describe{ +\item{\code{spectra}}{A named list of \link{GCIMSSpectrum} objects, one per sample, +named after their \code{SampleID}.} + +\item{\code{pData}}{A \code{DataFrame} with the phenotype data, or \code{NULL}.} +}} + +\seealso{ +Other GCIMSSpectrum: +\code{\link{GCIMSSpectrumSet}}, +\code{\link{findPeaks,GCIMSSpectrum-method}} +} +\concept{GCIMSSpectrum} diff --git a/man/GCIMSSpectrumSet.Rd b/man/GCIMSSpectrumSet.Rd new file mode 100644 index 00000000..07c1f519 --- /dev/null +++ b/man/GCIMSSpectrumSet.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/aaa-class-GCIMSSpectrumSet.R +\name{GCIMSSpectrumSet} +\alias{GCIMSSpectrumSet} +\title{Create a \linkS4class{GCIMSSpectrumSet} object} +\usage{ +GCIMSSpectrumSet(spectra = list(), pData = NULL) +} +\arguments{ +\item{spectra}{A named list of \link{GCIMSSpectrum} objects, one per sample, +named after their \code{SampleID}.} + +\item{pData}{A \code{data.frame}/\code{DataFrame}/tibble with the phenotype data, or \code{NULL}.} +} +\value{ +A \linkS4class{GCIMSSpectrumSet} object +} +\description{ +Create a \linkS4class{GCIMSSpectrumSet} object +} +\seealso{ +Other GCIMSSpectrum: +\code{\link{GCIMSSpectrumSet-class}}, +\code{\link{findPeaks,GCIMSSpectrum-method}} +} +\concept{GCIMSSpectrum} diff --git a/man/dtime-GCIMSChromatogram-method.Rd b/man/dtime-GCIMSChromatogram-method.Rd index f7a629be..623c1e03 100644 --- a/man/dtime-GCIMSChromatogram-method.Rd +++ b/man/dtime-GCIMSChromatogram-method.Rd @@ -19,6 +19,8 @@ Get the drift time of the chromatogram Other GCIMSChromatogram: \code{\link{GCIMSChromatogram}}, \code{\link{GCIMSChromatogram-class}}, +\code{\link{GCIMSChromatogramSet}}, +\code{\link{GCIMSChromatogramSet-class}}, \code{\link{estimateBaseline,GCIMSChromatogram-method}}, \code{\link{findPeaks,GCIMSChromatogram-method}}, \code{\link{intensity,GCIMSChromatogram-method}}, diff --git a/man/estimateBaseline-GCIMSChromatogram-method.Rd b/man/estimateBaseline-GCIMSChromatogram-method.Rd index 78ad84f6..53c155c7 100644 --- a/man/estimateBaseline-GCIMSChromatogram-method.Rd +++ b/man/estimateBaseline-GCIMSChromatogram-method.Rd @@ -45,6 +45,8 @@ The length of the regions are given in seconds in the \code{region_s} parameter. Other GCIMSChromatogram: \code{\link{GCIMSChromatogram}}, \code{\link{GCIMSChromatogram-class}}, +\code{\link{GCIMSChromatogramSet}}, +\code{\link{GCIMSChromatogramSet-class}}, \code{\link{dtime,GCIMSChromatogram-method}}, \code{\link{findPeaks,GCIMSChromatogram-method}}, \code{\link{intensity,GCIMSChromatogram-method}}, diff --git a/man/findPeaks-GCIMSChromatogram-method.Rd b/man/findPeaks-GCIMSChromatogram-method.Rd index 9b35abc9..1ac05f26 100644 --- a/man/findPeaks-GCIMSChromatogram-method.Rd +++ b/man/findPeaks-GCIMSChromatogram-method.Rd @@ -31,6 +31,8 @@ Peak detection for a GCIMSChromatogram Other GCIMSChromatogram: \code{\link{GCIMSChromatogram}}, \code{\link{GCIMSChromatogram-class}}, +\code{\link{GCIMSChromatogramSet}}, +\code{\link{GCIMSChromatogramSet-class}}, \code{\link{dtime,GCIMSChromatogram-method}}, \code{\link{estimateBaseline,GCIMSChromatogram-method}}, \code{\link{intensity,GCIMSChromatogram-method}}, diff --git a/man/findPeaks-GCIMSSpectrum-method.Rd b/man/findPeaks-GCIMSSpectrum-method.Rd index 208d758f..99c23fc6 100644 --- a/man/findPeaks-GCIMSSpectrum-method.Rd +++ b/man/findPeaks-GCIMSSpectrum-method.Rd @@ -27,4 +27,9 @@ The modified \link{GCIMSSpectrum}, with a peak list \description{ Peak detection for a GCIMSSpectrum } +\seealso{ +Other GCIMSSpectrum: +\code{\link{GCIMSSpectrumSet}}, +\code{\link{GCIMSSpectrumSet-class}} +} \concept{GCIMSSpectrum} diff --git a/man/getChromatogram-GCIMSDataset-method.Rd b/man/getChromatogram-GCIMSDataset-method.Rd new file mode 100644 index 00000000..491b06e5 --- /dev/null +++ b/man/getChromatogram-GCIMSDataset-method.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/getChromatogram_getSpectrum-GCIMSDataset.R +\name{getChromatogram,GCIMSDataset-method} +\alias{getChromatogram,GCIMSDataset-method} +\title{Get a chromatogram from each sample of a dataset} +\usage{ +\S4method{getChromatogram}{GCIMSDataset}( + object, + dt_range = NULL, + rt_range = NULL, + dt_idx = NULL, + rt_idx = NULL, + aggregate = colSums +) +} +\arguments{ +\item{object}{A \link{GCIMSDataset} object} + +\item{dt_range}{The minimum and maximum drift times to extract (length 2 vector)} + +\item{rt_range}{The minimum and maximum retention times to extract (length 2 vector)} + +\item{dt_idx}{A numeric vector with the drift time indices to extract (or a logical vector of the length of drift time)} + +\item{rt_idx}{A numeric vector with the retention time indices to extract (or a logical vector of the length of retention time)} + +\item{aggregate}{Function that takes the subsetted intensity matrix of each +sample according to the region of interest and aggregates the drift times, +returning a vector representing the chromatogram intensity. \code{colSums} by +default.} +} +\value{ +A \link{GCIMSChromatogramSet}, with one \link{GCIMSChromatogram} per sample +(each on its own retention time axis, no interpolation across samples) and +a copy of \code{pData(object)} +} +\description{ +Get a chromatogram from each sample of a dataset +} diff --git a/man/getChromatogram.Rd b/man/getChromatogram-GCIMSSample-method.Rd similarity index 91% rename from man/getChromatogram.Rd rename to man/getChromatogram-GCIMSSample-method.Rd index ac5252c4..fb8a94dc 100644 --- a/man/getChromatogram.Rd +++ b/man/getChromatogram-GCIMSSample-method.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/aaa-class-GCIMSSample.R -\name{getChromatogram} -\alias{getChromatogram} +\name{getChromatogram,GCIMSSample-method} +\alias{getChromatogram,GCIMSSample-method} \title{Get the extracted ion chromatogram} \usage{ -getChromatogram( +\S4method{getChromatogram}{GCIMSSample}( object, dt_range = NULL, rt_range = NULL, diff --git a/man/getSpectrum-GCIMSDataset-method.Rd b/man/getSpectrum-GCIMSDataset-method.Rd new file mode 100644 index 00000000..051c792f --- /dev/null +++ b/man/getSpectrum-GCIMSDataset-method.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/getChromatogram_getSpectrum-GCIMSDataset.R +\name{getSpectrum,GCIMSDataset-method} +\alias{getSpectrum,GCIMSDataset-method} +\title{Get a spectrum from each sample of a dataset} +\usage{ +\S4method{getSpectrum}{GCIMSDataset}( + object, + dt_range = NULL, + rt_range = NULL, + dt_idx = NULL, + rt_idx = NULL, + aggregate = rowSums +) +} +\arguments{ +\item{object}{A \link{GCIMSDataset} object} + +\item{dt_range}{The minimum and maximum drift times to extract (length 2 vector)} + +\item{rt_range}{The minimum and maximum retention times to extract (length 2 vector)} + +\item{dt_idx}{A numeric vector with the drift time indices to extract (or a logical vector of the length of drift time)} + +\item{rt_idx}{A numeric vector with the retention time indices to extract (or a logical vector of the length of retention time)} + +\item{aggregate}{Function that takes the subsetted intensity matrix of each +sample according to the region of interest and aggregates the retention +times, returning a vector representing the spectrum intensity. \code{rowSums} +by default.} +} +\value{ +A \link{GCIMSSpectrumSet}, with one \link{GCIMSSpectrum} per sample (each on +its own drift time axis, no interpolation across samples) and a copy of +\code{pData(object)} +} +\description{ +Get a spectrum from each sample of a dataset +} diff --git a/man/getSpectrum.Rd b/man/getSpectrum-GCIMSSample-method.Rd similarity index 92% rename from man/getSpectrum.Rd rename to man/getSpectrum-GCIMSSample-method.Rd index f4cb1634..3bc4ed76 100644 --- a/man/getSpectrum.Rd +++ b/man/getSpectrum-GCIMSSample-method.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/aaa-class-GCIMSSample.R -\name{getSpectrum} -\alias{getSpectrum} +\name{getSpectrum,GCIMSSample-method} +\alias{getSpectrum,GCIMSSample-method} \title{Get IMS spectrum from a sample} \usage{ -getSpectrum( +\S4method{getSpectrum}{GCIMSSample}( object, dt_range = NULL, rt_range = NULL, diff --git a/man/intensity-GCIMSChromatogram-method.Rd b/man/intensity-GCIMSChromatogram-method.Rd index a00c6e25..bd6dbac8 100644 --- a/man/intensity-GCIMSChromatogram-method.Rd +++ b/man/intensity-GCIMSChromatogram-method.Rd @@ -23,6 +23,8 @@ Get the intensity vector Other GCIMSChromatogram: \code{\link{GCIMSChromatogram}}, \code{\link{GCIMSChromatogram-class}}, +\code{\link{GCIMSChromatogramSet}}, +\code{\link{GCIMSChromatogramSet-class}}, \code{\link{dtime,GCIMSChromatogram-method}}, \code{\link{estimateBaseline,GCIMSChromatogram-method}}, \code{\link{findPeaks,GCIMSChromatogram-method}}, diff --git a/man/rtime-GCIMSChromatogram-method.Rd b/man/rtime-GCIMSChromatogram-method.Rd index b71b0bee..550dd3f2 100644 --- a/man/rtime-GCIMSChromatogram-method.Rd +++ b/man/rtime-GCIMSChromatogram-method.Rd @@ -19,6 +19,8 @@ Get the retention time vector Other GCIMSChromatogram: \code{\link{GCIMSChromatogram}}, \code{\link{GCIMSChromatogram-class}}, +\code{\link{GCIMSChromatogramSet}}, +\code{\link{GCIMSChromatogramSet-class}}, \code{\link{dtime,GCIMSChromatogram-method}}, \code{\link{estimateBaseline,GCIMSChromatogram-method}}, \code{\link{findPeaks,GCIMSChromatogram-method}}, diff --git a/man/smooth-GCIMSChromatogram-method.Rd b/man/smooth-GCIMSChromatogram-method.Rd index 4aa4fbe5..1e42abd8 100644 --- a/man/smooth-GCIMSChromatogram-method.Rd +++ b/man/smooth-GCIMSChromatogram-method.Rd @@ -23,6 +23,8 @@ Smoothing a GCIMS chromatogram using a Savitzky-Golay filter Other GCIMSChromatogram: \code{\link{GCIMSChromatogram}}, \code{\link{GCIMSChromatogram-class}}, +\code{\link{GCIMSChromatogramSet}}, +\code{\link{GCIMSChromatogramSet-class}}, \code{\link{dtime,GCIMSChromatogram-method}}, \code{\link{estimateBaseline,GCIMSChromatogram-method}}, \code{\link{findPeaks,GCIMSChromatogram-method}}, diff --git a/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R b/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R new file mode 100644 index 00000000..52ffe302 --- /dev/null +++ b/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R @@ -0,0 +1,48 @@ +make_chrom <- function(sample_id) { + GCIMSChromatogram( + retention_time = 1:5, + intensity = (1:5) * 10, + description = sample_id + ) +} + +test_that("GCIMSChromatogramSet stores chromatograms and pData", { + chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2")) + pd <- data.frame(SampleID = c("s1", "s2"), Group = c("A", "B")) + + cs <- GCIMSChromatogramSet(chromatograms = chroms, pData = pd) + + expect_s4_class(cs, "GCIMSChromatogramSet") + expect_equal(sampleNames(cs), c("s1", "s2")) + expect_equal(length(cs), 2L) + expect_equal(pData(cs)$Group, c("A", "B")) + expect_identical(cs[["s1"]], chroms$s1) +}) + +test_that("GCIMSChromatogramSet can be built with no pData", { + chroms <- list(s1 = make_chrom("s1")) + + cs <- GCIMSChromatogramSet(chromatograms = chroms) + + expect_null(pData(cs)) + expect_equal(sampleNames(cs), "s1") +}) + +test_that("GCIMSChromatogramSet can be empty", { + cs <- GCIMSChromatogramSet() + + expect_equal(length(cs), 0L) + expect_equal(sampleNames(cs), character(0)) +}) + +test_that("GCIMSChromatogramSet rejects an unnamed list of chromatograms", { + chroms <- list(make_chrom("s1"), make_chrom("s2")) + + expect_error(GCIMSChromatogramSet(chromatograms = chroms), "named list") +}) + +test_that("GCIMSChromatogramSet rejects elements that are not GCIMSChromatogram objects", { + chroms <- list(s1 = make_chrom("s1"), s2 = "not a chromatogram") + + expect_error(GCIMSChromatogramSet(chromatograms = chroms), "GCIMSChromatogram objects") +}) diff --git a/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R b/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R new file mode 100644 index 00000000..7dae0f49 --- /dev/null +++ b/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R @@ -0,0 +1,48 @@ +make_spec <- function(sample_id) { + GCIMSSpectrum( + drift_time = 1:5, + intensity = (1:5) * 10, + description = sample_id + ) +} + +test_that("GCIMSSpectrumSet stores spectra and pData", { + spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2")) + pd <- data.frame(SampleID = c("s1", "s2"), Group = c("A", "B")) + + ss <- GCIMSSpectrumSet(spectra = spectra, pData = pd) + + expect_s4_class(ss, "GCIMSSpectrumSet") + expect_equal(sampleNames(ss), c("s1", "s2")) + expect_equal(length(ss), 2L) + expect_equal(pData(ss)$Group, c("A", "B")) + expect_identical(ss[["s1"]], spectra$s1) +}) + +test_that("GCIMSSpectrumSet can be built with no pData", { + spectra <- list(s1 = make_spec("s1")) + + ss <- GCIMSSpectrumSet(spectra = spectra) + + expect_null(pData(ss)) + expect_equal(sampleNames(ss), "s1") +}) + +test_that("GCIMSSpectrumSet can be empty", { + ss <- GCIMSSpectrumSet() + + expect_equal(length(ss), 0L) + expect_equal(sampleNames(ss), character(0)) +}) + +test_that("GCIMSSpectrumSet rejects an unnamed list of spectra", { + spectra <- list(make_spec("s1"), make_spec("s2")) + + expect_error(GCIMSSpectrumSet(spectra = spectra), "named list") +}) + +test_that("GCIMSSpectrumSet rejects elements that are not GCIMSSpectrum objects", { + spectra <- list(s1 = make_spec("s1"), s2 = "not a spectrum") + + expect_error(GCIMSSpectrumSet(spectra = spectra), "GCIMSSpectrum objects") +}) diff --git a/tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R b/tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R new file mode 100644 index 00000000..694a292a --- /dev/null +++ b/tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R @@ -0,0 +1,95 @@ +make_dataset_mismatched_axes <- function() { + # s1 and s2 deliberately have retention/drift time axes of different + # length and range, to prove that getChromatogram()/getSpectrum() on a + # GCIMSDataset never interpolates samples onto a common grid: each + # extracted chromatogram/spectrum keeps its own sample's native axis. + s1 <- GCIMSSample( + drift_time = seq(1, 5, by = 1), + retention_time = seq(1, 10, by = 1), + data = matrix(seq_len(5 * 10), nrow = 5, ncol = 10) + ) + s2 <- GCIMSSample( + drift_time = seq(1, 5, by = 1), + retention_time = seq(1, 20, by = 2), + data = matrix(seq_len(5 * 10), nrow = 5, ncol = 10) + ) + pd <- data.frame(SampleID = c("s1", "s2"), Group = c("A", "B")) + GCIMSDataset$new_from_list(samples = list(s1 = s1, s2 = s2), pData = pd, on_ram = TRUE, scratch_dir = NULL) +} + +test_that("getChromatogram(dataset) returns one GCIMSChromatogram per sample, each on its own retention time axis", { + ds <- make_dataset_mismatched_axes() + + cs <- getChromatogram(ds, dt_range = c(2, 4)) + + expect_s4_class(cs, "GCIMSChromatogramSet") + expect_equal(sampleNames(cs), c("s1", "s2")) + expect_equal(rtime(cs[["s1"]]), 1:10) + expect_equal(rtime(cs[["s2"]]), seq(1, 20, by = 2)) + expect_equal(unname(intensity(cs[["s1"]])), unname(getChromatogram(ds$getSample("s1"), dt_range = c(2, 4))@intensity)) +}) + +test_that("getChromatogram(dataset) attaches pData(dataset)", { + ds <- make_dataset_mismatched_axes() + + cs <- getChromatogram(ds) + + expect_equal(pData(cs)$SampleID, c("s1", "s2")) + expect_equal(pData(cs)$Group, c("A", "B")) +}) + +test_that("getSpectrum(dataset) returns one GCIMSSpectrum per sample, each on its own drift time axis", { + ds <- make_dataset_mismatched_axes() + + ss <- getSpectrum(ds, rt_range = c(2, 5)) + + expect_s4_class(ss, "GCIMSSpectrumSet") + expect_equal(sampleNames(ss), c("s1", "s2")) + expect_equal(dtime(ss[["s1"]]), 1:5) + expect_equal(dtime(ss[["s2"]]), 1:5) +}) + +test_that("plot(GCIMSChromatogramSet) colors by SampleID by default and combines mismatched axes without error", { + ds <- make_dataset_mismatched_axes() + cs <- getChromatogram(ds) + + p <- plot(cs) + + expect_s3_class(p, "ggplot") + expect_setequal(as.character(unique(p$data$SampleID)), c("s1", "s2")) + expect_equal(range(p$data$retention_time_s[p$data$SampleID == "s2"]), c(1, 19)) +}) + +test_that("plot(GCIMSChromatogramSet, color_by=) colors by an arbitrary pData column", { + ds <- make_dataset_mismatched_axes() + cs <- getChromatogram(ds) + + p <- plot(cs, color_by = "Group") + + expect_equal(sort(unique(p$data$Group)), c("A", "B")) +}) + +test_that("plot(GCIMSChromatogramSet, color_by=) errors clearly for an unknown column", { + ds <- make_dataset_mismatched_axes() + cs <- getChromatogram(ds) + + expect_error(plot(cs, color_by = "NotAColumn"), "not a column") +}) + +test_that("plot(GCIMSSpectrumSet) colors by SampleID by default", { + ds <- make_dataset_mismatched_axes() + ss <- getSpectrum(ds) + + p <- plot(ss) + + expect_s3_class(p, "ggplot") + expect_setequal(as.character(unique(p$data$SampleID)), c("s1", "s2")) +}) + +test_that("plot() errors clearly on an empty set", { + cs <- GCIMSChromatogramSet() + expect_error(plot(cs), "empty") + + ss <- GCIMSSpectrumSet() + expect_error(plot(ss), "empty") +}) diff --git a/vignettes/introduction-to-gcims.Rmd b/vignettes/introduction-to-gcims.Rmd index f00a556c..dca72331 100644 --- a/vignettes/introduction-to-gcims.Rmd +++ b/vignettes/introduction-to-gcims.Rmd @@ -242,6 +242,22 @@ dataset <- smooth(dataset, rt_length_s = 3, dt_length_ms = 0.14) dataset$realize() ``` +`getChromatogram()` and `getSpectrum()` also work directly on a `GCIMSDataset`, +extracting one chromatogram/spectrum per sample at once (each on its own +retention/drift time axis) together with the dataset's annotations, so you +don't need to pick a single sample to see the effect of smoothing across the +whole dataset: + +```{r} +all_chroms <- getChromatogram(dataset, dt_range = 10.4) +plot(all_chroms) +``` + +```{r} +all_spectra <- getSpectrum(dataset, rt_range = 97.11) +plot(all_spectra) +``` + # Decimation From fd20868da771ecbd70c40406eea9974f1fbf628c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Jul 2026 04:48:27 +0000 Subject: [PATCH 2/5] Validate pData$SampleID matches chromatogram/spectrum names at construction GCIMSChromatogramSet/GCIMSSpectrumSet stored the sample identity in two places (the names of the chromatograms/spectra list, and pData$SampleID) with nothing enforcing they referred to the same set of samples. A mismatched pData built this way constructed silently, and plot()'s left_join(by = "SampleID") would then produce misleading NA/mismatched annotations instead of failing loudly. initialize() now aborts with a clear message when pData is missing a SampleID column, or its SampleID values don't match the sample names as a set. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019V3CHRGSzcEu3k6tpUFv56 --- R/aaa-class-GCIMSChromatogramSet.R | 13 +++++++++++++ R/aaa-class-GCIMSSpectrumSet.R | 13 +++++++++++++ .../test-aaa-class-GCIMSChromatogramSet.R | 16 ++++++++++++++++ tests/testthat/test-aaa-class-GCIMSSpectrumSet.R | 16 ++++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/R/aaa-class-GCIMSChromatogramSet.R b/R/aaa-class-GCIMSChromatogramSet.R index e292508e..3b6533ee 100644 --- a/R/aaa-class-GCIMSChromatogramSet.R +++ b/R/aaa-class-GCIMSChromatogramSet.R @@ -32,6 +32,19 @@ methods::setMethod( if (!all(purrr::map_lgl(chromatograms, inherits, "GCIMSChromatogram"))) { cli_abort("All elements of chromatograms should be GCIMSChromatogram objects") } + if (!is.null(pData)) { + if (!"SampleID" %in% colnames(pData)) { + cli_abort("pData should have a SampleID column") + } + if (!setequal(as.character(pData[["SampleID"]]), names(chromatograms))) { + cli_abort( + c( + "pData$SampleID does not match the names of chromatograms", + "i" = "Both should refer to exactly the same set of samples" + ) + ) + } + } .Object@chromatograms <- chromatograms .Object@pData <- pData .Object diff --git a/R/aaa-class-GCIMSSpectrumSet.R b/R/aaa-class-GCIMSSpectrumSet.R index 1bb62a23..d9a05719 100644 --- a/R/aaa-class-GCIMSSpectrumSet.R +++ b/R/aaa-class-GCIMSSpectrumSet.R @@ -32,6 +32,19 @@ methods::setMethod( if (!all(purrr::map_lgl(spectra, inherits, "GCIMSSpectrum"))) { cli_abort("All elements of spectra should be GCIMSSpectrum objects") } + if (!is.null(pData)) { + if (!"SampleID" %in% colnames(pData)) { + cli_abort("pData should have a SampleID column") + } + if (!setequal(as.character(pData[["SampleID"]]), names(spectra))) { + cli_abort( + c( + "pData$SampleID does not match the names of spectra", + "i" = "Both should refer to exactly the same set of samples" + ) + ) + } + } .Object@spectra <- spectra .Object@pData <- pData .Object diff --git a/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R b/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R index 52ffe302..170619a5 100644 --- a/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R +++ b/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R @@ -46,3 +46,19 @@ test_that("GCIMSChromatogramSet rejects elements that are not GCIMSChromatogram expect_error(GCIMSChromatogramSet(chromatograms = chroms), "GCIMSChromatogram objects") }) + +test_that("GCIMSChromatogramSet rejects pData whose SampleID doesn't match the chromatogram names", { + chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2")) + + # Different sample entirely: + pd_mismatched <- data.frame(SampleID = c("s1", "other"), Group = c("A", "B")) + expect_error(GCIMSChromatogramSet(chromatograms = chroms, pData = pd_mismatched), "does not match") + + # Missing a sample: + pd_missing <- data.frame(SampleID = "s1", Group = "A") + expect_error(GCIMSChromatogramSet(chromatograms = chroms, pData = pd_missing), "does not match") + + # No SampleID column at all: + pd_no_sampleid <- data.frame(Group = c("A", "B")) + expect_error(GCIMSChromatogramSet(chromatograms = chroms, pData = pd_no_sampleid), "SampleID column") +}) diff --git a/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R b/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R index 7dae0f49..1496da19 100644 --- a/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R +++ b/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R @@ -46,3 +46,19 @@ test_that("GCIMSSpectrumSet rejects elements that are not GCIMSSpectrum objects" expect_error(GCIMSSpectrumSet(spectra = spectra), "GCIMSSpectrum objects") }) + +test_that("GCIMSSpectrumSet rejects pData whose SampleID doesn't match the spectra names", { + spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2")) + + # Different sample entirely: + pd_mismatched <- data.frame(SampleID = c("s1", "other"), Group = c("A", "B")) + expect_error(GCIMSSpectrumSet(spectra = spectra, pData = pd_mismatched), "does not match") + + # Missing a sample: + pd_missing <- data.frame(SampleID = "s1", Group = "A") + expect_error(GCIMSSpectrumSet(spectra = spectra, pData = pd_missing), "does not match") + + # No SampleID column at all: + pd_no_sampleid <- data.frame(Group = c("A", "B")) + expect_error(GCIMSSpectrumSet(spectra = spectra, pData = pd_no_sampleid), "SampleID column") +}) From a41db01c871c4e908aaaa60960a8756c2c50d3d4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Jul 2026 09:00:28 +0000 Subject: [PATCH 3/5] Make sampleNames() defer to pData$SampleID, and fix plot()'s lookup sampleNames() previously always returned names(chromatograms)/ names(spectra), ignoring pData entirely. Now, when pData is present, it is treated as the source of truth for sample identity and order (mirrors GCIMSDataset, where sampleNames<- keeps pData$SampleID in sync). This also exposed a latent bug in plot(): it zipped x@chromatograms/x@spectra together with sample_names positionally via purrr::map2(), which was only correct because sampleNames() previously always matched the underlying list's own order. Switched both plot() methods to look up each sample by name (x[[sample_id]]) instead, so they stay correct regardless of how pData's row order relates to the list's order. Added regression tests for both the reordering and the plot() lookup. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019V3CHRGSzcEu3k6tpUFv56 --- R/aaa-class-GCIMSChromatogramSet.R | 8 ++++++++ R/aaa-class-GCIMSSpectrumSet.R | 8 ++++++++ R/plot-GCIMSChromatogramSet.R | 7 ++++--- R/plot-GCIMSSpectrumSet.R | 7 ++++--- .../testthat/test-aaa-class-GCIMSChromatogramSet.R | 12 ++++++++++++ tests/testthat/test-aaa-class-GCIMSSpectrumSet.R | 12 ++++++++++++ ...test-getChromatogram_getSpectrum-GCIMSDataset.R | 14 ++++++++++++++ 7 files changed, 62 insertions(+), 6 deletions(-) diff --git a/R/aaa-class-GCIMSChromatogramSet.R b/R/aaa-class-GCIMSChromatogramSet.R index 3b6533ee..479becb2 100644 --- a/R/aaa-class-GCIMSChromatogramSet.R +++ b/R/aaa-class-GCIMSChromatogramSet.R @@ -71,6 +71,14 @@ GCIMSChromatogramSet <- function(chromatograms = list(), pData = NULL) { #' @return A character vector with the sample names #' @export setMethod("sampleNames", "GCIMSChromatogramSet", function(object) { + # pData is the source of truth for sample identity/order when present + # (initialize() already checked it refers to the same samples as + # `chromatograms`), so plot() and other consumers that iterate + # sampleNames() and look up chromatograms by name stay correct + # regardless of the order `chromatograms` happens to be stored in. + if (!is.null(object@pData)) { + return(as.character(object@pData[["SampleID"]])) + } nms <- names(object@chromatograms) if (is.null(nms)) character(0) else nms }) diff --git a/R/aaa-class-GCIMSSpectrumSet.R b/R/aaa-class-GCIMSSpectrumSet.R index d9a05719..32b63af8 100644 --- a/R/aaa-class-GCIMSSpectrumSet.R +++ b/R/aaa-class-GCIMSSpectrumSet.R @@ -71,6 +71,14 @@ GCIMSSpectrumSet <- function(spectra = list(), pData = NULL) { #' @return A character vector with the sample names #' @export setMethod("sampleNames", "GCIMSSpectrumSet", function(object) { + # pData is the source of truth for sample identity/order when present + # (initialize() already checked it refers to the same samples as + # `spectra`), so plot() and other consumers that iterate sampleNames() + # and look up spectra by name stay correct regardless of the order + # `spectra` happens to be stored in. + if (!is.null(object@pData)) { + return(as.character(object@pData[["SampleID"]])) + } nms <- names(object@spectra) if (is.null(nms)) character(0) else nms }) diff --git a/R/plot-GCIMSChromatogramSet.R b/R/plot-GCIMSChromatogramSet.R index 524e53d4..18ffe743 100644 --- a/R/plot-GCIMSChromatogramSet.R +++ b/R/plot-GCIMSChromatogramSet.R @@ -13,9 +13,10 @@ setMethod( if (length(sample_names) == 0) { cli_abort("Can't plot an empty GCIMSChromatogramSet") } - df <- dplyr::bind_rows(purrr::map2( - x@chromatograms, sample_names, - function(chrom, sample_id) { + df <- dplyr::bind_rows(purrr::map( + sample_names, + function(sample_id) { + chrom <- x[[sample_id]] data.frame( SampleID = sample_id, retention_time_s = rtime(chrom), diff --git a/R/plot-GCIMSSpectrumSet.R b/R/plot-GCIMSSpectrumSet.R index 6a525729..fdf9ab94 100644 --- a/R/plot-GCIMSSpectrumSet.R +++ b/R/plot-GCIMSSpectrumSet.R @@ -13,9 +13,10 @@ setMethod( if (length(sample_names) == 0) { cli_abort("Can't plot an empty GCIMSSpectrumSet") } - df <- dplyr::bind_rows(purrr::map2( - x@spectra, sample_names, - function(spec, sample_id) { + df <- dplyr::bind_rows(purrr::map( + sample_names, + function(sample_id) { + spec <- x[[sample_id]] data.frame( SampleID = sample_id, drift_time_ms = dtime(spec), diff --git a/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R b/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R index 170619a5..4e1bb4b6 100644 --- a/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R +++ b/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R @@ -62,3 +62,15 @@ test_that("GCIMSChromatogramSet rejects pData whose SampleID doesn't match the c pd_no_sampleid <- data.frame(Group = c("A", "B")) expect_error(GCIMSChromatogramSet(chromatograms = chroms, pData = pd_no_sampleid), "SampleID column") }) + +test_that("sampleNames() follows pData's row order, even when it differs from the chromatograms list order", { + chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2")) + pd_reversed <- data.frame(SampleID = c("s2", "s1"), Group = c("B", "A")) + + cs <- GCIMSChromatogramSet(chromatograms = chroms, pData = pd_reversed) + + expect_equal(sampleNames(cs), c("s2", "s1")) + # [[ is unaffected by pData order, it always looks up by name: + expect_identical(cs[["s1"]], chroms$s1) + expect_identical(cs[["s2"]], chroms$s2) +}) diff --git a/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R b/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R index 1496da19..8b294b53 100644 --- a/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R +++ b/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R @@ -62,3 +62,15 @@ test_that("GCIMSSpectrumSet rejects pData whose SampleID doesn't match the spect pd_no_sampleid <- data.frame(Group = c("A", "B")) expect_error(GCIMSSpectrumSet(spectra = spectra, pData = pd_no_sampleid), "SampleID column") }) + +test_that("sampleNames() follows pData's row order, even when it differs from the spectra list order", { + spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2")) + pd_reversed <- data.frame(SampleID = c("s2", "s1"), Group = c("B", "A")) + + ss <- GCIMSSpectrumSet(spectra = spectra, pData = pd_reversed) + + expect_equal(sampleNames(ss), c("s2", "s1")) + # [[ is unaffected by pData order, it always looks up by name: + expect_identical(ss[["s1"]], spectra$s1) + expect_identical(ss[["s2"]], spectra$s2) +}) diff --git a/tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R b/tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R index 694a292a..aaa5298d 100644 --- a/tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R +++ b/tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R @@ -60,6 +60,20 @@ test_that("plot(GCIMSChromatogramSet) colors by SampleID by default and combines expect_equal(range(p$data$retention_time_s[p$data$SampleID == "s2"]), c(1, 19)) }) +test_that("plot(GCIMSChromatogramSet) attributes each line to the right sample even when pData's row order differs from the chromatograms list order", { + chroms <- list( + s1 = GCIMSChromatogram(retention_time = 1:3, intensity = c(100, 100, 100)), + s2 = GCIMSChromatogram(retention_time = 1:3, intensity = c(1, 1, 1)) + ) + pd_reversed <- data.frame(SampleID = c("s2", "s1"), Group = c("B", "A")) + cs <- GCIMSChromatogramSet(chromatograms = chroms, pData = pd_reversed) + + p <- plot(cs) + + expect_equal(unique(p$data$intensity[p$data$SampleID == "s1"]), 100) + expect_equal(unique(p$data$intensity[p$data$SampleID == "s2"]), 1) +}) + test_that("plot(GCIMSChromatogramSet, color_by=) colors by an arbitrary pData column", { ds <- make_dataset_mismatched_axes() cs <- getChromatogram(ds) From 39734930210511a2fd73fd1298a3c005ee09686b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Jul 2026 10:02:34 +0000 Subject: [PATCH 4/5] Guarantee chromatograms/spectra and pData$SampleID stay aligned Replace the previous fix (sampleNames() preferring pData, plot() doing name-based lookups) with what was actually being asked for: enforce the invariant structurally, at the points where it can be violated. - initialize() now reorders pData's rows to match the chromatograms/ spectra list order right after validating they refer to the same samples, so the two are positionally aligned from construction onward (matched by SampleID, not blindly reordered, so annotations stay attached to the right sample). - Added a sampleNames<- replacement method for both classes, mirroring GCIMSDataset's own setter: renaming updates both the list names and pData$SampleID together, so they can never drift apart afterwards. sampleNames() goes back to simply returning names(chromatograms)/ names(spectra), since alignment is now guaranteed rather than special-cased in the getter. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019V3CHRGSzcEu3k6tpUFv56 --- NAMESPACE | 1 + R/aaa-class-GCIMSChromatogramSet.R | 36 +++++++++++---- R/aaa-class-GCIMSSpectrumSet.R | 36 +++++++++++---- man/GCIMSChromatogramSet-class.Rd | 10 +++++ man/GCIMSSpectrumSet-class.Rd | 10 +++++ .../test-aaa-class-GCIMSChromatogramSet.R | 44 ++++++++++++++++--- .../test-aaa-class-GCIMSSpectrumSet.R | 44 ++++++++++++++++--- 7 files changed, 155 insertions(+), 26 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 677e1de9..ceeb45e3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -76,6 +76,7 @@ exportMethods("baseline<-") exportMethods("description<-") exportMethods("intensity<-") exportMethods("peaks<-") +exportMethods("sampleNames<-") exportMethods(align) exportMethods(baseline) exportMethods(decimate) diff --git a/R/aaa-class-GCIMSChromatogramSet.R b/R/aaa-class-GCIMSChromatogramSet.R index 479becb2..ef641ef7 100644 --- a/R/aaa-class-GCIMSChromatogramSet.R +++ b/R/aaa-class-GCIMSChromatogramSet.R @@ -44,6 +44,9 @@ methods::setMethod( ) ) } + # Guarantee pData's row order matches the chromatograms order, so the + # two never need to be reconciled again afterwards (e.g. in plot()): + pData <- pData[match(names(chromatograms), as.character(pData[["SampleID"]])), , drop = FALSE] } .Object@chromatograms <- chromatograms .Object@pData <- pData @@ -71,18 +74,35 @@ GCIMSChromatogramSet <- function(chromatograms = list(), pData = NULL) { #' @return A character vector with the sample names #' @export setMethod("sampleNames", "GCIMSChromatogramSet", function(object) { - # pData is the source of truth for sample identity/order when present - # (initialize() already checked it refers to the same samples as - # `chromatograms`), so plot() and other consumers that iterate - # sampleNames() and look up chromatograms by name stay correct - # regardless of the order `chromatograms` happens to be stored in. - if (!is.null(object@pData)) { - return(as.character(object@pData[["SampleID"]])) - } nms <- names(object@chromatograms) if (is.null(nms)) character(0) else nms }) +#' @describeIn GCIMSChromatogramSet-class Set the sample names +#' @param object A [GCIMSChromatogramSet] object +#' @param value A character vector of length the number of chromatograms with the new sample names +#' @return The [GCIMSChromatogramSet] object, with samples renamed in both +#' `chromatograms` and `pData()` +#' @export +setReplaceMethod("sampleNames", "GCIMSChromatogramSet", function(object, value) { + if (length(value) != length(object@chromatograms)) { + cli_abort( + c( + "Invalid sample names", + "x" = "The number of sample names given ({length(value)}) != Number of samples ({length(object@chromatograms)})" + ) + ) + } + if (anyNA(value) || anyDuplicated(value)) { + cli_abort("Sample names must be unique and not missing") + } + names(object@chromatograms) <- value + if (!is.null(object@pData)) { + object@pData[["SampleID"]] <- value + } + object +}) + #' @describeIn GCIMSChromatogramSet-class Get the phenotype data #' @param object A [GCIMSChromatogramSet] object #' @return A tibble with the phenotype data, or `NULL` if not set diff --git a/R/aaa-class-GCIMSSpectrumSet.R b/R/aaa-class-GCIMSSpectrumSet.R index 32b63af8..cb5a51ec 100644 --- a/R/aaa-class-GCIMSSpectrumSet.R +++ b/R/aaa-class-GCIMSSpectrumSet.R @@ -44,6 +44,9 @@ methods::setMethod( ) ) } + # Guarantee pData's row order matches the spectra order, so the two + # never need to be reconciled again afterwards (e.g. in plot()): + pData <- pData[match(names(spectra), as.character(pData[["SampleID"]])), , drop = FALSE] } .Object@spectra <- spectra .Object@pData <- pData @@ -71,18 +74,35 @@ GCIMSSpectrumSet <- function(spectra = list(), pData = NULL) { #' @return A character vector with the sample names #' @export setMethod("sampleNames", "GCIMSSpectrumSet", function(object) { - # pData is the source of truth for sample identity/order when present - # (initialize() already checked it refers to the same samples as - # `spectra`), so plot() and other consumers that iterate sampleNames() - # and look up spectra by name stay correct regardless of the order - # `spectra` happens to be stored in. - if (!is.null(object@pData)) { - return(as.character(object@pData[["SampleID"]])) - } nms <- names(object@spectra) if (is.null(nms)) character(0) else nms }) +#' @describeIn GCIMSSpectrumSet-class Set the sample names +#' @param object A [GCIMSSpectrumSet] object +#' @param value A character vector of length the number of spectra with the new sample names +#' @return The [GCIMSSpectrumSet] object, with samples renamed in both +#' `spectra` and `pData()` +#' @export +setReplaceMethod("sampleNames", "GCIMSSpectrumSet", function(object, value) { + if (length(value) != length(object@spectra)) { + cli_abort( + c( + "Invalid sample names", + "x" = "The number of sample names given ({length(value)}) != Number of samples ({length(object@spectra)})" + ) + ) + } + if (anyNA(value) || anyDuplicated(value)) { + cli_abort("Sample names must be unique and not missing") + } + names(object@spectra) <- value + if (!is.null(object@pData)) { + object@pData[["SampleID"]] <- value + } + object +}) + #' @describeIn GCIMSSpectrumSet-class Get the phenotype data #' @param object A [GCIMSSpectrumSet] object #' @return A tibble with the phenotype data, or `NULL` if not set diff --git a/man/GCIMSChromatogramSet-class.Rd b/man/GCIMSChromatogramSet-class.Rd index c6a66ee2..f066b043 100644 --- a/man/GCIMSChromatogramSet-class.Rd +++ b/man/GCIMSChromatogramSet-class.Rd @@ -5,6 +5,7 @@ \name{GCIMSChromatogramSet-class} \alias{GCIMSChromatogramSet-class} \alias{sampleNames,GCIMSChromatogramSet-method} +\alias{sampleNames<-,GCIMSChromatogramSet,ANY-method} \alias{pData,GCIMSChromatogramSet-method} \alias{length,GCIMSChromatogramSet-method} \alias{[[,GCIMSChromatogramSet-method} @@ -13,6 +14,8 @@ \usage{ \S4method{sampleNames}{GCIMSChromatogramSet}(object) +\S4method{sampleNames}{GCIMSChromatogramSet,ANY}(object) <- value + \S4method{pData}{GCIMSChromatogramSet}(object) \S4method{length}{GCIMSChromatogramSet}(x) @@ -24,6 +27,8 @@ \arguments{ \item{object}{A \link{GCIMSChromatogramSet} object} +\item{value}{A character vector of length the number of chromatograms with the new sample names} + \item{x}{A \link{GCIMSChromatogramSet} object to plot} \item{i}{A number or a string with the sample index or name} @@ -36,6 +41,9 @@ color the chromatograms} \value{ A character vector with the sample names +The \link{GCIMSChromatogramSet} object, with samples renamed in both +\code{chromatograms} and \code{pData()} + A tibble with the phenotype data, or \code{NULL} if not set An integer with the number of chromatograms @@ -57,6 +65,8 @@ interpolation is performed. \itemize{ \item \code{sampleNames(GCIMSChromatogramSet)}: Get the sample names +\item \code{sampleNames(object = GCIMSChromatogramSet) <- value}: Set the sample names + \item \code{pData(GCIMSChromatogramSet)}: Get the phenotype data \item \code{length(GCIMSChromatogramSet)}: Number of chromatograms (samples) in the set diff --git a/man/GCIMSSpectrumSet-class.Rd b/man/GCIMSSpectrumSet-class.Rd index 22cc7ad5..005dce15 100644 --- a/man/GCIMSSpectrumSet-class.Rd +++ b/man/GCIMSSpectrumSet-class.Rd @@ -5,6 +5,7 @@ \name{GCIMSSpectrumSet-class} \alias{GCIMSSpectrumSet-class} \alias{sampleNames,GCIMSSpectrumSet-method} +\alias{sampleNames<-,GCIMSSpectrumSet,ANY-method} \alias{pData,GCIMSSpectrumSet-method} \alias{length,GCIMSSpectrumSet-method} \alias{[[,GCIMSSpectrumSet-method} @@ -13,6 +14,8 @@ \usage{ \S4method{sampleNames}{GCIMSSpectrumSet}(object) +\S4method{sampleNames}{GCIMSSpectrumSet,ANY}(object) <- value + \S4method{pData}{GCIMSSpectrumSet}(object) \S4method{length}{GCIMSSpectrumSet}(x) @@ -24,6 +27,8 @@ \arguments{ \item{object}{A \link{GCIMSSpectrumSet} object} +\item{value}{A character vector of length the number of spectra with the new sample names} + \item{x}{A \link{GCIMSSpectrumSet} object to plot} \item{i}{A number or a string with the sample index or name} @@ -36,6 +41,9 @@ color the spectra} \value{ A character vector with the sample names +The \link{GCIMSSpectrumSet} object, with samples renamed in both +\code{spectra} and \code{pData()} + A tibble with the phenotype data, or \code{NULL} if not set An integer with the number of spectra @@ -57,6 +65,8 @@ performed. \itemize{ \item \code{sampleNames(GCIMSSpectrumSet)}: Get the sample names +\item \code{sampleNames(object = GCIMSSpectrumSet) <- value}: Set the sample names + \item \code{pData(GCIMSSpectrumSet)}: Get the phenotype data \item \code{length(GCIMSSpectrumSet)}: Number of spectra (samples) in the set diff --git a/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R b/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R index 4e1bb4b6..90404b8c 100644 --- a/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R +++ b/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R @@ -63,14 +63,48 @@ test_that("GCIMSChromatogramSet rejects pData whose SampleID doesn't match the c expect_error(GCIMSChromatogramSet(chromatograms = chroms, pData = pd_no_sampleid), "SampleID column") }) -test_that("sampleNames() follows pData's row order, even when it differs from the chromatograms list order", { +test_that("construction realigns pData's row order to match the chromatograms list order", { chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2")) + # pData given in the opposite order, with annotations tied to SampleID: pd_reversed <- data.frame(SampleID = c("s2", "s1"), Group = c("B", "A")) cs <- GCIMSChromatogramSet(chromatograms = chroms, pData = pd_reversed) - expect_equal(sampleNames(cs), c("s2", "s1")) - # [[ is unaffected by pData order, it always looks up by name: - expect_identical(cs[["s1"]], chroms$s1) - expect_identical(cs[["s2"]], chroms$s2) + expect_equal(sampleNames(cs), c("s1", "s2")) + expect_equal(pData(cs)$SampleID, c("s1", "s2")) + # Annotations stay correctly attached to their sample after realignment: + expect_equal(pData(cs)$Group, c("A", "B")) +}) + +test_that("sampleNames<-() renames both the chromatograms list and pData$SampleID", { + chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2")) + pd <- data.frame(SampleID = c("s1", "s2"), Group = c("A", "B")) + cs <- GCIMSChromatogramSet(chromatograms = chroms, pData = pd) + + sampleNames(cs) <- c("a", "b") + + expect_equal(sampleNames(cs), c("a", "b")) + expect_equal(pData(cs)$SampleID, c("a", "b")) + expect_equal(pData(cs)$Group, c("A", "B")) + expect_identical(cs[["a"]], chroms$s1) + expect_identical(cs[["b"]], chroms$s2) +}) + +test_that("sampleNames<-() works without pData", { + chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2")) + cs <- GCIMSChromatogramSet(chromatograms = chroms) + + sampleNames(cs) <- c("a", "b") + + expect_equal(sampleNames(cs), c("a", "b")) + expect_null(pData(cs)) +}) + +test_that("sampleNames<-() rejects the wrong length or non-unique/missing names", { + chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2")) + cs <- GCIMSChromatogramSet(chromatograms = chroms) + + expect_error(sampleNames(cs) <- "a", "Number of samples") + expect_error(sampleNames(cs) <- c("a", "a"), "unique") + expect_error(sampleNames(cs) <- c("a", NA), "unique and not missing") }) diff --git a/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R b/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R index 8b294b53..02c88687 100644 --- a/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R +++ b/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R @@ -63,14 +63,48 @@ test_that("GCIMSSpectrumSet rejects pData whose SampleID doesn't match the spect expect_error(GCIMSSpectrumSet(spectra = spectra, pData = pd_no_sampleid), "SampleID column") }) -test_that("sampleNames() follows pData's row order, even when it differs from the spectra list order", { +test_that("construction realigns pData's row order to match the spectra list order", { spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2")) + # pData given in the opposite order, with annotations tied to SampleID: pd_reversed <- data.frame(SampleID = c("s2", "s1"), Group = c("B", "A")) ss <- GCIMSSpectrumSet(spectra = spectra, pData = pd_reversed) - expect_equal(sampleNames(ss), c("s2", "s1")) - # [[ is unaffected by pData order, it always looks up by name: - expect_identical(ss[["s1"]], spectra$s1) - expect_identical(ss[["s2"]], spectra$s2) + expect_equal(sampleNames(ss), c("s1", "s2")) + expect_equal(pData(ss)$SampleID, c("s1", "s2")) + # Annotations stay correctly attached to their sample after realignment: + expect_equal(pData(ss)$Group, c("A", "B")) +}) + +test_that("sampleNames<-() renames both the spectra list and pData$SampleID", { + spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2")) + pd <- data.frame(SampleID = c("s1", "s2"), Group = c("A", "B")) + ss <- GCIMSSpectrumSet(spectra = spectra, pData = pd) + + sampleNames(ss) <- c("a", "b") + + expect_equal(sampleNames(ss), c("a", "b")) + expect_equal(pData(ss)$SampleID, c("a", "b")) + expect_equal(pData(ss)$Group, c("A", "B")) + expect_identical(ss[["a"]], spectra$s1) + expect_identical(ss[["b"]], spectra$s2) +}) + +test_that("sampleNames<-() works without pData", { + spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2")) + ss <- GCIMSSpectrumSet(spectra = spectra) + + sampleNames(ss) <- c("a", "b") + + expect_equal(sampleNames(ss), c("a", "b")) + expect_null(pData(ss)) +}) + +test_that("sampleNames<-() rejects the wrong length or non-unique/missing names", { + spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2")) + ss <- GCIMSSpectrumSet(spectra = spectra) + + expect_error(sampleNames(ss) <- "a", "Number of samples") + expect_error(sampleNames(ss) <- c("a", "a"), "unique") + expect_error(sampleNames(ss) <- c("a", NA), "unique and not missing") }) From 80576e82e9571bf08deb545b7df9e8e687fd9c16 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Jul 2026 10:59:56 +0000 Subject: [PATCH 5/5] Fix R CMD check WARNING: plot() docs claimed a y param that doesn't exist CI (PR #53) failed R CMD check with: checking Rd \usage sections ... WARNING Undocumented arguments in Rd file 'GCIMSChromatogramSet-class.Rd': '...' Documented arguments not in \usage in Rd file 'GCIMSChromatogramSet-class.Rd': 'y' (same for GCIMSSpectrumSet-class.Rd) plot()'s roxygen block was copy-pasted from plot-GCIMSChromatogram.R, which documents a `y` param inherited from the base plot generic's signature. But that method is function(x, ...), so roxygen renders its \usage using the generic's own (x, y, ...) signature, whereas my method is function(x, color_by = "SampleID", ...) -- an actual extra parameter, which makes roxygen use the method's own formals verbatim, with no y in it. Replaced the stale `@param y` with `@param ... Ignored`, which now matches the generated \S4method{plot}{...}(x, color_by = "SampleID", ...) usage exactly. Verified locally with rcmdcheck: `checking Rd \usage sections` is back to a NOTE (the one pre-existing, unrelated findPeaksImpl.Rd issue that already exists on master), not a WARNING. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019V3CHRGSzcEu3k6tpUFv56 --- R/plot-GCIMSChromatogramSet.R | 2 +- R/plot-GCIMSSpectrumSet.R | 2 +- man/GCIMSChromatogramSet-class.Rd | 2 +- man/GCIMSSpectrumSet-class.Rd | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/plot-GCIMSChromatogramSet.R b/R/plot-GCIMSChromatogramSet.R index 18ffe743..ed2472a2 100644 --- a/R/plot-GCIMSChromatogramSet.R +++ b/R/plot-GCIMSChromatogramSet.R @@ -2,7 +2,7 @@ #' @param x A [GCIMSChromatogramSet] object to plot #' @param color_by The name of a `pData(x)` column (or `"SampleID"`) used to #' color the chromatograms -#' @param y From the generic `plot` function, ignored for `GCIMSChromatogramSet` class objects +#' @param ... Ignored #' @return A ggplot2 plot object #' @export setMethod( diff --git a/R/plot-GCIMSSpectrumSet.R b/R/plot-GCIMSSpectrumSet.R index fdf9ab94..2fb9cc05 100644 --- a/R/plot-GCIMSSpectrumSet.R +++ b/R/plot-GCIMSSpectrumSet.R @@ -2,7 +2,7 @@ #' @param x A [GCIMSSpectrumSet] object to plot #' @param color_by The name of a `pData(x)` column (or `"SampleID"`) used to #' color the spectra -#' @param y From the generic `plot` function, ignored for `GCIMSSpectrumSet` class objects +#' @param ... Ignored #' @return A ggplot2 plot object #' @export setMethod( diff --git a/man/GCIMSChromatogramSet-class.Rd b/man/GCIMSChromatogramSet-class.Rd index f066b043..2b5f82d9 100644 --- a/man/GCIMSChromatogramSet-class.Rd +++ b/man/GCIMSChromatogramSet-class.Rd @@ -36,7 +36,7 @@ \item{color_by}{The name of a \code{pData(x)} column (or \code{"SampleID"}) used to color the chromatograms} -\item{y}{From the generic \code{plot} function, ignored for \code{GCIMSChromatogramSet} class objects} +\item{...}{Ignored} } \value{ A character vector with the sample names diff --git a/man/GCIMSSpectrumSet-class.Rd b/man/GCIMSSpectrumSet-class.Rd index 005dce15..6687177a 100644 --- a/man/GCIMSSpectrumSet-class.Rd +++ b/man/GCIMSSpectrumSet-class.Rd @@ -36,7 +36,7 @@ \item{color_by}{The name of a \code{pData(x)} column (or \code{"SampleID"}) used to color the spectra} -\item{y}{From the generic \code{plot} function, ignored for \code{GCIMSSpectrumSet} class objects} +\item{...}{Ignored} } \value{ A character vector with the sample names