【问题标题】:defining custom dplyr methods in R package在 R 包中定义自定义 dplyr 方法
【发布时间】:2017-06-17 11:41:18
【问题描述】:

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

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

Hadley 说“正确执行此操作取决于您 - 您需要为每个 dplyr 方法为您的类定义一个方法,以正确恢复所有类和属性”,我正在尝试采用 advice - 但我无法弄清楚如何正确包装 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 只是给了我通用摘要,而不是我的自定义方法,因为 small_cars 在 dplyr 过滤后不再保留 cars 类。

我尝试了什么

首先,我尝试围绕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 上的类,交给 dplyr,然后返回具有与 dplyr 调用之前相同的类名的对象。 如何更改我的 filter_ 包装器以完成此操作?谢谢!

【问题讨论】:

    标签: r dplyr r-package tidyverse r-s3


    【解决方案1】:

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

    the advice in the issue you linked 之后,尝试在您更新的方法中删除filter_ 之前的新类。

    class(out) <- class(out)[-1]
    

    【讨论】:

    • 这真的很有趣。当我写这篇文章时,我认为 dplyr::filter_ 保证内部调用得到了 dplyr 风格的调度,但这还不够!递归现在有意义了。
    【解决方案2】:

    更新:

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

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

    由于第二个项目符号,最近遇到了一个难以解决的问题,所以只想举一个更完整的例子。假设您正在使用一个名为 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) 添加到命名空间
    • 您需要@importFrom dplyr group_by 才能将importFrom(dplyr,group_by) 添加到您的命名空间

    我相信 R


    旧答案:

    the thread 提供了进一步的建议,所以我想我会更新似乎是最佳实践的方法,即使用 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
    }
    

    【讨论】:

    • 我喜欢你的最后一个例子,虽然我认为reclass(data, NextMethod()) 需要更改为reclass(.data, NextMethod())
    • 虽然这是一个很好的解决方法,但对于 dplyr 来说,删除开头的类似乎不太合理。这种解决方法意味着每个使用自定义数据框类的包现在都必须为每个 dplyr 动词添加方法...
    • Eric,我用.datadata 修正了拼写错误(直到我尝试自己实现代码时才注意到它。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多