S3 方法:扩展 ggplot2 `+.gg` 函数
S3 Methods: Extending ggplot2 `+.gg` function
我正在尝试用一个新的 class 来扩展 ggplot2
,在这个例子中我们将其称为 foo
。目标是编写一个 +.foo
方法来代替 +.gg
。但是我 运行 遇到了“不兼容方法”的问题
设置
目前我可以编写ggplot_add.foo_layer
,这将使plot
进入我的foo
class,然后正常添加相应的层。
想法是,一旦 plot 对象继承 foo
,它将在添加下一层时分派到 +.foo
。
我想这样做的原因是因为我想检查 foo
对象的结构是否仍然 valid/compatible 与传入层。这将使我不必为 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
)。
您可以做的一件事是覆盖 ggplot2:::+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 +
由 reprex package (v0.3.0)
于 2021 年 1 月 20 日创建
感谢 resource provided by @teunbrand,我们可以使用 S4 安全地调度 +
编辑 ----
似乎在会话之间这段代码实际上并没有在我定义的 S4 方法上调度 +
。由于某种原因,它仍在调度到 +.gg
。我会做一些关于为什么会这样的调度的研究,但暂时,下面的代码不起作用。
S4 class 定义
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
对象 'behaves' 就像一个标准的 ggplot
对象。
p2 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
foo_layer(geom_point())
p2 + geom_density(aes(y = after_stat(density)))
此外,其他包如 gganimate
仍将 return 一个 Foo
对象,但会调用它们自己的 ggplot_build
S3 方法
library(gganimate)
anim <- p2 + transition_states(Species)
is_foo(anim)
inherits(anim, "gganim")
anim
我正在尝试用一个新的 class 来扩展 ggplot2
,在这个例子中我们将其称为 foo
。目标是编写一个 +.foo
方法来代替 +.gg
。但是我 运行 遇到了“不兼容方法”的问题
设置
目前我可以编写ggplot_add.foo_layer
,这将使plot
进入我的foo
class,然后正常添加相应的层。
想法是,一旦 plot 对象继承 foo
,它将在添加下一层时分派到 +.foo
。
我想这样做的原因是因为我想检查 foo
对象的结构是否仍然 valid/compatible 与传入层。这将使我不必为 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
)。
您可以做的一件事是覆盖 ggplot2:::+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 +
由 reprex package (v0.3.0)
于 2021 年 1 月 20 日创建感谢 resource provided by @teunbrand,我们可以使用 S4 安全地调度 +
编辑 ----
似乎在会话之间这段代码实际上并没有在我定义的 S4 方法上调度 +
。由于某种原因,它仍在调度到 +.gg
。我会做一些关于为什么会这样的调度的研究,但暂时,下面的代码不起作用。
S4 class 定义
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
对象 'behaves' 就像一个标准的 ggplot
对象。
p2 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
foo_layer(geom_point())
p2 + geom_density(aes(y = after_stat(density)))
此外,其他包如 gganimate
仍将 return 一个 Foo
对象,但会调用它们自己的 ggplot_build
S3 方法
library(gganimate)
anim <- p2 + transition_states(Species)
is_foo(anim)
inherits(anim, "gganim")
anim