расширение ggplot, чтобы брать списки и делать их доступными для настраиваемых геометрий

Я пытаюсь заставить ggplot2 принимать списки и делать элементы списков доступными для других пользовательских функций geom.

У меня есть новая функция ggplot, которая принимает списки:

ggplot.list <- function(data = NULL,
                           mapping = ggplot2::aes(),
                           ...,
                           environment = parent.frame()) {

    p <- ggplot2::ggplot(data=data[[1]],mapping=mapping,..., environment=environment)

    p$data_ext <- data[[2]]
    p
}

Я создаю свой список и рисую первый data.frame:

l <- list(tibble(x=1:10, y=1:10), tibble(x=1:10+100, y =1:10+200))

ggplot(l) + geom_point(aes(x=x,y=y))

В идеале я хотел бы создать что-то вроде этого (что не работает), другой geom, который по умолчанию берет data_ext из объекта ggplot

geom_point2 <- function (mapping = NULL, data_ext = NULL, stat = "identity", position = "identity", 
                         ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
{
    layer(data_ext = data_ext, mapping = mapping, stat = stat, geom = GeomPoint, 
          position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
          params = list(na.rm = na.rm, ...))
}

ggplot(l) + geom_point(aes(x=x,y=y)) +  geom_point2(aes(x=x,y=y))

Я вижу, что мой второй data.frame находится внутри объекта ggplot, но я не знаю, как получить к нему доступ; то есть ggplot(l)$data_ext работает. Я пробовал играть с ggproto, но у меня недостаточно опыта, чтобы понять, что с ним делать и может ли это помочь.

ДОБАВЛЕНО. Между прочим, я могу добиться того, чего хочу, с помощью канала, но я не хочу запутывать потенциальных пользователей моих функций:

pipe_point2 <-function (plot, mapping = NULL, data = NULL, stat = "identity", position = "identity", 
                        ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
{

  plot +  layer(data = plot$data_ext, mapping = mapping, stat = stat, geom = GeomPoint,
          position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
          params = list(na.rm = na.rm, ...))
}

{ggplot(l) + geom_point(aes(x=x,y=y))} %>%  pipe_point2(aes(x=x,y=y))

person Bruno    schedule 29.04.2019    source источник


Ответы (1)


Важные указания с того момента, когда я начал создавать свои собственные объекты ggproto, были получены по следующей ссылке Расширение ggplot2. TL; DR: Будет очень сложно писать новые геометрии без объектов ggproto.

Я пробовал перенести матрицу в пользовательский geom_raster (), что было бы аналогично переносу списка в пользовательские геометрии. Я узнал, что шкалы очень сложно обучать, т.е. шкалы позиций не знали, каковы пределы матрицы, а шкала заполнения не могла знать, каковы были пределы матрицы. Я дошел до того, что все выглядело правильно, но цветовая шкала не показывала правильные числа.

Теперь списки, вероятно, станут немного проще, потому что вы можете легче разбивать их на значимые элементы, и вы можете протащить список внутри фрейма данных. Это означает, что вы можете легко делать такие вещи и передавать их в ggplot:

mydf <- data.frame(x = 1:3, y = 1:3)
mydf$z <- list(c("A","B"), 1:5, function(x) x/10)
print(mydf)
  x y                   z
1 1 1                A, B
2 2 2       1, 2, 3, 4, 5
3 3 3 function (x) , x/10

И я думаю, что это будет ваш лучший выбор для контрабанды списка геому. Вы просто называете это дополнительной эстетикой функции.

g <- ggplot(mydf) + geom_point(aes(x = x, y = y, z = z))
# Warning: Ignoring unknown aesthetics: z

И убедитесь, что у слоя есть доступ к нему:

layer_data(g)
  x y                   z PANEL group shape colour size fill alpha stroke
1 1 1                A, B     1    -1    19  black  1.5   NA    NA    0.5
2 2 2       1, 2, 3, 4, 5     1    -1    19  black  1.5   NA    NA    0.5
3 3 3 function (x) , x/10     1    -1    19  black  1.5   NA    NA    0.5

И это так, теперь вам нужно только написать свою функцию рисования пользовательской панели geom, чтобы изобразить этот список в сюжете, для чего я рекомендую ссылку выше.

Единственное, что нужно сделать, это распечатать z перед рисованием точек и принять z как необязательный эстетический вид:

MyGeom <- ggproto(
  "MyGeom", GeomPoint,
  draw_panel = function(data, panel_params, coord, na.rm = FALSE) {
    print(data$z)
    GeomPoint$draw_panel(data, panel_params, coord, na.rm = FALSE)
  },
  optional_aes = "z"
)

Теперь вам нужна оболочка для вашего слоя, указывающая на MyGeom:

geom_mine <- function (mapping = NULL, data = NULL, stat = "identity", position = "identity", 
                         ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
{
  layer(mapping = mapping, stat = stat, geom = MyGeom, 
        position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
        params = list(na.rm = na.rm, ...))
}

Теперь вы можете использовать это в сюжете:

ggplot(mydf) + geom_mine(aes(x = x, y = y, z = z))
[[1]]
[1] "A" "B"

[[2]]
[1] 1 2 3 4 5

[[3]]
function (x) 
x/10

И вуаля, он создает график и печатает z, как мы и сказали, что и должно быть.

Надеюсь, эти указатели помогут!

person teunbrand    schedule 29.04.2019
comment
Проблема в том, что у меня есть два data.frames, которые в реальном сценарии сильно различаются. Мне нужно было бы поместить список с фреймом данных внутри фрейма данных, и это означало бы, что фрейм данных будет повторяться столько раз, сколько строк в первом. Не лучшая идея в моем случае (с большим количеством данных). - person Bruno; 30.04.2019