Come tracciare due istogrammi insieme in R?


221

Sto usando R e ho due frame di dati: carote e cetrioli. Ogni frame di dati ha una singola colonna numerica che elenca la lunghezza di tutte le carote misurate (totale: 100k carote) e cetrioli (totale: 50k cetrioli).

Vorrei tracciare due istogrammi - la lunghezza della carota e la lunghezza dei cetrioli - sulla stessa trama. Si sovrappongono, quindi immagino di aver bisogno anche di un po 'di trasparenza. Ho anche bisogno di usare frequenze relative non numeri assoluti poiché il numero di istanze in ciascun gruppo è diverso.

qualcosa del genere sarebbe carino ma non capisco come crearlo dalle mie due tabelle:

densità sovrapposta


A proposito, quale software hai intenzione di usare? Per open source, consiglierei gnuplot.info [gnuplot]. Nella sua documentazione, credo che troverai alcune tecniche e script di esempio per fare quello che vuoi.
Noel aye il

1
Sto usando R come suggerisce il tag (post modificato per chiarire)
David B

1
qualcuno ha pubblicato un frammento di codice per farlo in questa discussione: stackoverflow.com/questions/3485456/…
nico

Risposte:


194

L'immagine a cui ti sei collegato era per le curve di densità, non per gli istogrammi.

Se hai letto su ggplot, forse l'unica cosa che ti manca è combinare i tuoi due frame di dati in uno lungo.

Quindi, iniziamo con qualcosa di simile a quello che hai, due serie separate di dati e combinali.

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)

Dopodiché, il che non è necessario se i tuoi dati sono già in formato lungo, hai solo una riga per creare la trama.

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

inserisci qui la descrizione dell'immagine

Ora, se davvero volevi istogrammi, funzionerà quanto segue. Si noti che è necessario modificare la posizione dall'argomento predefinito "stack". Potresti perdere ciò se non hai davvero idea di come dovrebbero essere i tuoi dati. Un'alfa superiore sembra migliore lì. Si noti inoltre che ho creato istogrammi di densità. È facile rimuoverlo y = ..density..per ripristinarlo.

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

inserisci qui la descrizione dell'immagine


8
Se desideri rimanere con gli istogrammi, usa ggplot(vegLengths, aes(length, fill = veg)) + geom_bar(pos="dodge"). Questo renderà istogrammi intrecciati, come in MATLAB.
mbq,

1
Grazie per la risposta! La parte 'position = "identity"' è in realtà importante poiché altrimenti le barre sono impilate, il che è fuorviante se combinato con una densità che per impostazione predefinita sembra essere "identità", cioè sovrapposta anziché sovrapposta.
Shadow

265

Ecco una soluzione ancora più semplice che utilizza la grafica di base e la fusione alfa (che non funziona su tutti i dispositivi grafici):

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 chiave è che i colori sono semi-trasparenti.

Modifica, più di due anni dopo : dato che questo ha appena ottenuto un voto, immagino che potrei anche aggiungere una visione di ciò che il codice produce come alfa-blending è così dannatamente utile:

inserisci qui la descrizione dell'immagine


6
+1 grazie a tutti, questo può essere convertito in un gistogramma più uniforme (come had.co.nz/ggplot2/graphics/55078149a733dd1a0b42a57faf847036.png )?
David B,

3
Perché hai separato i plotcomandi? Puoi mettere tutte queste opzioni nei histcomandi e solo due nelle due righe.
John,

@ Giovanni Come lo faresti?
HelloWorld,

Inserisci le opzioni nel plotcomando direttamente nel comando hist come ho detto. Pubblicare il codice non è ciò a cui servono i commenti
Giovanni,

44

Ecco una funzione che ho scritto che utilizza la pseudo-trasparenza per rappresentare gli istogrammi sovrapposti

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

Ecco un altro modo per farlo usando il supporto di R per i colori trasparenti

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

I risultati finiscono per assomigliare a questo: testo alternativo


+1 per un'opzione disponibile su tutti i dispositivi grafici (ad es. postscript)
Lenna

31

Esistono già belle risposte, ma ho pensato di aggiungere questo. Mi sembra buono. (Copia numeri casuali da @Dirk). library(scales)è necessario »

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)

Il risultato è ...

inserisci qui la descrizione dell'immagine

Aggiornamento: questa funzione sovrapposta può anche essere utile per alcuni.

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

Sento che il risultato hist0è più bello da guardare dihist

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

Il risultato di

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

è

inserisci qui la descrizione dell'immagine


24

Ecco un esempio di come puoi farlo nella grafica R "classica":

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

L'unico problema con questo è che sembra molto meglio se le interruzioni dell'istogramma sono allineate, il che potrebbe dover essere fatto manualmente (negli argomenti passati a hist).


Molto bella. Mi ha anche ricordato di quella stackoverflow.com/questions/3485456/...
George Dontas

Aumentare questo perché questa risposta è l'unica (oltre a quelle in ggplot) che spiega direttamente se i tuoi due istogrammi hanno dimensioni del campione sostanzialmente diverse.
MichaelChirico,

Mi piace questo metodo, nota che puoi sincronizzare le pause definendole con seq (). Ad esempio:breaks=seq(min(data$some_property), max(data$some_property), by=(max_prop - min_prop)/20)
Deruijter,

17

Ecco la versione come quella di ggplot2 che ho dato solo nella base R. Ne ho copiate alcune da @nullglob.

generare i dati

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

Non è necessario inserirlo in un frame di dati come con ggplot2. Lo svantaggio di questo metodo è che devi scrivere molti più dettagli della trama. Il vantaggio è che hai il controllo su maggiori dettagli della 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)

inserisci qui la descrizione dell'immagine


9

@Dirk Eddelbuettel: l'idea di base è eccellente ma il codice mostrato può essere migliorato. [Ci vuole molto tempo per spiegare, quindi una risposta separata e non un commento.]

La hist()funzione di default disegna grafici, quindi è necessario aggiungere l' plot=FALSEopzione. Inoltre, è più chiaro stabilire l'area della trama mediante una plot(0,0,type="n",...)chiamata in cui è possibile aggiungere le etichette degli assi, il titolo della trama ecc. Infine, vorrei ricordare che si potrebbe anche usare l'ombreggiatura per distinguere tra i due istogrammi. Ecco il codice:

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)

Ed ecco il risultato (un po 'troppo largo a causa di RStudio :-)):

inserisci qui la descrizione dell'immagine


risolvendolo perché è un'opzione molto semplice che utilizza dispositivi di base e praticabili postscript.
MichaelChirico,

6

L'API R di Plotly potrebbe essere utile per te. Il grafico qui sotto è qui .

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)

Divulgazione completa: sono nella squadra.

Grafico


1

Tante risposte fantastiche ma dato che ho appena scritto una funzione ( plotMultipleHistograms()) per fare questo, ho pensato di aggiungere un'altra risposta.

Il vantaggio di questa funzione è che imposta automaticamente i limiti appropriati degli assi X e Y e definisce un insieme comune di bin che utilizza in tutte le distribuzioni.

Ecco come 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")

inserisci qui la descrizione dell'immagine

La plotMultipleHistograms()funzione può prendere qualsiasi numero di distribuzioni, e tutti i parametri di tracciato generali dovrebbe funzionare con esso (per esempio: las, mainecc).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.