Форсировать ящики с geom_boxplot до постоянной ширины

Я делаю boxplot, в котором x и fill сопоставляются с разными переменными, примерно так:

ggplot(mpg, aes(x=as.factor(cyl), y=cty, fill=as.factor(drv))) + 
    geom_boxplot()

enter image description here

Как и в приведенном выше примере, ширина моих ящиков отличается по-разному при разных значениях x, потому что у меня нет всех возможных комбинаций значений x и fill, поэтому.

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

Моя первая мысль была

+ geom_boxplot(width=0.5)

но это не помогает; он регулирует ширину полного набора ящиков для заданного уровня фактора x.

Этот пост кажется почти актуальным, но я не совсем понимаю, как применить его к моей ситуации. Использование + scale_fill_discrete(drop=FALSE) не изменяет ширины столбцов.

Ответ 1

Проблема связана с тем, что некоторые клетки комбинаций факторов отсутствуют. Количество точек данных для всех комбинаций уровней cyl и drv можно проверить с помощью xtabs:

tab <- xtabs( ~ drv + cyl, mpg)

tab

#    cyl
# drv  4  5  6  8
#   4 23  0 32 48
#   f 58  4 43  1
#   r  0  0  4 21

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

Проверьте диапазон зависимой переменной (ось y). Поддельные данные должны быть вне этого диапазона.

range(mpg$cty)
# [1]  9 35

Создайте подмножество mpg с данными, необходимыми для графика:

tmp <- mpg[c("cyl", "drv", "cty")]

Создайте индекс для пустых ячеек:

idx <- which(tab == 0, arr.ind = TRUE)

idx

#   row col
# r   3   1
# 4   1   2
# r   3   2

Создайте три поддельные строки (с -1 как значение для cty):

fakeLines <- apply(idx, 1,
                   function(x) 
                     setNames(data.frame(as.integer(dimnames(tab)[[2]][x[2]]), 
                                         dimnames(tab)[[1]][x[1]], 
                                         -1), 
                              names(tmp)))

fakeLines

# $r
#   cyl drv cty
# 1   4   r  -1
# 
# $`4`
#   cyl drv cty
# 1   5   4  -1
# 
# $r
#   cyl drv cty
# 1   5   r  -1

Добавьте строки в существующие данные:

tmp2 <- rbind(tmp, do.call(rbind, fakeLines))

Plot:

library(ggplot2)
ggplot(tmp2, aes(x = as.factor(cyl), y = cty, fill = as.factor(drv))) + 
  geom_boxplot() +
  coord_cartesian(ylim = c(min(tmp$cty - 3), max(tmp$cty) + 3))
  # The axis limits have to be changed to suppress displaying the fake data.

enter image description here

Ответ 2

Просто используйте функцию facet_grid(), что значительно упрощает визуализацию:

ggplot(mpg, aes(x=as.factor(drv), y=cty, fill=as.factor(drv))) + 
    geom_boxplot() +
    facet_grid(.~cyl)

enter image description here

Посмотрите, как я переключаюсь с x=as.factor(cyl) на x=as.factor(drv).
Сделав это, вы всегда можете изменить способ отображения полос и убрать поля между панелями... это может выглядеть так, как вы ожидали.
Кстати, вам даже не нужно использовать as.factor(), прежде чем указывать столбцы, которые будут использоваться ggplot(). это снова улучшит читабельность вашего кода.