在 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)

summarysmall_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
}