1

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?

user20650
  • 24,654
  • 5
  • 56
  • 91
January
  • 16,320
  • 6
  • 52
  • 74

1 Answers1

4

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)

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • 1
    It's confusing to have a slot `data="data.frame"` in your class definition. You don't need it — when you make a class that `contains` another, a slot `.Data` is automatically added to the definition which contains the inherited class (so your example now has two data frame slots, `data` which contains the supplied data frame and `.Data` which should have contained it but doesn't). Try `nrow` on your `my_DF` object — you will find it violates the "otherwise, behaves exactly like a data frame" requirement. – JDL Apr 17 '20 at 14:35
  • @JDL You're right - my bad. I'll edit the answer. Thank you. – Allan Cameron Apr 17 '20 at 14:40
  • Thank you! And how can I make a_MyDF to inherit the show() method of data.frame? I mean – exactly like, because right now it also shows slot information and "Object of class "MyDF"", in other words – does not exactly behave like a data frame? – January Apr 17 '20 at 18:27
  • 1
    @January I think that's because there _is_ no `show` method for class `data.frame`, which you can confirm by doing `showMethods("show")`. Non-S4 objects that have no specific `show` method defined (like data frames) simply end up calling `print` (via `showDefault`) when you call `show`. So if you want `MyDF` to be an S4 class, then `show` is one of the functions for which you need to set a method: `setMethod("show", signature("MyDF"), function(object) print(object))` – Allan Cameron Apr 17 '20 at 19:31
  • Trying to use your solution during package development but I get a strange error when I run `devtools::check()`: ``` Error: c("unnamed argument to initialize() for S3 class must have a class definition; \"tbl_df\" does not", "unnamed argument to initialize() for S3 class must have a class definition; \"tbl\" does not", "unnamed argument to initialize() for S3 class must have a class definition; \"data.frame\" does not") ``` – andrew Patterson Aug 16 '21 at 14:25