Установка шестнадцатеричных ячеек в ggplot2 на тот же размер

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

set.seed(1) #Create data
bindata <- data.frame(x=rnorm(100), y=rnorm(100))
fac_probs <- dnorm(seq(-3, 3, length.out=26))
fac_probs <- fac_probs/sum(fac_probs)
bindata$factor <- sample(letters, 100, replace=TRUE, prob=fac_probs)

library(ggplot2) #Actual plotting
library(hexbin)

ggplot(bindata, aes(x=x, y=y)) +
  geom_hex() +
  facet_wrap(~factor)

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

Можно ли настроить что-то, чтобы все эти бункеры были физически одного размера?


person sebastian-c    schedule 24.01.2013    source источник
comment
Обычно помогает что-то вроде + stat_binhex(binwidth = c(0.5, 0.5)) вместо geom_hex(), но при добавлении фасетирования это, кажется, игнорируется. Интересно, что это работает, когда бункеры прямоугольные qplot(x,y, data = bindata, geom = "bin2d", binwidth = c(0.5, 0.5), facets=~factor)   -  person orizon    schedule 24.01.2013


Ответы (4)


Как говорит Юлиус, проблема в том, что hexGrob не получает информацию о размерах бункера, а догадывается по различиям, которые находит внутри фасета.

Очевидно, имеет смысл передать dx и dy hexGrob - отсутствие ширины и высоты шестиугольника похоже на указание круга по центру без указания радиуса.

Обходной путь:

обходной путь

Стратегия resolution работает, если фасет содержит два соседних шестиугольника, которые различаются как по x, так и по y. Итак, в качестве обходного пути я создам вручную data.frame, содержащий координаты x и y центров ячеек, а также фактор для фасетирования и подсчетов:

Помимо указанных в вопросе библиотек мне понадобится

library (reshape2)

а также bindata$factor действительно должен быть фактором:

bindata$factor <- as.factor (bindata$factor)

Теперь рассчитаем основную сетку шестиугольника.

h <- hexbin (bindata, xbins = 5, IDs = TRUE, 
             xbnds = range (bindata$x), 
             ybnds = range (bindata$y))

Далее нам нужно посчитать количество в зависимости от bindata$factor

counts <- hexTapply (h, bindata$factor, table)
counts <- t (simplify2array (counts))
counts <- melt (counts)
colnames (counts)  <- c ("ID", "factor", "counts")

Поскольку у нас есть идентификаторы ячеек, мы можем объединить этот data.frame с соответствующими координатами:

hexdf <- data.frame (hcell2xy (h),  ID = h@cell)
hexdf <- merge (counts, hexdf)

Вот как выглядит data.frame:

> head (hexdf)
  ID factor counts          x         y
1  3      e      0 -0.3681728 -1.914359
2  3      s      0 -0.3681728 -1.914359
3  3      y      0 -0.3681728 -1.914359
4  3      r      0 -0.3681728 -1.914359
5  3      p      0 -0.3681728 -1.914359
6  3      o      0 -0.3681728 -1.914359

ggplotting (используйте команду ниже), это дает правильные размеры бункера, но фигура имеет немного странный вид: рисуются шестиугольники с нулевым счетом, но только там, где этот бин заполнен каким-либо другим фасетом. Чтобы подавить прорисовку, мы можем установить там счетчики на NA и сделать na.value полностью прозрачным (по умолчанию серый50):

hexdf$counts [hexdf$counts == 0] <- NA

ggplot(hexdf, aes(x=x, y=y, fill = counts)) +
  geom_hex(stat="identity") +
  facet_wrap(~factor) +
  coord_equal () +
  scale_fill_continuous (low = "grey80", high = "#000040", na.value = "#00000000")

дает цифру вверху сообщения.

Эта стратегия работает до тех пор, пока значения ширины бинов верны без фасетирования. Если ширина бинов установлена ​​очень малой, resolution может по-прежнему давать слишком большие dx и dy. В этом случае мы можем предоставить hexGrob две соседние ячейки (но различающиеся как по x, так и по y) с NA счетчиками для каждого аспекта.

dummy <- hgridcent (xbins = 5, 
                    xbnds = range (bindata$x),  
                    ybnds = range (bindata$y),  
                    shape = 1)

dummy <- data.frame (ID = 0,
                     factor = rep (levels (bindata$factor), each = 2),
                     counts = NA,
                     x = rep (dummy$x [1] + c (0, dummy$dx/2), 
                              nlevels (bindata$factor)),
                     y = rep (dummy$y [1] + c (0, dummy$dy  ), 
                              nlevels (bindata$factor)))

Дополнительным преимуществом этого подхода является то, что мы можем удалить все строки с нулевым счетчиком уже в counts, в этом случае уменьшив размер hexdf примерно на 3/4 (122 строки вместо 520):

counts <- counts [counts$counts > 0 ,]
hexdf <- data.frame (hcell2xy (h),  ID = h@cell)
hexdf <- merge (counts, hexdf)
hexdf <- rbind (hexdf, dummy)

Сюжет выглядит точно так же, как указано выше, но вы можете визуализировать разницу, поскольку na.value не полностью прозрачен.


подробнее о проблеме

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

Вот ряд более минимальных данных, которые показывают проблему:

Сначала я отслеживаю hexBin, чтобы получить все координаты центра той же гексагональной сетки, что и ggplot2:::hexBin, и объект, возвращенный hexbin:

trace (ggplot2:::hexBin, exit = quote ({trace.grid <<- as.data.frame (hgridcent (xbins = xbins, xbnds = xbnds, ybnds = ybnds, shape = ybins/xbins) [1:2]); trace.h <<- hb}))

Настройте очень небольшой набор данных:

df <- data.frame (x = 3 : 1, y = 1 : 3)

И сюжет:

p <- ggplot(df, aes(x=x, y=y)) +  geom_hex(binwidth=c(1, 1)) +          
     coord_fixed (xlim = c (0, 4), ylim = c (0,4))

p # needed for the tracing to occur
p + geom_point (data = trace.grid, size = 4) + 
    geom_point (data = df, col = "red") # data pts

str (trace.h)

Formal class 'hexbin' [package "hexbin"] with 16 slots
  ..@ cell  : int [1:3] 3 5 7
  ..@ count : int [1:3] 1 1 1
  ..@ xcm   : num [1:3] 3 2 1
  ..@ ycm   : num [1:3] 1 2 3
  ..@ xbins : num 2
  ..@ shape : num 1
  ..@ xbnds : num [1:2] 1 3
  ..@ ybnds : num [1:2] 1 3
  ..@ dimen : num [1:2] 4 3
  ..@ n     : int 3
  ..@ ncells: int 3
  ..@ call  : language hexbin(x = x, y = y, xbins = xbins, shape = ybins/xbins, xbnds = xbnds, ybnds = ybnds)
  ..@ xlab  : chr "x"
  ..@ ylab  : chr "y"
  ..@ cID   : NULL
  ..@ cAtt  : int(0) 

Я повторяю сюжет, опуская точку данных 2:

p <- ggplot(df [-2,], aes(x=x, y=y)) +  geom_hex(binwidth=c(1, 1)) +          coord_fixed (xlim = c (0, 4), ylim = c (0,4))
p
p + geom_point (data = trace.grid, size = 4) + geom_point (data = df, col = "red")
str (trace.h)

Formal class 'hexbin' [package "hexbin"] with 16 slots
  ..@ cell  : int [1:2] 3 7
  ..@ count : int [1:2] 1 1
  ..@ xcm   : num [1:2] 3 1
  ..@ ycm   : num [1:2] 1 3
  ..@ xbins : num 2
  ..@ shape : num 1
  ..@ xbnds : num [1:2] 1 3
  ..@ ybnds : num [1:2] 1 3
  ..@ dimen : num [1:2] 4 3
  ..@ n     : int 2
  ..@ ncells: int 2
  ..@ call  : language hexbin(x = x, y = y, xbins = xbins, shape = ybins/xbins, xbnds = xbnds, ybnds = ybnds)
  ..@ xlab  : chr "x"
  ..@ ylab  : chr "y"
  ..@ cID   : NULL
  ..@ cAtt  : int(0) 

все в порядкеНеправильное построение шестиугольника

  • обратите внимание, что результаты из hexbin находятся в той же сетке (номера ячеек не изменились, просто ячейка 5 больше не заполнена и, следовательно, не указана), размеры и диапазоны сетки не изменились. Но нарисованные шестиугольники кардинально изменились.

  • Также обратите внимание, что hgridcent забывает вернуть координаты центра первой ячейки (внизу слева).

Хотя он заселяется:

df <- data.frame (x = 1 : 3, y = 1 : 3)

p <- ggplot(df, aes(x=x, y=y)) +  geom_hex(binwidth=c(0.5, 0.8)) +          
     coord_fixed (xlim = c (0, 4), ylim = c (0,4))

p # needed for the tracing to occur
p + geom_point (data = trace.grid, size = 4) + 
    geom_point (data = df, col = "red") + # data pts
    geom_point (data = as.data.frame (hcell2xy (trace.h)), shape = 1, size = 6)

все испортилось

Здесь отображение шестиугольников не может быть правильным - они не принадлежат одной шестиугольной сетке.

person cbeleites unhappy with SX    schedule 29.01.2013
comment
По-прежнему нет лучшего решения, чем этот обходной путь? У меня такая же проблема без использования фасетов. - person Herman Toothrot; 23.10.2020

Я попытался воспроизвести ваше решение с тем же набором данных, используя решетку hexbinplot. Изначально выдала ошибку xbnds[1] < xbnds[2] is not fulfilled. Эта ошибка возникла из-за неправильных числовых векторов, определяющих диапазон значений, которые должны быть покрыты бином. Я изменил эти аргументы в hexbinplot, и это как-то сработало. Не уверен, что это поможет вам решить эту проблему с помощью ggplot, но, вероятно, это какая-то отправная точка.

library(lattice)
library(hexbin)
hexbinplot(y ~ x | factor, bindata, xbnds = "panel", ybnds = "panel", xbins=5, 
           layout=c(7,3))

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

ИЗМЕНИТЬ

Хотя прямоугольные бункеры с stat_bin2d() работают нормально:

ggplot(bindata, aes(x=x, y=y, group=factor)) + 
    facet_wrap(~factor) +
    stat_bin2d(binwidth=c(0.6, 0.6))

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

person Geek On Acid    schedule 26.01.2013
comment
Заглянув в stat_bin2d.R / geom_bin2d.R и stat_binhex.R / geom_bhex.R, выясняется, что bin2d хранит xmin, xmax, ymin, ymax, тогда как шестиугольные имеют только центры, но не ширину и высоту - так что geomHex угадывает их (как указал Джулиан), и это становится неверным, если в сюжете / фасете нет диагностически смежных фасетов. - person cbeleites unhappy with SX; 30.01.2013

Нас интересуют два исходных файла: stat-binhex .r и geom-hex.r, в основном функции hexBin и hexGrob.

Как упоминал @Dinre, эта проблема на самом деле не связана с фасетированием. Мы видим, что binwidth не игнорируется и используется в hexBin особым образом, эта функция применяется для каждого аспекта отдельно. После этого для каждой грани применяется hexGrob. Чтобы убедиться, что вы можете проверить их, например,

trace(ggplot2:::hexGrob, quote(browser()))
trace(ggplot2:::hexBin, quote(browser()))

Следовательно, это объясняет, почему размеры различаются - они зависят как от binwidth, так и от данных каждого фасета.

Трудно отслеживать процесс из-за различных преобразований координат, но обратите внимание, что вывод hexBin

data.frame(
  hcell2xy(hb),
  count = hb@count,
  density = hb@count / sum(hb@count, na.rm=TRUE)
)

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

dx <- resolution(x, FALSE)
dy <- resolution(y, FALSE) / sqrt(3) / 2 * 1.15

в ?resolution мы можем видеть

Описание

 The resolution is is the smallest non-zero distance between adjacent
 values. If there is only one unique value, then the resolution is
 defined to be one.

по этой причине (resolution(x, FALSE) == 1 и resolution(y, FALSE) == 1) координаты x polygonGrob первого фасета в вашем примере равны

[1] 1.5native  1.5native  0.5native  -0.5native -0.5native 0.5native 

и если я не ошибаюсь, в этом случае собственные модули похожи на npc, поэтому они должны быть между 0 и 1. То есть в случае одного шестнадцатеричного бункера он выходит за пределы диапазона из-за resolution(). Эта функция также является причиной искажения, о котором упоминал @Dinre, даже при наличии до нескольких шестнадцатеричных ячеек.

Так что на данный момент, похоже, нет возможности иметь шестигранные ячейки одинакового размера. Временное (и очень неудобное для большого количества факторов) решение может начинаться примерно так:

library(gridExtra)
set.seed(2)
bindata <- data.frame(x = rnorm(100), y = rnorm(100))
fac_probs <- c(10, 40, 40, 10)
bindata$factor <- sample(letters[1:4], 100, 
                         replace = TRUE, prob = fac_probs)

binwidths <- list(c(0.4, 0.4), c(0.5, 0.5),
                  c(0.5, 0.5), c(0.4, 0.4))

plots <- mapply(function(w,z){
  ggplot(bindata[bindata$factor == w, ], aes(x = x, y = y)) +
    geom_hex(binwidth = z) + theme(legend.position = 'none')
}, letters[1:4], binwidths, SIMPLIFY = FALSE)

do.call(grid.arrange, plots)

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

person Julius Vainora    schedule 29.01.2013
comment
Боюсь, это не сработает с очень малонаселенными фасетами. Убедитесь, что у ваших данных есть проблемы с фасетами. Наоборот: на всех ваших графиках есть соседние по диагонали ячейки. В этом случае resolution дает правильную высоту и ширину. - person cbeleites unhappy with SX; 30.01.2013
comment
@cbeleites, полностью с вами согласен. Я просто хотел предложить частичное решение, в котором можно было бы вводить ширину бинов отдельно для каждого фактора, что могло бы частично решить проблему. - person Julius Vainora; 30.01.2013
comment
Но это не решает проблему - даже частично! Ширина бина не передается hexGrob, и проблема также возникает без фасетирования (хотя при фасетировании вероятность столкнуться с проблемой выше из-за меньшего числа регистров на фасет). Я добавил к своему ответу более подробные примеры. - person cbeleites unhappy with SX; 30.01.2013

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

Это наводит на мысль, что размер и форма конкретного шестнадцатеричного бункера в 'ggplot2' связаны с вычислением, уникальным для каждого аспекта, вместо того, чтобы выполнять один расчет для группы и затем строить график данных. Это несколько подкрепляется тем фактом, что я могу воспроизвести искажение в любом заданном аспекте, построив график только этого единственного фактора, например:

ggplot(bindata[bindata$factor=="e",], aes(x=x, y=y)) +
geom_hex()

Это похоже на то, что следует повысить до уровня сопровождающего пакета, Хэдли Уикхема (h.wickham на gmail.com). Эта информация находится в открытом доступе на CRAN.

Обновление. Я отправил электронное письмо Хэдли Уикхему с вопросом, не рассмотрит ли он этот вопрос, и он подтвердил, что такое поведение действительно является ошибкой.

person Dinre    schedule 29.01.2013
comment
Спасибо за это письмо. @cbeleites также отправил отчет об ошибке здесь. - person sebastian-c; 11.02.2013