Repository: rformassspectrometry/QFeatures
Pull request #241: Long form
Author: login: lgatto
Created: 2025-05-17T09:56:29Z | Merged: 2025-05-21T09:05:34Z



Diff:
diff --git a/.github/workflows/check-bioc.yml b/.github/workflows/check-bioc.yml
index db3020af..67310865 100644
--- a/.github/workflows/check-bioc.yml
+++ b/.github/workflows/check-bioc.yml
@@ -224,13 +224,13 @@ jobs:
           cd preprocessCore
           R CMD INSTALL --configure-args="--disable-threading"  .
 
-      - name: Install MsDataHub from Github (>= 1.3.3)
-        run: BiocManager::install("RforMassSpectrometry/MsDataHub")
-        shell: Rscript {0}
+      ## - name: Install MsDataHub from Github (>= 1.3.3)
+      ##   run: BiocManager::install("RforMassSpectrometry/MsDataHub")
+      ##   shell: Rscript {0}
 
-      - name: Install ProtGenerics from Github (>= 1.35.1)
-        run: BiocManager::install("RforMassSpectrometry/ProtGenerics")
-        shell: Rscript {0}
+      ## - name: Install ProtGenerics from Github (>= 1.35.1)
+      ##   run: BiocManager::install("RforMassSpectrometry/ProtGenerics")
+      ##   shell: Rscript {0}
 
       - name: Session info
         run: |
diff --git a/DESCRIPTION b/DESCRIPTION
index 31db25a2..f1c0c149 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -18,7 +18,7 @@ Description: The QFeatures infrastructure enables the management and
    experience to manages quantitative data across different assay
    levels (such as peptide spectrum matches, peptides and proteins) in
    a coherent and tractable format.
-Depends: R (>= 4.0), MultiAssayExperiment
+Depends: R (>= 4.1), MultiAssayExperiment (>= 1.33.6)
 Imports:
    methods,
    stats,
@@ -26,7 +26,7 @@ Imports:
    S4Vectors,
    IRanges,
    SummarizedExperiment,
-   BiocGenerics,
+   BiocGenerics (>= 0.53.4),
    ProtGenerics (>= 1.35.1),
    AnnotationFilter,
    lazyeval,
@@ -74,6 +74,7 @@ Collate:
     'AssayLinks.R'
     'QFeatures-class.R'
     'QFeatures-functions.R'
+    'QFeatures-longForm.R'
     'QFeatures-processing.R'
     'QFeatures-filter.R'
     'QFeatures-missing-data.R'
diff --git a/NAMESPACE b/NAMESPACE
index 88593aab..e6e2fb70 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -47,6 +47,7 @@ exportMethods(filterNA)
 exportMethods(impute)
 exportMethods(infIsNA)
 exportMethods(logTransform)
+exportMethods(longForm)
 exportMethods(nNA)
 exportMethods(ncols)
 exportMethods(normalize)
@@ -66,6 +67,7 @@ import(SummarizedExperiment)
 importFrom(Biobase,fData)
 importFrom(BiocGenerics,dims)
 importFrom(BiocGenerics,do.call)
+importFrom(BiocGenerics,longForm)
 importFrom(BiocGenerics,ncols)
 importFrom(BiocGenerics,normalize)
 importFrom(BiocGenerics,nrows)
@@ -74,7 +76,7 @@ importFrom(MsCoreUtils,aggregate_by_vector)
 importFrom(MsCoreUtils,colCounts)
 importFrom(MsCoreUtils,impute_matrix)
 importFrom(MsCoreUtils,robustSummary)
-importFrom(MultiAssayExperiment,longFormat)
+importFrom(MultiAssayExperiment,longForm)
 importFrom(ProtGenerics,adjacencyMatrix)
 importFrom(grDevices,rgb)
 importFrom(igraph,V)
diff --git a/NEWS.md b/NEWS.md
index 3f38b5cb..74fda45a 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,9 +1,12 @@
 # QFeatures 1.19
 
-
 ## QFeatures 1.19.1
 
 - Fix duplicated fnames bug (see issue #237).
+- Use `longForm()` methods (replacing `longFormat()`, now defunct).
+- Add `longForm,SummarizedExperiment`, supporting `rowvars` and
+  `colvars`, as `longForm,QFeatures` does. Without this method, uses
+  `longForm,ANY` that doesn't.
 
 ## QFeatures 1.19.0
 
diff --git a/R/QFeatures-class.R b/R/QFeatures-class.R
index 4d1a4530..1a0ec2f7 100644
--- a/R/QFeatures-class.R
+++ b/R/QFeatures-class.R
@@ -69,11 +69,9 @@
 ##' - The `rowDataNames` accessor returns a list with the `rowData`
 ##'   variable names.
 ##'
-##' - The `longFormat` accessor takes a `QFeatures` object and returns
-##'   it in a long format `DataFrame`. Each quantitative value is
-##'   reported on a separate line. `colData` and `rowData` data can
-##'   also be added. This function is an extension of the `longFormat`
-##'   function in the [MultiAssayExperiment::MultiAssayExperiment].
+##' - The [longForm()] accessor takes a `QFeatures` instance and returns it in a
+##'   long *tidy* `DataFrame`, where each quantitative value is reported on a
+##'   separate line.
 ##'
 ##' @section Adding, removing and replacing assays:
 ##'
@@ -153,6 +151,9 @@
 ##' @param drop logical (default `TRUE`) whether to drop empty assay
 ##'     elements in the `ExperimentList`.
 ##'
+##' @param rowvars A `character()` with the names of the `rowData`
+##'     variables (columns) to retain in any assay.
+##'
 ##' @param ... See `MultiAssayExperiment` for details. For `plot`,
 ##'     further arguments passed to `igraph::plot.igraph`.
 ##'
@@ -181,7 +182,6 @@
 ##' @aliases addAssay
 ##' @aliases dims,QFeatures-method show,QFeatures-method
 ##' @aliases [,QFeatures,ANY,ANY,ANY-method [,QFeatures,character,ANY,ANY-method
-##'
 ##' @aliases rowDataNames selectRowData
 ##'
 ##' @rdname QFeatures-class
@@ -732,9 +732,6 @@ rbindRowData <- function(object, i)  {
 
 ##' @rdname QFeatures-class
 ##'
-##' @param rowvars A `character()` with the names of the `rowData`
-##'     variables (columns) to retain in any assay.
-##'
 ##' @export
 selectRowData <- function(x, rowvars) {
     stopifnot(inherits(x, "QFeatures"))
@@ -787,49 +784,6 @@ setReplaceMethod("names", c("QFeatures", "character"),
                      x
                  })
 
-
-##' @rdname QFeatures-class
-##'
-##' @param colvars A `character()` that selects column(s) in the
-##'     `colData`.
-##' @param index The assay indicator within each `SummarizedExperiment`
-##'     object. A vector input is supported in the case that the
-##'     `SummarizedExperiment` object(s) has more than one assay
-##'     (default `1L`)
-##'
-##' @importFrom MultiAssayExperiment longFormat
-##' @importFrom reshape2 melt
-##'
-##' @export
-longFormat <- function(object,
-                       colvars = NULL,
-                       rowvars = NULL,
-                       index = 1L) {
-    if (!is.null(rowvars)) {
-        rdNames <- rowDataNames(object)
-        misNames <- sapply(rdNames,
-                           function (x) any(!rowvars %in% x))
-        ## Check that all required
-        if (any(misNames))
-            stop("Some 'rowvars' not found in assay(s): ",
-                 paste0(names(misNames)[misNames], collapse = ", "))
-        ## Get long format table with quantification values and colvars
-        longDataFrame <-
-            MultiAssayExperiment::longFormat(object, colvars, index)
-        ## Get the required rowData
-        rds <- lapply(rowData(object),
-                      function(rd) rd[, rowvars, drop = FALSE])
-        rds <- do.call(rbind, rds)
-        ## Merge the rowData to the long table
-        cbind(longDataFrame,
-              rds[as.character(longDataFrame$rowname), , drop = FALSE])
-    } else {
-        ## If rowvars is null, return the MAE longFormat output
-        MultiAssayExperiment::longFormat(object, colvars, index)
-    }
-}
-
-
 ##' @param y An object that inherits from `SummarizedExperiment` or a
 ##'     *named* list of assays. When `y` is a list, each element must
 ##'     inherit from a `SummarizedExperiment` and the names of the
@@ -1183,4 +1137,4 @@ dropEmptyAssays <- function(object, dims = 1:2) {
         object <- object[, , ncols(object) > 0]
     if (!length(object)) return(QFeatures())
     object
-}
+}
\ No newline at end of file
diff --git a/R/QFeatures-longForm.R b/R/QFeatures-longForm.R
new file mode 100644
index 00000000..f846bac1
--- /dev/null
+++ b/R/QFeatures-longForm.R
@@ -0,0 +1,182 @@
+##' @title Reshape into a long data format
+##'
+##' @description
+##'
+##' The `longForm()` method transform a [QFeatures] or [SummarizedExperiment]
+##' instance into a long *tidy* [DataFrame] that contains the assay data, where
+##' each quantitative value is reported on a separate line. `colData` and
+##' `rowData` varibales can also be added. This function is an extension of the
+##' `longForm()` method in the [MultiAssayExperiment::MultiAssayExperiment].
+##'
+##' Note that the previous `longFormat` implementation is not defunct.
+##'
+##' @param object An instance of class [QFeatures] or [SummarizedExperiment].
+##'
+##' @param colvars A `character()` that selects column(s) in the `colData`.
+##'
+##' @param rowvars A `character()` with the names of the `rowData`
+##'     variables (columns) to retain in any assay.
+##'
+##' @param i When `object` is an instance of class `QFeatures`, a `numeric(1)`
+##'     indicating what assay within each `SummarizedExperiment` object to
+##'     return. Default is `1L`. If `object` is a `SummarizedExperiment`, a
+##'     `numeric()` indicating what assays to pull and convert. Default is to
+##'     use all assays.
+##'
+##' @return A `DataFrame` instance.
+##'
+##' @rdname QFeatures-longForm
+##'
+##' @importFrom MultiAssayExperiment longForm
+##' @importFrom reshape2 melt
+##'
+##' @importFrom BiocGenerics longForm
+##'
+##' @exportMethod longForm
+##'
+##' @aliases longForm longForm,QFeatures
+##' @aliases longFormat
+##'
+##' @examples
+##'
+##' data(feat2)
+##'
+##' longForm(feat2)
+##'
+##' ## add a colData variable and use it in longFrom
+##' colData(feat2)$colvar <- paste0("Var", 1:12)
+##' colData(feat2)
+##' longForm(feat2, colvars = "colvar")
+##'
+##' ## use a rowData variable in longFrom
+##' rowDataNames(feat2)
+##' longForm(feat2, rowvar = "Prot")
+##'
+##' ## use both col/rowData
+##' longForm(feat2, colvar = "colvar", rowvar = "Prot")
+##'
+##' ## also works on a single SE
+##' se <- getWithColData(feat2, 1)
+##' longForm(se)
+##' longForm(se, colvar = "colvar")
+##' longForm(se, rowvar = "Prot")
+##' longForm(se, colvar = "colvar", rowvar = "Prot")
+
+setMethod("longForm", "QFeatures",
+          function(object, colvars = NULL,
+                   rowvars = NULL,
+                   i = 1L)
+              longFormQFeatures(object, colvars, rowvars, i))
+
+##' @rdname QFeatures-longForm
+##'
+##' @exportMethod longForm
+##'
+##' @aliases longForm,SummarizedExperiment
+setMethod("longForm", "SummarizedExperiment",
+          function(object, colvars = NULL,
+                   rowvars = NULL,
+                   i = seq_along(assays(object)))
+              longFormSE(object, colvars, rowvars, i))
+
+longFormQFeatures <- function(object, colvars = NULL,
+                              rowvars = NULL,
+                              i = 1L) {
+    if (length(i) > 1) {
+        warning("'i' must be of length 1 - using first element.")
+        i <- i[1]
+    }
+    if (!is.null(rowvars)) {
+        rdNames <- rowDataNames(object)
+        misNames <- sapply(rdNames,
+                           function (x) any(!rowvars %in% x))
+        ## Check that all required
+        if (any(misNames))
+            stop("Some 'rowvars' not found in assay(s): ",
+                 paste0(names(misNames)[misNames], collapse = ", "))
+        ## Get long format table with quantification values and colvars
+        longDataFrame <-
+            MultiAssayExperiment::longForm(
+                                      as(object, "MultiAssayExperiment"),
+                                      colDataCols = colvars,
+                                      i = i)
+        ## Get the required rowData
+        rds <- lapply(rowData(object),
+                      function(rd) rd[, rowvars, drop = FALSE])
+        rds <- do.call(rbind, rds)
+        ## Merge the rowData to the long table
+        cbind(longDataFrame,
+              rds[as.character(longDataFrame$rowname), , drop = FALSE])
+    } else {
+        ## If rowvars is null, return the MAE longForm output
+        MultiAssayExperiment::longForm(
+                                  as(object, "MultiAssayExperiment"),
+                                  colDataCols = colvars,
+                                  i = i)
+    }
+}
+
+
+##' @importFrom reshape2 melt
+longFormSE <- function(object, colvars = NULL, rowvars = NULL,
+                       i = seq_along(assays(object))) {
+    ## Check that indices are within bounds
+    if (max(i) > length(assays(object)) | min(i) < 1)
+        stop("Argument 'i' out of (assay) bounds.")
+    ## Check that all colvars exist
+    if (!is.null(colvars)) {
+        if (!all(colvars %in% names(colData(object))))
+            stop("Some 'colvars' not found in colData(.).")
+    }
+    ## Check that all rowvars exist
+    if (!is.null(rowvars)) {
+        if (!all(rowvars %in% names(rowData(object))))
+            stop("Some 'rowvars' not found in rowData(.).")
+    }
+    ## Need names for the assayNames columns. If the object's assays don't have
+    ## any names, use the index i set above.
+    if (is.null(nms <- assayNames(object)))
+        nms <- i
+    res <- lapply(seq_along(i),
+                  function(ii) {
+                      ans <- reshape2::melt(assay(object, i[ii]),
+                                            varnames = c("rowname", "colname"),
+                                            value.name = "value",
+                                            as.is = TRUE)
+                      ans$assayName <- nms[ii]
+                      rownames(ans) <- NULL
+                      ans
+                  })
+    res <- do.call(rbind, res)
+    if (!is.null(colvars)) { ## Add colData variables.
+        ## Need object to have colnames
+        if (is.null(colnames(object)))
+            colnames(object) <- seq_len(ncol(object))
+        cd <- colData(object)[as.character(res$colname),
+                              colvars,
+                              drop = FALSE]
+        rownames(cd) <- NULL
+        res <- cbind(res, cd)
+    }
+    if (!is.null(rowvars)) { ## Add rowData variables
+        ## Need object to have rownames
+        if (is.null(rownames(object)))
+            rownames(object) <- seq_len(nrow(object))
+        rd <- rowData(object)[as.character(res$rowname),
+                              rowvars,
+                              drop = FALSE]
+        rownames(rd) <- NULL
+        res <- cbind(res, rd)
+    }
+    as(res, "DataFrame")
+}
+
+##' @noRd
+##'
+##' @export
+longFormat <- function(object,
+                       colvars = NULL,
+                       rowvars = NULL,
+                       i = 1L) {
+    .Defunct("longForm")
+}
\ No newline at end of file
diff --git a/man/QFeatures-class.Rd b/man/QFeatures-class.Rd
index e833c332..9067dc1a 100644
--- a/man/QFeatures-class.Rd
+++ b/man/QFeatures-class.Rd
@@ -22,7 +22,6 @@
 \alias{rowData<-,QFeatures,ANY-method}
 \alias{rbindRowData}
 \alias{names<-,QFeatures,character-method}
-\alias{longFormat}
 \alias{removeAssay}
 \alias{replaceAssay}
 \alias{[[<-,QFeatures,ANY,ANY-method}
@@ -62,8 +61,6 @@ rowDataNames(x)
 
 \S4method{names}{QFeatures,character}(x) <- value
 
-longFormat(object, colvars = NULL, rowvars = NULL, index = 1L)
-
 addAssay(x, y, name, assayLinks)
 
 removeAssay(x, i)
@@ -112,14 +109,6 @@ corresponding section in the documentation for more details.}
 \item{rowvars}{A \code{character()} with the names of the \code{rowData}
 variables (columns) to retain in any assay.}
 
-\item{colvars}{A \code{character()} that selects column(s) in the
-\code{colData}.}
-
-\item{index}{The assay indicator within each \code{SummarizedExperiment}
-object. A vector input is supported in the case that the
-\code{SummarizedExperiment} object(s) has more than one assay
-(default \code{1L})}
-
 \item{y}{An object that inherits from \code{SummarizedExperiment} or a
 \emph{named} list of assays. When \code{y} is a list, each element must
 inherit from a \code{SummarizedExperiment} and the names of the
@@ -205,11 +194,9 @@ assays. In this context, \code{i} is a \code{character()}, \code{integer()} or
 that are common to all assays are kept.
 \item The \code{rowDataNames} accessor returns a list with the \code{rowData}
 variable names.
-\item The \code{longFormat} accessor takes a \code{QFeatures} object and returns
-it in a long format \code{DataFrame}. Each quantitative value is
-reported on a separate line. \code{colData} and \code{rowData} data can
-also be added. This function is an extension of the \code{longFormat}
-function in the \link[MultiAssayExperiment:MultiAssayExperiment]{MultiAssayExperiment::MultiAssayExperiment}.
+\item The \code{\link[=longForm]{longForm()}} accessor takes a \code{QFeatures} instance and returns it in a
+long \emph{tidy} \code{DataFrame}, where each quantitative value is reported on a
+separate line.
 }
 }
 
diff --git a/man/QFeatures-longForm.Rd b/man/QFeatures-longForm.Rd
new file mode 100644
index 00000000..a19df35e
--- /dev/null
+++ b/man/QFeatures-longForm.Rd
@@ -0,0 +1,66 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/QFeatures-longForm.R
+\name{longForm,QFeatures-method}
+\alias{longForm,QFeatures-method}
+\alias{longForm}
+\alias{longForm,QFeatures}
+\alias{longFormat}
+\alias{longForm,SummarizedExperiment-method}
+\alias{longForm,SummarizedExperiment}
+\title{Reshape into a long data format}
+\usage{
+\S4method{longForm}{QFeatures}(object, colvars = NULL, rowvars = NULL, i = 1L)
+
+\S4method{longForm}{SummarizedExperiment}(object, colvars = NULL, rowvars = NULL, i = seq_along(assays(object)))
+}
+\arguments{
+\item{object}{An instance of class \link{QFeatures} or \link{SummarizedExperiment}.}
+
+\item{colvars}{A \code{character()} that selects column(s) in the \code{colData}.}
+
+\item{rowvars}{A \code{character()} with the names of the \code{rowData}
+variables (columns) to retain in any assay.}
+
+\item{i}{When \code{object} is an instance of class \code{QFeatures}, a \code{numeric(1)}
+indicating what assay within each \code{SummarizedExperiment} object to
+return. Default is \code{1L}. If \code{object} is a \code{SummarizedExperiment}, a
+\code{numeric()} indicating what assays to pull and convert. Default is to
+use all assays.}
+}
+\value{
+A \code{DataFrame} instance.
+}
+\description{
+The \code{longForm()} method transform a \link{QFeatures} or \link{SummarizedExperiment}
+instance into a long \emph{tidy} \link{DataFrame} that contains the assay data, where
+each quantitative value is reported on a separate line. \code{colData} and
+\code{rowData} varibales can also be added. This function is an extension of the
+\code{longForm()} method in the \link[MultiAssayExperiment:MultiAssayExperiment]{MultiAssayExperiment::MultiAssayExperiment}.
+
+Note that the previous \code{longFormat} implementation is not defunct.
+}
+\examples{
+
+data(feat2)
+
+longForm(feat2)
+
+## add a colData variable and use it in longFrom
+colData(feat2)$colvar <- paste0("Var", 1:12)
+colData(feat2)
+longForm(feat2, colvars = "colvar")
+
+## use a rowData variable in longFrom
+rowDataNames(feat2)
+longForm(feat2, rowvar = "Prot")
+
+## use both col/rowData
+longForm(feat2, colvar = "colvar", rowvar = "Prot")
+
+## also works on a single SE
+se <- getWithColData(feat2, 1)
+longForm(se)
+longForm(se, colvar = "colvar")
+longForm(se, rowvar = "Prot")
+longForm(se, colvar = "colvar", rowvar = "Prot")
+}
diff --git a/tests/testthat/test_Features.R b/tests/testthat/test_Features.R
index 6d9e4787..2bf8424b 100644
--- a/tests/testthat/test_Features.R
+++ b/tests/testthat/test_Features.R
@@ -823,38 +823,6 @@ test_that("assays must have unique rownames", {
 })
 
 
-test_that("longFormat", {
-    colData(feat2)$X <- 1:12
-    ## Select a single colvars and rowvars
-    lt <- longFormat(feat2, rowvars = "Prot", colvars = "X")
-    ## Check dimensions
-    expect_equal(nrow(lt),
-                 sum(apply(dims(feat2), 2, prod)))
-    expect_identical(ncol(lt),
-                     5L+2L)
-    ## Check content
-    expect_identical(lt$Prot,
-                     unname(unlist(lapply(rowData(feat2),
-                                          ## Repeat 4x because 4 samples
-                                          function(x) rep(x$Prot, 4)))))
-    ## Select a single colvars and no rowvars (make sure that
-    ## the implementation does not break the MAE implementation)
-    lt <- longFormat(feat2, colvars = "X")
-    expect_equal(nrow(lt),
-                 sum(apply(dims(feat2), 2, prod)))
-    expect_identical(ncol(lt),
-                     5L+1L)
-    ## Select multiple rowvars
-    lt <- longFormat(feat2, rowvars = c("Prot", "x"))
-    expect_equal(nrow(lt),
-                 sum(apply(dims(feat2), 2, prod)))
-    expect_identical(ncol(lt),
-                     5L+2L)
-    ## Test error: rowvars is missing in rowData
-    expect_error(longFormat(feat2, rowvars = "y"),
-                 regexp = "not found")
-})
-
 test_that("dropEmptyAssays", {
     ## Object is not of class QFeatures = error
     expect_error(
diff --git a/tests/testthat/test_api.R b/tests/testthat/test_api.R
index 68b7db04..c6a4c316 100644
--- a/tests/testthat/test_api.R
+++ b/tests/testthat/test_api.R
@@ -1,21 +1,19 @@
-## version 1.15.1
-api <- c("addAssay", "addAssayLink", "addAssayLinkOneToOne",
-         "adjacencyMatrix", "adjacencyMatrix<-", "aggcounts",
-         "aggregateFeatures", "assayLink", "AssayLink", "assayLinks",
-         "AssayLinks", "coerce", "countUniqueFeatures", "dims",
-         "display", "dropEmptyAssays", "expandDataFrame",
-         "filterFeatures", "filterNA", "impute", "infIsNA",
-         "joinAssays", "logTransform", "longFormat", "ncols", "nNA",
-         "normalize", "nrows", "QFeatures", "rbindRowData",
-         "readQFeatures", "readQFeaturesFromDIANN",
-         "readSummarizedExperiment", "reduceDataFrame", "removeAssay",
-         "replaceAssay", "rowData<-", "rowDataNames",
-         "scaleTransform", "selectRowData", "show", "subsetByFeature",
-         "sweep", "unfoldDataFrame", "updateObject", "VariableFilter",
-         "zeroIsNA")
+## version 1.17.2
+api <- c("addAssay", "addAssayLink", "addAssayLinkOneToOne", "adjacencyMatrix",
+         "adjacencyMatrix<-", "aggcounts", "aggregateFeatures", "assayLink",
+         "AssayLink", "assayLinks", "AssayLinks", "coerce",
+         "countUniqueFeatures", "dims", "display", "dropEmptyAssays",
+         "expandDataFrame", "filterFeatures", "filterNA", "impute", "infIsNA",
+         "joinAssays", "logTransform", "longFormat", "longForm", "ncols", "nNA",
+         "normalize", "nrows", "QFeatures", "rbindRowData", "readQFeatures",
+         "readQFeaturesFromDIANN", "readSummarizedExperiment",
+         "reduceDataFrame", "removeAssay", "replaceAssay", "rowData<-",
+         "rowDataNames", "scaleTransform", "selectRowData", "show",
+         "subsetByFeature", "sweep", "unfoldDataFrame", "updateObject",
+         "VariableFilter", "zeroIsNA")
 
 test_that("API hasn't changed", {
     current_api <- sort(ls(pos = "package:QFeatures"))
     expect_identical(length(current_api), length(api))
     expect_identical(current_api, sort(api))
-})
+})
\ No newline at end of file
diff --git a/tests/testthat/test_longForm.R b/tests/testthat/test_longForm.R
new file mode 100644
index 00000000..19a4f1c1
--- /dev/null
+++ b/tests/testthat/test_longForm.R
@@ -0,0 +1,92 @@
+data(feat2)
+colData(feat2)$X <- 1:12
+
+test_that("longFormat is defunct", {
+    expect_error(longFormat(feat2, rowvars = "Prot", colvars = "X"))
+})
+
+test_that("longForm", {
+    ## Select a single colvars and rowvars
+    lt <- longForm(feat2, rowvars = "Prot", colvars = "X")
+    ## Check dimensions
+    expect_equal(nrow(lt),
+                 sum(apply(dims(feat2), 2, prod)))
+    expect_identical(ncol(lt),
+                     5L+2L)
+    ## Check content
+    expect_identical(lt$Prot,
+                     unname(unlist(lapply(rowData(feat2),
+                                          ## Repeat 4x because 4 samples
+                                          function(x) rep(x$Prot, 4)))))
+    ## Select a single colvars and no rowvars (make sure that
+    ## the implementation does not break the MAE implementation)
+    lt <- longForm(feat2, colvars = "X")
+    expect_equal(nrow(lt),
+                 sum(apply(dims(feat2), 2, prod)))
+    expect_identical(ncol(lt),
+                     5L+1L)
+    ## Select multiple rowvars
+    lt <- longForm(feat2, rowvars = c("Prot", "x"))
+    expect_equal(nrow(lt),
+                 sum(apply(dims(feat2), 2, prod)))
+    expect_identical(ncol(lt),
+                     5L+2L)
+    ## Test error: rowvars is missing in rowData
+    expect_error(longForm(feat2, rowvars = "y"),
+                 regexp = "not found")
+})
+
+
+
+longDF <- data.frame(
+    rowname = rep(paste0("G", 1:10), 10),
+    colname = rep(paste0("S", 1:10), each = 10),
+    value = 1:100,
+    assayName = "counts",
+    colX = rep(LETTERS[1:10], each = 10),
+    rowX = rep(letters[1:10], 10))
+counts <- matrix(1:100, nrow = 10)
+dimnames(counts) <- list(
+    paste0("G", 1:10),
+    paste0("S", 1:10))
+cd <- data.frame(colX = LETTERS[1:10])
+rd <- data.frame(rowX = letters[1:10])
+se <- SummarizedExperiment(list(counts = counts),
+                           colData = cd,
+                           rowData = rd)
+longDF <- as(longDF, "DataFrame")
+
+test_that("longFormSE works with 1 assay", {
+    expect_identical(longForm(se), longDF[, 1:4])
+    expect_identical(longForm(se, colvars = "colX"), longDF[, 1:5])
+    expect_identical(longForm(se, colvars = "colX", rowvars = "rowX"), longDF)
+})
+
+
+test_that("longFormSE works with 2 assays", {
+    assay(se, "count2") <- assay(se)
+    longDF2 <- longDF
+    longDF2$assayName <- "count2"
+    expect_identical(
+        longForm(se, colvars = "colX", rowvars = "rowX"),
+        rbind(longDF, longDF2))
+})
+
+test_that("longFormSE works with unnamed assays", {
+    ## one "" names
+    assay(se, 2) <- assay(se)
+    longDF2 <- longDF
+    longDF2$assayName <- ""
+    expect_identical(
+        longForm(se, colvars = "colX", rowvars = "rowX"),
+        rbind(longDF, longDF2))
+    alst <- assays(se)
+    names(alst) <- NULL
+    assays(se) <- alst
+    longDF$assayName <- 1L
+    longDF2$assayName <- 2L
+    ## No names
+    expect_equal(
+        longForm(se, colvars = "colX", rowvars = "rowX"),
+        rbind(longDF, longDF2))
+})
\ No newline at end of file
diff --git a/tests/testthat/test_subsetByFeatures.R b/tests/testthat/test_subsetByFeatures.R
index 70ca9e34..97704abc 100644
--- a/tests/testthat/test_subsetByFeatures.R
+++ b/tests/testthat/test_subsetByFeatures.R
@@ -1,4 +1,3 @@
-
 data(feat3)
 
 test_that("subsetByFeatures", {
@@ -24,9 +23,9 @@ test_that("subsetByFeatures", {
     res3 <- feat1["SYGFNAAR", , ]
     expect_identical(res1, res2)
     expect_identical(res1, res3)
-    ## Check the QFeatures subsetting follows the same behavior as 
+    ## Check the QFeatures subsetting follows the same behavior as
     ## MultiAssayExperiment
-    expect_identical(dims(subsetByFeature(feat1, "PSM1")), 
+    expect_identical(dims(subsetByFeature(feat1, "PSM1")),
                      dims(subsetByRow(feat1, "PSM1")))
 })
 
@@ -52,6 +51,6 @@ test_that("subsetByFeatures: full pipeline", {
     expect_identical(dims(ftsub)[2, ], dims(feat3)[2, ])
     expect_identical(dims(ftsub)[1, ], dims(feat3)[1, ] - c(4L, 7L, 7L, rep(2L, 4)))
     expect_identical("SYGFNAAR",
-                     unique(unlist(lapply(rowData(ftsub), 
+                     unique(unlist(lapply(rowData(ftsub),
                                           function(x) x$Sequence))))
 })
diff --git a/vignettes/Processing.Rmd b/vignettes/Processing.Rmd
index d0835aca..da54a537 100644
--- a/vignettes/Processing.Rmd
+++ b/vignettes/Processing.Rmd
@@ -239,7 +239,6 @@ deterministic minimum value imputation on the
 `r sum(!rowData(se_na2)$randna)` proteins that display a non-random pattern
 of missing values (brown on figure \@ref(fig:miximp)).
 
-
 # Data transformation
 
 When analysing continuous data using parametric methods (such as
@@ -252,22 +251,23 @@ Below, we use the `logTransform` function to log2-transform our
 data. This time, instead of overwriting the peptides assay, we are
 going to create a new one to contain the log2-transformed data.
 
-```{r}
-cptac <- addAssay(cptac,
-                  logTransform(cptac[[1]]),
-                  name = "peptides_log")
-cptac
+```{r addLogTransform, eval = FALSE}
+addAssay(cptac,
+         logTransform(cptac[[1]]),
+         name = "peptides_log")
 ```
 
 The `addAssay()` function is the general function that adds new assays
-to a `QFeatures` object. The step above could also be fun with the
-following syntax, that implicitly returns an updated `QFeatures`
-object.
-
-```{r, eval = FALSE}
-logTransform(cptac,
-             i = "peptides",
-             name = "log_peptides")
+to a `QFeatures` object. The step above could more easily be exectuted
+with the `logTransform()` method, that directly returns an updated
+`QFeatures` object. Using `logTransform()` also automatically adds
+links between assays.
+
+```{r logTransform, eval = TRUE}
+cptac <- logTransform(cptac,
+                      i = "peptides",
+                      name = "peptides_log")
+cptac
 ```
 
 ```{r, fig.cap = "Quantitative data in its original scale (left) and log2-transformed (right)."}
@@ -279,25 +279,30 @@ limma::plotDensities(assay(cptac[[2]]))
 # Normalisation
 
 Assays in `QFeatures` objects can be normalised with the `normalize`
- function. The type of normalisation is defined by the `method`
- argument; below, we use quantile normalisation, store the normalised
- data into a new experiment, and visualise the resulting data.
-
-```{r}
-cptac <- addAssay(cptac,
-                  normalize(cptac[["peptides_log"]], method = "center.median"),
-                  name = "peptides_norm")
+function. The type of normalisation is defined by the `method`
+argument; below, we use median normalisation, store the normalised
+data into a new experiment, and visualise the resulting data.
+
+The `normalize()` function can also be directly applied to the
+`QFeatures` object.
+
+```{r normalize}
+cptac <- normalize(cptac,
+                   i = "peptides_log",
+                   name = "peptides_norm",
+                   method = "diff.median")
 cptac
 ```
 
-As above, the `normalize()` function can also be firectly applied to
-the `QFeatures` object.
+It is also possible to extract and normalise the `peptides_log`
+`SummarizedExperiment` and add it back to the `QFeatures` object with
+`addAssay()`.
 
-```{r, eval = FALSE}
-normalize(cptac,
-          i = "log_peptides",
-          name = "lognorm_peptides",
-          method = "center.median")
+```{r addNormalize, eval = FALSE}
+addAssay(cptac,
+         normalize(cptac[["peptides_log"]],
+                   method = "center.median"),
+         name = "peptides_norm")
 ```
 
 
@@ -331,8 +336,11 @@ that takes the following inputs:
   `robustSummary` [@Sticker:2019].
 
 
-```{r, warning = FALSE}
-cptac <- aggregateFeatures(cptac, i = "peptides_norm", fcol = "Proteins", name = "proteins")
+```{r aggregate, warning = FALSE}
+cptac <- aggregateFeatures(cptac,
+                           i = "peptides_norm",
+                           fcol = "Proteins",
+                           name = "proteins")
 cptac
 ```
 
@@ -357,31 +365,32 @@ table(rowData(cptac[["proteins"]])$.n)
 ```
 
 Let's choose `P02787ups|TRFE_HUMAN_UPS` and visualise its expression
-pattern in the 2 groups at the protein and peptide level.
+pattern in the 2 groups at the protein and (log-tranformed and
+normalised) peptide level. We drop the first peptide-level assay as it
+is on a different scale (i.e. not log-transformed).
 
 ```{r, message = TRUE, fig.cap = "Expression intensities for the protein *P02787ups|TRFE_HUMAN_UPS* (right, green) and its peptides (left) in groups A (circles) and B (triangles)."}
 library("ggplot2")
 library("dplyr")
-longFormat(cptac["P02787ups|TRFE_HUMAN_UPS", ]) |>
+longForm(cptac["P02787ups|TRFE_HUMAN_UPS", , -1]) |>
     as.data.frame() |>
     mutate(group = ifelse(grepl("A", colname), "A", "B")) |>
     mutate(sample = sub("Intensity\\.", "", colname)) |>
-    ggplot(aes(x = sample, y = value, colour = rowname, shape = group)) +
-    geom_point() +
+    ggplot(aes(x = sample, y = value,
+               colour = rowname,
+               shape = group)) +
+    geom_line(aes(group = rowname)) +
+    geom_point(size = 3) +
     facet_grid(~ assay)
 ```
 
-# TODO
-
-- Improve on data visualisation.
-
 # See also
 
+- The other vignettes in the `QFeatures` package.
 - The
   [QFeaturesWorkshop2020](https://lgatto.github.io/QFeaturesWorkshop2020/index.html)
   workshop, presented at the EuroBioc2020 meeting. It also documents
   how to use a custom docker container to run the workshop code.
-
 - The [Quantitative proteomics data
   analysis](https://uclouvain-cbio.github.io/WSBIM2122/sec-prot.html)
   chapter of the WSBIM2122 course.
@@ -392,5 +401,10 @@ longFormat(cptac["P02787ups|TRFE_HUMAN_UPS", ]) |>
 sessionInfo()
 ```
 
+# License {-}
+
+This vignette is distributed under a
+[CC BY-SA license](https://creativecommons.org/licenses/by-sa/2.0/)
+license.
 
-# References {-}
+# References {-}
\ No newline at end of file
diff --git a/vignettes/QFeatures.Rmd b/vignettes/QFeatures.Rmd
index 95d39389..48af844f 100644
--- a/vignettes/QFeatures.Rmd
+++ b/vignettes/QFeatures.Rmd
@@ -283,7 +283,7 @@ once converted into a `data.frame`. See the visualization vignette for
 more details about data exploration from a `QFeatures` object.
 
 ```{r plotstat3}
-stat3_df <- data.frame(longFormat(stat3))
+stat3_df <- data.frame(longForm(stat3))
 stat3_df$assay <- factor(stat3_df$assay,
                         levels = c("psms", "peptides", "proteins"))
 library("ggplot2")
@@ -309,7 +309,7 @@ stat
 Below, we visualise the expression profiles for the two proteins.
 
 ```{r plotstat}
-stat_df <- data.frame(longFormat(stat))
+stat_df <- data.frame(longForm(stat))
 stat_df$stat3 <- ifelse(stat_df$rowname %in% stat3_df$rowname,
                         "STAT3", "STAT1")
 stat_df$assay <- factor(stat_df$assay,
@@ -339,7 +339,7 @@ and possibly
 ```{r subsetpipe, eval = FALSE}
 hl |>
     subsetByFeature("P42227-2") |>
-    longFormat() |>
+    longForm() |>
     as.data.frame() |>
     ggplot(aes(x = colname,
                y = value,
@@ -405,5 +405,10 @@ filterFeatures(hl, ~ qValue <= 0.001)
 sessionInfo()
 ```
 
+# License {-}
+
+This vignette is distributed under a
+[CC BY-SA license](https://creativecommons.org/licenses/by-sa/2.0/)
+license.
 
 # References {-}
diff --git a/vignettes/Visualization.Rmd b/vignettes/Visualization.Rmd
index dcd339f8..8bb5c28d 100644
--- a/vignettes/Visualization.Rmd
+++ b/vignettes/Visualization.Rmd
@@ -166,10 +166,10 @@ reference [book](https://jokergoo.github.io/ComplexHeatmap-reference/book/).
 
 # Advanced data exploration
 
-In this section, we show how to combine in a single table
-different pieces of information available in a `QFeatures` object,
-that are quantitation data, feature metadata and sample metadata. The
-`QFeatures` package provides the `longFormat` function that converts a
+In this section, we show how to combine in a single table different
+pieces of information available in a `QFeatures` object, that are
+quantitation data, feature metadata and sample metadata. The
+`QFeatures` package provides the `longForm()` function that converts a
 `QFeatures` object into a long table. Long tables are very useful when
 using `ggplot2` for data visualization. For instance, suppose we want
 to visualize the distribution of protein quantitation (present in the
@@ -179,10 +179,10 @@ to visualize the distribution of protein quantitation (present in the
 quantitation values coming from the same protein using lines. This can
 all be plotted at once in a few lines of code.
 
-```{r longFormat, fig.height = 7, fig.width = 9}
-lf <- longFormat(hl[, , "proteins"],
-                 rowvars = "markers",
-                 colvars = "tag")
+```{r longForm, fig.height = 7, fig.width = 9}
+lf <- longForm(hl[, , "proteins"],
+               rowvars = "markers",
+               colvars = "tag")
 ggplot(data.frame(lf)) +
     aes(x = tag,
         y = value,
@@ -191,7 +191,7 @@ ggplot(data.frame(lf)) +
     facet_wrap(~ markers, scales = "free_y", ncol = 3)
 ```
 
-`longFormat` allows to retrieve and combine all available data from a
+`longForm()` allows to retrieve and combine all available data from a
 `Qfeatures` object. We here demonstrate the ease to combine different
 pieces that could highlight sample specific and/or feature specific
 effects on data quantitation.
@@ -229,5 +229,11 @@ interest, which can then be visualised as a heatmap (figure
 sessionInfo()
 ```
 
+# License {-}
+
+This vignette is distributed under a
+[CC BY-SA license](https://creativecommons.org/licenses/by-sa/2.0/)
+license.
+
 
 # References {-}


Reviews:
user: login: leopoldguyot
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T12:28:57Z
user: login: leopoldguyot
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-19T14:08:52Z
user: login: cvanderaa
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T05:52:42Z
user: login: cvanderaa
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-21T09:03:55Z

Review comments:
diff_hunk: @@ -0,0 +1,134 @@
+##' @rdname QFeatures-class
+##'
+##' @param colvars A `character()` that selects column(s) in the `colData`.
+##'
+##' @param rowvars A `character()` with the names of the `rowData`
+##'     variables (columns) to retain in any assay.
+##'
+##' @param index When object is an instance of class `QFeatures`, a `numeric(1)`
+##'     indicating what assay within each `SummarizedExperiment` object to
+##'     return. Default is `1L`. If object is a `SummarizedExperiment`, a
+##'     `numeric()` indicating what assays to pull and convert. Default is to
+##'     use all assays.
+##'
+##' @importFrom MultiAssayExperiment longForm
+##' @importFrom reshape2 melt
+##'
+##' @importFrom BiocGenerics longForm
+##'
+##' @exportMethod longForm
+##'
+##' @aliases longForm longForm,QFeatures
+##' @aliases longFormat
+setMethod("longForm", "QFeatures",
path: R/QFeatures-longForm.R
user: login: leopoldguyot
body: Maybe it would be better to put the code of the `longForm` QFeatures method in a wrapper function like you did with the `SummarizedExperiment` method? (it will be easier to debug)
created_at: 2025-05-19T12:19:27Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,139 @@
+##' @rdname QFeatures-class
+##'
+##' @param colvars A `character()` that selects column(s) in the `colData`.
+##'
+##' @param rowvars A `character()` with the names of the `rowData`
+##'     variables (columns) to retain in any assay.
+##'
+##' @param index When object is an instance of class `QFeatures`, a `numeric(1)`
+##'     indicating what assay within each `SummarizedExperiment` object to
+##'     return. Default is `1L`. If object is a `SummarizedExperiment`, a
+##'     `numeric()` indicating what assays to pull and convert. Default is to
+##'     use all assays.
+##'
+##' @importFrom MultiAssayExperiment longForm
+##' @importFrom reshape2 melt
+##'
+##' @importFrom BiocGenerics longForm
+##'
+##' @exportMethod longForm
+##'
+##' @aliases longForm longForm,QFeatures
+##' @aliases longFormat
+setMethod("longForm", "QFeatures",
+          function(object, colvars = NULL,
+                   rowvars = NULL,
+                   index = 1L)
+              longFormQFeatures(object, colvars, rowvars, index))
+
+##' @rdname QFeatures-class
+##'
+##' @exportMethod longForm
+##'
+##' @aliases longForm,SummarizedExperiment
+setMethod("longForm", "SummarizedExperiment",
+          function(object, colvars = NULL,
+                   rowvars = NULL,
+                   index = seq_along(assays(object)))
+              longFormSE(object, colvars, rowvars, index, na.rm = FALSE))
+
+longFormQFeatures <- function(object, colvars = NULL,
+                              rowvars = NULL,
+                              index = 1L) {
+    if (!is.null(rowvars)) {
+        rdNames <- rowDataNames(object)
+        misNames <- sapply(rdNames,
+                           function (x) any(!rowvars %in% x))
+        ## Check that all required
+        if (any(misNames))
+            stop("Some 'rowvars' not found in assay(s): ",
+                 paste0(names(misNames)[misNames], collapse = ", "))
+        ## Get long format table with quantification values and colvars
+        longDataFrame <-
+            MultiAssayExperiment::longForm(
+                                      as(object, "MultiAssayExperiment"),
+                                      colDataCols = colvars,
+                                      i = index)
+        ## Get the required rowData
+        rds <- lapply(rowData(object),
+                      function(rd) rd[, rowvars, drop = FALSE])
+        rds <- do.call(rbind, rds)
+        ## Merge the rowData to the long table
+        cbind(longDataFrame,
+              rds[as.character(longDataFrame$rowname), , drop = FALSE])
+    } else {
+        ## If rowvars is null, return the MAE longForm output
+        MultiAssayExperiment::longForm(
+                                  as(object, "MultiAssayExperiment"),
+                                  colDataCols = colvars,
+                                  i = index)
+    }
+}
+
+
+##' @importFrom reshape2 melt
+longFormSE <- function(object, colvars = NULL, rowvars = NULL,
+                       index = seq_along(assays(object)),
+                       na.rm = FALSE) {
+    ## Check that indices are within bounds
+    if (max(index) > length(assays(object)) | min(index) < 1)
+        stop("Index out of (assay) bounds.")
+    ## Check that all colvars exist
+    if (!is.null(colvars)) {
+        if (!all(colvars %in% names(colData(object))))
+            stop("Some 'colvars' not found in colData(.).")
+    }
+    ## Check that all rowvars exist
+    if (!is.null(rowvars)) {
+        if (!all(rowvars %in% names(rowData(object))))
+            stop("Some 'rowvars' not found in rowData(.).")
+    }
+    ## Need names for the assayNames columns. If the object's assays don't have
+    ## any names, use the index set above.
+    if (is.null(nms <- assayNames(object)))
+        nms <- index
+    res <- lapply(seq_along(index),
+                  function(i) {
+                      ans <- reshape2::melt(assay(object, index[i]),
+                                            varnames = c("rowname", "colname"),
+                                            value.name = "value",
+                                            as.is = TRUE,
+                                            na.rm = na.rm)
+                      ans$assayName <- nms[i]
+                      rownames(ans) <- NULL
+                      ans
+                  })
+    res <- do.call(rbind, res)
+    if (!is.null(colvars)) { ## Add colData variables.
+        ## Need object to have colnames
+        if (is.null(colnames(object)))
+            colnames(object) <- seq_len(ncol(object))
+        cd <- colData(object)[as.character(res$colname),
+                              colvars,
+                              drop = FALSE]
+        rownames(cd) <- NULL
+        res <- cbind(res, cd)
+    }
+    if (!is.null(rowvars)) { ## Add rowData variables
+        ## Need object to have rownames
+        if (is.null(rownames(object)))
+            rownames(object) <- seq_len(nrow(object))
+        rd <- rowData(object)[as.character(res$rowname),
+                              rowvars,
+                              drop = FALSE]
+        rownames(rd) <- NULL
+        res <- cbind(res, rd)
+    }
+    ## as(res, "DataFrame")
+    res
+}
+
+##' @noRd
+##'
+##' @export
+longFormat <- function(object,
+                       colvars = NULL,
+                       rowvars = NULL,
+                       index = 1L) {
+    .Defunct("longForm")
path: R/QFeatures-longForm.R
user: login: cvanderaa
body: Should we not start by a deprecation? This would give users time to adapt their workflow. 
created_at: 2025-05-20T05:39:04Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,139 @@
+##' @rdname QFeatures-class
+##'
+##' @param colvars A `character()` that selects column(s) in the `colData`.
+##'
+##' @param rowvars A `character()` with the names of the `rowData`
+##'     variables (columns) to retain in any assay.
+##'
+##' @param index When object is an instance of class `QFeatures`, a `numeric(1)`
+##'     indicating what assay within each `SummarizedExperiment` object to
+##'     return. Default is `1L`. If object is a `SummarizedExperiment`, a
+##'     `numeric()` indicating what assays to pull and convert. Default is to
+##'     use all assays.
+##'
+##' @importFrom MultiAssayExperiment longForm
+##' @importFrom reshape2 melt
+##'
+##' @importFrom BiocGenerics longForm
+##'
+##' @exportMethod longForm
+##'
+##' @aliases longForm longForm,QFeatures
+##' @aliases longFormat
+setMethod("longForm", "QFeatures",
+          function(object, colvars = NULL,
+                   rowvars = NULL,
+                   index = 1L)
+              longFormQFeatures(object, colvars, rowvars, index))
+
+##' @rdname QFeatures-class
+##'
+##' @exportMethod longForm
+##'
+##' @aliases longForm,SummarizedExperiment
+setMethod("longForm", "SummarizedExperiment",
+          function(object, colvars = NULL,
+                   rowvars = NULL,
+                   index = seq_along(assays(object)))
+              longFormSE(object, colvars, rowvars, index, na.rm = FALSE))
+
+longFormQFeatures <- function(object, colvars = NULL,
path: R/QFeatures-longForm.R
user: login: cvanderaa
body: The longFormSE allows to remove missing values (`na.rm` argument). Is it intentional that `longFormQFeatures` doesn't?

Also both function take an `index` argument. Should this not be `i` to comply with the interface of the other functions/methods in the package?

I would personnally prefer a harmonized interface. 
created_at: 2025-05-20T05:43:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,139 @@
+##' @rdname QFeatures-class
+##'
+##' @param colvars A `character()` that selects column(s) in the `colData`.
+##'
+##' @param rowvars A `character()` with the names of the `rowData`
+##'     variables (columns) to retain in any assay.
+##'
+##' @param index When object is an instance of class `QFeatures`, a `numeric(1)`
path: R/QFeatures-longForm.R
user: login: cvanderaa
body: For a QFeatures, the `index` seems to allow for `numeric()`, not `numeric(1)`. Also the default is not `1L` but all assays as for `SE`s.
created_at: 2025-05-20T05:45:35Z
author_association: COLLABORATOR

PR comments:
user: login: lgatto
created_at: 2025-05-17T12:53:10Z
body: @leopoldguyot - please also have a look at this PR before merging. 
author_association: MEMBER
user: login: lgatto
created_at: 2025-05-20T10:56:59Z
body: @cvanderaa - I have addressed all you comments, hopefully to your satisfaction. 
author_association: MEMBER
user: login: cvanderaa
created_at: 2025-05-20T14:47:00Z
body: Regarding the `index` and `i` naming, I realise I got confused and didn't catch it was about the assay index **within** the `SE` :man_facepalming: The documentation of `i` is now incorrect for the other functions. Do you want me to revert it? 

The rest looks good to me!
author_association: COLLABORATOR
user: login: lgatto
created_at: 2025-05-20T16:24:42Z
body: Yes, I can see that now. 

I have now documented `longForm()` in it's own manual page, with a reference and link in the main class manual page. I think this is the cleanest. Manual pages that are very long become too difficult to read.  
author_association: MEMBER
user: login: lgatto
created_at: 2025-05-20T16:40:16Z
body: Another point that I would appreciate your input in. Currently, we have the following discrepancy:
- `longForm,QFeatures` returns a `DataFrame`
- `longForm,SummarizedExperiment` returns a `data.frame`

`MultiAssayExperiment::longForm` returns a `DataFrame` for all methods but `MultiAssayExperiment::longForm,ANY`, that returns a `data.frame`. 

What should we do?
author_association: MEMBER
user: login: leopoldguyot
created_at: 2025-05-20T17:11:46Z
body: > Another point that I would appreciate your input in. Currently, we have the following discrepancy:
> 
>     * `longForm,QFeatures` returns a `DataFrame`
> 
>     * `longForm,SummarizedExperiment` returns a `data.frame`
> 
> 
> `MultiAssayExperiment::longForm` returns a `DataFrame` for all methods but `MultiAssayExperiment::longForm,ANY`, that returns a `data.frame`.
> 
> What should we do?

I'd say it's better to use `DataFrame` for both cases. It's more consistent and aligns with the expectations of users working in Bioconductor workflows. 
In the case of `MultiAssayExperiment` I think they return a `data.frame` for `MultiAssayExperiment::longForm,ANY` and `MultiAssayExperiment::longForm,ExperimentList` because they assume that these implementation will only be used internally.
author_association: COLLABORATOR
user: login: lgatto
created_at: 2025-05-20T17:24:37Z
body: Yes, ok. 

One reason I see to use `data.frame` is to pipe it directly into tidyverse functions (the main use of `longForm()`), whereas otherwise, one needs to coerce it to a `data.frame` or a `tibble`. 
author_association: MEMBER
user: login: lgatto
created_at: 2025-05-20T17:29:00Z
body: Latest commit always returns a `DataFrame`, for consistency. 
author_association: MEMBER
user: login: leopoldguyot
created_at: 2025-05-20T17:35:56Z
body: > Yes, ok.
> 
> One reason I see to use `data.frame` is to pipe it directly into tidyverse functions (the main use of `longForm()`), whereas otherwise, one needs to coerce it to a `data.frame` or a `tibble`.

That's also a good point I didn't think of. What do you think @cvanderaa?
author_association: COLLABORATOR
user: login: lgatto
created_at: 2025-05-20T18:23:20Z
body: After thinking a bit more, I think we should (1) be consistent and (2) not deviate from what MAE does, hence return a `DataFrame` in both cases (which is what is currently implemented in the last commit).
author_association: MEMBER
user: login: cvanderaa
created_at: 2025-05-21T08:48:58Z
body: I agree with both of you that `longForm` should always return a `DataFrame`. 

Good news is this (I'm currently using R 4.5):

```r 
library(S4Vectors)
library(ggplot2)
DataFrame(x = 1:10) |> 
    ggplot(aes(x = x)) + 
    geom_histogram()
```
Tadaa :tada: !! I don't know when this has been implemented.
This does not apply to `dplyr` though :sweat:
author_association: COLLABORATOR
user: login: lgatto
created_at: 2025-05-21T09:02:42Z
body: @cvanderaa - could you approve the PR for me to be able to merge
author_association: MEMBER