6

我正在尝试使用 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 包(v0.3.0)于 2020 年 5 月 28 日创建

我想要 1 个 data.frame 包含此案例的所有数据。我们在上面看到我们打印了 3 个带有不同group变量的 data.frames,这意味着数据被分成了 3 组。我认为到达那里需要的是让value变量逃脱自动组检测。

我考虑了以下几点:

  • 我可以让组默认为-1,这是标准的“无组”组。但是,当我这样做时,数据不会自动分组,例如aes(colour = some_variable). 这是我绝对想要发生的。
  • 查看ggplot2:::add_group()函数,似乎我可以通过命名我的value变量来逃避自动分组label,但是这会使 stat 不兼容geom_text()并且它不能value自然地描述的含义。
  • 我可以用layer()这个函数的一个变体替换调用,这将创建一个不同的 Layer ggproto 对象,其中compute_aesthetics()不同的组工作。然而,这是我宁愿避免负担的大量工作。
  • 我可能会按照 . 的方式来做一个技巧,但是在那个类中包装我的变量vctrs::new_vctr(..., class = "not_discrete")的合适位置在哪里?value

欢迎提供有用的建议,或者对“仅使用label”参数进行新的处理。

4

1 回答 1

2

如果这是一个偶然的用例,可以运行一个简单的(尽管是手动的)hack ,trace(ggplot2:::add_group, edit = TRUE)并将其作为变量名称添加到自动组检测之外。"value""label", "PANEL"

实现相同效果的较少手动(但可能更脆弱)的方法将涉及以下步骤:

  1. add_group用上述修改定义函数的修改版本;
  2. 定义在其函数Layer中使用修改后的 ggproto 对象的修改版本;add_groupcompute_aesthetics
  3. 将自定义统计信息指向修改后的图层。
# 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
于 2020-06-30T11:20:02.933 回答