R语言 S3方法:扩展ggplot2 `+.gg`函数

kuhbmx9i  于 2023-04-18  发布在  其他
关注(0)|答案(2)|浏览(160)

我试图用一个新的类来扩展ggplot2,在这个例子中我们将称之为foo。目标是编写一个+.foo方法,用于代替+.gg。然而,我遇到了一个“不兼容方法”的问题

设置

目前我可以写ggplot_add.foo_layer,这将使plot到我的foo类,然后添加相应的层正常。
其思想是,一旦打印对象继承了foo,它将在添加下一个图层时分派到+.foo
我想这样做的原因是因为我想检查foo对象的结构是否仍然有效/与传入层兼容。这将防止我不得不为ggplot_build编写方法。

代码定义

library(ggplot2)

`+.foo` <- function(e1, e2){
  cat("Using foo ggplot +") # for Debugging
  NextMethod() #ideally just dispatches to `+.gg`
}

ggplot_add.foo_layer <- function(object, plot, object_name) {
  plot <- as_foo(plot)
  ggplot2:::add_ggplot(plot, object$layer, object_name) 
}

as_foo <- function(x){
  if(!is_foo(x)){
    class(x) <- c("foo", class(x))
  }
  x
}

is_foo <- function(x) inherits(x, "foo")

foo_layer <- function(x) structure(list(layer = x), class = "foo_layer")

错误

p1 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
  geom_point()
class(p1)
#[1] "gg"     "ggplot"
p1 + geom_density(aes(y = after_stat(density)))

p2 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
  foo_layer(geom_point()) 

class(p2)
#[1] "foo"    "gg"     "ggplot"
p2 + geom_density(aes(y = after_stat(density)))
#Error in p2 + geom_density(aes(y = after_stat(density))) : 
#  non-numeric argument to binary operator
#In addition: Warning message:
#Incompatible methods ("+.foo", "+.gg") for "+"

从上面的代码中,p1 + geom_*执行得很好。但是,由于上面关于不兼容方法的错误,p2 + geom_*无法执行。从我对S3方法调度的了解来看,我不明白为什么这不起作用。有人能解释一下为什么会这样吗?或者我如何补救?
理想情况下,我不需要写一个方法ggplot_build.foo,因为我希望其他包的ggplot_build可以被使用,如果它们存在的话(例如gganimate)。

enxuqcxy

enxuqcxy1#

你可以做的一件事是覆盖ggplot 2:+gg方法来支持S3中的双重分派。如果你正在编写一个包,这不是一个很好的行为,但它完成了任务。注意,这种顽皮的行为并没有阻止其他包覆盖ggplot的函数(看看你,ggtern)。

library(ggplot2)

`+.gg` <- function(e1, e2) {
  UseMethod("+.gg")
}

`+.gg.default` <- ggplot2:::`+.gg`

`+.gg.foo` <- function(e1, e2) {
  cat("Using foo ggplot +")
  NextMethod()
}

ggplot_add.foo_layer <- function(object, plot, object_name) {
  plot <- as_foo(plot)
  ggplot2:::add_ggplot(plot, object$layer, object_name) 
}

as_foo <- function(x){
  if(!is_foo(x)){
    class(x) <- c("foo", class(x))
  }
  x
}

is_foo <- function(x) inherits(x, "foo")

foo_layer <- function(x) structure(list(layer = x), class = "foo_layer")

p1 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
  geom_point()

p2 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
  foo_layer(geom_point()) 

p2 + geom_density(aes(y = after_stat(density)))
#> Using foo ggplot +

创建于2021-01-20由reprex package(v0.3.0)

ddhy6vgd

ddhy6vgd2#

感谢resource provided by @teunbrand,我们可以使用S4安全地使用+进行调度

编辑----

看起来在会话之间,这段代码实际上并没有在我定义的S4方法+上调度。由于某种原因,它仍然在调度到+.gg。我会做一些关于调度的研究,但目前,下面的代码不起作用。

S4类定义

setOldClass(c("gg", "ggplot")) #required to make it inherit from gg
setClass("Foo", contains = c("gg","ggplot", "list"))
setMethod("initialize", "Foo",
          function(.Object, plot){
            .Object[names(plot)] <- plot
            .Object 
          } )

setMethod("+", signature(e1 = "Foo",e2 = "gg"),
          function(e1, e2){
            cat("Using S4 Method")
            gg <- ggplot2:::`+.gg`(e1, e2)
            as_foo(gg) #ensure that new layers (from other packages) dont return S3
          })
setMethod("show", signature("Foo"),
          function(object){
          ggplot2:::print.ggplot(object)})

ggplot_add.foo_layer <- function(object, plot, object_name) {
  plot <- as_foo(plot)
  ggplot2:::add_ggplot(plot, object$layer, object_name)
}

as_foo <- function(x){
  if(!is_foo(x)){
    x <- new("Foo", x)
  }
  x
}

is_foo <- function(x) inherits(x, "Foo")

foo_layer <- function(x) structure(list(layer = x), class = "foo_layer")

对于所有意图和目的,新的foo对象的“行为”与标准的ggplot对象类似。

p2 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
  foo_layer(geom_point())
p2 + geom_density(aes(y = after_stat(density)))

此外,其他包(如gganimate)仍将返回Foo对象,但会调用自己的ggplot_build S3方法。

library(gganimate)
anim <- p2 + transition_states(Species)
is_foo(anim)
inherits(anim, "gganim")
anim

相关问题