Избегайте `` дискретная эстетика подразумевает группу '' в настраиваемом стате

Я пытаюсь создать настраиваемую функцию статистики с помощью 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

Создано 28 мая 2020 г. пакетом REPEX (v0.3.0)

Я хотел бы иметь 1 data.frame, содержащий все данные для этого случая. Выше мы видим, что мы распечатали 3 кадра data.frames с разными group переменными, что означает, что данные были разделены на 3 группы. Я думаю, что для этого нужно, чтобы переменная value избежала автоматического определения группы.

Я учел следующие моменты:

  • Я мог бы позволить группе по умолчанию -1, которая является стандартной группой без группы. Однако, когда я это сделаю, данные не будут автоматически сгруппированы, например, aes(colour = some_variable). Я хочу, чтобы это произошло окончательно.
  • Глядя на функцию ggplot2:::add_group(), кажется, что я могу избежать автогруппировки, назвав мою value переменную label, однако это сделало бы статистику несовместимой с geom_text(), и она не описывает значение value естественным образом.
  • Я мог бы заменить вызов layer() вариантом этой функции, который создал бы другой объект Layer ggproto, в котором compute_aesthetics() по-разному работает с группами. Однако это большая работа, которую я предпочел бы избежать.
  • Я, вероятно, мог бы проделать трюк в духе vctrs::new_vctr(..., class = "not_discrete"), но где подходящее место, чтобы обернуть мою value переменную в этом классе?

Приветствуются полезные предложения или новые варианты аргументов типа «просто используйте label».


person teunbrand    schedule 28.05.2020    source источник
comment
Мне это действительно очень любопытно. (+1) зачем вам это нужно? Разве не все дело в aes, что они создают группы? Если это значение, которое следует передать всем данным, почему бы не передать его как param, а не как aes?   -  person tjebo    schedule 30.06.2020
comment
Краткий ответ: для этой статистики: github.com/teunbrand/ ggh4x / blob / master / R / stat_rle.R. Я не хочу передавать его в качестве параметра, так как хочу, чтобы выполнялась нестандартная оценка, и я хочу, чтобы она гарантированно выполнялась параллельно с данными. (но не триггерные группы).   -  person teunbrand    schedule 30.06.2020


Ответы (1)


Если это случайный вариант использования, можно выполнить простой (хотя и ручной) способ взлома trace(ggplot2:::add_group, edit = TRUE) и добавить "value" вместе с "label", "PANEL" в качестве имен переменных, которые будут исключены из автоматического определения группы.

Менее ручной (но, вероятно, более хрупкий) способ добиться того же эффекта будет включать следующие шаги:

  1. Определите измененную версию функции add_group с указанным выше изменением;
  2. Определите измененную версию объекта Layer ggproto, который использует измененный add_group в своей функции compute_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
person Z.Lin    schedule 30.06.2020
comment
Спасибо, это отлично работает и намного меньше кода, чем я ожидал! - person teunbrand; 30.06.2020