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