在自定义统计中转义 'discrete aesthetic implies group'
Escape 'discrete aesthetic implies group' in custom stat
我正在尝试使用 ggplot2 构建一个自定义统计函数,其中我想访问一个离散变量来计算每个组的统计数据。但是,ggplot 层的默认行为是自动将隐式组分配给任何离散变量(大部分)。这意味着我的数据会通过自动分组进行拆分,这是我不希望的。
我可以显示如下;我有一个非常标准的构造函数:
library(ggplot2)
stat_example <- function(
mapping = NULL,
data = NULL,
geom = "point",
position = "identity",
...,
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE
) {
layer(data = data,
mapping = mapping,
stat = StatExample,
geom = geom,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(na.rm = na.rm))
}
我有一个 Stat ggproto 对象,它只传递数据,但为了说明目的打印数据的头部。我在这里调用了我感兴趣的用于计算实际统计数据的位 value
。
StatExample <- ggproto(
"StatExample",
Stat,
required_aes = c("x", "y", "value"),
default_aes = aes(x = after_stat(x), y = after_stat(y)),
compute_group = function(data, scales) {
print(head(data, 2))
data
}
)
现在,如果我用这个统计数据构建一个图,我们可以看到 compute_group()
函数中有什么 data
。
g <- ggplot(iris) +
stat_example(aes(Sepal.Width, Sepal.Length, value = Species))
# To get only the print side-effect, not the plot (which looks normal)
g <- ggplotGrob(g)
#> x y value PANEL group
#> 1 3.5 5.1 setosa 1 1
#> 2 3.0 4.9 setosa 1 1
#> x y value PANEL group
#> 51 3.2 7.0 versicolor 1 2
#> 52 3.2 6.4 versicolor 1 2
#> x y value PANEL group
#> 101 3.3 6.3 virginica 1 3
#> 102 2.7 5.8 virginica 1 3
由 reprex package (v0.3.0)
于 2020-05-28 创建
我想要 1 data.frame 包含此案例的所有数据。我们在上面看到我们用不同的 group
变量打印了 3 data.frames,这意味着数据被分成了 3 组。我认为到达那里需要的是让 value
变量逃脱自动组检测。
我考虑了以下几点:
- 我可以让组默认为
-1
,这是标准的 'no group'-组。但是,当我这样做时,数据不会自动分组,例如 aes(colour = some_variable)
。这是我绝对希望发生的事情。
- 查看
ggplot2:::add_group()
函数,似乎我可以通过将我的 value
变量命名为 label
来逃避自动分组,但这会使统计与 geom_text()
不兼容,并且它没有自然地描述 value
的含义。
- 我可以用此函数的变体替换
layer()
调用,这将生成不同的 Layer ggproto 对象,其中 compute_aesthetics()
以不同方式处理组。然而,这是很多我宁愿避免负担的工作。
- 我可能可以按照
vctrs::new_vctr(..., class = "not_discrete")
的方式使用技巧,但是在那个 class 中包装我的 value
变量的合适位置在哪里?
欢迎提出有用的建议,或者对 'just use label
' 论点提出新的看法。
如果这是一个偶然的用例,一个简单的(尽管是手动的)hack 可以是 运行 trace(ggplot2:::add_group, edit = TRUE)
并在 "label", "PANEL"
旁边添加 "value"
作为变量名从自动组检测中排除。
实现相同效果的手动操作较少(但可能更脆弱)的方法包括以下步骤:
- 使用上述修改定义
add_group
函数的修改版本;
- 定义
Layer
ggproto 对象的修改版本,在其 compute_aesthetics
函数中使用修改后的 add_group
;
- 将自定义统计指向修改后的图层。
# define modified add_group function
add_group2 <- function (data) {
if (ggplot2:::empty(data))
return(data)
if (is.null(data$group)) {
disc <- vapply(data, ggplot2:::is.discrete, logical(1))
disc[names(disc) %in% c("label", "PANEL", "value")] <- FALSE # change here
if (any(disc)) {
data$group <- vctrs::vec_group_id(data[disc])
}
else {
data$group <- ggplot2:::NO_GROUP
}
} else {
data$group <- vctrs::vec_group_id(data["group"])
}
data
}
# define modified compute_aesthetics function that uses modified add_group in second last line
compute_aesthetics_alt <- .subset2(ggplot2:::Layer, "compute_aesthetics")
body(compute_aesthetics_alt)[[length(body(compute_aesthetics_alt)) - 1]] <-
quote(evaled <- add_group2(evaled))
# define modified Layer ggproto object that uses alternative compute_aesthetics
Layer2 <- ggproto("Layer2",
ggplot2:::Layer,
compute_aesthetics = compute_aesthetics_alt)
# define modified stat with Layer2 specified as its layer_class
stat_example <- function(
mapping = NULL,
data = NULL,
geom = "point",
position = "identity",
...,
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE
) {
layer(data = data,
mapping = mapping,
stat = StatExample,
geom = geom,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(na.rm = na.rm),
layer_class = Layer2) # change here
}
用法:
# add new column to simulate different colour
iris$gg <- sample(c("a", "b"), size = nrow(iris), replace = TRUE)
ggplot(iris) +
stat_example(aes(Sepal.Width, Sepal.Length,
value = Species))
# prints one data frame, because there's only one group by default
ggplot(iris) +
stat_example(aes(Sepal.Width, Sepal.Length,
value = Species, colour = gg))
# prints two data frames, because grouping is based on the colour aesthetic,
# which has two possible values
我正在尝试使用 ggplot2 构建一个自定义统计函数,其中我想访问一个离散变量来计算每个组的统计数据。但是,ggplot 层的默认行为是自动将隐式组分配给任何离散变量(大部分)。这意味着我的数据会通过自动分组进行拆分,这是我不希望的。
我可以显示如下;我有一个非常标准的构造函数:
library(ggplot2)
stat_example <- function(
mapping = NULL,
data = NULL,
geom = "point",
position = "identity",
...,
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE
) {
layer(data = data,
mapping = mapping,
stat = StatExample,
geom = geom,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(na.rm = na.rm))
}
我有一个 Stat ggproto 对象,它只传递数据,但为了说明目的打印数据的头部。我在这里调用了我感兴趣的用于计算实际统计数据的位 value
。
StatExample <- ggproto(
"StatExample",
Stat,
required_aes = c("x", "y", "value"),
default_aes = aes(x = after_stat(x), y = after_stat(y)),
compute_group = function(data, scales) {
print(head(data, 2))
data
}
)
现在,如果我用这个统计数据构建一个图,我们可以看到 compute_group()
函数中有什么 data
。
g <- ggplot(iris) +
stat_example(aes(Sepal.Width, Sepal.Length, value = Species))
# To get only the print side-effect, not the plot (which looks normal)
g <- ggplotGrob(g)
#> x y value PANEL group
#> 1 3.5 5.1 setosa 1 1
#> 2 3.0 4.9 setosa 1 1
#> x y value PANEL group
#> 51 3.2 7.0 versicolor 1 2
#> 52 3.2 6.4 versicolor 1 2
#> x y value PANEL group
#> 101 3.3 6.3 virginica 1 3
#> 102 2.7 5.8 virginica 1 3
由 reprex package (v0.3.0)
于 2020-05-28 创建我想要 1 data.frame 包含此案例的所有数据。我们在上面看到我们用不同的 group
变量打印了 3 data.frames,这意味着数据被分成了 3 组。我认为到达那里需要的是让 value
变量逃脱自动组检测。
我考虑了以下几点:
- 我可以让组默认为
-1
,这是标准的 'no group'-组。但是,当我这样做时,数据不会自动分组,例如aes(colour = some_variable)
。这是我绝对希望发生的事情。 - 查看
ggplot2:::add_group()
函数,似乎我可以通过将我的value
变量命名为label
来逃避自动分组,但这会使统计与geom_text()
不兼容,并且它没有自然地描述value
的含义。 - 我可以用此函数的变体替换
layer()
调用,这将生成不同的 Layer ggproto 对象,其中compute_aesthetics()
以不同方式处理组。然而,这是很多我宁愿避免负担的工作。 - 我可能可以按照
vctrs::new_vctr(..., class = "not_discrete")
的方式使用技巧,但是在那个 class 中包装我的value
变量的合适位置在哪里?
欢迎提出有用的建议,或者对 'just use label
' 论点提出新的看法。
如果这是一个偶然的用例,一个简单的(尽管是手动的)hack 可以是 运行 trace(ggplot2:::add_group, edit = TRUE)
并在 "label", "PANEL"
旁边添加 "value"
作为变量名从自动组检测中排除。
实现相同效果的手动操作较少(但可能更脆弱)的方法包括以下步骤:
- 使用上述修改定义
add_group
函数的修改版本; - 定义
Layer
ggproto 对象的修改版本,在其compute_aesthetics
函数中使用修改后的add_group
; - 将自定义统计指向修改后的图层。
# define modified add_group function
add_group2 <- function (data) {
if (ggplot2:::empty(data))
return(data)
if (is.null(data$group)) {
disc <- vapply(data, ggplot2:::is.discrete, logical(1))
disc[names(disc) %in% c("label", "PANEL", "value")] <- FALSE # change here
if (any(disc)) {
data$group <- vctrs::vec_group_id(data[disc])
}
else {
data$group <- ggplot2:::NO_GROUP
}
} else {
data$group <- vctrs::vec_group_id(data["group"])
}
data
}
# define modified compute_aesthetics function that uses modified add_group in second last line
compute_aesthetics_alt <- .subset2(ggplot2:::Layer, "compute_aesthetics")
body(compute_aesthetics_alt)[[length(body(compute_aesthetics_alt)) - 1]] <-
quote(evaled <- add_group2(evaled))
# define modified Layer ggproto object that uses alternative compute_aesthetics
Layer2 <- ggproto("Layer2",
ggplot2:::Layer,
compute_aesthetics = compute_aesthetics_alt)
# define modified stat with Layer2 specified as its layer_class
stat_example <- function(
mapping = NULL,
data = NULL,
geom = "point",
position = "identity",
...,
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE
) {
layer(data = data,
mapping = mapping,
stat = StatExample,
geom = geom,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(na.rm = na.rm),
layer_class = Layer2) # change here
}
用法:
# add new column to simulate different colour
iris$gg <- sample(c("a", "b"), size = nrow(iris), replace = TRUE)
ggplot(iris) +
stat_example(aes(Sepal.Width, Sepal.Length,
value = Species))
# prints one data frame, because there's only one group by default
ggplot(iris) +
stat_example(aes(Sepal.Width, Sepal.Length,
value = Species, colour = gg))
# prints two data frames, because grouping is based on the colour aesthetic,
# which has two possible values