Подтвердить что ты не робот

Отправка `rbind` и` cbind` для `data.frame`

Фон

Механизм отправки функций R rbind() и cbind() является нестандартным. Я изучил некоторые возможности написания функций rbind.myclass() или cbind.myclass(), когда один из аргументов является data.frame, но до сих пор у меня нет удовлетворительного подхода. Этот пост концентрируется на rbind, но то же самое верно для cbind.

Проблема

Создадим функцию rbind.myclass(), которая просто эха, когда она была вызвана.

rbind.myclass <- function(...) "hello from rbind.myclass"

Мы создаем объект класса myclass, а следующие вызовы rbind все правильно отправить на rbind.myclass()

a <- "abc"
class(a) <- "myclass"
rbind(a, a)
rbind(a, "d")
rbind(a, 1)
rbind(a, list())
rbind(a, matrix())

Однако, если один из аргументов (это не обязательно будет первый), rbind() вместо этого вызовет base::rbind.data.frame():

rbind(a, data.frame())

Такое поведение немного удивительно, но оно фактически задокументировано в dispatch раздела rbind(). Предоставленный совет:

Если вы хотите объединить другие объекты с кадрами данных, может потребоваться сначала принудить их к кадрам данных.

На практике этот совет может быть трудно реализован. Преобразование в кадр данных может удалить важную информацию о классе. Кроме того, пользователь, который может не знать совета, может застрять с ошибкой или неожиданным результатом после выдачи команды rbind(a, x).

Подходы

Предупреждать пользователя

Первая возможность - предупредить пользователя о том, что вызов rbind(a, x) не должен выполняться, когда x является фреймом данных. Вместо этого пользователь пакета mypackage должен сделать явный вызов скрытой функции:

mypackage:::rbind.myclass(a, x)

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

Перехват rbind

В качестве альтернативы я пытался защитить пользователя, перехватив отправку. Моя первая попытка заключалась в предоставлении локального определения base::rbind.data.frame():

rbind.data.frame <- function(...) "hello from my rbind.data.frame"
rbind(a, data.frame())
rm(rbind.data.frame)

Это не работает, поскольку rbind() не обманывается при вызове rbind.data.frame из .GlobalEnv и вызывает base версию, как обычно.

Другая стратегия - переопределить rbind() локальной функцией, которая была предложена в S3 диспетчеризации` rbind` и `cbind`.

rbind <- function (...) {
  if (attr(list(...)[[1]], "class") == "myclass") return(rbind.myclass(...))
  else return(base::rbind(...))
}

Это отлично работает для отправки на rbind.myclass(), поэтому теперь пользователь может ввести rbind(a, x) для любого типа объекта x.

rbind(a, data.frame())

Недостатком является то, что после library(mypackage) мы получаем сообщение The following objects are masked from ‘package:base’: rbind.

Хотя технически все работает так, как ожидалось, должны быть лучшие способы, чем переопределение функции base.

Заключение

Ни один из вышеуказанных альтернатив не является удовлетворительным. Я читал об альтернативах с помощью отправки S4, но до сих пор я не нашел никаких реализаций этой идеи. Любая помощь или указатели?

4b9b3361

Ответ 1

Как вы сами упоминаете, использование S4 было бы одним хорошим решением, которое прекрасно работает. Я не исследовал недавно, с кадрами данных, поскольку меня гораздо больше интересуют другие обобщенные матрицы, в оба моих долголетних пакета CRAN "Матрица" (= "рекомендуется", т.е. Часть каждого R-распределения) и в "Rmpfr".

На самом деле даже два разных способа:
1) Rmpfr использует новый способ определения методов для "..." в rbind()/cbind().  это хорошо документировано в ?dotsMethods (мнемоника: '...' = точки) и реализовано в Rmpfr/R/array.R строке 511 ff (например, https://r-forge.r-project.org/scm/viewvc.php/pkg/R/array.R?view=annotate&root=rmpfr)

2) Matrix использует более старый подход, определяя (S4) методы для rbind2() и cbind2(): Если вы читаете ?rbind, это упоминает об этом и когда используется rbind2/cbind2. Идея там: "2" означает, что вы определяете методы S4 с сигнатурой для двух ( "2" ) матричных объектов, а rbind/cbind использует их для двух из своих потенциально многих аргументов рекурсивно.

Ответ 2

Я не думаю, что вы сможете придумать что-то вполне удовлетворяющее. Лучшее, что вы можете сделать, это экспортировать rbind.myclass, чтобы пользователи могли вызывать его напрямую, не делая mypackage:::rbind.myclass. Вы можете назвать это чем-то другим, если хотите (dplyr вызывает свою версию bind_rows), но если вы решите сделать это, я бы использовал имя, которое вызывает rbind, например rbind_myclass.

Даже если вы можете заставить r-core согласиться изменить поведение отправки, чтобы rbind отправил свой первый аргумент, все еще будут случаи, когда пользователи захотят rbind несколько объектов вместе с myclass объект где-то, кроме первого. Как еще пользователи могут отправлять сообщения rbind.myclass(df, df, myclass)?

Решение data.table кажется опасным; Я не удивлюсь, если сопровождающие CRAN проведут проверку и не разрешают это в какой-то момент.

Ответ 3

Подход dotsMethod был предложен Мартином Маклером и реализован в пакете Rmpfr. Нам нужно определить новый общий, класс и метод с использованием S4.

setGeneric("rbind", signature = "...")
mychar <- setClass("myclass", slots = c(x = "character"))
b <- mychar(x = "b")
rbind.myclass <- function(...) "hello from rbind.myclass"
setMethod("rbind", "myclass",
      function(..., deparse.level = 1) {
        args <- list(...)
        if(all(vapply(args, is.atomic, NA)))
          return( base::cbind(..., deparse.level = deparse.level) )
        else
          return( rbind.myclass(..., deparse.level = deparse.level))
      })

# these work as expected
rbind(b, "d")
rbind(b, b)
rbind(b, matrix())

# this fails in R 3.4.3
rbind(b, data.frame())

Error in rbind2(..1, r) :
    no method for coercing this S4 class to a vector

Я не смог разрешить ошибку. Видеть R: Должны ли общие методы работать внутри пакета без привязки? для связанной проблемы.

Поскольку этот подход отменяет rbind(), мы получаем предупреждение The following objects are masked from 'package:base': rbind.