Nuage de points avec histogrammes marginaux dans ggplot2

137

Existe-t-il un moyen de créer des nuages ​​de points avec des histogrammes marginaux, comme dans l'exemple ci-dessous ggplot2? Dans Matlab, c'est la scatterhist()fonction et il existe également des équivalents pour R. Cependant, je ne l'ai pas vu pour ggplot2.

nuage de points avec histogrammes marginaux

J'ai commencé une tentative en créant les graphiques uniques mais je ne sais pas comment les organiser correctement.

 require(ggplot2)
 x<-rnorm(300)
 y<-rt(300,df=2)
 xy<-data.frame(x,y)
     xhist <- qplot(x, geom="histogram") + scale_x_continuous(limits=c(min(x),max(x))) + opts(axis.text.x = theme_blank(), axis.title.x=theme_blank(), axis.ticks = theme_blank(), aspect.ratio = 5/16, axis.text.y = theme_blank(), axis.title.y=theme_blank(), background.colour="white")
     yhist <- qplot(y, geom="histogram") + coord_flip() + opts(background.fill = "white", background.color ="black")

     yhist <- yhist + scale_x_continuous(limits=c(min(x),max(x))) + opts(axis.text.x = theme_blank(), axis.title.x=theme_blank(), axis.ticks = theme_blank(), aspect.ratio = 16/5, axis.text.y = theme_blank(), axis.title.y=theme_blank() )


     scatter <- qplot(x,y, data=xy)  + scale_x_continuous(limits=c(min(x),max(x))) + scale_y_continuous(limits=c(min(y),max(y)))
none <- qplot(x,y, data=xy) + geom_blank()

et les organiser avec la fonction affichée ici . Mais pour faire court: existe-t-il un moyen de créer ces graphiques?

Seb
la source
@DWin tout à fait merci - mais je pense que c'est à peu près la solution que j'ai donnée dans ma question. cependant, j'aime le geom_rag () pense beaucoup que vous avez donné ci-dessous!
Seb
1
à partir d'un article de blog récent qui présente le même sujet: blog.mckuhn.de/2009/09/learning-ggplot2-2d-plot-with.html est également très joli :)
Seb
Le nouveau site Web pour la Galerie Graphics est: gallery.r-enthusiasts.com
IRTFM
@Seb, vous pourriez envisager de remplacer la "réponse acceptée" par celle concernant le paquet ggExtra si vous pensez que cela a du sens
DeanAttali

Réponses:

93

Le gridExtrapackage devrait fonctionner ici. Commencez par créer chacun des objets ggplot:

hist_top <- ggplot()+geom_histogram(aes(rnorm(100)))
empty <- ggplot()+geom_point(aes(1,1), colour="white")+
         theme(axis.ticks=element_blank(), 
               panel.background=element_blank(), 
               axis.text.x=element_blank(), axis.text.y=element_blank(),           
               axis.title.x=element_blank(), axis.title.y=element_blank())

scatter <- ggplot()+geom_point(aes(rnorm(100), rnorm(100)))
hist_right <- ggplot()+geom_histogram(aes(rnorm(100)))+coord_flip()

Ensuite, utilisez la fonction grid.arrange:

grid.arrange(hist_top, empty, scatter, hist_right, ncol=2, nrow=2, widths=c(4, 1), heights=c(1, 4))

terrain

oeo4b
la source
6
1+ pour démontrer le placement, mais vous ne devriez pas refaire l'échantillonnage aléatoire si vous voulez que la dispersion intérieure "s'aligne" avec les histogrammes marginaux.
IRTFM
1
Vous avez raison. Cependant, ils sont échantillonnés à partir de la même distribution, de sorte que les histogrammes marginaux doivent théoriquement correspondre au nuage de points.
oeo4b
8
En «théorie», ils seront asymptotiquement «concordants»; en pratique, le nombre de fois qu'ils correspondent est infiniment petit. Il est très facile d'utiliser l'exemple fourni xy <- data.frame(x=rnorm(300), y=rt(300,df=2) )et de l'utiliser data=xydans les appels ggplot.
IRTFM
7
Je ne recommanderais pas cette solution car les axes des tracés ne s'alignent généralement pas exactement. Espérons que les futures versions de ggplot2 faciliteront l'alignement des axes, ou permettront même des annotations personnalisées sur les côtés d'un panneau de tracé (comme les fonctions d'axes secondaires personnalisées dans le treillis).
baptiste
9
Non, ils ne le feraient pas, en général. ggplot2 affiche actuellement une largeur de panneau variable qui change en fonction de l'étendue des étiquettes des axes, etc. Jetez un œil à ggExtra :: align.plots pour voir le type de hack actuellement requis pour aligner les axes.
baptiste
115

Ce n'est pas une réponse complètement réactive mais c'est très simple. Il illustre une autre méthode pour afficher les densités marginales et comment utiliser les niveaux alpha pour une sortie graphique prenant en charge la transparence:

scatter <- qplot(x,y, data=xy)  + 
         scale_x_continuous(limits=c(min(x),max(x))) + 
         scale_y_continuous(limits=c(min(y),max(y))) + 
         geom_rug(col=rgb(.5,0,0,alpha=.2))
scatter

entrez la description de l'image ici

IRTFM
la source
5
C'est une façon intéressante de montrer la densité. Merci d'avoir ajouté cette réponse. :)
Michelle
21
Il est à noter que cette méthode est beaucoup plus courante que de mettre des histogrammes marginaux. En fait, avoir des parcelles de tapis est courant dans les articles publiés où je n'ai jamais vu un article publié avec des historgrammes marginaux.
Xu Wang
Réponse alternative très intéressante et intuitive! Et très simple! Pas étonnant qu'il obtienne encore plus de voix que la bonne réponse. Je crois comprendre qu'il s'agit essentiellement d'une carte thermique unidimensionnelle : les tapis sont plus sombres partout où il y a du monde. Mon seul souci serait que la résolution de la carte thermique n'est pas aussi élevée qu'un histogramme. par exemple. lorsque l'intrigue est petite, tous les tapis seront pressés ensemble, ce qui rend difficile la perception de la distribution. Alors que l'histogramme ne souffre pas de la limitation. Merci pour l'idée!
HongboZhu
94

C'est peut-être un peu tard, mais j'ai décidé de créer un package ( ggExtra) pour cela car il impliquait un peu de code et pouvait être fastidieux à écrire. Le paquet essaie également de résoudre certains problèmes courants, comme s'assurer que même s'il y a un titre ou que le texte est agrandi, les tracés seront toujours alignés les uns avec les autres.

L'idée de base est similaire à ce que les réponses ont donné ici, mais cela va un peu plus loin. Voici un exemple de la façon d'ajouter des histogrammes marginaux à un ensemble aléatoire de 1000 points. J'espère que cela facilitera l'ajout d'histogrammes / tracés de densité à l'avenir.

Lien vers le package ggExtra

library(ggplot2)
df <- data.frame(x = rnorm(1000, 50, 10), y = rnorm(1000, 50, 10))
p <- ggplot(df, aes(x, y)) + geom_point() + theme_classic()
ggExtra::ggMarginal(p, type = "histogram")

entrez la description de l'image ici

DeanAttali
la source
1
Merci beaucoup pour le paquet. Cela fonctionne hors de la boîte!
heroxbd
Est-il possible de dessiner des tracés de densité marginale pour des objets regroupés par couleur avec ce package?
GegznaV
Non, il n'a pas ce genre de logique
DeanAttali
1
@jjrr Je ne suis pas sûr de ce qui ne fonctionne pas et des problèmes que vous rencontrez, mais il y a eu un problème récent sur github concernant le rendu dans un cahier et il y a aussi une solution, cela pourrait être utile github.com/daattali/ ggExtra / issues / 89
DeanAttali
1
@GegznaV, si vous cherchez toujours un moyen de regrouper les tracés de densité marginale par couleur, c'est possible avec ggExtra 0.9: ggMarginal (p, type = "densité", size = 5, groupColour = TRUE)
MartineJ
46

Un ajout, juste pour gagner du temps de recherche pour les personnes qui font cela après nous.

Les légendes, les étiquettes d'axes, les textes des axes, les graduations font que les tracés s'éloignent les uns des autres, de sorte que votre intrigue aura l'air moche et incohérente.

Vous pouvez corriger cela en utilisant certains de ces paramètres de thème,

+theme(legend.position = "none",          
       axis.title.x = element_blank(),
       axis.title.y = element_blank(),
       axis.text.x = element_blank(),
       axis.text.y = element_blank(), 
       plot.margin = unit(c(3,-5.5,4,3), "mm"))

et aligner les échelles,

+scale_x_continuous(breaks = 0:6,
                    limits = c(0,6),
                    expand = c(.05,.05))

donc les résultats sembleront OK:

un exemple

Lorinc Nyitrai
la source
3
voir ceci pour une solution plus fiable pour aligner les panneaux de parcelle
baptiste
Oui. Ma réponse est dépassée, utilisez la solution @baptiste proposée.
Lorinc Nyitrai
@LorincNyitrai Pouvez-vous s'il vous plaît partager votre code pour générer ce graphique. J'ai aussi une condition où je veux faire un nuage de points de précision-rappel dans ggplot2 avec une distribution marginale pour 2 groupes mais je suis incapable de faire une distribution marginale pour 2 groupes. Merci
Débutant
@Newbie, cette réponse date de 3 ans, aussi dépassée que possible. Utilisez rdocumentation.org/packages/gtable/versions/0.2.0/topics/gtable ou quelque chose de similaire.
Lorinc Nyitrai
29

Juste une très petite variation de la réponse de BondedDust , dans l'esprit général des indicateurs marginaux de distribution.

Edward Tufte a appelé cette utilisation des tracés de tapis un «tracé point-tiret», et a un exemple dans VDQI d'utilisation des lignes d'axe pour indiquer la plage de chaque variable. Dans mon exemple, les étiquettes des axes et les lignes de la grille indiquent également la distribution des données. Les étiquettes sont situées aux valeurs du résumé à cinq chiffres de Tukey (minimum, charnière inférieure, médiane, charnière supérieure, maximum), donnant une impression rapide de la répartition de chaque variable.

Ces cinq nombres sont donc une représentation numérique d'un boxplot. C'est un peu délicat car les lignes de grille espacées de manière inégale suggèrent que les axes ont une échelle non linéaire (dans cet exemple, ils sont linéaires). Il serait peut-être préférable d'omettre les lignes de la grille ou de les forcer à se trouver à des emplacements réguliers, et de laisser simplement les étiquettes afficher le résumé à cinq chiffres.

x<-rnorm(300)
y<-rt(300,df=10)
xy<-data.frame(x,y)

require(ggplot2); require(grid)
# make the basic plot object
ggplot(xy, aes(x, y)) +        
  # set the locations of the x-axis labels as Tukey's five numbers   
  scale_x_continuous(limit=c(min(x), max(x)), 
                     breaks=round(fivenum(x),1)) +     
  # ditto for y-axis labels 
  scale_y_continuous(limit=c(min(y), max(y)),
                     breaks=round(fivenum(y),1)) +     
  # specify points
  geom_point() +
  # specify that we want the rug plot
  geom_rug(size=0.1) +   
  # improve the data/ink ratio
  theme_set(theme_minimal(base_size = 18))

entrez la description de l'image ici

Ben
la source
12

Comme il n'y avait pas de solution satisfaisante pour ce type d'intrigue lors de la comparaison de différents groupes, j'ai écrit une fonction pour le faire.

Il fonctionne pour les données groupées et non groupées et accepte des paramètres graphiques supplémentaires:

marginal_plot(x = iris$Sepal.Width, y = iris$Sepal.Length)

entrez la description de l'image ici

marginal_plot(x = Sepal.Width, y = Sepal.Length, group = Species, data = iris, bw = "nrd", lm_formula = NULL, xlab = "Sepal width", ylab = "Sepal length", pch = 15, cex = 0.5)

entrez la description de l'image ici

Hav0k
la source
9

J'ai trouvé le package ( ggpubr) qui semble très bien fonctionner pour ce problème et il considère plusieurs possibilités pour afficher les données.

Le lien vers le package est ici , et dans ce lien, vous trouverez un joli tutoriel pour l'utiliser. Par souci d'exhaustivité, je joins l'un des exemples que j'ai reproduits.

J'ai d'abord installé le package (il nécessite devtools)

if(!require(devtools)) install.packages("devtools")
devtools::install_github("kassambara/ggpubr")

Pour l'exemple particulier d'affichage de différents histogrammes pour différents groupes, il mentionne en relation avec ggExtra: "Une limitation de ggExtraest qu'il ne peut pas faire face à plusieurs groupes dans le nuage de points et les graphiques marginaux. Dans le code R ci-dessous, nous fournissons un solution en utilisant le cowplotpackage. " Dans mon cas, j'ai dû installer ce dernier package:

install.packages("cowplot")

Et j'ai suivi ce morceau de code:

# Scatter plot colored by groups ("Species")
sp <- ggscatter(iris, x = "Sepal.Length", y = "Sepal.Width",
            color = "Species", palette = "jco",
            size = 3, alpha = 0.6)+
border()                                         
# Marginal density plot of x (top panel) and y (right panel)
xplot <- ggdensity(iris, "Sepal.Length", fill = "Species",
               palette = "jco")
yplot <- ggdensity(iris, "Sepal.Width", fill = "Species", 
               palette = "jco")+
rotate()
# Cleaning the plots
sp <- sp + rremove("legend")
yplot <- yplot + clean_theme() + rremove("legend") 
xplot <- xplot + clean_theme() + rremove("legend")
# Arranging the plot using cowplot
library(cowplot)
plot_grid(xplot, NULL, sp, yplot, ncol = 2, align = "hv", 
      rel_widths = c(2, 1), rel_heights = c(1, 2))

Ce qui a bien fonctionné pour moi:

Iris set nuage de points histogrammes marginaux

entrez la description de l'image ici

Alf Pascu
la source
Que devez-vous faire pour faire de l'intrigue au milieu un carré?
JAQuent
La forme des points que vous voulez dire? Essayez d'ajouter l'argument shape = 19dans ggscatter. Codes pour les formes ici
Alf Pascu
7

Vous pouvez facilement créer des nuages ​​de points attrayants avec des histogrammes marginaux à l'aide de ggstatsplot (il ajustera et décrira également un modèle):

data(iris)

library(ggstatsplot)

ggscatterstats(
  data = iris,                                          
  x = Sepal.Length,                                                  
  y = Sepal.Width,
  xlab = "Sepal Length",
  ylab = "Sepal Width",
  marginal = TRUE,
  marginal.type = "histogram",
  centrality.para = "mean",
  margins = "both",
  title = "Relationship between Sepal Length and Sepal Width",
  messages = FALSE
)

entrez la description de l'image ici

Ou un peu plus attrayant (par défaut) ggpubr :

devtools::install_github("kassambara/ggpubr")
library(ggpubr)

ggscatterhist(
  iris, x = "Sepal.Length", y = "Sepal.Width",
  color = "Species", # comment out this and last line to remove the split by species
  margin.plot = "histogram", # I'd suggest removing this line to get density plots
  margin.params = list(fill = "Species", color = "black", size = 0.2)
)

entrez la description de l'image ici

METTRE À JOUR:

Comme suggéré par @aickley, j'ai utilisé la version de développement pour créer l'intrigue.

epo3
la source
1
L'histogramme sur l'axe des y est incorrect car il s'agit simplement d'une copie de celui sur l'axe des x. Cela n'a été corrigé que récemment github.com/kassambara/ggpubr/issues/85 .
aickley
7

C'est une vieille question, mais j'ai pensé qu'il serait utile de poster une mise à jour ici car j'ai rencontré ce même problème récemment (merci à Stefanie Mueller pour l'aide!).

La réponse la plus positive utilisant gridExtra fonctionne, mais l'alignement des axes est difficile / piraté, comme cela a été souligné dans les commentaires. Cela peut maintenant être résolu en utilisant la commande ggMarginal du package ggExtra, comme tel:

#load packages
library(tidyverse) #for creating dummy dataset only
library(ggExtra)

#create dummy data
a = round(rnorm(1000,mean=10,sd=6),digits=0)
b = runif(1000,min=1.0,max=1.6)*a
b = b+runif(1000,min=9,max=15)

DummyData <- data.frame(var1 = b, var2 = a) %>% 
  filter(var1 > 0 & var2 > 0)

#plot
p = ggplot(DummyData, aes(var1, var2)) + geom_point(alpha=0.3)
ggMarginal(p, type = "histogram")

entrez la description de l'image ici

Victoria Auyeung
la source
Je viens de réaliser que cela a été publié par le développeur du package ggExtra original dans une autre réponse. Je recommanderais d'en faire la réponse acceptée à la place, pour la raison que j'ai expliquée ci-dessus!
Victoria Auyeung
6

J'ai essayé ces options, mais je n'ai pas été satisfait des résultats ou du code désordonné qu'il faudrait utiliser pour y arriver. Heureusement pour moi, Thomas Lin Pedersen vient de développer un package appelé patchwork , qui fait le travail de manière assez élégante.

Si vous souhaitez créer un nuage de points avec des histogrammes marginaux, vous devez d'abord créer ces trois graphiques séparément.

library(ggplot2)

x <- rnorm(300)
y <- rt(300, df = 2)
xy <- data.frame(x, y)

plot1 <- ggplot(xy, aes(x = x, y = y)) + 
  geom_point() 

dens1 <- ggplot(xy, aes(x = x)) + 
  geom_histogram(color = "black", fill = "white") + 
  theme_void()

dens2 <- ggplot(xy, aes(x = y)) + 
  geom_histogram(color = "black", fill = "white") + 
  theme_void() + 
  coord_flip()

La seule chose qui reste à faire est d'ajouter ces parcelles avec un simple +et de spécifier la mise en page avec la fonction plot_layout().

library(patchwork)

dens1 + plot_spacer() + plot1 + dens2 + 
  plot_layout(
    ncol = 2, 
    nrow = 2, 
    widths = c(4, 1),
    heights = c(1, 4)
  ) 

La fonction plot_spacer()ajoute un tracé vide dans le coin supérieur droit. Tous les autres arguments doivent être explicites.

entrez la description de l'image ici

Étant donné que les histogrammes dépendent fortement de la largeur de zone choisie, on pourrait dire qu'il faut préférer les graphiques de densité. Avec quelques petites modifications, on obtiendrait par exemple pour les données de suivi oculaire un beau tracé.

library(ggpubr)

plot1 <- ggplot(df, aes(x = Density, y = Face_sum, color = Group)) + 
  geom_point(aes(color = Group), size = 3) + 
  geom_point(shape = 1, color = "black", size = 3) + 
  stat_smooth(method = "lm", fullrange = TRUE) +
  geom_rug() + 
  scale_y_continuous(name = "Number of fixated faces", 
                     limits = c(0, 205), expand = c(0, 0)) + 
  scale_x_continuous(name = "Population density (lg10)", 
                     limits = c(1, 4), expand = c(0, 0)) + 
  theme_pubr() +
  theme(legend.position = c(0.15, 0.9)) 

dens1 <- ggplot(df, aes(x = Density, fill = Group)) + 
  geom_density(alpha = 0.4) + 
  theme_void() + 
  theme(legend.position = "none")

dens2 <- ggplot(df, aes(x = Face_sum, fill = Group)) + 
  geom_density(alpha = 0.4) + 
  theme_void() + 
  theme(legend.position = "none") + 
  coord_flip()

dens1 + plot_spacer() + plot1 + dens2 + 
  plot_layout(ncol = 2, nrow = 2, widths = c(4, 1), heights = c(1, 4))

entrez la description de l'image ici

Bien que les données ne soient pas fournies à ce stade, les principes sous-jacents doivent être clairs.

j3ypi
la source
4

Pour construire sur la réponse de @ alf-pascu, configurer chaque parcelle manuellement et les organiser avec cowplotoffre une grande flexibilité en ce qui concerne à la fois les parcelles principales et marginales (par rapport à certaines des autres solutions). Les distributions par groupes en sont un exemple. Changer le tracé principal en un tracé de densité 2D en est une autre.

Ce qui suit crée un nuage de points avec des histogrammes marginaux (correctement alignés).

library("ggplot2")
library("cowplot")

# Set up scatterplot
scatterplot <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
  geom_point(size = 3, alpha = 0.6) +
  guides(color = FALSE) +
  theme(plot.margin = margin())


# Define marginal histogram
marginal_distribution <- function(x, var, group) {
  ggplot(x, aes_string(x = var, fill = group)) +
    geom_histogram(bins = 30, alpha = 0.4, position = "identity") +
    # geom_density(alpha = 0.4, size = 0.1) +
    guides(fill = FALSE) +
    theme_void() +
    theme(plot.margin = margin())
}

# Set up marginal histograms
x_hist <- marginal_distribution(iris, "Sepal.Length", "Species")
y_hist <- marginal_distribution(iris, "Sepal.Width", "Species") +
  coord_flip()

# Align histograms with scatterplot
aligned_x_hist <- align_plots(x_hist, scatterplot, align = "v")[[1]]
aligned_y_hist <- align_plots(y_hist, scatterplot, align = "h")[[1]]

# Arrange plots
plot_grid(
  aligned_x_hist
  , NULL
  , scatterplot
  , aligned_y_hist
  , ncol = 2
  , nrow = 2
  , rel_heights = c(0.2, 1)
  , rel_widths = c(1, 0.2)
)

nuage de points avec histogrammes marginaux

Pour tracer un tracé de densité 2D à la place, changez simplement le tracé principal.

# Set up 2D-density plot
contour_plot <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
  stat_density_2d(aes(alpha = ..piece..)) +
  guides(color = FALSE, alpha = FALSE) +
  theme(plot.margin = margin())

# Arrange plots
plot_grid(
  aligned_x_hist
  , NULL
  , contour_plot
  , aligned_y_hist
  , ncol = 2
  , nrow = 2
  , rel_heights = c(0.2, 1)
  , rel_widths = c(1, 0.2)
)

entrez la description de l'image ici

crsh
la source
3

Une autre solution utilisant ggpubret cowplot, mais ici nous créons des tracés en utilisant cowplot::axis_canvaset les ajoutons au tracé d'origine avec cowplot::insert_xaxis_grob:

library(cowplot) 
library(ggpubr)

# Create main plot
plot_main <- ggplot(faithful, aes(eruptions, waiting)) +
  geom_point()

# Create marginal plots
# Use geom_density/histogram for whatever you plotted on x/y axis 
plot_x <- axis_canvas(plot_main, axis = "x") +
  geom_density(aes(eruptions), faithful)
plot_y <- axis_canvas(plot_main, axis = "y", coord_flip = TRUE) +
  geom_density(aes(waiting), faithful) +
  coord_flip()

# Combine all plots into one
plot_final <- insert_xaxis_grob(plot_main, plot_x, position = "top")
plot_final <- insert_yaxis_grob(plot_final, plot_y, position = "right")
ggdraw(plot_final)

entrez la description de l'image ici

PoGibas
la source
2

De nos jours, il existe au moins un package CRAN qui crée le nuage de points avec ses histogrammes marginaux.

library(psych)
scatterHist(rnorm(1000), runif(1000))

Exemple de graphique de scatterHist

Père
la source
0

Vous pouvez utiliser la forme interactive de ggExtra::ggMarginalGadget(yourplot) et choisir entre des boîtes à moustaches, des graphiques de violon, des graphiques de densité et des histogrammes avec facilité.

comme ça

allan
la source