Escape 'дискретная эстетика c подразумевает группу' в настраиваемом стате - PullRequest
5 голосов
/ 29 мая 2020

Я пытаюсь создать настраиваемую функцию статистики с помощью ggplot2, в которой я хотел бы получить доступ к дискретной переменной для вычисления статистики c для каждой группы. Однако по умолчанию слои ggplot автоматически назначают неявные группы любым дискретным переменным (в основном). Это означает, что мои данные разделяются на автоматическую c группировку, чего я бы не хотел.

Я могу показать это следующим образом; У меня есть довольно стандартный конструктор:

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

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

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

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

  • Я мог бы позволить группе по умолчанию -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» тоже.

1 Ответ

2 голосов
/ 30 июня 2020

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

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

  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
...