Comment tracer deux histogrammes ensemble dans R?


221

J'utilise R et j'ai deux trames de données: les carottes et les concombres. Chaque bloc de données comporte une seule colonne numérique qui répertorie la longueur de toutes les carottes mesurées (total: 100 000 carottes) et concombres (total: 50 000 concombres).

Je souhaite tracer deux histogrammes - longueur des carottes et longueurs des concombres - sur la même parcelle. Ils se chevauchent, donc je suppose que j'ai aussi besoin de transparence. J'ai également besoin d'utiliser des fréquences relatives et non des nombres absolus car le nombre d'instances dans chaque groupe est différent.

quelque chose comme ça serait bien mais je ne comprends pas comment le créer à partir de mes deux tables:

densité de chevauchement


Btw, quel logiciel prévoyez-vous d'utiliser? Pour l'open source, je recommanderais gnuplot.info [gnuplot]. Dans sa documentation, je pense que vous trouverez certaines techniques et des exemples de scripts pour faire ce que vous voulez.
noel aye

1
J'utilise R comme le suggère le tag (article édité pour que cela soit clair)
David B

1
quelqu'un a posté un extrait de code pour le faire dans ce fil: stackoverflow.com/questions/3485456/…
nico

Réponses:


194

Cette image à laquelle vous avez lié était pour les courbes de densité, pas les histogrammes.

Si vous avez lu sur ggplot, peut-être que la seule chose qui vous manque est de combiner vos deux trames de données en une longue.

Commençons donc par quelque chose comme ce que vous avez, deux ensembles de données distincts et combinons-les.

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)

Après cela, ce qui n'est pas nécessaire si vos données sont déjà au format long, vous n'avez besoin que d'une seule ligne pour créer votre tracé.

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

entrez la description de l'image ici

Maintenant, si vous vouliez vraiment des histogrammes, ce qui suit fonctionnera. Notez que vous devez changer de position à partir de l'argument "pile" par défaut. Vous pourriez le manquer si vous n'avez pas vraiment une idée de ce à quoi vos données devraient ressembler. Un alpha plus élevé semble mieux là-bas. Notez également que je lui ai fait des histogrammes de densité. Il est facile de supprimer le y = ..density..pour le ramener au décompte.

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

entrez la description de l'image ici


8
Si vous souhaitez conserver les histogrammes, utilisez ggplot(vegLengths, aes(length, fill = veg)) + geom_bar(pos="dodge"). Cela fera des histogrammes entrelacés, comme dans MATLAB.
mbq

1
Merci pour la réponse! La partie 'position = "identité"' est en fait importante car sinon les barres sont empilées, ce qui est trompeur lorsqu'elles sont combinées avec une densité qui par défaut semble être "identité", c'est-à-dire superposée par opposition à empilée.
Shadow

265

Voici une solution encore plus simple utilisant des graphiques de base et un mélange alpha (qui ne fonctionne pas sur tous les périphériques graphiques):

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 clé est que les couleurs sont semi-transparentes.

Edit, plus de deux ans plus tard : comme cela vient d'obtenir une augmentation, je pense que je pourrais aussi bien ajouter un visuel de ce que le code produit car l'alpha-blending est tellement utile:

entrez la description de l'image ici


6
+1 merci à tous, cela peut-il être converti en un gistogramme plus fluide (comme had.co.nz/ggplot2/graphics/55078149a733dd1a0b42a57faf847036.png )?
David B

3
Pourquoi avez-vous séparé les plotcommandes? Vous pouvez mettre toutes ces options dans les histcommandes et seulement deux dans les deux lignes.
John

@John Comment feriez-vous?
HelloWorld

Mettez les options de la plotcommande directement dans la commande hist comme je l'ai dit. Publier le code n'est pas à quoi servent les commentaires.
John

44

Voici une fonction que j'ai écrite qui utilise la pseudo-transparence pour représenter les histogrammes qui se chevauchent

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

Voici une autre façon de le faire en utilisant le support de R pour les couleurs 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) )

Les résultats finissent par ressembler à ceci: texte alternatif


+1 pour une option disponible sur tous les périphériques graphiques (par exemple postscript)
Lenna

31

Déjà de belles réponses sont là, mais j'ai pensé à les ajouter. Cela me semble correct. (Copie des nombres aléatoires de @Dirk). library(scales)est nécessaire`

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)

Le résultat est...

entrez la description de l'image ici

Mise à jour: Cette fonction de chevauchement peut également être utile à certains.

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

Je pense que le résultat hist0est plus joli à regarder 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) }

Le résultat de

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

est

entrez la description de l'image ici


24

Voici un exemple de la façon dont vous pouvez le faire dans les graphiques R "classiques":

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

Le seul problème avec cela est qu'il semble beaucoup mieux si les sauts d'histogramme sont alignés, ce qui peut devoir être fait manuellement (dans les arguments passés à hist).


Très agréable. Cela m'a également rappelé celui stackoverflow.com/questions/3485456/…
George Dontas

Augmenter cela parce que cette réponse est la seule (à part celles de ggplot) qui explique directement si vos deux histogrammes ont des tailles d'échantillon sensiblement différentes.
MichaelChirico

J'aime cette méthode, notez que vous pouvez synchroniser les pauses en les définissant avec seq (). Par exemple:breaks=seq(min(data$some_property), max(data$some_property), by=(max_prop - min_prop)/20)
Deruijter

17

Voici la version comme celle de ggplot2 que j'ai donnée uniquement en base R. J'en ai copié de @nullglob.

générer les données

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

Vous n'avez pas besoin de le mettre dans un bloc de données comme avec ggplot2. L'inconvénient de cette méthode est que vous devez écrire beaucoup plus de détails sur l'intrigue. L'avantage est que vous avez le contrôle sur plus de détails de l'intrigue.

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

entrez la description de l'image ici


9

@Dirk Eddelbuettel: L'idée de base est excellente mais le code tel qu'illustré peut être amélioré. [Prend beaucoup de temps à expliquer, d'où une réponse séparée et non un commentaire.]

La hist()fonction dessine par défaut des tracés, vous devez donc ajouter l' plot=FALSEoption. De plus, il est plus clair d'établir la zone de tracé par un plot(0,0,type="n",...)appel dans lequel vous pouvez ajouter les étiquettes d'axe, le titre du tracé, etc. Enfin, je voudrais mentionner que l'on pourrait également utiliser un ombrage pour distinguer les deux histogrammes. Voici le code:

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)

Et voici le résultat (un peu trop large à cause de RStudio :-)):

entrez la description de l'image ici


augmentant cela parce que c'est une option très simple utilisant la base et viable sur les postscriptappareils.
MichaelChirico

6

L'API R de Plotly pourrait vous être utile. Le graphique ci-dessous est ici .

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)

Divulgation complète: je fais partie de l'équipe.

Graphique


1

Tant de bonnes réponses, mais comme je viens d'écrire une fonction function ( plotMultipleHistograms()) pour ce faire, j'ai pensé ajouter une autre réponse.

L'avantage de cette fonction est qu'elle définit automatiquement les limites d'axe X et Y appropriées et définit un ensemble commun de casiers qu'elle utilise dans toutes les distributions.

Voici comment l'utiliser:

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

entrez la description de l'image ici

La plotMultipleHistograms()fonction peut prendre un certain nombre de distributions et tous les paramètres généraux complotant doit travailler avec elle (par exemple: las, main, etc.).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.