Ggplot2: добавление информации о размере выборки в метки меток по оси x

Этот вопрос связан с Создайте собственную геометрию для вычисления сводной статистики и отображения их * вне * области построения графика (ПРИМЕЧАНИЕ. Все функции упрощены, ошибки проверки правильности типов объектов, NA и т.д.)

В базе R довольно легко создать функцию, которая создает стример с размером выборки, указанным ниже каждого уровня переменной группировки: вы можете добавить информацию о размере выборки с помощью функции mtext():

stripchart_w_n_ver1 <- function(data, x.var, y.var) {
    x <- factor(data[, x.var])
    y <- data[, y.var]
# Need to call plot.default() instead of plot because 
# plot() produces boxplots when x is a factor.
    plot.default(x, y, xaxt = "n",  xlab = x.var, ylab = y.var)
    levels.x <- levels(x)
    x.ticks <- 1:length(levels(x))
    axis(1, at = x.ticks, labels = levels.x)
    n <- sapply(split(y, x), length)
    mtext(paste0("N=", n), side = 1, line = 2, at = x.ticks)
}

stripchart_w_n_ver1(mtcars, "cyl", "mpg")

или вы можете добавить информацию о размере выборки в метки метки по оси x с помощью функции axis():

stripchart_w_n_ver2 <- function(data, x.var, y.var) {
    x <- factor(data[, x.var])
    y <- data[, y.var]
# Need to set the second element of mgp to 1.5 
# to allow room for two lines for the x-axis tick labels.
    o.par <- par(mgp = c(3, 1.5, 0))
    on.exit(par(o.par))
# Need to call plot.default() instead of plot because 
# plot() produces boxplots when x is a factor.
    plot.default(x, y, xaxt = "n", xlab = x.var, ylab = y.var)
    n <- sapply(split(y, x), length)
    levels.x <- levels(x)
    axis(1, at = 1:length(levels.x), labels = paste0(levels.x, "\nN=", n))
}

stripchart_w_n_ver2(mtcars, "cyl", "mpg")

Пример использования оси()

Хотя это очень простая задача в базе R, она безумно сложна в ggplot2, потому что очень сложно получить данные, используемые для генерации графика, и хотя существуют функции, эквивалентные axis() (например, scale_x_discrete и т.д.) нет эквивалента mtext(), который позволяет легко размещать текст по заданным координатам в пределах полей.

Я попытался использовать встроенную функцию stat_summary() для вычисления размеров выборки (т.е. fun.y = "length"), а затем поместить эту информацию на метки меток по оси x, но насколько я могу судить, вы не можете извлеките размеры выборки и затем каким-то образом добавьте их к меткам меток по оси x, используя функцию scale_x_discrete(), вы должны указать stat_summary(), какую геометрию вы хотите использовать. Вы можете установить geom="text", но тогда вам нужно поставить метки, и дело в том, что метки должны быть значениями размеров выборки, что означает stat_summary(), но которые вы не можете получить (и вам также необходимо указать, где вы хотите разместить текст, и, опять же, трудно определить, куда его поместить, чтобы он лежал непосредственно под отметками тика по оси x).

Виньетка "Расширение ggplot2" (http://docs.ggplot2.org/dev/vignettes/extending-ggplot2.html) показывает вам, как создать свою собственную функцию stat, которая позволяет вам напрямую перейти на данные, но проблема в том, что вам всегда нужно определить геометрию, чтобы перейти с вашей функцией stat (т.е. ggplot считает, что вы хотите построить эту информацию в сюжете, а не на полях); насколько я могу судить, вы не можете взять информацию, которую вы вычисляете в своей пользовательской функции stat, а не рисовать что-либо в области графика, а вместо этого передавать информацию функции масштабирования, например scale_x_discrete(). Вот моя попытка сделать это таким образом; самое лучшее, что я мог сделать, - разместить информацию о размере выборки при минимальном значении y для каждой группы:

StatN <- ggproto("StatN", Stat,
    required_aes = c("x", "y"), 
    compute_group = function(data, scales) {
    y <- data$y
    y <- y[!is.na(y)]
    n <- length(y)
    data.frame(x = data$x[1], y = min(y), label = paste0("n=", n))
    }
)

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

ggplot(mtcars, aes(x = factor(cyl), y = mpg)) + geom_point() + stat_n()

введите описание изображения здесь

Я думал, что решил проблему, просто создав функцию-обертку ggplot:

ggstripchart <- function(data, x.name, y.name,  
    point.params = list(), 
    x.axis.params = list(labels = levels(x)), 
    y.axis.params = list(), ...) {
    if(!is.factor(data[, x.name]))
    data[, x.name] <- factor(data[, x.name])
    x <- data[, x.name]
    y <- data[, y.name]
    params <- list(...)
    point.params    <- modifyList(params, point.params)
    x.axis.params   <- modifyList(params, x.axis.params)
    y.axis.params   <- modifyList(params, y.axis.params)

    point <- do.call("geom_point", point.params)

    stripchart.list <- list(
        point, 
        theme(legend.position = "none")
    )

    n <- sapply(split(y, x), length)
    x.axis.params$labels <- paste0(x.axis.params$labels, "\nN=", n)
    x.axis <- do.call("scale_x_discrete", x.axis.params)
    y.axis <- do.call("scale_y_continuous", y.axis.params)
    stripchart.list <- c(stripchart.list, x.axis, y.axis)           

    ggplot(data = data, mapping = aes_string(x = x.name, y = y.name)) + stripchart.list
}


ggstripchart(mtcars, "cyl", "mpg")

Пример использования ggstripchart()

Однако эта функция не работает правильно с огранкой. Например:

ggstripchart(mtcars, "cyl", "mpg") + facet_wrap(~am)

показывает размеры выборки для обеих фасок, объединенных для каждой грани. Мне нужно было бы создать огранку в функцию обертки, которая побеждает в попытке использовать все, что может предложить ggplot.

Пример использования ggstripchart с facet_wrap

Если у кого-нибудь будет понимание этой проблемы, я был бы благодарен. Большое спасибо за ваше время!

Ответ 1

Я обновил EnvStats пакет, включающий stat под названием stat_n_text, который добавит размер выборки (количество уникальных значений y) под каждым уникальным значением x. Подробнее см. файл справки для stat_n_text и список примеров. Ниже приведен простой пример:

library(ggplot2)
library(EnvStats)

p <- ggplot(mtcars, 
  aes(x = factor(cyl), y = mpg, color = factor(cyl))) + 
  theme(legend.position = "none")

p + geom_point() + 
  stat_n_text() + 
  labs(x = "Number of Cylinders", y = "Miles per Gallon")

Demo of stat_n_text

Ответ 2

Вы можете распечатать подсчеты под символами оси x, используя geom_text, если вы отключите отсечение, но вам, вероятно, придется настроить место размещения. Я включил параметр "nudge" для этого в приведенном ниже коде. Кроме того, приведенный ниже метод предназначен для случаев, когда все грани (если они есть) являются фасетками столбцов.

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

library(ggplot2)
library(dplyr)

pgg = function(dat, x, y, facet=NULL, nudge=0.17) {

  # Convert x-variable to a factor
  dat[,x] = as.factor(dat[,x])

  # Plot points
  p = ggplot(dat, aes_string(x, y)) +
    geom_point(position=position_jitter(w=0.3, h=0)) + theme_bw() 

  # Summarise data to get counts by x-variable and (if present) facet variables
  dots = lapply(c(facet, x), as.symbol)
  nn = dat %>% group_by_(.dots=dots) %>% tally

  # If there are facets, add them to the plot
  if (!is.null(facet)) {
    p = p + facet_grid(paste("~", paste(facet, collapse="+")))
  }

  # Add counts as text labels
  p = p + geom_text(data=nn, aes(label=paste0("N = ", nn$n)),
                    y=min(dat[,y]) - nudge*1.05*diff(range(dat[,y])), 
                    colour="grey20", size=3.5) +
    theme(axis.title.x=element_text(margin=unit(c(1.5,0,0,0),"lines")))

  # Turn off clipping and return plot
  p <- ggplot_gtable(ggplot_build(p))
  p$layout$clip[p$layout$name=="panel"] <- "off"
  grid.draw(p)

}

pgg(mtcars, "cyl", "mpg")
pgg(mtcars, "cyl", "mpg", facet=c("am","vs"))

введите описание изображения здесь

введите описание изображения здесь

Другим, потенциально более гибким, является добавление счетчиков в нижней части панели графика. Например:

pgg = function(dat, x, y, facet_r=NULL, facet_c=NULL) {

  # Convert x-variable to a factor
  dat[,x] = as.factor(dat[,x])

  # Plot points
  p = ggplot(dat, aes_string(x, y)) +
    geom_point(position=position_jitter(w=0.3, h=0)) + theme_bw() 

  # Summarise data to get counts by x-variable and (if present) facet variables
  dots = lapply(c(facet_r, facet_c, x), as.symbol)
  nn = dat %>% group_by_(.dots=dots) %>% tally

  # If there are facets, add them to the plot
  if (!is.null(facet_r) | !is.null(facet_c)) {

    facets = paste(ifelse(is.null(facet_r),".",facet_r), " ~ " , 
                   ifelse(is.null(facet_c),".",facet_c))

    p = p + facet_grid(facets)
  }

  # Add counts as text labels
  p + geom_text(data=nn, aes(label=paste0("N = ", nn$n)),
                y=min(dat[,y]) - 0.15*min(dat[,y]), colour="grey20", size=3) +
    scale_y_continuous(limits=range(dat[,y]) + c(-0.1*min(dat[,y]), 0.01*max(dat[,y])))
}

pgg(mtcars, "cyl", "mpg")
pgg(mtcars, "cyl", "mpg", facet_c="am")
pgg(mtcars, "cyl", "mpg", facet_c="am", facet_r="vs")

введите описание изображения здесь