R: Отправка метода S3 в зависимости от аргументов

У меня есть общая функция foo, которую я хочу вызывать тремя разными способами в зависимости от переданных ей аргументов.

foo <- function(...) UseMethod("foo")

#default
foo.default <- function(x, y, ...) {
#does some magic
print("this is the default method")
}

#formula
foo.formula <- function(formula, data = list(), ...) {
print("this is the formula method")
}

#data.frame
foo.data.frame <- function(data, x, y, ...) {
print("this is the data.frame method")
}

Далее я собираюсь показать, как я ожидаю, чтобы диспетчеризация метода работала, но выходные данные представлены при каждом вызове...

mydata <- data.frame(x=c(1,2,3,4),y=c(5,6,7,8))

#ways to call default function
foo(x = mydata$x, y = mydata$y)
#[1] "this is the default method"

#ways to call formula
foo(formula = mydata$x~mydata$y)
#[1] "this is the formula method"
foo(formula = x~y, data = mydata)
#[1] "this is the formula method"
foo(data = mydata, formula = x~y)  #ERROR
#[1] "this is the data.frame method"

#ways to call data.frame method
foo(data = mydata, x = x, y = y)
#[1] "this is the data.frame method"
foo(x = x, y = y, data = mydata) #ERROR
#Error in foo(x = x, y = y, data = mydata) : object 'x' not found

насколько я могу судить, используемый метод зависит от класса первого аргумента. По сути, я хотел бы, чтобы диспетчеризация метода зависела от аргументов, переданных универсальной функции foo, а не от первого аргумента.

Я хотел бы, чтобы отправка имела следующий приоритет:

Если присутствует аргумент формулы, используется метод формулы (аргумент данных должен быть здесь необязательным)

Затем, если аргумент формулы не найден, если присутствует аргумент данных, используйте метод data.frame (для которого требуются аргументы x и y)

иначе foo ожидает аргументы x и y, иначе произойдет сбой.

Примечание

Я хотел бы избежать определения общей функции foo следующим образом

foo <- function(formula, data,...) UseMethod("foo")

в то время как это исправит все мои проблемы (я полагаю, что все, кроме последнего случая), это вызовет предупреждение devtools::check(), потому что некоторые из функций S3 не будут иметь те же аргументы, что и общая функция, и больше не будут согласованы (в частности, foo.default и foo.data.frame). И я не хотел бы включать отсутствующие аргументы, потому что эти методы не используются для этих аргументов.


person Justin Landis    schedule 08.08.2018    source источник
comment
Имеет значение первый именованный аргумент в сигнатуре, а не порядок их вызова. Для foo(a,b) {...}, foo(a = 1, b = 2) и foo(b = 2, a = 1) отправка одного и того же метода на основе класса a. Как правило, вы ищете множественную отправку, которая доступна только в S4, а не в S3.   -  person Thomas    schedule 08.08.2018


Ответы (1)


Как указал Томас, это не стандартное поведение для классов S3. Однако, если вы действительно хотите придерживаться S3, вы можете написать свои функции так, чтобы «имитировать» UseMethod, даже если это будет некрасиво и, вероятно, не то, что вы хотите делать. Тем не менее, здесь идея, основанная на том, чтобы сначала захватить все аргументы, а затем проверить наличие вашего «предпочтительного» типа аргумента:

Сначала получите некоторые объекты:

a <- 1; class(a) <- "Americano"
b <- 2; class(b) <- "Espresso"

Пусть рассматриваемая функция фиксирует все аргументы точками, а затем проверяет наличие типа аргумента в порядке ваших предпочтений:

drink <- function(...){
  dots <- list(...)

  if(any(sapply(dots, function(cup) class(cup)=="Americano"))){
    drink.Americano(...)
    } else { # you can add more checks here to get a hierarchy
        # try to find appropriate method first if one exists, 
        # using the first element of the arguments as usual
        tryCatch(get(paste0("drink.", class(dots[[1]])))(), 
        # if no appropriate method is found, try the default method:
             error = function(e) drink.default(...)) 
  }
}

drink.Americano <- function(...) print("Hmm, gimme more!")
drink.Espresso <- function(...) print("Tripple, please!")
drink.default <- function(...) print("Any caffeine in there?")

drink(a) # "Americano", dispatch hard-coded.
# [1] "Hmm, gimme more!"
drink(b) # "Espresso", not hard-coded, but correct dispatch anyway
# [1] "Tripple, please!"
drink("sthelse") # Dispatches to default method
# [1] "Any caffeine in there?"
drink(a,b,"c")
# [1] "Hmm, gimme more!"
drink(b,"c", a)
# [1] "Hmm, gimme more!"
person coffeinjunky    schedule 08.08.2018