Отправка `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, но до сих пор я не нашел ни одной реализации этой идеи. Любая помощь или указатели?

3 ответа

Как вы упоминаете, использование 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 использует их для двух из своих потенциально многих аргументов рекурсивно.

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,

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

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

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

Другие вопросы по тегам