Как преобразовать многоточие в список и обратно в необработанный вызов в R без разрыва

Есть количество хороших (1 2 3) StackOverflow вопросы об использовании многоточия («точки») в R. Тем не менее, я еще не встречал ни одной, которая относилась бы к моему конкретному варианту использования. Я изо всех сил пытаюсь написать оболочку совместимости для функции, которая изменяет имя аргумента с исходного вызова функции на вызов базовой функции. Например, можно вызвать newMean(c(1:10,1000,NA),.2,rmNA=TRUE), а не mean(c(1:10,1000,NA),.2,na.rm=TRUE). Мы могли бы даже захотеть написать нашу оболочку таким образом, чтобы мы не уничтожали вызовы S3 для mean, предполагая, что все вызовы newMean предназначены для отправки для mean.default. Очевидный путь вперед выглядит примерно так (в псевдокоде):

newMean <- function(x,...) {
  dots <- list(...)
  names(dots)[names(dots)=="rmNA"] <- "na.rm"
  x.list <- list(x=x)
  dots <- c(x.list,dots)
  do.call(base::mean,dots)
}

Это оставляет нам значение в точках вроде:

dots <- structure(list(x = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1000, NA), 
    0.2, na.rm = TRUE), .Names = c("x", "", "na.rm"))

Все идет нормально. Но что-то не так, как я мог бы ожидать. Рассмотрим команду newMean(c(1:10,1000,NA),.2,TRUE) по сравнению с base::mean(c(1:10,1000,NA),.2,TRUE). Когда мы вызываем base::mean, R вызывает mean.default, потому что getS3method("base::mean",class(x)) для x в вызове является числовым и не имеет совпадений. match.call(mean.default), вызываемый при отладке newMean показывает, что (безымянный) второй и третий аргументы, конечно, назначаются в порядке трем аргументам перед многоточием в mean.default. Напротив, do.call, похоже, игнорирует безымянные аргументы (может быть, рассматривает их как часть многоточия в вызове mean.default?).

Я попытался определить используемый метод S3 с помощью getS3method, а затем с помощью formals, чтобы вникнуть и определить количество формальных аргументов для конкретного отправляемого метода S3, но мне не удалось сгенерировать способ вызова mean :: base, который гибко учитывает количество формальных элементов, присутствующих в вызываемом методе, и соответствующим образом сопоставляет их.

Есть ли способ исправить поведение newMean таким образом, чтобы trim и na.rm соответствовали соответствующим образом, без удаления сопоставленного вызова из newMean в base: mean (например, deparse(match.call(base::mean))) и вручную подстановки имен аргументов? Причина, по которой я не хочу, чтобы строка манипулировала подстановкой имен аргументов, заключается в том, что я могу представить себе случаи (за пределами этого игрушечного примера), когда значения в вызове могут быть частично сопоставлены с именами аргументов.


person russellpierce    schedule 27.11.2013    source источник
comment
Где-то по пути я потерял ход мыслей, но потом заметил, что у вас есть и rm.na, и na.rm, поэтому я предполагаю, что вы не использовали распечатку значений в том, что, как вы предполагали, будет. Функция match.arg допускает частичные совпадения. (Я также обнаружил, что использование слова «эллипсы» сбивает с толку, поскольку считаю, что правильным термином для обозначения трех точек вместе является просто многоточие.)   -  person IRTFM    schedule 27.11.2013
comment
Я думаю, проблема в том, что вы передаете аргументы newMean как неназванные, когда вы намеренно написали newMean только с одним именованным аргументом. Итак .... для списка точек нет имен. Поэтому попытки соответствовать им обречены. Если вы вместо этого позвоните с newMean(c(1:10,1000,NA), trim=.2, rmNA=TRUE), вы сможете использовать match.args.   -  person IRTFM    schedule 27.11.2013
comment
@DWin: Вы правы насчет rm.na, я исправил эту опечатку, так что я люблю ненавидеть именно na.rm. Я также исправил многоточие на многоточие. Я знаю, что match.args будет работать с именованными аргументами ... это как раз проблема. : /   -  person russellpierce    schedule 28.11.2013


Ответы (2)


Один из подходов - изменить вызов newMean() и заменить его вызовом base::mean():

newMean <- function(...) {
  call <- match.call()
  call[[1]] <- quote(base::mean)

  rmNA <- names(call) == "rmNA"
  names(call)[rmNA] <- "na.rm"

  eval(call, parent.frame())
}
newMean(c(1:10, NA), rmNA = T)

Но это все равно хуже простого:

newMean <- function(x, trim = 0, rmNA = FALSE, ...) {
  mean(x, trim = trim, na.rm = rmNA, ...)
}
person hadley    schedule 27.11.2013
comment
На самом деле я предпочитаю первое второму в качестве общего ответа на мой вопрос, потому что второе должно предполагать, какой метод среднего делает для отправки на x. Есть ли еще какие-то опасности, о которых я должен знать? Или вам это просто не понравилось из соображений скупости? Я понятия не имел, что звонок был организован (и назван) таким образом; очень полезно. - person russellpierce; 28.11.2013
comment
Я думаю, вы не понимаете, как работает диспетчеризация S3 - обе эти функции будут отправляться одинаково в один и тот же метод mean - person hadley; 28.11.2013
comment
За исключением того, что вторая версия newMean, которую вы написали, не будет означать, и если среднее значение, выбранное классом x, не будет mean.default, а вместо этого будет какое-то другое значение со списком аргументов, например ... mean (x, blah = 10, orange = purple, ...) отправляется с первыми двумя аргументами ... с конкретными именами trim и na.rm, а затем не может сопоставить их с blah и orange, оставляя эти значения по умолчанию, а не принимая 2-й и 3-й аргументы из исходного вызова в newMean? - person russellpierce; 28.11.2013
comment
@rpierce нет, аргументы в ... будут переданы одинаково в обоих случаях - person hadley; 29.11.2013
comment
Я вижу, что есть некоторые концептуальные проблемы с тем, как я сформулировал свой вопрос. Верно, что аргументы в ... передаются одинаково в обоих случаях. Разница в том, что включено ... между 1-й и 2-й версиями newMean. Это имеет большое значение для обработки 2-го и 3-го аргументов, если они не имеют имени. - person russellpierce; 29.11.2013
comment
Рассмотрим: x ‹- c (1: 10,1000, NA); class (x) ‹- нет; mean.nope ‹- function (x, blah = 10, orange = purple, ...) {print (match.call ()); cat (blah, orange, \ n); if (blah == 10) return ( ИСТИНА)} else {return (FALSE)}}; newMean (x, .2,10) # это возвращает разные значения в зависимости от версии newMean. - person russellpierce; 29.11.2013

Как я уже указывал, я не совсем уверен, какова ваша цель, но если все, что вам нужно, это преобразовать безымянный список аргументов в тот, который вы могли бы передать в mean.default, тогда это было бы так же просто, как:

newMean <- function(x,...) {
  dots <- list(...)
  mean(x, trim = dots[[1]], na.rm = dots[[2]] )
}

Абсолютная простота этого заставляет меня думать, что я не понимаю проблему или ошибку, с которыми вы сталкиваетесь, но это демонстрирует сопоставление имен, если они существуют:

newMean.partial <- function(x,...) {
   dots <- list(...)
   names(dots)[names(dots)=="rmNA"] <- "na.rm"
   x.list <- list(x=x)
   dots <- c(x.list,dots)
  print(dput(dots))
 }
 newMean.partial(c(1:10,1000,NA),.2,rmNA=TRUE)
# structure(list(x = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1000, NA), 
#     0.2, na.rm = TRUE), .Names = c("x", "", "na.rm"))
person IRTFM    schedule 27.11.2013