在 R 包中定义自定义 dplyr 方法
defining custom dplyr methods in R package
我有一个包,其中包含针对具有特定 class 的对象的自定义 summary()
、print()
方法。这个包还使用了很棒的 dplyr
包来进行数据操作——我希望我的用户编写同时使用我的包和 dplyr 的脚本。
其他人已经注意到的一个障碍 here and here 是 dplyr 动词不保留自定义 classes - 这意味着 ungroup
命令可以删除我的 data.frames 他们的自定义 classes,因此搞砸了 summary
等的方法调度
Hadley 说“正确执行此操作取决于您 - 您需要为每个 dplyr 方法定义 class 的方法,以正确恢复所有 classes 和属性”并且我'我正在尝试使用 advice - 但我不知道如何正确包装 dplyr 动词。
这是一个简单的玩具示例。假设我已经定义了一个 cars
class,并且我有一个自定义的 summary
。
这有效
library(tidyverse)
class(mtcars) <- c('cars', class(mtcars))
summary.cars <- function(x, ...) {
#gather some summary stats
df_dim <- dim(x)
quantile_sum <- map(mtcars, quantile)
cat("A cars object with:\n")
cat(df_dim[[1]], 'rows and ', df_dim[[2]], 'columns.\n')
print(quantile_sum)
}
summary(mtcars)
问题出在这里
small_cars <- mtcars %>% filter(cyl < 6)
summary(small_cars)
class(small_cars)
summary
对 small_cars
的调用只给出了通用摘要,而不是我的自定义方法,因为 small_cars
不再保留 cars
class在 dplyr 过滤之后。
我试过的
首先,我尝试围绕 filter
(filter.cars
) 编写自定义方法。那没有用,因为 filter
实际上是 filter_
的包装器,允许非标准评估。
所以我为 cars
对象编写了自定义 filter_
方法,试图实现@jwdink 的 advice
filter_.cars <- function(df, ...) {
old_classes <- class(df)
out <- dplyr::filter_(df, ...)
new_classes <- class(out)
class(out) <- c(new_classes, old_classes) %>% unique()
out
}
这不起作用 - 我收到无限递归错误:
Error: evaluation nested too deeply: infinite recursion / options(expressions=)?
Error during wrapup: evaluation nested too deeply: infinite recursion / options(expressions=)?
我想要做的就是在传入的 df 上获取 classes,交给 dplyr,然后 return 与之前具有相同 classnames 的对象dplyr 调用。 如何更改我的 filter_
包装器来完成此操作? 谢谢!
您的新 filter_
方法尝试应用到定义中的新 class,因此递归。
在 the advice in the issue you linked 之后,尝试在更新后的方法中删除 filter_
之前的新 class。
class(out) <- class(out)[-1]
更新:
自从我最初的回答以来,有些事情发生了变化:
- 许多 dplyr 动词不再删除自定义 classes;例如,
dplyr::filter
保留 class。然而,有些人——比如 dplyr::group_by
——仍然删除了 class,所以这个问题仍然存在。
- 在 R 3.5 及更高版本中,方法查找更改了其作用域规则
- 动词的尾部下划线版本已弃用
最近 运行 由于第二个项目符号而陷入难以理解的问题,所以只想举一个更完整的例子。假设您正在使用名称为 custom_class
的自定义 class,并且您想要添加一个 groupby 方法。假设您使用的是氧气:
#' group_by.custom_class
#'
#' @description Preserve the class of a `custom_class` object.
#' @inheritParams dplyr::group_by
#'
#' @importFrom dplyr group_by
#'
#' @export
#' @method group_by custom_class
group_by.custom_class <- function(.data, ...) {
result <- NextMethod()
return(reclass(.data, result))
}
(请参阅 reclass
函数定义的原始答案)
亮点:
- 您需要
@method group_by custom_class
才能将 S3method(group_by,custom_class)
添加到 NAMESPACE
- 您需要
@importFrom dplyr group_by
才能将 importFrom(dplyr,group_by)
添加到您的 NAMESPACE
我相信 R < 3.5 你可以只用第二个,但现在你需要两个。
旧答案:
the thread 中提供了进一步的建议,所以我想我会更新似乎是最佳实践的内容,即使用 NextMethod()
.
filter_.cars <- function(.data, ...) {
result <- NextMethod()
reclass(.data, result)
}
其中reclass
是你写的;它只是一个通用的,(至少)将原来的 class 添加回:
reclass <- function(x, result) {
UseMethod('reclass')
}
reclass.default <- function(x, result) {
class(result) <- unique(c(class(x)[[1]], class(result)))
result
}
我有一个包,其中包含针对具有特定 class 的对象的自定义 summary()
、print()
方法。这个包还使用了很棒的 dplyr
包来进行数据操作——我希望我的用户编写同时使用我的包和 dplyr 的脚本。
其他人已经注意到的一个障碍 here and here 是 dplyr 动词不保留自定义 classes - 这意味着 ungroup
命令可以删除我的 data.frames 他们的自定义 classes,因此搞砸了 summary
等的方法调度
Hadley 说“正确执行此操作取决于您 - 您需要为每个 dplyr 方法定义 class 的方法,以正确恢复所有 classes 和属性”并且我'我正在尝试使用 advice - 但我不知道如何正确包装 dplyr 动词。
这是一个简单的玩具示例。假设我已经定义了一个 cars
class,并且我有一个自定义的 summary
。
这有效
library(tidyverse)
class(mtcars) <- c('cars', class(mtcars))
summary.cars <- function(x, ...) {
#gather some summary stats
df_dim <- dim(x)
quantile_sum <- map(mtcars, quantile)
cat("A cars object with:\n")
cat(df_dim[[1]], 'rows and ', df_dim[[2]], 'columns.\n')
print(quantile_sum)
}
summary(mtcars)
问题出在这里
small_cars <- mtcars %>% filter(cyl < 6)
summary(small_cars)
class(small_cars)
summary
对 small_cars
的调用只给出了通用摘要,而不是我的自定义方法,因为 small_cars
不再保留 cars
class在 dplyr 过滤之后。
我试过的
首先,我尝试围绕 filter
(filter.cars
) 编写自定义方法。那没有用,因为 filter
实际上是 filter_
的包装器,允许非标准评估。
所以我为 cars
对象编写了自定义 filter_
方法,试图实现@jwdink 的 advice
filter_.cars <- function(df, ...) {
old_classes <- class(df)
out <- dplyr::filter_(df, ...)
new_classes <- class(out)
class(out) <- c(new_classes, old_classes) %>% unique()
out
}
这不起作用 - 我收到无限递归错误:
Error: evaluation nested too deeply: infinite recursion / options(expressions=)?
Error during wrapup: evaluation nested too deeply: infinite recursion / options(expressions=)?
我想要做的就是在传入的 df 上获取 classes,交给 dplyr,然后 return 与之前具有相同 classnames 的对象dplyr 调用。 如何更改我的 filter_
包装器来完成此操作? 谢谢!
您的新 filter_
方法尝试应用到定义中的新 class,因此递归。
在 the advice in the issue you linked 之后,尝试在更新后的方法中删除 filter_
之前的新 class。
class(out) <- class(out)[-1]
更新:
自从我最初的回答以来,有些事情发生了变化:
- 许多 dplyr 动词不再删除自定义 classes;例如,
dplyr::filter
保留 class。然而,有些人——比如dplyr::group_by
——仍然删除了 class,所以这个问题仍然存在。 - 在 R 3.5 及更高版本中,方法查找更改了其作用域规则
- 动词的尾部下划线版本已弃用
最近 运行 由于第二个项目符号而陷入难以理解的问题,所以只想举一个更完整的例子。假设您正在使用名称为 custom_class
的自定义 class,并且您想要添加一个 groupby 方法。假设您使用的是氧气:
#' group_by.custom_class
#'
#' @description Preserve the class of a `custom_class` object.
#' @inheritParams dplyr::group_by
#'
#' @importFrom dplyr group_by
#'
#' @export
#' @method group_by custom_class
group_by.custom_class <- function(.data, ...) {
result <- NextMethod()
return(reclass(.data, result))
}
(请参阅 reclass
函数定义的原始答案)
亮点:
- 您需要
@method group_by custom_class
才能将S3method(group_by,custom_class)
添加到 NAMESPACE - 您需要
@importFrom dplyr group_by
才能将importFrom(dplyr,group_by)
添加到您的 NAMESPACE
我相信 R < 3.5 你可以只用第二个,但现在你需要两个。
旧答案:
the thread 中提供了进一步的建议,所以我想我会更新似乎是最佳实践的内容,即使用 NextMethod()
.
filter_.cars <- function(.data, ...) {
result <- NextMethod()
reclass(.data, result)
}
其中reclass
是你写的;它只是一个通用的,(至少)将原来的 class 添加回:
reclass <- function(x, result) {
UseMethod('reclass')
}
reclass.default <- function(x, result) {
class(result) <- unique(c(class(x)[[1]], class(result)))
result
}