Como plotar dois histogramas juntos em R?

221

Estou usando R e tenho dois quadros de dados: cenouras e pepinos. Cada quadro de dados possui uma única coluna numérica que lista o comprimento de todas as cenouras medidas (total: 100 mil cenouras) e pepinos (total: 50 mil pepinos).

Desejo plotar dois histogramas - comprimento da cenoura e pepino - no mesmo gráfico. Eles se sobrepõem, então acho que também preciso de um pouco de transparência. Também preciso usar frequências relativas e não números absolutos, pois o número de instâncias em cada grupo é diferente.

algo assim seria bom, mas não entendo como criá-lo nas minhas duas tabelas:

densidade sobreposta

David B
fonte
Btw, qual software você planeja usar? Para código aberto, eu recomendo o gnuplot.info [gnuplot]. Na documentação, acredito que você encontrará certas técnicas e exemplos de scripts para fazer o que deseja.
Noel aye
1
Estou usando R como a tag sugere (post editado para deixar isso claro)
David B
1
alguém postou algum trecho de código para fazê-lo neste segmento: stackoverflow.com/questions/3485456/…
nico

Respostas:

194

Essa imagem à qual você vinculou era para curvas de densidade, não para histogramas.

Se você leu no ggplot, talvez a única coisa que falta é combinar seus dois quadros de dados em um longo.

Então, vamos começar com algo como o que você tem, dois conjuntos separados de dados e combiná-los.

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)

Depois disso, que é desnecessário se seus dados já estiverem em formato longo, você precisará apenas de uma linha para fazer seu gráfico.

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

insira a descrição da imagem aqui

Agora, se você realmente queria histogramas, o seguinte funcionará. Observe que você deve alterar a posição do argumento "pilha" padrão. Você pode sentir falta disso se realmente não tiver uma ideia de como devem ser seus dados. Um alfa mais alto parece melhor lá. Observe também que eu fiz histogramas de densidade. É fácil remover o y = ..density..para recolocar as contagens.

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

insira a descrição da imagem aqui

John
fonte
8
Se você quiser ficar com histogramas, use ggplot(vegLengths, aes(length, fill = veg)) + geom_bar(pos="dodge"). Isso criará histogramas entrelaçados, como no MATLAB.
MBq
1
Thx pela resposta! A parte 'position = "identity"' é realmente importante, caso contrário, as barras são empilhadas, o que é enganoso quando combinadas com uma densidade que, por padrão, parece ser "identidade", ou seja, sobreposta em vez de empilhada.
Shadow
265

Aqui está uma solução ainda mais simples usando gráficos básicos e mistura alfa (que não funciona em todos os 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

A chave é que as cores são semi-transparentes.

Edite, mais de dois anos depois : como isso acabou de ser votado, acho que também posso adicionar um visual do que o código produz como mistura alfa é tão útil:

insira a descrição da imagem aqui

Dirk Eddelbuettel
fonte
6
+1 obrigado a todos, isso pode ser convertido em um histograma mais suave (como had.co.nz/ggplot2/graphics/55078149a733dd1a0b42a57faf847036.png )?
David B
3
Por que você separou os plotcomandos? Você pode colocar todas essas opções nos histcomandos e apenas duas nas duas linhas.
John John
@ John Como você faria isso?
HelloWorld
Coloque as opções no plotcomando diretamente no comando hist, como eu disse. A publicação do código não serve para comentários.
John
44

Aqui está uma função que escrevi que usa pseudo-transparência para representar histogramas sobrepostos

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

Aqui está outra maneira de fazer isso usando o suporte de R para cores 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) )

Os resultados acabam parecendo algo assim: texto alternativo

chrisamiller
fonte
+1 para uma opção disponível em todos os dispositivos gráficos (por exemplo postscript)
Lenna
31

Já existem respostas bonitas, mas pensei em adicionar isso. Parece bom para mim. (Números aleatórios copiados do @Dirk). library(scales)é necessário`

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)

O resultado é...

insira a descrição da imagem aqui

Atualização: Essa função sobreposta também pode ser útil para alguns.

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

Eu sinto que o resultado hist0é mais bonito do que parecerhist

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

O resultado de

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

é

insira a descrição da imagem aqui

Stat-R
fonte
24

Aqui está um exemplo de como você pode fazer isso nos gráficos R "clássicos":

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

O único problema com isso é que parece muito melhor se as quebras do histograma estiverem alinhadas, o que pode ter que ser feito manualmente (nos argumentos passados ​​para hist).

nullglob
fonte
Muito agradável. Ele também me fez lembrar de que um stackoverflow.com/questions/3485456/...
George Dontas
Aumente isso porque essa resposta é a única (além das que estão dentro ggplot) que responde diretamente se seus dois histogramas têm tamanhos de amostra substancialmente diferentes.
precisa saber é o seguinte
Eu gosto desse método, observe que você pode sincronizar quebras definindo-as com seq (). Por exemplo:breaks=seq(min(data$some_property), max(data$some_property), by=(max_prop - min_prop)/20)
Deruijter 02/09/16
17

Aqui está a versão como a do ggplot2 que eu dei apenas na base R. Copiei algumas do @nullglob.

gerar os dados

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

Você não precisa colocá-lo em um quadro de dados como no ggplot2. A desvantagem desse método é que você precisa escrever muito mais detalhes da plotagem. A vantagem é que você tem controle sobre mais detalhes da 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)

insira a descrição da imagem aqui

John
fonte
9

@Dirk Eddelbuettel: A ideia básica é excelente, mas o código mostrado pode ser aprimorado. [Demora muito para explicar, portanto, uma resposta separada e não um comentário.]

A hist()função, por padrão, desenha plotagens, então você precisa adicionar a plot=FALSEopção. Além disso, é mais claro estabelecer a área da plotagem por meio de uma plot(0,0,type="n",...)chamada na qual você pode adicionar os rótulos dos eixos, o título da plotagem etc. Por fim, gostaria de mencionar que também se pode usar sombreamento para distinguir os dois histogramas. Aqui está o 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)

E aqui está o resultado (um pouco grande demais por causa do RStudio :-)):

insira a descrição da imagem aqui

Laringe Decidua
fonte
aumentando isso porque é uma opção muito simples usando base e viável em postscriptdispositivos.
precisa saber é o seguinte
6

A API R da Plotly pode ser útil para você. O gráfico abaixo está aqui .

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)

Divulgação completa: estou no time.

Gráfico

Mateo Sanchez
fonte
1

Tantas ótimas respostas, mas como acabei de escrever uma função ( plotMultipleHistograms()) para fazer isso, pensei em adicionar outra resposta.

A vantagem dessa função é que ela define automaticamente os limites apropriados dos eixos X e Y e define um conjunto comum de posições que utiliza em todas as distribuições.

Veja como usá-lo:

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

insira a descrição da imagem aqui

A plotMultipleHistograms()função pode ter qualquer número de distribuições, e todos os parâmetros de traçagem gerais devem trabalhar com ele (por exemplo: las, main, etc.).

Joseph Crispell
fonte