При группировке переопределить нижний, верхний и т. Д. - PullRequest
0 голосов
/ 08 декабря 2018

По умолчанию для нижнего, среднего и верхнего квантиля в geom_boxplot учитываются квантили 25%, 50% и 75%.Они вычисляются из y, но могут быть установлены вручную с помощью эстетических аргументов lower, upper, middle (обеспечивая также x, ymin и ymax и настройку stat="identity").

Однако при этом возникает несколько нежелательных эффектов (см. Версию 1 в примере кода):

  • Аргумент group игнорируется, поэтому все значения столбца являютсяучитывается в расчетах (например, при вычислении наименьшего квантиля для каждой группы)
  • Полученные идентичные прямоугольники сгруппированы по x и повторяются внутри группы так часто, как конкретное значение группы встречается в данных (вместо этогообъединения блоков в более широкое)
  • выбросы не отображаются

Путем предварительного вычисления желаемых значений и сохранения их в новом фрейме данных можно обработать первые дваточки (см. версию 2 в примере кода), в то время как третья точка фиксируется путем определения выбросов и добавления их отдельно к диаграмме с помощью geom_point.

. Есть ли более прямой способ получитьквантили изменились, не имея этих нежелательных эффектов?

Пример кода:

set.seed(12)

# Random data in B, grouped by values 1 to 4 in A
u <- data.frame(A = sample.int(4, 100, replace = TRUE), B = rnorm(100))

# Desired arguments
qymax <- 0.9
qymin <- 0.1
qmiddle <- 0.5
qupper <- 0.8
qlower <- 0.2

Версия 1: повторные блокпосты на значение в A,сгруппированы по A

ggplot(u, aes(x = A, y = B)) + 
  geom_boxplot(aes(group=A, 
                   lower = quantile(B, qlower), 
                   upper = quantile(B, qupper), 
                   middle = quantile(B, qmiddle), 
                   ymin = quantile(B, qymin), 
                   ymax = quantile(B, qymax) ), 
               stat="identity")

Версия 2: Сначала вычислите аргументы для каждой группы.Базовый раствор R

Bgrouped <- lapply(unique(u$A), function(a) u$B[u$A == a])
.lower <- sapply(Bgrouped, function(x) quantile(x, qlower))
.upper <- sapply(Bgrouped, function(x) quantile(x, qupper))
.middle <- sapply(Bgrouped, function(x) quantile(x, qmiddle))
.ymin <- sapply(Bgrouped, function(x) quantile(x, qymin))
.ymax <- sapply(Bgrouped, function(x) quantile(x, qymax))

u <- data.frame(A = unique(u$A), 
                lower = .lower, 
                upper = .upper, 
                middle = .middle, 
                ymin = .ymin, 
                ymax = .ymax)    

ggplot(u, aes(x = A)) + 
  geom_boxplot(aes(lower = lower, upper = upper, 
                   middle = middle, ymin = ymin, ymax = ymax ), 
               stat="identity")

1 Ответ

0 голосов
/ 10 декабря 2018

Это не то, что я действительно сделал бы без лота оправдания, поскольку люди обычно ожидают, что значения min / max / box бокса соответствуют тем же квантильным позициям, но это можно сделать.

Используемые данные (с экстремальными значениями, добавленными для демонстрации выбросов):

set.seed(12)
u <- data.frame(A = sample.int(4, 100, replace = TRUE), B = rnorm(100))
u$B[c(30, 70, 76)] <- c(4, -4, -5)

Решение 1 : Вы можете предварительно вычислить значения, не переходяпо основному маршруту R, и включить расчеты для выбросов в том же шаге.Я сделал бы это полностью в библиотеках Tidyverse Хэдли, которые я нахожу более точными:

library(dplyr)
library(tidyr)

u %>%
  group_by(A) %>%
  summarise(lower = quantile(B, qlower),
            upper = quantile(B, qupper), 
            middle = quantile(B, qmiddle), 
            IQR = diff(c(lower, upper)),
            ymin = max(quantile(B, qymin), lower - 1.5 * IQR), 
            ymax = min(quantile(B, qymax), upper + 1.5 * IQR),
            outliers = list(B[which(B > upper + 1.5 * IQR | 
                                      B < lower - 1.5 * IQR)])) %>%
  ungroup() %>% 
  ggplot(aes(x = A)) + 
  geom_boxplot(aes(lower = lower, upper = upper,
                   middle = middle, ymin = ymin, ymax = ymax ),
               stat="identity") + 
  geom_point(data = . %>% 
               filter(sapply(outliers, length) > 0) %>%
               select(A, outliers) %>%
               unnest(), 
             aes(y = unlist(outliers)))

tidyverse solution

Решение 2 : Выможет переопределить фактические спецификации квантилей, используемые ggplot.Расчеты для квантилей geom_boxplot() в действительности выполняются в функции StatBoxplot compute_group(), найденной здесь :

compute_group = function(data, scales, width = NULL, na.rm = FALSE, coef = 1.5) {
    qs <- c(0, 0.25, 0.5, 0.75, 1)

    if (!is.null(data$weight)) {
      mod <- quantreg::rq(y ~ 1, weights = weight, data = data, tau = qs)
      stats <- as.numeric(stats::coef(mod))
    } else {
      stats <- as.numeric(stats::quantile(data$y, qs))
    }

... (omitted for space)

Вектор qs определяет позиции квантиля.На него не влияют параметры, переданные в compute_group(), поэтому единственный способ изменить это изменить само определение compute_group():

# save a copy of the original function, in case you need to revert
original.function <- environment(ggplot2::StatBoxplot$compute_group)$f

# define new function (only the first line for qs is changed, but you'll have to
# copy & paste the whole thing)
new.function <- function (data, scales, width = NULL, na.rm = FALSE, coef = 1.5) {
  qs <- c(0.1, 0.2, 0.5, 0.8, 0.9)
  if (!is.null(data$weight)) {
    mod <- quantreg::rq(y ~ 1, weights = weight, data = data, 
                        tau = qs)
    stats <- as.numeric(stats::coef(mod))
  }
  else {
    stats <- as.numeric(stats::quantile(data$y, qs))
  }
  names(stats) <- c("ymin", "lower", "middle", "upper", "ymax")
  iqr <- diff(stats[c(2, 4)])
  outliers <- data$y < (stats[2] - coef * iqr) | data$y > (stats[4] + 
                                                             coef * iqr)
  if (any(outliers)) {
    stats[c(1, 5)] <- range(c(stats[2:4], data$y[!outliers]), 
                            na.rm = TRUE)
  }
  if (length(unique(data$x)) > 1) 
    width <- diff(range(data$x)) * 0.9
  df <- as.data.frame(as.list(stats))
  df$outliers <- list(data$y[outliers])
  if (is.null(data$weight)) {
    n <- sum(!is.na(data$y))
  }
  else {
    n <- sum(data$weight[!is.na(data$y) & !is.na(data$weight)])
  }
  df$notchupper <- df$middle + 1.58 * iqr/sqrt(n)
  df$notchlower <- df$middle - 1.58 * iqr/sqrt(n)
  df$x <- if (is.factor(data$x)) 
    data$x[1]
  else mean(range(data$x))
  df$width <- width
  df$relvarwidth <- sqrt(n)
  df
}

Результат:

# toggle between the two definitions
environment(StatBoxplot$compute_group)$f <- original.function
ggplot(u, aes(x = A, y = B, group = A)) +
  geom_boxplot() +
  ggtitle("original definition for calculated quantiles")

environment(StatBoxplot$compute_group)$f <- new.function
ggplot(u, aes(x = A, y = B, group = A)) +
  geom_boxplot() +
  ggtitle("new definition for calculated quantiles")

side-by-side

Обратите внимание, что при изменении определения это влияет на каждый объект ggplot в вашей среде.Таким образом, если вы создали объект коробочного графика ggplot до изменения определения, и распечатали его после , блокплот будет следовать новому определению.(Для сравнения, приведенного выше, мне пришлось немедленно преобразовать каждый ggplot в объект grob, чтобы сохранить разницу.)

...