My question is how do you extend rbind()
to work with a data.frame
subclass? I cannot seem to properly extend rbind()
to work with even a very simple subclass. The following example demonstrates the issue:
Subclass and method definition:
new_df2 <- function(x, ...)
{
stopifnot(is.data.frame(x))
structure(x, class = c("df2", "data.frame"), author = "some user")
}
rbind.df2 <- function(..., deparse.level = 1)
{
NextMethod()
}
I realize that extending rbind()
is not necessary in this case, but my grand plan is to use rbind.data.frame()
on a my subclass and then add a few additional checks/attributes to its result.
If you call the following, you get an error: Error in NextMethod() : generic function not specified
.
does not work:
t1 <- data.frame(a = 1:12, b = month.abb)
t2 <- new_df2(t1)
rbind(t2, t2)
I also tried using NextMethod(generic = "rbind")
, but in that case, you receive this error: Error in NextMethod(generic = "rbind") : wrong value for .Method
.
also does not work:
rbind.df2 <- function(..., deparse.level = 1)
{
NextMethod(generic = "rbind")
}
rbind(t2, t2)
I'm at wits end and guess at the limits of my understanding of subclasses/methods too. Thanks for any help.
I will treat the rbind()
specific case below, but I will first note we could generate additional examples showing that there are not problems generally with NextMethod()
when the first argument is ...
(regarding the bounty request):
f <- function(..., b = 3) UseMethod("f")
f.a <- function(..., b = 3) { print("yes"); NextMethod() }
f.integer <- function(..., b = 4) sapply(list(...), "*", b)
x <- 1:10
class(x) <- c("a", class(x))
f(x)
[1] "yes"
[,1]
[1,] 4
[2,] 8
[3,] 12
[4,] 16
[5,] 20
[6,] 24
[7,] 28
[8,] 32
[9,] 36
[10,] 40
f(x, b = 5)
[1] "yes"
[,1]
[1,] 5
[2,] 10
[3,] 15
[4,] 20
[5,] 25
[6,] 30
[7,] 35
[8,] 40
[9,] 45
[10,] 50
As it turns out, rbind()
and cbind()
are not normal generics. First, they are internally generic; see the "Internal Generics" section here from Hadley Wickham's old S3 page on Advanced R, or this excerpt from the current Advanced R:
Some S3 generics, like [, sum(), and cbind(), don’t call UseMethod() because they are implemented in C. Instead, they call the C functions DispatchGroup() or DispatchOrEval().
This isn't quite enough to cause us trouble, as we can see using sum()
as an example:
sum.a <- function(x, na.rm = FALSE) { print("yes"); NextMethod() }
sum(x)
[1] "yes"
[1] 55
However, for rbind
and cbind
it's even weirder, as recognized in comments in the source code (starting around line 1025):
/* cbind(deparse.level, ...) and rbind(deparse.level, ...) : */
/* This is a special .Internal */
... (Some code omitted) ...
/* Lazy evaluation and method dispatch based on argument types are
* fundamentally incompatible notions. The results here are
* ghastly.
After that, some explanation of the dispatch rules are given, but so far I haven't been able to use that information to make NextMethod()
work. In the use case given above, I would follow the advice of F. Privé from the comments and do this:
new_df2 <- function(x, ...)
{
stopifnot(is.data.frame(x))
structure(x, class = c("df2", "data.frame"))
}
rbind.df2 <- function(..., deparse.level = 1)
{
print("yes") # Or whatever else you want/need to do
base::rbind.data.frame(..., deparse.level = deparse.level)
}
t1 <- data.frame(a = 1:12, b = month.abb)
t2 <- new_df2(t1)
rbind(t2, t2)
[1] "yes"
a b
1 1 Jan
2 2 Feb
3 3 Mar
4 4 Apr
5 5 May
6 6 Jun
7 7 Jul
8 8 Aug
9 9 Sep
10 10 Oct
11 11 Nov
12 12 Dec
13 1 Jan
14 2 Feb
15 3 Mar
16 4 Apr
17 5 May
18 6 Jun
19 7 Jul
20 8 Aug
21 9 Sep
22 10 Oct
23 11 Nov
24 12 Dec
The answer is to extend rbind2
, not rbind
. From the help page from rbind2
:
"These are (S4) generic functions with default methods.
...
The main use of cbind2 (rbind2) is to be called recursively by cbind() (rbind()) when both of these requirements are met:
There is at least one argument that is an S4 object, and
S3 dispatch fails (see the Dispatch section under cbind)."
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With