8

我有一个包含自定义方法的包summary()print()用于具有特定类的对象。这个包还使用了出色的dplyr包进行数据操作——我希望我的用户编写使用我的包和 dplyr 的脚本。

其他人在这里这里已经注意到的一个障碍是 dplyr 动词不保留自定义类 - 这意味着ungroup命令可以剥离我的 data.frames 他们的自定义类,从而搞砸了方法调度summary等。

Hadley 说“正确地执行此操作取决于您 - 您需要为每个 dplyr 方法为您的类定义一个方法,以正确恢复所有类和属性”,我正在尝试接受建议- 但我无法弄清楚如何正确包装 dplyr 动词。

这是一个简单的玩具示例。假设我已经定义了一个cars类,并且我对它有一个自定义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只是给了我通用摘要,而不是我的自定义方法,因为在 dplyr 过滤后small_cars不再保留该类。cars

我试过的

filter首先,我尝试围绕( )编写自定义方法filter.cars。那没有用,因为实际上是一个允许非标准评估filter的包装器。filter_

所以我filter_为对象编写了一个自定义方法cars,试图实现@jwdink 的建议

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 上的类,移交给 dplyr,然后返回具有与 dplyr 调用之前相同的类名的对象。 我如何更改我的filter_包装器来实现这一点? 谢谢!

4

2 回答 2

9

更新:

自从我最初的回答以来,有些事情发生了变化:

  • 许多 dplyr 动词不再删除自定义类;例如,dplyr::filter保留课程。然而,有些人——比如dplyr::group_by——仍然删除了这个类,所以这个问题仍然存在。
  • 在 R 3.5 及更高版本中,方法查找更改了其范围规则
  • 动词的尾随下划线版本已弃用

由于第二个子弹,最近遇到了一个难以解决的问题,所以只想举一个更完整的例子。假设您正在使用带有 name 的自定义类,custom_class并且您想添加一个 groupby 方法。假设您使用的是 roxygen:

#' 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 你可以只用第二个,但现在你需要两者。


旧答案:

线程中提供了进一步的建议,所以我想我会更新似乎是最佳实践的方法,即使用NextMethod().

filter_.cars <- function(.data, ...) {
   result <- NextMethod()
   reclass(.data, result)
}

你在哪里reclass写的;它只是一个泛型,(至少)重新添加了原始类:

reclass <- function(x, result) {
  UseMethod('reclass')
}

reclass.default <- function(x, result) {
  class(result) <- unique(c(class(x)[[1]], class(result)))
  result
}
于 2017-06-29T15:27:05.903 回答
8

您的新filter_方法尝试应用于定义中的新类,因此是递归。

按照您链接的问题中的建议,尝试filter_在更新方法之前删除该新类。

class(out) <- class(out)[-1]
于 2017-01-31T22:33:08.340 回答