I would like to write a [. method for my ReferenceClass. So far, I have something like this:
DT <- data.table(Index=1:5)
MySeries <- setRefClass("MySeries", fields = list(data="data.table"))
setMethod("[","MySeries",function(x, i,j,drop) {
ii <- substitute(i)
x$data <- x$data[eval(ii)]
return(x)
})
S <- MySeries(data=DT)
... but it throws an error when I finally call S[Index>3]. How to fix the above to get this expected result?
Index
4: 4
5: 5
This is really about the use of eval(substitute()) as much as about S4 methods. Here is the generic that you are interested in
> getGeneric("[")
standardGeneric for "[" defined from package "base"
function (x, i, j, ..., drop = TRUE)
standardGeneric("[", .Primitive("["))
<bytecode: 0x42f4fe0>
<environment: 0x3214270>
Methods may be defined for arguments: x, i, j, drop
Use showMethods("[") for currently available ones.
Your method signature differs from the generic (no '...' and no default for 'drop') so the method has a nested '.local' function
> getMethod("[", "MySeries")
Method Definition:
function (x, i, j, ..., drop = TRUE)
{
.local <- function (x, i, j, drop)
{
ii <- substitute(i)
x$data <- x$data[eval(ii)]
return(x)
}
.local(x, i, j, ..., drop)
}
Signatures:
x
target "MySeries"
defined "MySeries"
and subsitute(i) is not what you think it is. Instead, write a method matching the generic signature
setMethod("[", "MySeries", function(x, i, j, ..., drop=TRUE) {
x$data <- x$data[eval(substitute(i))]
x
})
nested functions are a general problem with the eval(substitute()) paradigm, not just definition of S4 methods; see this question.
Related
I defined some S4 matrices, whose class is lazyMatrix. If M is such a matrix, I want to define M[2, ] as the second row of M, and M[2] as the second coefficient of M (when enumerating the coefficients column after column).
So I defined these two S4 methods:
setMethod( # to extract a coefficient
"[",
signature("lazyMatrix", i = "numeric"),
function(x, i) {
......
}
)
setMethod( # to extract a row
"[",
signature("lazyMatrix", i = "numeric", j = "missing", drop = "ANY"),
function(x, i, j, drop) {
......
}
)
But both M[2, ] and M[2] return the second row of M. I tried to exchange the order of the two method definitions, that does not change anything.
I found a solution in the source code of the onion package:
setMethod(
"[",
signature("lazyMatrix", i = "numeric", j = "missing", drop = "missing"),
function(x, i, j, ..., drop) {
n_args <- nargs()
if(n_args == 3L) { # M[i, ]
......
} else if(n_args == 2L) { # M[i]
......
} else {
stop("Invalid arguments in subsetting.")
}
}
)
setMethod(
"[",
signature("lazyMatrix", i = "numeric", j = "missing", drop = "ANY"),
function(x, i, j, ..., drop) {
n_args <- nargs()
if(n_args == 4L) { # M[i, ]
......
} else if(n_args == 3L) { # M[i]
......
} else {
stop("Invalid arguments in subsetting.")
}
}
)
Before finding this solution, I tried using nargs() in my attempts, unsuccessfully. The trick is to add the ... argument. But I don't understand how this works. Please leave another answer or a comment if you can explain.
The dispatch issue has nothing to do with the dots, though it is problematic that the formal arguments of your methods do not match those of the generic function:
> getGeneric("[")
standardGeneric for "[" defined from package "base"
function (x, i, j, ..., drop = TRUE)
standardGeneric("[", .Primitive("["))
<bytecode: 0x1403d1a28>
<environment: 0x1403c9d10>
Methods may be defined for arguments: x, i, j, drop
Use showMethods([) for currently available ones.
?setMethod is quite clear about that:
The definition must be a function with the same formal arguments as the generic; however, setMethod() will handle methods that add arguments, if ... is a formal argument to the generic. See the Details section.
Anyway, the real issue is that setMethod interprets your first signature
signature("lazyMatrix", i = "numeric")
as
signature("lazyMatrix", i = "numeric", j = "ANY", drop = "ANY")
and both of the calls M[2] and M[2, ] match your second signature
signature("lazyMatrix", i = "numeric", j = "missing", drop = "ANY")
more closely than the first (because "missing" is more specific than "ANY"). Hence dispatch in both cases favours the second method.
It is still true that the primitive function [ is the trickiest generic function out there, due to all of the special cases handled in C code for traditional matrices (of implicit class matrix). The nargs() approach is correct and also used by package Matrix, which is really the canonical reference for this kind of thing:
> library(Matrix)
> selectMethod("[", signature = c("Matrix", "numeric", "missing", "missing"))
Method Definition:
function (x, i, j, ..., drop = TRUE)
{
Matrix.msg("M[i,m,m] : nargs()=", nargs(), .M.level = 2)
if (nargs() == 2) {
.M.vectorSub(x, i)
}
else {
callGeneric(x, i = i, , drop = TRUE)
}
}
<bytecode: 0x128e94a90>
<environment: namespace:Matrix>
Signatures:
x i j drop
target "Matrix" "numeric" "missing" "missing"
defined "Matrix" "index" "missing" "missing"
I am pretty new to R, I have coded with Python and here OOP is quite different to python. I am trying to understand it, so in S3 you can create methods/functions that are not directly attached to a single class, just the same as the objects as they can be in multiple classes (which is quite flexible I guess). However what I do not understand is when I am creating a class such as:
> my_mean <- function (x, ...) {
UseMethod("my_mean", x)}
> my_mean
function (x, ...) {
UseMethod("my_mean", x)}
> my_mean.default <- function(obj){cat("this is a generic function")}
> my_mean.default
function(obj){cat("this is a generic function")}
But then when I have to run for example summary:
summary.default
function (object, ..., digits, quantile.type = 7)
{
if (is.factor(object))
return(summary.factor(object, ...))
else if (is.matrix(object)) {
if (missing(digits))
return(summary.matrix(object, quantile.type = quantile.type,
...))
else return(summary.matrix(object, digits = digits, quantile.type = quantile.type,
...))
}
value <- if (is.logical(object))
c(Mode = "logical", {
tb <- table(object, exclude = NULL, useNA = "ifany")
if (!is.null(n <- dimnames(tb)[[1L]]) && any(iN <- is.na(n))) dimnames(tb)[[1L]][iN] <- "NA's"
tb
})
else if (is.numeric(object)) {
nas <- is.na(object)
object <- object[!nas]
qq <- stats::quantile(object, names = FALSE, type = quantile.type)
qq <- c(qq[1L:3L], mean(object), qq[4L:5L])
if (!missing(digits))
qq <- signif(qq, digits)
names(qq) <- c("Min.", "1st Qu.", "Median",
"Mean", "3rd Qu.", "Max.")
if (any(nas))
c(qq, `NA's` = sum(nas))
else qq
}
else if (is.recursive(object) && !is.language(object) &&
(n <- length(object))) {
sumry <- array("", c(n, 3L), list(names(object),
c("Length", "Class", "Mode")))
ll <- numeric(n)
for (i in 1L:n) {
ii <- object[[i]]
ll[i] <- length(ii)
cls <- oldClass(ii)
sumry[i, 2L] <- if (length(cls))
cls[1L]
else "-none-"
sumry[i, 3L] <- mode(ii)
}
sumry[, 1L] <- format(as.integer(ll))
sumry
}
else c(Length = length(object), Class = class(object), Mode = mode(object))
class(value) <- c("summaryDefault", "table")
value
}
<bytecode: 0x000001926eaaf8f8>
<environment: namespace:base>
> summary
function (object, ...)
UseMethod("summary")
<bytecode: 0x000001926e9ec2c0>
<environment: namespace:base>
I cannot see the difference in why when you call summary in the console it does not give you the function, it gives you a reference to that object. There's any explanation? Furthermore, is it generic in some way similar to init?
S3 classes work nothing like any OOP you may be familiar with from other languages. They are a losely connected set of mechanisms that only work when you stick to certain rules.
x <- 1:11
mean(x)
#> [1] 6
This implcitely calls the function mean.default because x is a simple atomic vector.
Now we create a method for our own class evil
mean.evil <- function( x ) {
return(666) # always retuns 666 that is why it is evil
}
And we convert the vector x to a class evil:
class(x) <- "evil" # you can actually do it just like that
Now, calling mean determines that xis of class evil and calls the according function.
mean(x) # calls mean.evil
#> [1] 666
mean.default(x) # coerces R to use the default method which is still possible
#> [1] 6
The reason is that mean uses UseMethod() which checks the class and tries to find a function that has name with the pattern mean.[myclass]. And that is all that happens.
mean
#> function (x, ...)
#> UseMethod("mean")
#> <bytecode: 0x0000000015812e18>
#> <environment: namespace:base>
In other languages everything is held together by the syntax. S3 mechanisms on the other hand can be used to "approximate" OOP but they can be easily misused. They are simply and effective and appropriate for many use cases in R. If you are interested in more advanced OOP in R I recommend R6 classes.
Created on 2020-06-30 by the reprex package (v0.3.0)
This is my function:
f <- function(a, b, ...){
c(as.list(environment()), list(...))
}
If I call f(a = 2) no error will be raised, although b is missing. I would like to get an error in this case:
Error in f(a = 2) : argument "b" is missing, with no default
What piece of dynamic and efficient code I must add such that this error be raised? I was thinking something in line of the following: force(as.symbol(names(formals()))).
Note: In case you wonder why I need this kind of function: It is a way to standardize the kinds of lists. Such a list must have a and b, and possibly other keys. I could play with objects too...
Solutions: See Carl's answer or comments below.
f <- function(a, b, ...){
sapply(ls(environment()), get, envir = environment(), inherits = FALSE)
c(as.list(environment()), list(...))
}
Or
f <- function(a, b, ...){
stopifnot(all(setdiff(names(formals()), '...') %in% names(as.list(match.call()[-1]))))
c(as.list(environment()), list(...))
}
An idea... first check for all arguments that exist in the any function anonymously... meaning regardless of the functions, get the arguments into a list with no preset requirements:
#' A function to grab all arguments of any calling environment.. ie.. a function
#'
#'
#' \code{grab.args}
#'
grab.args <- function() {
envir <- parent.frame()
func <- sys.function(-1)
call <- sys.call(-1)
dots <- match.call(func, call, expand.dots=FALSE)$...
c(as.list(envir), dots)
}
Then, in whatever function you use it for.. store the initial arguments on a list does_have, then find all the arguments that are pre-defined in the environment with should_have, loop through the list to match names and find if any are missing values... if any are... create the error with the names that are missing, if not... do your thing...
#' As an example
#'
f <- function(a, b, ...){
does_have <- grab.args()
should_have <- ls(envir = environment())
check_all <- sapply(should_have, function(i){
!nchar(does_have[[i]])
})
if(any(mapply(isTRUE, check_all))){
need_these <- paste(names(which(mapply(isTRUE,check_all))), collapse = " and ")
cat(sprintf('Values needed for %s', need_these))
}else {
does_have
}
}
Outputs for cause....
> f(mine = "yours", a = 3)
Values needed for b
> f(b = 12)
Values needed for a
> f(hey = "you")
Values needed for a and b
Edit to throw an actual error...
f <- function(a,b,...){
Filter(missing, sapply(ls(environment()), get, environment()))
}
> f(a = 2, wtf = "lol")
Error in FUN(X[[i]], ...) : argument "b" is missing, with no default
I would like to have a variable number of arguments in my S4 generic myMethod such that these are both valid:
myMethod(100)
myMethod(100, 200)
Here is my attempt at a definition:
setGeneric(
"myMethod",
function(x) {
standardGeneric("myMethod")
})
setMethod(
"myMethod",
signature = c("numeric"),
definition = function(x) {
print("MyMethod on numeric")
})
setMethod(
"myMethod",
signature = c("numeric", "numeric"),
definition = function(x, y) {
print("MyMethod on numeric, numeric")
})
However, this gives the error:
Error in matchSignature(signature, fdef) :
more elements in the method signature (2) than in the generic signature (1) for function ‘myMethod’
It's worth clarifying whether you want to dispatch (select a method) on more than one argument (in which case include all argument names in the signature= of setGeneric())
setGeneric("fun", function(x, y) standardGeneric("fun"),
signature=c("x", "y"))
setMethod("fun", c(x="numeric", y="numeric"), function(x, y) {
"fun,numeric,numeric-method"
})
versus dispatching based on the first (include only the first argument in signature=) and either require all methods to have additional arguments (name the arguments in the generic function)
setGeneric("fun", function(x, y) standardGeneric("fun"),
signature="x")
setMethod("fun", c(x="numeric"), function(x, y) {
"fun,numeric-method"
})
or only some methods (use ... in the generic, and name the arguments in the method).
setGeneric("fun", function(x, ...) standardGeneric("fun"),
signature="x")
setMethod("fun", c(x="numeric"), function(x, y) {
"fun,numeric-method"
})
Your generic should support 2 arguments.
setGeneric(
"myMethod",
function(x, y) {
standardGeneric("myMethod")
})
Also the function in your second method should actually support two arguments:
setMethod(
"myMethod",
signature = c("numeric", "numeric"),
definition = function(x, y) {
print("MyMethod on numeric, numeric")
})
More generally, if you want to specify arbitrarily many arguments, you should have a look at the elipsis argument ....
I have a subclass of data.frame that needs an extra argument when subsetting. NextMethod() passes extra arguments along, which generates an error because the next method recognizes neither the argument itself, nor the 'dots' arguments.
Example:
class(Theoph) <- c('special','data.frame')
`[.special` <- function(x, i, j, drop, k, ...){
y <- NextMethod()
attr(y, 'k') <- k
y
}
Theoph[1:5,k='head']
Result:
Error in `[.data.frame`(Theoph, 1:5, k = "head") :
unused argument (k = k)
Can I make 'k' invisible downstream? I've tried removing it, defining as NULL, passing only arguments of interest, writing a wrapper. The subset operator [ is a particularly difficult generic because of some non-default argument matching rules.
Since in this case you know what the next method is, why not just call it?
class(Theoph) <- c('special','data.frame')
`[.special` <- function(x, i, j, drop = TRUE, k, ...) {
y <- `[.data.frame`(x, i, j, drop = drop)
attr(y, 'k') <- k
y
}
Theoph[1:5, k = 'head']
However, I would be cautious about this sort of approach since [ is a rather special function, and I don't think it actually includes ... in its argument list. (It looks like it does in the docs, but I think this is a simplification and it's not using the standard ... object)