【问题标题】:How to use ggplot_add inside another package如何在另一个包中使用 ggplot_add
【发布时间】:2021-07-20 14:40:05
【问题描述】:

我正在尝试构建一个严重依赖 ggplot2 的数据可视化包,但为我面临的一些日常问题提供了一些自定义快捷方式。

我可以使用ggplot_add 函数将+ 的功能扩展为来自脚本的自定义类,但是当我将这些脚本添加到包中时,ggplot_add 不再起作用。

下面我粘贴了一个 minrep,要复制第一个需要创建一个包(我正在使用 RStudio),我称之为 SOExa。 该项目包含以下文件:

.Rbuildignore

^.*\.Rproj$
^\.Rproj\.user$

描述

Package: SOExa
Type: Package
Title: An minrep for a problem I'm having
Version: 0.1.0
Author: Col Bates
Maintainer: The package maintainer <yourself@somewhere.net>
Description: I want to use ggplot2's ggplot_add from inside another package, i.e. this one.
    It seems that when I do I get an error.
License: GPLv2
Encoding: UTF-8
Imports:
    dplyr,
    magrittr,
    tidyr,
    glue,
    ggplot2
LazyData: true
RoxygenNote: 7.1.1

我的项目文件SOExa.Rproj

一个名为 R 的文件夹,包含 minrep 的使用示例:

R/design_by.R

#' the function to add a 'designed by' to the plot
#' as a designed_by class
#'@export
designed_by<-function(x){
  return(new_designed_by(x))
}


#' generic constructor.
#' @export
new_designed_by<-function(x){
  x <- list('designed_by' = x)
  class(x) <- 'designed_by'
  return(x)
}


#' generic print for designed_by
#' @export
print.designed_by <- function(x){
  print(paste('Designed by:', format(x)))

}


#' defines the addition of an designed_by object for
#' @export
ggplot_add.designed_by <- function(object, plot, objectname){
  plot$designed_by <-  object$designed_by
  plot
}

ggplot_add <- function(x){
  UseMethod("ggplot_add")
  }

我运行以下代码来构建命名空间文件

devtools::document()

创建了一个新文件:

命名空间

# Generated by roxygen2: do not edit by hand

S3method(ggplot_add,designed_by)
S3method(print,designed_by)
export(designed_by)
export(new_designed_by)

在此之后我安装并加载库:

devtools::install()
library(SOExa)

然后创建一个空图:

p <- ggplot2::ggplot()

以下会导致错误:

p <- p + designed_by('Col Bates')

我得到的错误是:

# Error: Can't add `designed_by("Col Bates")` to a ggplot object.
# Run `rlang::last_error()` to see where the error occurred.

所以遵循这些步骤:

rlang::last_error()

返回

# <error/rlang_error>
#   Can't add `designed_by("Col Bates")` to a ggplot object.
# Backtrace:
#  1. ggplot2:::`+.gg`(p, designed_by("Col Bates"))
#  2. ggplot2:::add_ggplot(e1, e2, e2name)
#  4. ggplot2:::ggplot_add.default(object, p, objectname)
# Run `rlang::last_trace()` to see the full context.

跑步

rlang::last_trace()

我明白了

<error/rlang_error>
Can't add `designed_by("Col Bates")` to a ggplot object.
Backtrace:
    x
 1. \-ggplot2:::`+.gg`(p, designed_by("Col Bates"))
 2.   \-ggplot2:::add_ggplot(e1, e2, e2name)
 3.     +-ggplot2::ggplot_add(object, p, objectname)
 4.     \-ggplot2:::ggplot_add.default(object, p, objectname)

由此我可以推断出调用UseMethod('ggplot_add')的ggplot2::ggplot_add()决定应用函数ggplot_add.default,并且没有识别出我的类designed_by

顺便说一句,在库中使用 print() 函数确实有效。

print(designed_by('Col Bates'))

但是,如果我要获取脚本,而不是像下面这样使用包:

source('./R/designed_by.R')
p <- p + designed_by('Col Bates')

它确实按我预期的方式工作。

深入研究,我可以看到designed_by类上的泛型ggplot_add的来源是我的包。

 sloop::s3_methods_generic("ggplot_add")
## A tibble: 1 x 4
#  generic    class       visible source
#  <chr>      <chr>       <lgl>   <chr> 
# 1 ggplot_add designed_by TRUE    SOExa 

而对于 ggplot 类,它是“注册 S3 方法”

> sloop::s3_methods_generic("ggplot_add")
## A tibble: 14 x 4
#   generic    class      visible source             
#   <chr>      <chr>      <lgl>   <chr>              
# 1 ggplot_add by         FALSE   registered S3method
# 2 ggplot_add Coord      FALSE   registered S3method
# 3 ggplot_add data.frame FALSE   registered S3method
# 4 ggplot_add default    FALSE   registered S3method
# 5 ggplot_add Facet      FALSE   registered S3method
# ...

我查看了ggplot2 源代码,但无法真正弄清楚它是如何工作的。我也一直在阅读https://adv-r.hadley.nz/s3.html,但没有看到任何关于使用适用于另一个库中的类的 S3 方法的任何内容。

如果可以将调用打包到我的自定义包中,或者我是否总是需要依赖采购,那就太好了。

谢谢。

【问题讨论】:

  • 我认为您需要将您的方法 ggplot_add.designed_by 分配给 ggplot2 命名空间。 Answered here
  • 感谢@SmokeyShakers,但我认为这个解决方案仍然只适用于命令行。至少当我试图实现它时,它只能以这种方式工作。然而,这仍然很有用,因为它指向命名空间作为我应该关注的方向。
  • 你在哪里试过?你可能需要它在你的包的onLoad
  • 我在 onLoad 和定义 ggplot_add.designed_by 的文件中都试过了。
  • 我认为问题在于您的ggplot_add 功能。如果你删除它,当你在你的DESCRIPTION文件中声明ggplot2时,一切似乎都可以工作,无论是在DependsImports下。

标签: r ggplot2 r-s3


【解决方案1】:

如果您将ggplot2 添加到Depends 而不是DESCRIPTION 文件中的Imports,您的设置应该可以工作。例如,如果我创建一个包含以下文件的新包:

.Rbuildignore

^.*\.Rproj$
^\.Rproj\.user$

描述

Package: SOExa
Type: Package
Title: An minrep for a problem I'm having
Version: 0.1.0
Author: Col Bates
Maintainer: The package maintainer <yourself@somewhere.net>
Description: I want to use ggplot2's ggplot_add from inside another package, i.e. this one.
    It seems that when I do I get an error.
License: GPLv2
Encoding: UTF-8
Depends: ggplot2
Imports:
    dplyr,
    magrittr,
    tidyr,
    glue
LazyData: true
RoxygenNote: 7.1.1

design_by.R

#' the function to add a 'designed by' to the plot
#' as a designed_by class
#'@export
designed_by<-function(x){
  return(new_designed_by(x))
}


#' generic constructor.
#' @export
new_designed_by<-function(x){
  x <- list('designed_by' = x)
  class(x) <- 'designed_by'
  return(x)
}


#' generic print for designed_by
#' @export
print.designed_by <- function(x){
  print(paste('Designed by:', format(x)))

}


#' defines the addition of an designed_by object for
#' @export
ggplot_add.designed_by <- function(object, plot, objectname){
  plot$designed_by <-  object$designed_by
  plot
}

然后运行devtools::document() 给我:

命名空间

# Generated by roxygen2: do not edit by hand

S3method(ggplot_add,designed_by)
S3method(print,designed_by)
export(designed_by)
export(new_designed_by)

所以在我执行devtools::install() 之后,我得到以下输出:

library(SOExa)
#> Loading required package: ggplot2

p <- ggplot(data = NULL, aes(x = 1:10, y = 1:10)) + geom_point()

p <- p + designed_by('Col Bates')

p


p$designed_by
#> [1] "Col Bates"

【讨论】:

  • 谢谢艾伦!这确实成功了!我真的很高兴。
  • 万一以后的用户看到这篇文章,我真的想指出,将ggplot2 放在依赖与导入中真的不是问题。依赖和导入之间的唯一区别是依赖中的包将在你的包被加载时被加载。由于它已加载,您不再需要为 roxygen2 声明 #' @import {package}。问题是如何为另一个包中的泛型导出 S3 方法。 vctrs 就是一个很好的例子,因为他们有关于如何使用 extend their generics if you were making a package 的文档
  • @JustinLandis 是的,你所说的为另一个包中的泛型导出 S3 方法是正确的,但鉴于该包的所述用例实际上是 ggplot2 的包装器/扩展器,那么实现此目的最简单、最自然的方法是将ggplot2 移动到Depends。如果您可以制作一个同样有效的可重现示例,请将其添加为答案。我会很高兴地赞成它。不幸的是,您现有的答案会导致 OP 描述的相同错误。
  • @Allan Cameron,请参阅我对答案的最后评论。 OP 的错误是在他们的包中定义了ggplot_add &lt;- function(x) UseMethod("ggplot_add") 的结果,如他们的帖子所示。如果您将其包含在您的 reprex 包中 - 即使在依赖中使用 ggplot2,您也会收到错误。
【解决方案2】:

这是一个让我很头疼的常见问题。您需要确保您的包可以访问ggplot2ggplot_add 通用函数。您可以选择以下两种方式之一。

您需要在包中的某处包含以下行:

#' @import ggplot2

这将使所有 ggplot2 函数都可用,或者如果您只使用该函数:

#' @importFrom ggplot2 ggplot_add

这有点不直观,但仅仅因为DESCRIPTION 文件说它导入了一个包,并不意味着您的包的命名空间将能够看到该包的功能。您始终可以通过查看NAMESPACE 文件来检查这一点。当您包含上述建议时,您应该会在 NAMESPACE 文件的底部看到以下行之一

import(ggplot2)
importFrom(ggplot2,ggplot_add)

【讨论】:

  • 谢谢贾斯汀,我已经试过了,我创建了一个文件 imports.R 并添加了两行,第一行 #' @import ggplot2 后跟一行 NULL。 NAMESPACE 更新了你描述的方式,但是该函数仍然无法访问并且上面的代码会产生同样的错误。
  • 我已经测试了几次,请确保您的包中没有定义您自己的ggplot_add 定义。它似乎导致了错误。我的猜测是您的包将注册ggplot_add.designed_by 到您的内部,非导出的ggplot_add
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多