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