¿Cómo trazar dos histogramas juntos en R?

221

Estoy usando R y tengo dos marcos de datos: zanahorias y pepinos. Cada marco de datos tiene una sola columna numérica que enumera la longitud de todas las zanahorias medidas (total: 100k zanahorias) y pepinos (total: 50k pepinos).

Deseo trazar dos histogramas, la longitud de la zanahoria y la longitud del pepino, en la misma parcela. Se superponen, así que supongo que también necesito algo de transparencia. También necesito usar frecuencias relativas, no números absolutos, ya que el número de instancias en cada grupo es diferente.

algo así sería bueno, pero no entiendo cómo crearlo desde mis dos tablas:

densidad superpuesta

David B
fuente
Por cierto, ¿qué software estás planeando usar? Para el código abierto, recomendaría gnuplot.info [gnuplot]. En su documentación, creo que encontrará cierta técnica y scripts de muestra para hacer lo que quiera.
noel aye
1
Estoy usando R como sugiere la etiqueta (publicación editada para aclarar esto)
David B
1
alguien publicó un fragmento de código para hacerlo en este hilo: stackoverflow.com/questions/3485456/…
nico

Respuestas:

194

Esa imagen que vinculaste fue para curvas de densidad, no para histogramas.

Si has estado leyendo en ggplot, entonces quizás lo único que te falta es combinar tus dos marcos de datos en uno largo.

Entonces, comencemos con algo como lo que tiene, dos conjuntos de datos separados y combínelos.

carrots <- data.frame(length = rnorm(100000, 6, 2))
cukes <- data.frame(length = rnorm(50000, 7, 2.5))

# Now, combine your two dataframes into one.  
# First make a new column in each that will be 
# a variable to identify where they came from later.
carrots$veg <- 'carrot'
cukes$veg <- 'cuke'

# and combine into your new data frame vegLengths
vegLengths <- rbind(carrots, cukes)

Después de eso, lo cual es innecesario si sus datos ya están en formato largo, solo necesita una línea para hacer su trazado.

ggplot(vegLengths, aes(length, fill = veg)) + geom_density(alpha = 0.2)

ingrese la descripción de la imagen aquí

Ahora, si realmente quería histogramas, lo siguiente funcionará. Tenga en cuenta que debe cambiar la posición del argumento predeterminado de "pila". Puede perder eso si realmente no tiene una idea de cómo deberían ser sus datos. Un alfa más alto se ve mejor allí. También tenga en cuenta que lo hice histogramas de densidad. Es fácil quitarlo y = ..density..para volver a contar.

ggplot(vegLengths, aes(length, fill = veg)) + 
   geom_histogram(alpha = 0.5, aes(y = ..density..), position = 'identity')

ingrese la descripción de la imagen aquí

Juan
fuente
8
Si desea permanecer con histogramas, use ggplot(vegLengths, aes(length, fill = veg)) + geom_bar(pos="dodge"). Esto hará histogramas entrelazados, como en MATLAB.
mbq
1
Gracias por la respuesta! La parte 'position = "identity"' es realmente importante ya que, de lo contrario, las barras se apilan, lo que es engañoso cuando se combina con una densidad que por defecto parece ser "identidad", es decir, superpuesta en lugar de apilada.
Sombra
265

Aquí hay una solución aún más simple que utiliza gráficos básicos y mezcla alfa (que no funciona en todos los dispositivos gráficos):

set.seed(42)
p1 <- hist(rnorm(500,4))                     # centered at 4
p2 <- hist(rnorm(500,6))                     # centered at 6
plot( p1, col=rgb(0,0,1,1/4), xlim=c(0,10))  # first histogram
plot( p2, col=rgb(1,0,0,1/4), xlim=c(0,10), add=T)  # second

La clave es que los colores son semitransparentes.

Editar, más de dos años después : como esto acaba de recibir un voto positivo, creo que también podría agregar una imagen de lo que produce el código, ya que la combinación alfa es muy útil:

ingrese la descripción de la imagen aquí

Dirk Eddelbuettel
fuente
66
+1 gracias a todos, ¿se puede convertir en un gistograma más uniforme (como had.co.nz/ggplot2/graphics/55078149a733dd1a0b42a57faf847036.png )?
David B
3
¿Por qué separaste los plotcomandos? Puede poner todas esas opciones en los histcomandos y solo dos en las dos líneas.
John
@ John ¿Cómo lo harías?
HelloWorld
Ponga las opciones en el plotcomando directamente en el comando hist como dije. Publicar el código no es para lo que son los comentarios.
John
44

Aquí hay una función que escribí que usa pseudo-transparencia para representar histogramas superpuestos

plotOverlappingHist <- function(a, b, colors=c("white","gray20","gray50"),
                                breaks=NULL, xlim=NULL, ylim=NULL){

  ahist=NULL
  bhist=NULL

  if(!(is.null(breaks))){
    ahist=hist(a,breaks=breaks,plot=F)
    bhist=hist(b,breaks=breaks,plot=F)
  } else {
    ahist=hist(a,plot=F)
    bhist=hist(b,plot=F)

    dist = ahist$breaks[2]-ahist$breaks[1]
    breaks = seq(min(ahist$breaks,bhist$breaks),max(ahist$breaks,bhist$breaks),dist)

    ahist=hist(a,breaks=breaks,plot=F)
    bhist=hist(b,breaks=breaks,plot=F)
  }

  if(is.null(xlim)){
    xlim = c(min(ahist$breaks,bhist$breaks),max(ahist$breaks,bhist$breaks))
  }

  if(is.null(ylim)){
    ylim = c(0,max(ahist$counts,bhist$counts))
  }

  overlap = ahist
  for(i in 1:length(overlap$counts)){
    if(ahist$counts[i] > 0 & bhist$counts[i] > 0){
      overlap$counts[i] = min(ahist$counts[i],bhist$counts[i])
    } else {
      overlap$counts[i] = 0
    }
  }

  plot(ahist, xlim=xlim, ylim=ylim, col=colors[1])
  plot(bhist, xlim=xlim, ylim=ylim, col=colors[2], add=T)
  plot(overlap, xlim=xlim, ylim=ylim, col=colors[3], add=T)
}

Aquí hay otra forma de hacerlo usando el soporte de R para colores transparentes

a=rnorm(1000, 3, 1)
b=rnorm(1000, 6, 1)
hist(a, xlim=c(0,10), col="red")
hist(b, add=T, col=rgb(0, 1, 0, 0.5) )

Los resultados terminan pareciéndose a esto: texto alternativo

Chrisamiller
fuente
+1 para una opción disponible en todos los dispositivos gráficos (por ejemplo postscript)
Lenna
31

Ya hay respuestas hermosas, pero pensé en agregar esto. Me parece bien. (Copió números aleatorios de @Dirk). library(scales)es necesario`

set.seed(42)
hist(rnorm(500,4),xlim=c(0,10),col='skyblue',border=F)
hist(rnorm(500,6),add=T,col=scales::alpha('red',.5),border=F)

El resultado es...

ingrese la descripción de la imagen aquí

Actualización: esta función superpuesta también puede ser útil para algunos.

hist0 <- function(...,col='skyblue',border=T) hist(...,col=col,border=border) 

Siento que el resultado hist0es más bonito de ver quehist

hist2 <- function(var1, var2,name1='',name2='',
              breaks = min(max(length(var1), length(var2)),20), 
              main0 = "", alpha0 = 0.5,grey=0,border=F,...) {    

library(scales)
  colh <- c(rgb(0, 1, 0, alpha0), rgb(1, 0, 0, alpha0))
  if(grey) colh <- c(alpha(grey(0.1,alpha0)), alpha(grey(0.9,alpha0)))

  max0 = max(var1, var2)
  min0 = min(var1, var2)

  den1_max <- hist(var1, breaks = breaks, plot = F)$density %>% max
  den2_max <- hist(var2, breaks = breaks, plot = F)$density %>% max
  den_max <- max(den2_max, den1_max)*1.2
  var1 %>% hist0(xlim = c(min0 , max0) , breaks = breaks,
                 freq = F, col = colh[1], ylim = c(0, den_max), main = main0,border=border,...)
  var2 %>% hist0(xlim = c(min0 , max0),  breaks = breaks,
                 freq = F, col = colh[2], ylim = c(0, den_max), add = T,border=border,...)
  legend(min0,den_max, legend = c(
    ifelse(nchar(name1)==0,substitute(var1) %>% deparse,name1),
    ifelse(nchar(name2)==0,substitute(var2) %>% deparse,name2),
    "Overlap"), fill = c('white','white', colh[1]), bty = "n", cex=1,ncol=3)

  legend(min0,den_max, legend = c(
    ifelse(nchar(name1)==0,substitute(var1) %>% deparse,name1),
    ifelse(nchar(name2)==0,substitute(var2) %>% deparse,name2),
    "Overlap"), fill = c(colh, colh[2]), bty = "n", cex=1,ncol=3) }

El resultado de

par(mar=c(3, 4, 3, 2) + 0.1) 
set.seed(100) 
hist2(rnorm(10000,2),rnorm(10000,3),breaks = 50)

es

ingrese la descripción de la imagen aquí

Stat-R
fuente
24

Aquí hay un ejemplo de cómo puede hacerlo en gráficos R "clásicos":

## generate some random data
carrotLengths <- rnorm(1000,15,5)
cucumberLengths <- rnorm(200,20,7)
## calculate the histograms - don't plot yet
histCarrot <- hist(carrotLengths,plot = FALSE)
histCucumber <- hist(cucumberLengths,plot = FALSE)
## calculate the range of the graph
xlim <- range(histCucumber$breaks,histCarrot$breaks)
ylim <- range(0,histCucumber$density,
              histCarrot$density)
## plot the first graph
plot(histCarrot,xlim = xlim, ylim = ylim,
     col = rgb(1,0,0,0.4),xlab = 'Lengths',
     freq = FALSE, ## relative, not absolute frequency
     main = 'Distribution of carrots and cucumbers')
## plot the second graph on top of this
opar <- par(new = FALSE)
plot(histCucumber,xlim = xlim, ylim = ylim,
     xaxt = 'n', yaxt = 'n', ## don't add axes
     col = rgb(0,0,1,0.4), add = TRUE,
     freq = FALSE) ## relative, not absolute frequency
## add a legend in the corner
legend('topleft',c('Carrots','Cucumbers'),
       fill = rgb(1:0,0,0:1,0.4), bty = 'n',
       border = NA)
par(opar)

El único problema con esto es que se ve mucho mejor si los saltos de histograma están alineados, lo que puede tener que hacerse manualmente (en los argumentos pasados ​​a hist).

nullglob
fuente
Muy agradable. También me recordó a ese stackoverflow.com/questions/3485456/…
George Dontas
Aumentando esto porque esta respuesta es la única (además de las incluidas ggplot) que explica directamente si sus dos histogramas tienen tamaños de muestra sustancialmente diferentes.
MichaelChirico
Me gusta este método, tenga en cuenta que puede sincronizar saltos definiéndolos con seq (). Por ejemplo:breaks=seq(min(data$some_property), max(data$some_property), by=(max_prop - min_prop)/20)
Deruijter
17

Aquí está la versión como la ggplot2 que di solo en la base R. Copié algunas de @nullglob.

generar los datos

carrots <- rnorm(100000,5,2)
cukes <- rnorm(50000,7,2.5)

No necesita ponerlo en un marco de datos como con ggplot2. El inconveniente de este método es que debes escribir muchos más detalles de la trama. La ventaja es que tiene control sobre más detalles de la trama.

## calculate the density - don't plot yet
densCarrot <- density(carrots)
densCuke <- density(cukes)
## calculate the range of the graph
xlim <- range(densCuke$x,densCarrot$x)
ylim <- range(0,densCuke$y, densCarrot$y)
#pick the colours
carrotCol <- rgb(1,0,0,0.2)
cukeCol <- rgb(0,0,1,0.2)
## plot the carrots and set up most of the plot parameters
plot(densCarrot, xlim = xlim, ylim = ylim, xlab = 'Lengths',
     main = 'Distribution of carrots and cucumbers', 
     panel.first = grid())
#put our density plots in
polygon(densCarrot, density = -1, col = carrotCol)
polygon(densCuke, density = -1, col = cukeCol)
## add a legend in the corner
legend('topleft',c('Carrots','Cucumbers'),
       fill = c(carrotCol, cukeCol), bty = 'n',
       border = NA)

ingrese la descripción de la imagen aquí

Juan
fuente
9

@Dirk Eddelbuettel: La idea básica es excelente, pero el código que se muestra se puede mejorar. [Toma mucho tiempo explicarlo, de ahí una respuesta separada y no un comentario.]

La hist()función por defecto dibuja gráficos, por lo que debe agregar la plot=FALSEopción. Además, es más claro establecer el área de trazado mediante una plot(0,0,type="n",...)llamada en la que puede agregar etiquetas de eje, título de trazado, etc. Finalmente, me gustaría mencionar que también se puede usar sombreado para distinguir entre los dos histogramas. Aquí está el código:

set.seed(42)
p1 <- hist(rnorm(500,4),plot=FALSE)
p2 <- hist(rnorm(500,6),plot=FALSE)
plot(0,0,type="n",xlim=c(0,10),ylim=c(0,100),xlab="x",ylab="freq",main="Two histograms")
plot(p1,col="green",density=10,angle=135,add=TRUE)
plot(p2,col="blue",density=10,angle=45,add=TRUE)

Y aquí está el resultado (un poco demasiado amplio debido a RStudio :-)):

ingrese la descripción de la imagen aquí

Laryx Decidua
fuente
aumentando esto porque es una opción muy simple usando base y viable en postscriptdispositivos.
MichaelChirico
6

La API R de Plotly puede ser útil para usted. El siguiente gráfico está aquí .

library(plotly)
#add username and key
p <- plotly(username="Username", key="API_KEY")
#generate data
x0 = rnorm(500)
x1 = rnorm(500)+1
#arrange your graph
data0 = list(x=x0,
         name = "Carrots",
         type='histogramx',
         opacity = 0.8)

data1 = list(x=x1,
         name = "Cukes",
         type='histogramx',
         opacity = 0.8)
#specify type as 'overlay'
layout <- list(barmode='overlay',
               plot_bgcolor = 'rgba(249,249,251,.85)')  
#format response, and use 'browseURL' to open graph tab in your browser.
response = p$plotly(data0, data1, kwargs=list(layout=layout))

url = response$url
filename = response$filename

browseURL(response$url)

Divulgación completa: estoy en el equipo.

Grafico

Mateo Sanchez
fuente
1

Tantas respuestas geniales, pero como acabo de escribir una función function ( plotMultipleHistograms()) para hacer esto, pensé que agregaría otra respuesta.

La ventaja de esta función es que establece automáticamente los límites apropiados de los ejes X e Y y define un conjunto común de contenedores que utiliza en todas las distribuciones.

Aquí se explica cómo usarlo:

# Install the plotteR package
install.packages("devtools")
devtools::install_github("JosephCrispell/basicPlotteR")
library(basicPlotteR)

# Set the seed
set.seed(254534)

# Create random samples from a normal distribution
distributions <- list(rnorm(500, mean=5, sd=0.5), 
                      rnorm(500, mean=8, sd=5), 
                      rnorm(500, mean=20, sd=2))

# Plot overlapping histograms
plotMultipleHistograms(distributions, nBins=20, 
                       colours=c(rgb(1,0,0, 0.5), rgb(0,0,1, 0.5), rgb(0,1,0, 0.5)), 
                       las=1, main="Samples from normal distribution", xlab="Value")

ingrese la descripción de la imagen aquí

La plotMultipleHistograms()función puede tomar cualquier número de distribuciones, y todos los parámetros de trazado general debe trabajar con él (por ejemplo: las, main, etc.).

Joseph Crispell
fuente