Search code examples
rr-s4r-s3

Create an S4 class inheriting from a data frame


I am writing an R package. Within this package, I wish to have a special type of a data frame that some of the functions can recognize, with some extra attributes, say, but otherwise behaving exactly like a data frame. One way to achieve what I want is just to set some attributes on a regular data frame:

   makedf <- function() {
     df <- data.frame(ID=1:3)
     attr(df, "myDF") <- TRUE
     attr(df, "source") <- "my nose"
     return(df)
   }

   dosmth <- function(df) {
     if(!is.null(attr(df, "myDF"))) message(sprintf("Oh my! My DF! From %s!", attr(df, "source")))
     message(sprintf("num of rows: %d", nrow(df)))
   }

When dosmth() receives a "myDF", it has additional information on the source of the data frame:

dosmth(data.frame(1:5))
#> num of rows: 5
dosmth(makedf())
#> Oh my! My DF! From my nose!
#> num of rows: 3

Likewise, it would be fairly simple with S3, and we could even write different variants of dosmth taking advantage of method dispatch. How do I do that with S4?


Solution

  • I'm not entirely sure whether this is what you're looking for, but it sounds as though you want to be able to define a generic function which specializes for your MyDF and data.frame, like this reprex

    MyDF <- setClass(Class = "MyDF",
                     slots = c(source = "character"),
                     contains = c("data.frame"))
    
    setGeneric("dosmth", function(x) message("Can only dosmth to a df or MyDF"))
    
    setMethod("dosmth", signature("data.frame"), 
              function(x) message(sprintf("num of rows: %d", nrow(x))))
    
    setMethod("dosmth", signature("MyDF"), 
              function(x)  message(sprintf("Oh my! My DF! From %s!", x@source)))
    
    a_df   <- data.frame(a = 1, b = 2)
    a_MyDF <- MyDF(a_df, source = "my nose")
    
    dosmth("my nose")
    #> Can only dosmth to a df or MyDF
    dosmth(a_df)
    #> num of rows: 1
    dosmth(a_MyDF)
    #> Oh my! My DF! From my nose!
    

    Thanks to @JDL for the comment pointing out that we don't need an extra "data.frame" slot, so that data.frame behaviour can be inherited correctly, as the following example shows:

    a_MyDF[1,]
    #>   a b
    #> 1 1 2
    

    Created on 2020-04-17 by the reprex package (v0.3.0)