Задний план
Механизм диспетчеризации функций 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, но пока не нашел никаких реализаций этой идеи. Любая помощь или указатели?
data.table
. Когда пользователь выдаетlibrary(data.table)
, пакет переопределяетbase::rbind.data.frame
для отправки внутреннему методу. Это работает дляdata.table
, но я боюсь, что принцип, делающий это одновременно в нескольких пакетах, вызывает проблемы. В FAQ 2.23 Мэтт говорит: Если есть лучшее решение, мы с радостью его изменим. - person Stef van Buuren   schedule 25.12.2017