функция расширения ggplot для построения наложенного среднего значения на диаграмме рассеяния

#r #ggplot2 #plyr #ggproto

Вопрос:

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

 library(plyr)
library(tidyverse)

summ <- ddply(mtcars,.(),summarise,
              dratSE = sqrt(var(drat))/length(drat),
              mpgSE = sqrt(var(mpg))/length(mpg),
              drat = mean(drat),
              mpg = mean(mpg))

ggplot(data = mtcars, mapping = aes(x = drat, y = mpg))  
  geom_point(shape = 21, fill = 'black', color = 'white', size = 3)   
  geom_errorbarh(data = summ, aes(xmin = drat - dratSE, xmax = drat   dratSE))  
  geom_errorbar(data = summ, aes(ymin = mpg - mpgSE, ymax = mpg mpgSE), width = .1)  
  geom_point(data = summ, color='red',size=4) 

 

В идеале для этого потребовалась бы только такая функция, как geom_scattermeans() выполнение всего этого. Но я не уверен, как эстетика переходит в последующие geom функции ggplot() .

Кроме того, у меня возникли трудности с созданием функции, которая принимает имена столбцов в качестве аргумента и заставляет ее работать ddply() . введите описание изображения здесь

Ответ №1:

Я думаю plyr , что на данный момент это довольно несуществующее. Я бы рекомендовал этот dplyr пакет. При программировании с dplyr помощью вы можете использовать {{ (фигурные-фигурные или охватывающие, как указано в документации) для правильного цитирования выражений.

 library(ggplot2)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

geom_point_error <- function(data, x, y, color = 'red', size = 4) {
  
  data <- dplyr::summarise(
    data,
    x_se = sqrt(var({{x}}))/length({{x}}),
    y_se = sqrt(var({{y}}))/length({{y}}),
    x = mean({{x}}),
    y = mean({{y}})
  )
  
  list(
    geom_errorbarh(data = data,
                   mapping = aes(y = y,
                                 xmin = x - x_se, xmax = x   x_se), inherit.aes = F),
    geom_errorbar(data = data,
                  mapping = aes(x = x,
                                ymin = y - y_se, ymax = y   y_se), width = .1,inherit.aes = F),
    geom_point(data = data,
               mapping = aes(x = x, y = y),
               color = color, size = size)
  )
}

ggplot(data = mtcars, mapping = aes(x = drat, y = mpg))  
  geom_point(shape = 21, fill = 'black', color = 'white', size = 3)   
  geom_point_error(mtcars, x = drat, y = mpg)
 

Создано 2021-05-17 пакетом reprex (v1.0.0)

Вторым вариантом было бы создать свой собственный ggproto Geom , чтобы обрабатывать эти вычисления внутри ggplot2 , но сейчас это немного чересчур.

Комментарии:

1. спасибо за вашу помощь! На самом деле я пытался создать ggproto , но безрезультатно

Ответ №2:

Поскольку мой первый ответ все еще является более простым решением, я решил оставить его. Этот ответ должен приблизить ОП к их цели.

Создание объекта ggproto может быть громоздким в зависимости от того, что вы пытаетесь сделать. В вашем случае вы объединяете 3 ggproto Geoms класса вместе с возможностью нового Stat .

Эти три геомы являются:

  • GeomErrorbar
  • GeomErrorbarh
  • GeomPoint

Чтобы начать, иногда вам просто нужно наследовать от одного из классов и перезаписать метод, но для объединения всех трех вместе вам потребуется проделать больше работы.

Давайте сначала рассмотрим, как каждый из них Geoms рисует свои grid объекты. В зависимости от Geom того, находится ли он в одной из этих функций draw_layer() , draw_panel() , и draw_group() . К счастью, каждая из геом, которые мы хотим использовать, использует только draw_panel() то, что означает для нас немного меньше работы — мы просто вызовем эти методы напрямую и построим новый grobTree объект. Нам просто нужно будет быть осторожными, чтобы все правильные параметры соответствовали нашему новому Geom draw_panel() методу.

Прежде чем мы приступим к написанию нашего собственного draw_panel , мы должны сначала рассмотреть setup_params() и setup_data() функции. Иногда они изменяют данные прямо за воротами. Эти шаги обычно полезны для автоматической обработки и часто используются для стандартизации/преобразования данных. Хорошим примером является GeomTile и GeomRect , по сути, они являются одними и теми же Geom s, но их setup_data() функции различаются, потому что они по-разному параметризованы.

Давайте предположим, что вы хотите назначить только x эстетику и y эстетику, а вычисления xmin , ymin ,, и оставьте xmax ymax для геомов/статистики.

К счастью, GeomPoint просто возвращает данные без изменений, поэтому нам нужно GeomErrorbar будет сначала включить GeomErrorbarh и. setup_data() Чтобы пропустить некоторые шаги, я просто собираюсь создать новый Stat , который позаботится о преобразовании этих ценностей для нас в рамках compute_group() метода.

Обратите внимание здесь GeomErrorbar и GeomErrorbarh позвольте включить еще один параметр — width и height , соответственно, который определяет ширину плоских участков полос ошибок.

кроме того, в рамках этих функций каждый будет создавать свои собственные xmin , xmax , ymin , ymax — поэтому нам нужно будет различать эти параметры.

Сначала загрузите необходимую информацию в пространство имен

 library(ggplot2)
library(grid)
"%||%" <- ggplot2:::`%||%`
 

Начните с нового Stat , я решил назвать это PointError

 StatPointError <- ggproto(
  "StatPointError",
  Stat,
  #having `width` and `height` as named parameters here insure
  #that they will be available to the `Stat` ggproto object.
  compute_group = function(data, scales, width = NULL, height = NULL){
    data$width <- data$width %||% width %||% (resolution(data$x, FALSE)*0.9)
    data$height <- data$height %||% height %||% (resolution(data$y, FALSE)*0.9)
    
    data <- transform(
      data,
      x = mean(x),
      y = mean(y),
      # positions for flat parts of vertical error bars
      xmin = mean(x) - width /2,
      xmax = mean(x)   width / 2,
      width = NULL, 
      # y positions of vertical error bars
      ymin = mean(y) - sqrt(var(y))/length(y),
      ymax = mean(y)   sqrt(var(y))/length(y),
      #positions for flat parts of horizontal error bars
      ymin_h = mean(y) - height /2,
      ymax_h = mean(y)   height /2,
      height = NULL,
      # x positions of horizontal error bars
      xmin_h = mean(x) - sqrt(var(x))/length(x),
      xmax_h = mean(x)   sqrt(var(x))/length(x)
    )
    unique(data)
  }
)
 

Теперь, что касается забавной части , то Geom , опять же, я собираюсь PointError использовать это имя как последовательное.

 GeomPointError <- ggproto(
  "GeomPointError",
  GeomPoint,
  #include some additional defaults
  default_aes = aes(
    shape = 19,
    colour = "black",
    size = 1.5, # error bars have defaults of 0.5 - you may want to add another parameter?
    fill = NA,
    alpha = NA,
    linetype = 1,
    stroke = 0.5, # for GeomPoint
    width = 0.5,  # for GeomErrorbar
    height = 0.5, # for GeomErrorbarh
  ),
  draw_panel = function(data, panel_params, coord, width = NULL, height = NULL, na.rm = FALSE) {
    #make errorbar grobs
    data_errbar <- data
    data_errbar[["size"]] <- 0.5
    errorbar_grob <- GeomErrorbar$draw_panel(data = data_errbar,
                                             panel_params = panel_params, coord = coord, 
                                             width = width, flipped_aes = FALSE)
    #re-parameterize errbarh data
    data_errbarh <- transform(data, 
                              xmin = xmin_h, xmax = xmax_h, ymin = ymin_h, ymax = ymax_h,
                              xmin_h = NULL, xmax_h = NULL, ymin_h = NULL, ymax_h = NULL,
                              size = 0.5)
    #make errorbarh grobs
    errorbarh_grob <- GeomErrorbarh$draw_panel(data = data_errbarh,
                                               panel_params = panel_params, coord = coord,
                                               height = height)
    point_grob <- GeomPoint$draw_panel(data = data, panel_params = panel_params,
                                       coord = coord, na.rm = na.rm)
    gt <- grobTree(
      errorbar_grob,
      errorbarh_grob,
      point_grob, name = 'geom_point_error')
    gt
  }
)
 

Наконец, нам нужна функция для вызова пользователем, которая создаст Layer объект.

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

Теперь мы можем проверить, правильно ли это работает

 ggplot(data = mtcars, mapping = aes(x = drat, y = mpg))  
  geom_point(shape = 21, fill = 'black', color = 'white', size = 3)  
  geom_point_error(color = "red", width = .1, height = .3)
 

 ggplot(data = mtcars, mapping = aes(x = drat, y = mpg))  
  geom_point(shape = 21, fill = 'black', color = 'white', size = 3)  
  geom_point_error(aes(color = hp>100))
 

Создано 2021-05-18 пакетом reprex (v1.0.0)

Очевидно, что вы могли бы сделать с этим гораздо больше, включив дополнительную эстетику по умолчанию, чтобы вы могли отдельно контролировать цвет и размер линий/точек (возможно, захотите переопределить GeomPointError$setup_data() , чтобы все отображалось правильно).

Наконец, эта геометрия довольно наивна в том смысле, что она предполагает x y , что сопоставления данных и являются непрерывными. Он по-прежнему работает со смешиванием непрерывного и дискретного, но выглядит немного обалденно

 ggplot(mpg, aes(cty, model))  
    geom_point()  
    geom_point_error(color = 'red')
 

Комментарии:

1. спасибо тебе, Джастин! это здорово. есть ли у вас какие-либо книги, которые вы хотели бы предложить по этой теме?

2. Все, чему я научился, было взято из других учебных пособий или из-за возни с исходным кодом путем отладки. Я думаю, что это руководство по расширению немного более надежное, чем книга ggplot2 . Честно говоря, если вы хотите ggplot2 значительно расширить, я бы рекомендовал пройти ggplot_build.ggplot через отладчик, чтобы получить представление о том, когда вызываются определенные вещи.

3. спасибо, вы-абсолютная легенда. я прочитал руководство по расширению, прежде чем опубликовать свой вопрос здесь, но все еще не мог по-настоящему понять его. но ваш пост помог мне соединить точки.