los - Configuración de contenedores hexadecimales en ggplot2 al mismo tamaño
leyenda en ggplot2 (4)
Estoy tratando de hacer una representación hexbin de datos en varias categorías. El problema es que facetar estos contenedores parece hacer que todos ellos sean de diferentes tamaños.
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)
¿Es posible configurar algo para que todos estos contenedores sean físicamente del mismo tamaño?
Como dice Julius, el problema es que hexGrob
no obtiene la información sobre los tamaños de los contenedores y lo adivina a partir de las diferencias que encuentra dentro de la faceta .
Obviamente, tendría sentido entregar dx
y dy
a un hexGrob
, ya que no tener el ancho y la altura de un hexágono es como especificar un círculo por centro sin dar el radio.
Solución:
La estrategia de resolution
funciona, si la faceta contiene dos haxagones adyacentes que difieren tanto en x como en y. Entonces, como solución alternativa, construiré manualmente un data.frame que contiene las coordenadas del centro xey de las celdas, y el factor de facetado y los conteos:
Además de las bibliotecas especificadas en la pregunta, necesitaré
library (reshape2)
y también el bindata$factor
realidad debe ser un factor:
bindata$factor <- as.factor (bindata$factor)
Ahora, calcula la rejilla hexagonal básica
h <- hexbin (bindata, xbins = 5, IDs = TRUE,
xbnds = range (bindata$x),
ybnds = range (bindata$y))
A continuación, debemos calcular los recuentos en función del bindata$factor
counts <- hexTapply (h, bindata$factor, table)
counts <- t (simplify2array (counts))
counts <- melt (counts)
colnames (counts) <- c ("ID", "factor", "counts")
Como tenemos las ID de celda, podemos combinar este data.frame con las coordenadas apropiadas:
hexdf <- data.frame (hcell2xy (h), ID = h@cell)
hexdf <- merge (counts, hexdf)
Así es como se ve el 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
ggplot
ting (use el comando a continuación) esto produce los tamaños de bin correctos, pero la figura tiene una apariencia un poco extraña: se dibujan 0 hexágonos de conteo, pero solo cuando alguna otra faceta tiene este bin rellenado. Para suprimir el dibujo, podemos establecer los conteos allí en NA
y hacer que el na.value
completamente transparente (por defecto es grey50):
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")
Se obtiene la cifra en la parte superior del post.
Esta estrategia funciona siempre que los binwidths sean correctos sin facetado. Si los binwidths se establecen muy pequeños, la resolution
aún puede producir dx
y dy
demasiado grandes. En ese caso, podemos suministrar hexGrob
con dos contenedores adyacentes (pero diferenciándose tanto en x como en y) con conteos de NA
para cada faceta.
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)))
Una ventaja adicional de este enfoque es que podemos eliminar todas las filas con 0 conteos ya en counts
, en este caso reduciendo el tamaño de hexdf
en aproximadamente 3/4 (122 filas en lugar de 520):
counts <- counts [counts$counts > 0 ,]
hexdf <- data.frame (hcell2xy (h), ID = h@cell)
hexdf <- merge (counts, hexdf)
hexdf <- rbind (hexdf, dummy)
La trama se ve exactamente igual a la anterior, pero puede visualizar la diferencia con un valor de na.value
no es totalmente transparente.
más sobre el problema
El problema no es exclusivo de las facetas, pero ocurre siempre si hay muy pocas bandejas ocupadas, de modo que no se rellenan bandejas adyacentes "en diagonal".
Aquí hay una serie de datos más mínimos que muestran el problema:
Primero, trazo hexBin
para obtener todas las coordenadas centrales de la misma cuadrícula hexagonal que ggplot2:::hexBin
y el objeto devuelto por 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}))
Configurar un conjunto de datos muy pequeño:
df <- data.frame (x = 3 : 1, y = 1 : 3)
Y la trama:
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)
Repito la trama, omitiendo el punto de datos 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)
tenga en cuenta que los resultados del
hexbin
están en la misma cuadrícula (los números de celda no cambiaron, simplemente la celda 5 ya no se rellena y, por lo tanto, no se enumeran), las dimensiones y los rangos de la cuadrícula no cambiaron. Pero los hexágonos trazados cambiaron dramáticamente.Observe también que
hgridcent
olvida de devolver las coordenadas del centro de la primera celda (abajo a la izquierda).
Aunque se llena:
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)
Aquí, la representación de los hexágonos no puede ser correcta, no pertenecen a una cuadrícula hexagonal.
Hay dos archivos de origen que nos interesan: stat-binhex.r y geom-hex.r , principalmente funciones hexBin
y hexGrob
.
Como mencionó @Dinre, este problema no está realmente relacionado con las facetas. Lo que podemos ver es que binwidth
no se ignora y se usa de una manera especial en hexBin
, esta función se aplica para cada faceta por separado. Después de eso, se aplica hexGrob
para cada faceta. Para asegurarse de que puede inspeccionarlos con por ejemplo
trace(ggplot2:::hexGrob, quote(browser()))
trace(ggplot2:::hexBin, quote(browser()))
Por lo tanto, esto explica por qué los tamaños difieren: dependen del binwidth
y de los datos de cada faceta.
Es difícil realizar un seguimiento del proceso debido a varias transformadas de coordenadas, pero observe que la salida de hexBin
data.frame(
hcell2xy(hb),
count = hb@count,
density = hb@count / sum(hb@count, na.rm=TRUE)
)
Parece que siempre parece bastante común y que hexGrob
es responsable de dibujar contenedores hexadecimales, distorsión, es decir, tiene polygonGrob
. En el caso de que solo haya un contenedor hexadecimal en una faceta, existe una anomalía más grave.
dx <- resolution(x, FALSE)
dy <- resolution(y, FALSE) / sqrt(3) / 2 * 1.15
En ?resolution
podemos ver
Descripción
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.
por esta razón ( resolution(x, FALSE) == 1
y resolution(y, FALSE) == 1
) las coordenadas x de polygonGrob
de la primera faceta en su ejemplo son
[1] 1.5native 1.5native 0.5native -0.5native -0.5native 0.5native
y si no estoy equivocado, en este caso, las unidades nativas son como npc, por lo que deberían estar entre 0 y 1. Es decir, en el caso de un solo contenedor hexadecimal, se sale del rango debido a la resolution()
. Esta función también es la razón de distorsión que mencionó @Dinre incluso cuando tiene hasta varios contenedores de hex.
Así que por ahora no parece haber una opción para tener contenedores hexadecimales de igual tamaño. Una solución temporal (y muy inconveniente para una gran cantidad de factores) podría comenzar con algo como esto:
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)
Intenté replicar su solución con el mismo conjunto de datos utilizando un hexbinplot
enrejado. Inicialmente, me dio un error xbnds[1] < xbnds[2] is not fulfilled
. Este error se debió a vectores numéricos incorrectos que especificaron el rango de valores que debería cubrir el agrupamiento. Cambié esos argumentos en hexbinplot
, y de alguna manera funcionó. No estoy seguro de si te ayuda a resolverlo con ggplot, pero probablemente sea un punto de partida.
library(lattice)
library(hexbin)
hexbinplot(y ~ x | factor, bindata, xbnds = "panel", ybnds = "panel", xbins=5,
layout=c(7,3))
EDITAR
Aunque los contenedores rectangulares con stat_bin2d()
funcionan bien:
ggplot(bindata, aes(x=x, y=y, group=factor)) +
facet_wrap(~factor) +
stat_bin2d(binwidth=c(0.6, 0.6))
También jugué un poco con las gráficas hexadecimales en ''ggplot2'', y pude producir de manera consistente una distorsión significativa de los contenedores cuando la población de un factor se redujo a 8 o menos. No puedo explicar por qué sucede esto sin excavar en la fuente del paquete (lo cual me resisto a hacer), pero puedo decirles que factores dispersos parecen destruir constantemente el trazado de cubos hexagonales en ''ggplot2''.
Esto me sugiere que el tamaño y la forma de un contenedor hexadecimal en particular en ''ggplot2'' está relacionado con un cálculo que es único para cada faceta, en lugar de hacer un solo cálculo para el grupo y trazar los datos posteriormente. Esto se ve algo reforzado por el hecho de que puedo reproducir la distorsión en cualquier faceta dada al trazar solo ese único factor, de esta manera:
ggplot(bindata[bindata$factor=="e",], aes(x=x, y=y)) +
geom_hex()
Esto se siente como algo que debe ser elevado al mantenedor del paquete, Hadley Wickham (h.wickham en gmail.com). Esta información está disponible públicamente de CRAN.
Actualización : envié un correo electrónico a Hadley Wickham para preguntarle si echaría un vistazo a esta pregunta, y confirmó que este comportamiento es de hecho un error.