[Bioc-devel] Problem with S4 method dispatch: .. bug in methods package ? SummarizedExperiment, but not SingleCellExperiment

Martin Maechler m@ech|er @end|ng |rom @t@t@m@th@ethz@ch
Sun Jan 26 08:51:17 CET 2025


>>>>> Axel Klenk 
>>>>>     on Thu, 23 Jan 2025 15:31:18 +0100 writes:

    > Hi Martin and all,
    >> In principle it should be reproducible with only base R
    >> (incl methods package), not involving your given extra
    >> packages; just using functions setClassUnion(), ... etc
    >> from methods.
    >> 
    >> Is that possible?

    > Yes, it is -- but I found that in order to reproduce the
    > issue I need to mimic the "original" constellation where
    > class A and its subclass C are defined in some example
    > package expkg and D, a subclass of C is defined in a
    > dependent package depkg.  Then a method defined for a
    > class union U of class A and some other class B will be
    > found for an object of class C (same package as A), but
    > not one of class D (different package)...  I have prepared
    > two packages expkg and depkg on my GitHub to demonstrate
    > this as follows:

    >   if(!requireNamespace("remotes", quietly = TRUE))
    > install.packages("remotes")
    > remotes::install_github(c("axelklenk/expkg",
    > "axelklenk/depkg"))

    >   library("expkg") setClass("B") setClassUnion("U", c("A",
    > "B")) setGeneric("Umethod", function(x)
    > standardGeneric("Umethod")) setMethod("Umethod",
    > signature="U", function(x) cat("Found Umethod!\n"))

    >   o <- new("C") Umethod(o) ## it works!!

    >   library("depkg") p <- new("D") Umethod(p) ## Error:
    > unable to find an inherited method for function 'Umethod'
    > for signature 'x = "D"'

    > The packages contain nothing but the necessary setClass()
    > and their respective NAMESPACE files are pretty simple.
    > For expkg, the package defining superclasses A and C:

    >   exportClasses(A) exportClasses(C) import(methods)

    > and for depkg, the package defining subclass D

    >   exportClasses(D) import(methods)
    > importClassesFrom(expkg,A) importClassesFrom(expkg,C)

    > I have studied WRE 1.5[.6] and added "Depends: methods¨ to
    > the packages' DESCRIPTION files and "import(methods)" to
    > their NAMESPACE files but that didn't seem to make a
    > difference.  Otherwise I didn't find any hints in WRE.

    > Ah, and after running the code above, the inheritance
    > relation between U and C is "known" as well as between A
    > and D, but not between U and D:

    >> getClass("C")
    > Class "C" [package "expkg"]

    > No Slots, prototype of class "S4"

    > Extends: Class "A", directly Class "U", by class "A",
    > distance 2

    >> getClass("D")
    > Class "D" [package "depkg"]

    > No Slots, prototype of class "S4"

    > Extends: Class "C", directly Class "A", by class "C",
    > distance 2

    >> getClass("U")
    > Extended class definition ( "ClassUnionRepresentation" )
    > Virtual Class "U" [in ".GlobalEnv"]

    > No Slots, prototype of class "C"

    > Known Subclasses: Class "A", directly Class "B", directly
    > Class "C", by class "A", distance 2


    > Now, does that look like a bug in the methods package or
    > rather like 'some "unhappy coincidence" in how the
    > involved packages interact there'?  I have no idea how to
    > tell -- and therefore will follow this advice:

    >> Otherwise, yes, do as you are advised, i.e. -->
    >> https://www.r-project.org/bugs.html and apply for an R
    >> bugzilla account.

Thanks a lot, Axel, for your reproducible example.
and so, as I am also one of the recipients of the R bugzilla applications,
I was wondering why we have not yet received yours ...

    > Thanks to all for your help so far and any additional
    > advice,

    >  - axel

Indeed, notably thanks for your own help by reducing the example!

Ideally your packages could become even much smaller (to be
added to a test suite in the future):
      - no LICENSE, README.md nor  man/
      - no 'Depends:' in DESCRIPTION, only 'Imports: methods'

      - in  NAMESPACE, wrt methods, you'd only have

      	    importFrom(methods, setClass)
      	
Best,
Martin


    > Am Mi., 15. Jan. 2025 um 09:08 Uhr schrieb Martin Maechler
    > <maechler using stat.math.ethz.ch>:
    >> 
    >> >>>>> Axel Klenk >>>>> on Sat, 11 Jan 2025 01:16:49 +0100
    >> writes:
    >> 
    >> > Hi Hervé, Kasper, Helena and all, > and thanks a lot
    >> for your replies!
    >> 
    >> > First of all, if this is a bug in the methods package,
    >> I'd of course > volunteer to report it -- sounds like a
    >> great learning opportunity > for the New Year. ;-)
    >> 
    >> As an R corer with special interest in S4/ methods, I had
    >> wanted to chime in earlier: Yes, _if this is a bug in the
    >> methods package_
    >> 
    >> In principle it should be reproducible with only base R
    >> (incl methods package), not involving your given extra
    >> packages; just using functions setClassUnion(), ... etc
    >> from methods.
    >> 
    >> Is that possible?
    >> 
    >> Of course, it might be that it is related to the use of
    >> 
    >> ImportClassesFrom(), ImportMethodsFrom(), ExportClasses()
    >> ...
    >> 
    >> directives in the involved packages' NAMESPACE files, and
    >> some "unhappy coincidence" in how the involved packages
    >> interact there ..  but probably Hervé or you have already
    >> looked into these.
    >> 
    >> Otherwise, yes, do as you are advised, i.e. -->
    >> https://www.r-project.org/bugs.html and apply for an R
    >> bugzilla account.
    >> 
    >> Best, Martin
    >> 
    >> 
    >> > In my original message I tried to focus on the error
    >> and provided only > the code necessary to reproduce it
    >> but of course I'm happy > to provide more background and
    >> receive advice on design and maintainability.
    >> 
    >> > The reason for class unions was the need to store any
    >> user-provided > data container in a slot of GSVA's
    >> parameter objects when > those were introduced.
    >> (Actually there is one for data > matrices/containers and
    >> another one for gene sets in lists or >
    >> GSEABase::GeneSetCollection.)  As you seem to be a bit
    >> reluctant to > use class unions, is there a good way to
    >> avoid them as > the slot class in this case?
    >> 
    >> > Once the class union existed, methods on the union were
    >> added to avoid > long functions with unwieldy if/else
    >> constructs, as the > number of supported containers is
    >> growing.  However, so far there are > only two actual use
    >> cases for methods on class unions: > (1) the (containing)
    >> parameter object's show() method that includes > output
    >> from show() methods of the (contained) data objects, >
    >> with more specialized methods only required for matrix
    >> and dgCMatrix > that otherwise show() all of their
    >> content, and > (2) some method for extracting a
    >> matrix-like data object from a data > container --
    >> assay() for SummarizedExperiment and derivatives, >
    >> exprs() for ExpressionSet, the object itself for
    >> matrix/dgCMatrix.
    >> 
    >> > So, we are actually starting from the class unions
    >> rather than the > methods that could be replaced the way
    >> Hervé suggested.  > The methods are just where the error
    >> message happened to appear and I > was not sure if this
    >> was caused by a bug or not expected > to work at all or a
    >> known issue requiring some special measures or ...
    >> 
    >> > By the way, in the meantime we have been experimenting
    >> with the mock > example below -- which works as
    >> expected?!?
    >> 
    >> > setClass("A") ## this would be like 'dgCMatrix' >
    >> setClass("B") ## this would be like
    >> 'SummarizedExperiment' > setClass("C", contains="B") ##
    >> this would be like 'SingleCellExperiment' >
    >> setClassUnion("U", c("A", "B")) > setGeneric("Umethod",
    >> function(x) standardGeneric("Umethod")) >
    >> setMethod("Umethod", signature="U", function(x)
    >> cat("Umethod!\n")) > o <- new("C") > Umethod(o) ## it
    >> works!!
    >> 
    >> > Thanks in advance for your support and suggestions,
    >> 
    >> > - axel
    >> 
    >> 
    >> > Am Fr., 10. Jan. 2025 um 18:17 Uhr schrieb Kasper
    >> Daniel Hansen > <kasperdanielhansen using gmail.com>:
    >> >>
    >> >> In my experience, class unions are hard to work with
    >> and quickly get out of hand. I don't really understand
    >> what you actually want to do, beyond defining a class
    >> union. My impression is that your main goal is to write
    >> simplified code. I could be wrong, but I doubt class
    >> unions is the best way to solve your problem. What you
    >> really need is that these classes share a common API in
    >> that you can do stuff like >> assay() >> on them. Either
    >> this common API already exists or it does not. It it
    >> already exists, there is - as far as I can tell - no
    >> gains from the class union. If it does not exist, you
    >> still need to write class specific code to get the API
    >> working.
    >> >>
    >> >> Like Herve, I suggest not starting with a method. I
    >> find that too many developers are too quick to reach for
    >> methods.
    >> >>
    >> >> We might be able to give you more useful feedback and
    >> help if you can tell us more precisely what you want to
    >> do.
    >> >>
    >> >> Best, >> Kasper
    >> >>
    >> >>
    >> >> On Thu, Jan 9, 2025 at 2:38 PM Hervé Pagès
    >> <hpages.on.github using gmail.com> wrote:
    >> >>>
    >> >>> Looks like a bug in the methods package to me.
    >> >>>
    >> >>> The extends() relationship looks transitive, as it
    >> should be:
    >> >>>
    >> >>> > extends("SingleCellExperiment",
    >> "SummarizedExperiment") >>> [1] TRUE >>> >
    >> is(new("SingleCellExperiment"), "ExpData") >>> [1] TRUE
    >> >>> > extends("SingleCellExperiment", "ExpData") >>> [1]
    >> TRUE
    >> >>>
    >> >>> Also is() is doing the right thing as Helena pointed
    >> out:
    >> >>>
    >> >>> > is(new("SingleCellExperiment"), "ExpData") >>> [1]
    >> TRUE
    >> >>>
    >> >>> However, when using the single-arg form of extends()
    >> to list all the >>> ancestor classes, ExpData is no
    >> longer seen as an ancestor of >>> SingleCellExperiment:
    >> >>>
    >> >>> > extends("SummarizedExperiment") >>> [1]
    >> "SummarizedExperiment" "RectangularData" "Vector" >>> [4]
    >> "ExpData" "Annotated" "vector_OR_Vector"
    >> >>>
    >> >>> > extends("SingleCellExperiment") >>> [1]
    >> "SingleCellExperiment" "RangedSummarizedExperiment" >>>
    >> [3] "SummarizedExperiment" "RectangularData" >>> [5]
    >> "Vector" "Annotated" >>> [7] "vector_OR_Vector"
    >> >>>
    >> >>> Same problem with selectSuperClasses(), which is an
    >> other way to list >>> all the ancestors of a given class:
    >> >>>
    >> >>> > selectSuperClasses("SummarizedExperiment",
    >> directOnly=FALSE) >>> [1] "RectangularData" "Vector"
    >> "ExpData" "Annotated" >>> [5] "vector_OR_Vector"
    >> >>>
    >> >>> > selectSuperClasses("SingleCellExperiment",
    >> directOnly=FALSE) >>> [1] "RangedSummarizedExperiment"
    >> "SummarizedExperiment" >>> [3] "RectangularData" "Vector"
    >> >>> [5] "Annotated" "vector_OR_Vector"
    >> >>>
    >> >>> And same problem when trying to retrieve this list
    >> directly from the >>> 'contains' slot of the class
    >> definitions:
    >> >>>
    >> >>> > names(getClass("SummarizedExperiment")@contains)
    >> >>> [1] "RectangularData" "Vector" "ExpData" "Annotated"
    >> >>> [5] "vector_OR_Vector"
    >> >>>
    >> >>> >
    >> names(getClass("RangedSummarizedExperiment")@contains)
    >> >>> [1] "SummarizedExperiment" "RectangularData" "Vector"
    >> >>> [4] "ExpData" "Annotated" "vector_OR_Vector"
    >> >>>
    >> >>> > names(getClass("SingleCellExperiment")@contains)
    >> >>> [1] "RangedSummarizedExperiment"
    >> "SummarizedExperiment" >>> [3] "RectangularData" "Vector"
    >> >>> [5] "Annotated" "vector_OR_Vector"
    >> >>>
    >> >>> Any volunteer to report this to the methods
    >> maintainer? (via the R bug >>> tracker)
    >> >>>
    >> >>> Anyways, not sure exactly what methods you're
    >> planning to implement for >>> ExpData derivatives. Have
    >> you considered doing something like this instead:
    >> >>>
    >> >>> .check_expdata <- function(expdata) >>> { >>> ok <-
    >> is.matrix(expdata) || >>> is(expdata, "dgCMatrix") || >>>
    >> is(expdata, "ExpressionSet") || >>> is(expdata,
    >> "SummarizedExperiment") >>> if (!ok) stop("'expdata' must
    >> be a matrix or dgCMatrix or ", >>> "ExpressionSet or
    >> SummarizedExperiment derivative") >>> }
    >> >>>
    >> >>> foo <- function(expdata) >>> { >>>
    >> .check_expdata(expdata) >>> ...  >>> ...  >>> }
    >> >>>
    >> >>> Personally I find that using a union and method
    >> dispatch for this is a >>> little bit overkill.
    >> >>>
    >> >>> Best,
    >> >>>
    >> >>> H.
    >> >>>
    >> >>>
    >> >>> On 1/8/25 05:35, Helena L. Crowell wrote: >>> > Ui,
    >> my bad, messed up my environment… Indeed, I think what
    >> you had in mind doesn’t work. My guess is that the class
    >> union is “strict” in that method dispatch doesn’t just
    >> propagate to classes that inherit from what’s in the
    >> union.
    >> >>> >
    >> >>> > Anything against something like … which should
    >> definitely work (I think):
    >> >>> >
    >> >>> > setClassUnion("ExpData", c( >>> > "matrix",
    >> "dgCMatrix", "ExpressionSet", >>> >
    >> "SingleCellExperiment", "SummarizedExperiment”))
    >> >>> >
    >> >>> > What’s really weird imo is that is(se, "ExpData”)
    >> and is(sce, "ExpData”) both return TRUE, but still the
    >> SCE method can’t be found?  >>> > Maybe someone else has
    >> more insights, sorry!!
    >> >>> >
    >> >>> >> On Jan 8, 2025, at 14:11, Axel Klenk
    >> <axel.klenk using gmail.com> wrote:
    >> >>> >>
    >> >>> >> Hi Helena,
    >> >>> >>
    >> >>> >> thanks for your reply.  Unfortunately it doesn't
    >> work for me -- when >>> >> copy+paste'ing your code >>>
    >> >> the error now occurs for SummarizedExperiment
    >> (transcript below, >>> >> including session info >>> >>
    >> that I had forgotten yesterday).
    >> >>> >>
    >> >>> >> In the current version of GSVA the class union
    >> contains all of SE, SCE >>> >> and SPE and we >>> >>
    >> still need to define all methods for all of them which
    >> doesn't feel >>> >> right... but I must be doing >>> >>
    >> something wrong.
    >> >>> >>
    >> >>> >> Any ideas?
    >> >>> >>
    >> >>> >> Cheers,
    >> >>> >>
    >> >>> >> - axel
    >> >>> >>
    >> >>> >>
    >> >>> >>> library("Matrix") >>> >>> library("Biobase") >>>
    >> >> Loading required package: BiocGenerics
    >> >>> >>
    >> >>> >> Attaching package: 'BiocGenerics'
    >> >>> >>
    >> >>> >> The following objects are masked from
    >> 'package:stats':
    >> >>> >>
    >> >>> >> IQR, mad, sd, var, xtabs
    >> >>> >>
    >> >>> >> The following objects are masked from
    >> 'package:base':
    >> >>> >>
    >> >>> >> anyDuplicated, aperm, append, as.data.frame,
    >> basename, cbind, >>> >> colnames, dirname, do.call,
    >> duplicated, eval, evalq, Filter, Find, >>> >> get, grep,
    >> grepl, intersect, is.unsorted, lapply, Map, mapply, >>>
    >> >> match, mget, order, paste, pmax, pmax.int, pmin,
    >> pmin.int, >>> >> Position, rank, rbind, Reduce, rownames,
    >> sapply, saveRDS, setdiff, >>> >> table, tapply, union,
    >> unique, unsplit, which.max, which.min
    >> >>> >>
    >> >>> >> Welcome to Bioconductor
    >> >>> >>
    >> >>> >> Vignettes contain introductory material; view with
    >> >>> >> 'browseVignettes()'. To cite Bioconductor, see >>>
    >> >> 'citation("Biobase")', and for packages
    >> 'citation("pkgname")'.
    >> >>> >>
    >> >>> >>> library("SummarizedExperiment") >>> >> Loading
    >> required package: MatrixGenerics >>> >> Loading required
    >> package: matrixStats
    >> >>> >>
    >> >>> >> Attaching package: 'matrixStats'
    >> >>> >>
    >> >>> >> The following objects are masked from
    >> 'package:Biobase':
    >> >>> >>
    >> >>> >> anyMissing, rowMedians
    >> >>> >>
    >> >>> >>
    >> >>> >> Attaching package: 'MatrixGenerics'
    >> >>> >>
    >> >>> >> The following objects are masked from
    >> 'package:matrixStats':
    >> >>> >>
    >> >>> >> colAlls, colAnyNAs, colAnys, colAvgsPerRowSet,
    >> colCollapse, >>> >> colCounts, colCummaxs, colCummins,
    >> colCumprods, colCumsums, >>> >> colDiffs, colIQRDiffs,
    >> colIQRs, colLogSumExps, colMadDiffs, >>> >> colMads,
    >> colMaxs, colMeans2, colMedians, colMins, colOrderStats,
    >> >>> >> colProds, colQuantiles, colRanges, colRanks,
    >> colSdDiffs, colSds, >>> >> colSums2, colTabulates,
    >> colVarDiffs, colVars, colWeightedMads, >>> >>
    >> colWeightedMeans, colWeightedMedians, colWeightedSds, >>>
    >> >> colWeightedVars, rowAlls, rowAnyNAs, rowAnys,
    >> rowAvgsPerColSet, >>> >> rowCollapse, rowCounts,
    >> rowCummaxs, rowCummins, rowCumprods, >>> >> rowCumsums,
    >> rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps, >>> >>
    >> rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians,
    >> rowMins, >>> >> rowOrderStats, rowProds, rowQuantiles,
    >> rowRanges, rowRanks, >>> >> rowSdDiffs, rowSds, rowSums2,
    >> rowTabulates, rowVarDiffs, rowVars, >>> >>
    >> rowWeightedMads, rowWeightedMeans, rowWeightedMedians,
    >> >>> >> rowWeightedSds, rowWeightedVars
    >> >>> >>
    >> >>> >> The following object is masked from
    >> 'package:Biobase':
    >> >>> >>
    >> >>> >> rowMedians
    >> >>> >>
    >> >>> >> Loading required package: GenomicRanges >>> >>
    >> Loading required package: stats4 >>> >> Loading required
    >> package: S4Vectors
    >> >>> >>
    >> >>> >> Attaching package: 'S4Vectors'
    >> >>> >>
    >> >>> >> The following objects are masked from
    >> 'package:Matrix':
    >> >>> >>
    >> >>> >> expand, unname
    >> >>> >>
    >> >>> >> The following object is masked from
    >> 'package:utils':
    >> >>> >>
    >> >>> >> findMatches
    >> >>> >>
    >> >>> >> The following objects are masked from
    >> 'package:base':
    >> >>> >>
    >> >>> >> expand.grid, I, unname
    >> >>> >>
    >> >>> >> Loading required package: IRanges >>> >> Loading
    >> required package: GenomeInfoDb >>> >>>
    >> library("SingleCellExperiment") >>> >>>
    >> setClassUnion("ExpData", c("matrix", "dgCMatrix", >>> >>
    >> + "ExpressionSet", "SingleCellExperiment")) >>> >>>
    >> setGeneric("expShow", \(.) standardGeneric("expShow"))
    >> >>> >> [1] "expShow" >>> >>> setMethod("expShow",
    >> "ExpData", \(.) show(.))  >>> >>> p <- 10 >>> >>> n <- 30
    >> >>> >>> y <- matrix(rnorm(n*p), nrow=p, ncol=n, >>> >> +
    >> dimnames=list( >>> >> + paste("g", 1:p, sep="") , >>> >>
    >> + paste("s", 1:n, sep=""))) >>> >>> se <-
    >> SummarizedExperiment(y) >>> >>> sce <-
    >> SingleCellExperiment(y) >>> >>> expShow(se) >>> >> Error:
    >> unable to find an inherited method for function 'expShow'
    >> for >>> >> signature '. = "SummarizedExperiment"' >>> >>>
    >> expShow(sce) >>> >> class: SingleCellExperiment >>> >>
    >> dim: 10 30 >>> >> metadata(0): >>> >> assays(1): '' >>>
    >> >> rownames(10): g1 g2 ... g9 g10 >>> >> rowData
    >> names(0): >>> >> colnames(30): s1 s2 ... s29 s30 >>> >>
    >> colData names(0): >>> >> reducedDimNames(0): >>> >>
    >> mainExpName: NULL >>> >> altExpNames(0):
    >> >>> >>>
    >> >>> >>>
    >> >>> >>> sessionInfo() >>> >> R version 4.4.2 (2024-10-31)
    >> >>> >> Platform: x86_64-pc-linux-gnu >>> >> Running
    >> under: Ubuntu 20.04.6 LTS
    >> >>> >>
    >> >>> >> Matrix products: default >>> >> BLAS:
    >> /home/axel/R/local/R-4.4.2/lib/libRblas.so >>> >> LAPACK:
    >> /home/axel/R/local/R-4.4.2/lib/libRlapack.so; LAPACK
    >> version 3.12.0
    >> >>> >>
    >> >>> >> locale: >>> >> [1] LC_CTYPE=en_US.UTF-8
    >> LC_NUMERIC=C >>> >> [3] LC_TIME=es_ES.UTF-8
    >> LC_COLLATE=en_US.UTF-8 >>> >> [5] LC_MONETARY=es_ES.UTF-8
    >> LC_MESSAGES=en_US.UTF-8 >>> >> [7] LC_PAPER=es_ES.UTF-8
    >> LC_NAME=C >>> >> [9] LC_ADDRESS=C LC_TELEPHONE=C >>> >>
    >> [11] LC_MEASUREMENT=es_ES.UTF-8 LC_IDENTIFICATION=C
    >> >>> >>
    >> >>> >> time zone: Europe/Madrid >>> >> tzcode source:
    >> system (glibc)
    >> >>> >>
    >> >>> >> attached base packages: >>> >> [1] stats4 stats
    >> graphics grDevices utils datasets methods >>> >> [8] base
    >> >>> >>
    >> >>> >> other attached packages: >>> >> [1]
    >> SingleCellExperiment_1.28.1 SummarizedExperiment_1.36.0
    >> >>> >> [3] GenomicRanges_1.58.0 GenomeInfoDb_1.42.1 >>>
    >> >> [5] IRanges_2.40.1 S4Vectors_0.44.0 >>> >> [7]
    >> MatrixGenerics_1.18.0 matrixStats_1.5.0 >>> >> [9]
    >> Biobase_2.66.0 BiocGenerics_0.52.0 >>> >> [11]
    >> Matrix_1.7-1
    >> >>> >>
    >> >>> >> loaded via a namespace (and not attached): >>> >>
    >> [1] R6_2.5.1 SparseArray_1.6.0 zlibbioc_1.52.0 >>> >> [4]
    >> lattice_0.22-6 abind_1.4-8 GenomeInfoDbData_1.2.13 >>> >>
    >> [7] S4Arrays_1.6.0 XVector_0.46.0 UCSC.utils_1.2.0 >>> >>
    >> [10] fortunes_1.5-4 grid_4.4.2 DelayedArray_0.32.0 >>> >>
    >> [13] compiler_4.4.2 httr_1.4.7 tools_4.4.2 >>> >> [16]
    >> crayon_1.5.3 jsonlite_1.8.9
    >> >>> >>
    >> >>> >> Am Mi., 8. Jan. 2025 um 13:41 Uhr schrieb Helena
    >> L. Crowell <helena using crowell.eu>: >>> >>> SCE inherits from
    >> SE, but not vice versa. So setting the class union on SCE
    >> (not SE) will do the trick. Briefly, Anything defined on
    >> an SCE will work upstream (SE), but anything defined on
    >> SE will not work downstream (SPE, SCE).
    >> >>> >>>
    >> >>> >>> ***
    >> >>> >>>
    >> >>> >>> This works:
    >> >>> >>>
    >> >>> >>> library("Matrix") >>> >>> library("Biobase") >>>
    >> >>> library("SummarizedExperiment") >>> >>>
    >> library("SingleCellExperiment")
    >> >>> >>>
    >> >>> >>> setClassUnion("ExpData", c("matrix", "dgCMatrix",
    >> >>> >>> "ExpressionSet", "SingleCellExperiment"))
    >> >>> >>>
    >> >>> >>> setGeneric("expShow", \(.)
    >> standardGeneric("expShow")) >>> >>> setMethod("expShow",
    >> "ExpData", \(.) show(.))
    >> >>> >>>
    >> >>> >>> p <- 10 >>> >>> n <- 30 >>> >>> y <-
    >> matrix(rnorm(n*p), nrow=p, ncol=n, >>> >>> dimnames=list(
    >> >>> >>> paste("g", 1:p, sep="") , >>> >>> paste("s", 1:n,
    >> sep="")))
    >> >>> >>>
    >> >>> >>> se <- SummarizedExperiment(y) >>> >>> sce <-
    >> SingleCellExperiment(y)
    >> >>> >>>
    >> >>> >>> expShow(se) >>> >>> expShow(sce)
    >> >>> >>>
    >> >>> >>>> On Jan 7, 2025, at 21:33, Axel Klenk
    >> <axel.klenk using gmail.com> wrote:
    >> >>> >>>>
    >> >>> >>>> Dear Community, dear S4 Experts,
    >> >>> >>>>
    >> >>> >>>> in the GSVA package I want to use an S4 class
    >> union as a superclass >>> >>>> for all supported data
    >> containers and S4 methods >>> >>>> defined for this
    >> superclass, rather than for each subclass, where a >>>
    >> >>>> class-specific implementation is not necessary.  In
    >> particular >>> >>>> I want to avoid having to implement
    >> individual methods for all current >>> >>>> (and
    >> possibly, future) subclasses of SummarizedExperiment >>>
    >> >>>> for common operations like accessing assay names and
    >> dimensions, assay >>> >>>> data, etc.
    >> >>> >>>>
    >> >>> >>>> As you can see from the example code and output
    >> below, this works as >>> >>>> expected for
    >> SummarizedExperiment objects but not >>> >>>> for its
    >> subclasses such as SingleCellExperiment or
    >> SpatialExperiment >>> >>>> (if SummarizedExperiment is
    >> part of the class union and >>> >>>> the others are not).
    >> In the latter case the result is "Error: unable >>> >>>>
    >> to find an inherited method for function ..." (please see
    >> below).
    >> >>> >>>>
    >> >>> >>>> I'd be very grateful if someone with more S4
    >> expertise than myself >>> >>>> could please let me know
    >> if and how this can be solved -- or >>> >>>> if the whole
    >> thing is not a good idea at all. ;-)
    >> >>> >>>>
    >> >>> >>>> Thanks a lot,
    >> >>> >>>>
    >> >>> >>>> - axel
    >> >>> >>>>
    >> >>> >>>>
    >> >>> >>>>
    >> >>> >>>> ### define a class union as a common superclass
    >> >>> >>>> library("Matrix") >>> >>>> library("Biobase")
    >> >>> >>>> library("SummarizedExperiment") >>> >>>>
    >> library("SingleCellExperiment") >>> >>>> ## [package
    >> startup messages omitted]
    >> >>> >>>>
    >> >>> >>>> setClassUnion("ExpData", >>> >>>> c("matrix",
    >> "dgCMatrix", "ExpressionSet", "SummarizedExperiment"))
    >> >>> >>>>
    >> >>> >>>> ### ... and an example method for the superclass
    >> >>> >>>> setGeneric("expShow", function(object)
    >> standardGeneric("expShow")) >>> >>>> setMethod("expShow",
    >> >>> >>>> signature=signature(object="ExpData"), >>> >>>>
    >> function(object) { >>> >>>> show(object) >>> >>>> })
    >> >>> >>>>
    >> >>> >>>> ### generate some example data and test the
    >> method: >>> >>>> p <- 10 >>> >>>> n <- 30 >>> >>>> y <-
    >> matrix(rnorm(n*p), nrow=p, ncol=n, >>> >>>>
    >> dimnames=list(paste("g", 1:p, sep="") , paste("s", 1:n,
    >> sep="")))
    >> >>> >>>>
    >> >>> >>>> se <- SummarizedExperiment(y) >>> >>>> show(se)
    >> >>> >>>> ## class: SummarizedExperiment >>> >>>> ## dim:
    >> 10 30 >>> >>>> ## metadata(0): >>> >>>> ## assays(1): ''
    >> >>> >>>> ## rownames(10): g1 g2 ... g9 g10 >>> >>>> ##
    >> rowData names(0): >>> >>>> ## colnames(30): s1 s2 ... s29
    >> s30 >>> >>>> ## colData names(0):
    >> >>> >>>>
    >> >>> >>>> expShow(se) >>> >>>> ## class:
    >> SummarizedExperiment >>> >>>> ## dim: 10 30 >>> >>>> ##
    >> metadata(0): >>> >>>> ## assays(1): '' >>> >>>> ##
    >> rownames(10): g1 g2 ... g9 g10 >>> >>>> ## rowData
    >> names(0): >>> >>>> ## colnames(30): s1 s2 ... s29 s30 >>>
    >> >>>> ## colData names(0):
    >> >>> >>>>
    >> >>> >>>> sce <- SingleCellExperiment(y) >>> >>>>
    >> show(sce) >>> >>>> ## class: SingleCellExperiment >>>
    >> >>>> ## dim: 10 30 >>> >>>> ## metadata(0): >>> >>>> ##
    >> assays(1): '' >>> >>>> ## rownames(10): g1 g2 ... g9 g10
    >> >>> >>>> ## rowData names(0): >>> >>>> ## colnames(30):
    >> s1 s2 ... s29 s30 >>> >>>> ## colData names(0): >>> >>>>
    >> ## reducedDimNames(0): >>> >>>> ## mainExpName: NULL >>>
    >> >>>> ## altExpNames(0):
    >> >>> >>>>
    >> >>> >>>> expShow(sce) >>> >>>> ## Error: unable to find
    >> an inherited method for function 'expShow' >>> >>>> for
    >> signature 'object = "SingleCellExperiment"'
    >> >>> >>>>
    >> >>> >>>> ### ### ###
    >> >>> >>>>
    >> >>> >>>> ## we can define a new subclass of
    >> SummarizedExperiment in the global >>> >>>> environment
    >> that works -- as >>> >>>> ## long as it is not coerced to
    >> SingleCellExperiment
    >> >>> >>>>
    >> >>> >>>> setClass("expA", >>> >>>>
    >> contains="RangedSummarizedExperiment")
    >> >>> >>>>
    >> >>> >>>> ea <- new("expA") >>> >>>> show(ea) >>> >>>> ##
    >> An object of class "expA" >>> >>>> ## Slot "rowRanges":
    >> >>> >>>> ## GRanges object with 0 ranges and 0 metadata
    >> columns: >>> >>>> ## seqnames ranges strand >>> >>>> ##
    >> <Rle> <IRanges> <Rle>
    >> >>> >>>> ##   -------
    >> >>> >>>> ## seqinfo: no sequences
    >> >>> >>>> ##
    >> >>> >>>> ## Slot "colData": >>> >>>> ## DataFrame with 0
    >> rows and 0 columns
    >> >>> >>>> ##
    >> >>> >>>> ## Slot "assays": >>> >>>> ## NULL
    >> >>> >>>> ##
    >> >>> >>>> ## Slot "NAMES": >>> >>>> ## NULL
    >> >>> >>>> ##
    >> >>> >>>> ## Slot "elementMetadata": >>> >>>> ## DataFrame
    >> with 0 rows and 0 columns
    >> >>> >>>> ##
    >> >>> >>>> ## Slot "metadata": >>> >>>> ## list()
    >> >>> >>>>
    >> >>> >>>> expShow(ea) >>> >>>> ## class:
    >> SummarizedExperiment >>> >>>> ## dim: 0 0 >>> >>>> ##
    >> metadata(0): >>> >>>> ## assays(0): >>> >>>> ## rownames:
    >> NULL >>> >>>> ## rowData names(0): >>> >>>> ## colnames:
    >> NULL >>> >>>> ## colData names(0):
    >> >>> >>>>
    >> >>> >>>> scea <- as(ea, "SingleCellExperiment") >>> >>>>
    >> show(scea) >>> >>>> ## class: SingleCellExperiment >>>
    >> >>>> ## dim: 0 0 >>> >>>> ## metadata(0): >>> >>>> ##
    >> assays(0): >>> >>>> ## rownames: NULL >>> >>>> ## rowData
    >> names(0): >>> >>>> ## colnames: NULL >>> >>>> ## colData
    >> names(0): >>> >>>> ## reducedDimNames(0): >>> >>>> ##
    >> mainExpName: NULL >>> >>>> ## altExpNames(0):
    >> >>> >>>>
    >> >>> >>>> expShow(scea) >>> >>>> ## Error: unable to find
    >> an inherited method for function 'expShow' >>> >>>> for
    >> signature 'object = "SingleCellExperiment"'
    >> >>> >>>>
    >> >>> >>>> ### ### ### ### ###
    >> >>> >>>>
    >> >>> >>>> ## as shown below, SummarizedExperiment and
    >> ExpData "know" about their >>> >>>> inheritance relation
    >> but >>> >>>> ## SingleCellExperiment does not...
    >> >>> >>>>
    >> >>> >>>> getClass("SummarizedExperiment") >>> >>>> ##
    >> Class "SummarizedExperiment" [package
    >> "SummarizedExperiment"]
    >> >>> >>>> ##
    >> >>> >>>> ## Slots:
    >> >>> >>>> ##
    >> >>> >>>> ## Name: colData assays NAMES >>> >>>>
    >> elementMetadata metadata >>> >>>> ## Class: DataFrame
    >> Assays_OR_NULL character_OR_NULL >>> >>>> DataFrame list
    >> >>> >>>> ##
    >> >>> >>>> ## Extends: >>> >>>> ## Class "RectangularData",
    >> directly >>> >>>> ## Class "Vector", directly >>> >>>> ##
    >> Class "ExpData", directly >>> >>>> ## Class "Annotated",
    >> by class "Vector", distance 2 >>> >>>> ## Class
    >> "vector_OR_Vector", by class "Vector", distance 2
    >> >>> >>>> ##
    >> >>> >>>> ## Known Subclasses: >>> >>>> ## Class
    >> "RangedSummarizedExperiment", directly, with explicit
    >> coerce
    >> >>> >>>>
    >> >>> >>>> getClass("ExpData") >>> >>>> ## Extended class
    >> definition ( "ClassUnionRepresentation" ) >>> >>>> ##
    >> Virtual Class "ExpData" [in ".GlobalEnv"]
    >> >>> >>>> ##
    >> >>> >>>> ## No Slots, prototype of class "matrix"
    >> >>> >>>> ##
    >> >>> >>>> ## Known Subclasses: >>> >>>> ## Class "matrix",
    >> directly >>> >>>> ## Class "dgCMatrix", directly >>> >>>>
    >> ## Class "ExpressionSet", directly >>> >>>> ## Class
    >> "SummarizedExperiment", directly >>> >>>> ## Class "mts",
    >> by class "matrix", distance 2 >>> >>>> ## Class
    >> "RangedSummarizedExperiment", by class >>> >>>>
    >> "SummarizedExperiment", distance 2, with explicit coerce
    >> >>> >>>>
    >> >>> >>>> getClass("SingleCellExperiment") >>> >>>> ##
    >> Class "SingleCellExperiment" [package
    >> "SingleCellExperiment"]
    >> >>> >>>> ##
    >> >>> >>>> ## Slots:
    >> >>> >>>> ##
    >> >>> >>>> ## Name: int_elementMetadata int_colData >>>
    >> >>>> int_metadata >>> >>>> ## Class: DataFrame DataFrame
    >> >>> >>>> list
    >> >>> >>>> ##
    >> >>> >>>> ## Name: rowRanges colData >>> >>>> assays >>>
    >> >>>> ## Class: GenomicRanges_OR_GRangesList DataFrame >>>
    >> >>>> Assays_OR_NULL
    >> >>> >>>> ##
    >> >>> >>>> ## Name: NAMES elementMetadata >>> >>>> metadata
    >> >>> >>>> ## Class: character_OR_NULL DataFrame >>> >>>>
    >> list
    >> >>> >>>> ##
    >> >>> >>>> ## Extends: >>> >>>> ## Class
    >> "RangedSummarizedExperiment", directly >>> >>>> ## Class
    >> "SummarizedExperiment", by class >>> >>>>
    >> "RangedSummarizedExperiment", distance 2 >>> >>>> ##
    >> Class "RectangularData", by class
    >> "RangedSummarizedExperiment", distance 3 >>> >>>> ##
    >> Class "Vector", by class "RangedSummarizedExperiment",
    >> distance 3 >>> >>>> ## Class "Annotated", by class
    >> "RangedSummarizedExperiment", distance 4 >>> >>>> ##
    >> Class "vector_OR_Vector", by class
    >> "RangedSummarizedExperiment", distance 4
    >> >>> >>>>
    >> >>> >>>> ### ### ### ### ###
    >> >>> >>>>
    >> >>> >>>> getClass("SummarizedExperiment") >>> >>>> ##
    >> Class "SummarizedExperiment" [package
    >> "SummarizedExperiment"]
    >> >>> >>>> ##
    >> >>> >>>> ## Slots:
    >> >>> >>>> ##
    >> >>> >>>> ## Name: colData assays NAMES >>> >>>>
    >> elementMetadata metadata >>> >>>> ## Class: DataFrame
    >> Assays_OR_NULL character_OR_NULL >>> >>>> DataFrame list
    >> >>> >>>> ##
    >> >>> >>>> ## Extends: >>> >>>> ## Class "RectangularData",
    >> directly >>> >>>> ## Class "Vector", directly >>> >>>> ##
    >> Class "ExpData", directly >>> >>>> ## Class "Annotated",
    >> by class "Vector", distance 2 >>> >>>> ## Class
    >> "vector_OR_Vector", by class "Vector", distance 2
    >> >>> >>>> ##
    >> >>> >>>> ## Known Subclasses: >>> >>>> ## Class
    >> "RangedSummarizedExperiment", directly, with explicit
    >> coerce >>> >>>> ## Class "expA", by class
    >> "RangedSummarizedExperiment", distance 2, >>> >>>> with
    >> explicit coerce
    >> >>> >>>>
    >> >>> >>>> getClass("expA") >>> >>>> ## Class "expA" [in
    >> ".GlobalEnv"]
    >> >>> >>>> ##
    >> >>> >>>> ## Slots:
    >> >>> >>>> ##
    >> >>> >>>> ## Name: rowRanges colData >>> >>>> assays >>>
    >> >>>> ## Class: GenomicRanges_OR_GRangesList DataFrame >>>
    >> >>>> Assays_OR_NULL
    >> >>> >>>> ##
    >> >>> >>>> ## Name: NAMES elementMetadata >>> >>>> metadata
    >> >>> >>>> ## Class: character_OR_NULL DataFrame >>> >>>>
    >> list
    >> >>> >>>> ##
    >> >>> >>>> ## Extends: >>> >>>> ## Class
    >> "RangedSummarizedExperiment", directly >>> >>>> ## Class
    >> "SummarizedExperiment", by class >>> >>>>
    >> "RangedSummarizedExperiment", distance 2, with explicit
    >> coerce >>> >>>> ## Class "RectangularData", by class
    >> "RangedSummarizedExperiment", distance 3 >>> >>>> ##
    >> Class "Vector", by class "RangedSummarizedExperiment",
    >> distance 3 >>> >>>> ## Class "ExpData", by class
    >> "RangedSummarizedExperiment", distance 3, >>> >>>> with
    >> explicit coerce >>> >>>> ## Class "Annotated", by class
    >> "RangedSummarizedExperiment", distance 4 >>> >>>> ##
    >> Class "vector_OR_Vector", by class
    >> "RangedSummarizedExperiment", distance 4
    >> >>> >>>>
    >> >>> >>>> getClass("SingleCellExperiment") >>> >>>> ##
    >> Class "SingleCellExperiment" [package
    >> "SingleCellExperiment"]
    >> >>> >>>> ##
    >> >>> >>>> ## Slots:
    >> >>> >>>> ##
    >> >>> >>>> ## Name: int_elementMetadata int_colData >>>
    >> >>>> int_metadata >>> >>>> ## Class: DataFrame DataFrame
    >> >>> >>>> list
    >> >>> >>>> ##
    >> >>> >>>> ## Name: rowRanges colData >>> >>>> assays >>>
    >> >>>> ## Class: GenomicRanges_OR_GRangesList DataFrame >>>
    >> >>>> Assays_OR_NULL
    >> >>> >>>> ##
    >> >>> >>>> ## Name: NAMES elementMetadata >>> >>>> metadata
    >> >>> >>>> ## Class: character_OR_NULL DataFrame >>> >>>>
    >> list
    >> >>> >>>> ##
    >> >>> >>>> ## Extends: >>> >>>> ## Class
    >> "RangedSummarizedExperiment", directly >>> >>>> ## Class
    >> "SummarizedExperiment", by class >>> >>>>
    >> "RangedSummarizedExperiment", distance 2 >>> >>>> ##
    >> Class "RectangularData", by class
    >> "RangedSummarizedExperiment", distance 3 >>> >>>> ##
    >> Class "Vector", by class "RangedSummarizedExperiment",
    >> distance 3 >>> >>>> ## Class "Annotated", by class
    >> "RangedSummarizedExperiment", distance 4 >>> >>>> ##
    >> Class "vector_OR_Vector", by class
    >> "RangedSummarizedExperiment", distance 4
    >> >>> >>>>
    >> >>> >>>> _______________________________________________
    >> >>> >>>> Bioc-devel using r-project.org mailing list >>> >>>>
    >> https://stat.ethz.ch/mailman/listinfo/bioc-devel
    >> >>> >>>>
    >> >>> > _______________________________________________ >>>
    >> > Bioc-devel using r-project.org mailing list >>> >
    >> https://stat.ethz.ch/mailman/listinfo/bioc-devel
    >> >>>
    >> >>> --
    >> >>> Hervé Pagès
    >> >>>
    >> >>> Bioconductor Core Team >>> hpages.on.github using gmail.com
    >> >>>
    >> >>> _______________________________________________ >>>
    >> Bioc-devel using r-project.org mailing list >>>
    >> https://stat.ethz.ch/mailman/listinfo/bioc-devel
    >> >>
    >> >>
    >> >>
    >> >> --
    >> >> Best, >> Kasper
    >> 
    >> > _______________________________________________ >
    >> Bioc-devel using r-project.org mailing list >
    >> https://stat.ethz.ch/mailman/listinfo/bioc-devel



More information about the Bioc-devel mailing list