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")

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

Хотя это очень простая задача в базе 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/ ( т.е. ggplot думает, что вы хотите нанести эту информацию на график, а не на поля); Насколько я могу судить, вы не можете взять информацию, которую вычисляете в своей пользовательской статистической функции, не строить ничего в области графика, а вместо этого передать информацию в функцию масштабирования, такую ​​как 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

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


person Steve M    schedule 18.10.2016    source источник


Ответы (3)


Я обновил пакет 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")

Демонстрация stat_n_text

person Steve M    schedule 22.10.2016
comment
Привет, Стив! Есть ли способ удалить n =? Я просто хочу показать цифры. - person l0110; 03.04.2021

Мое решение может быть немного простым, но оно работает хорошо.

Учитывая пример с фасетированием по am, я начинаю с создания меток с использованием paste и \n.

mtcars2 <- mtcars %>% 
  group_by(cyl, am) %>% mutate(n = n()) %>% 
  mutate(label = paste0(cyl,'\nN = ',n))

Затем я использую эти метки вместо цил в коде ggplot.

ggplot(mtcars2,
   aes(x = factor(label), y = mpg, color = factor(label))) + 
  geom_point() + 
  xlab('cyl') + 
  facet_wrap(~am, scales = 'free_x') +
  theme(legend.position = "none")

Произвести что-то вроде рисунка ниже.

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

person Gabra    schedule 11.03.2018

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

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

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")

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

person eipi10    schedule 10.11.2016
comment
Большое спасибо за вашу помощь с этим! После того, как я разместил свой вопрос, я понял, как разместить размеры выборки в нижней части панели графика в соответствии с вашим вторым предложением. Я почти доработал новые функции статистики и геометрии, которые будут делать то, что я хочу, и включу их в следующую версию моего пакета EnvStats (и опубликую здесь, когда я это сделаю). Еще раз спасибо за вашу помощь и предложения! - person Steve M; 26.11.2016