6

My aim is a compounded plot which combines a scatterplot and 2 plots for density estimates. The problem I'm facing is that the density plots do not align correctly with the scatter plot due to the missing axes labeling of the density plots and the legend of the scatter plot. It could be adjusted by playing arround with plot.margin. However, this would not be a preferable solution since I would have to adjust it over and over again if changes to the plots are made. Is there a way to position all plots in a way so that the actual plotting panels align perfectly?

enter image description here

I tried to keep the code as minimal as possible but in order to reproduce the problem it is still quite a lot.

library(ggplot2)
library(gridExtra)

df <- data.frame(y     = c(rnorm(50, 1, 1), rnorm(50, -1, 1)), 
                 x     = c(rnorm(50, 1, 1), rnorm(50, -1, 1)), 
                 group = factor(c(rep(0, 50), rep(1,50))))


empty <- ggplot() + 
              geom_point(aes(1,1), colour="white") +
              theme(                              
                plot.background = element_blank(), 
                panel.grid.major = element_blank(), 
                panel.grid.minor = element_blank(), 
                panel.border = element_blank(), 
                panel.background = element_blank(),
                axis.title.x = element_blank(),
                axis.title.y = element_blank(),
                axis.text.x = element_blank(),
                axis.text.y = element_blank(),
                axis.ticks = element_blank()
              )


scatter <-  ggplot(df, aes(x = x, y = y, color = group)) + 
                geom_point() +
                theme(legend.position = "bottom")

top_plot <- ggplot(df, aes(x = y)) + 
                geom_density(alpha=.5, mapping = aes(fill = group)) + 
                theme(legend.position = "none") +
                theme(axis.title.y = element_blank(),
                      axis.title.x = element_blank(),
                      axis.text.y=element_blank(),
                      axis.text.x=element_blank(),
                      axis.ticks=element_blank() )

right_plot <- ggplot(df, aes(x = x)) + 
                geom_density(alpha=.5, mapping = aes(fill = group)) + 
                coord_flip() + theme(legend.position = "none") +
                theme(axis.title.y = element_blank(),
                      axis.title.x = element_blank(),
                      axis.text.y  = element_blank(),
                      axis.text.x=element_blank(),
                      axis.ticks=element_blank())

grid.arrange(top_plot, empty, scatter, right_plot, ncol=2, nrow=2, widths=c(4, 1), heights=c(1, 4))
Alex
  • 4,925
  • 2
  • 32
  • 48
  • FYI, you should `set.seed()` before examples where you sample in R so that output is reproducible – Chris Jul 28 '16 at 14:07
  • @Chris: The actual data are not important here. So I think it does not matter. – Alex Jul 28 '16 at 14:17
  • 1
    http://stackoverflow.com/questions/17370853/align-ggplot2-plots-vertically/17371177#17371177 – user20650 Jul 28 '16 at 15:23
  • @user20650: Thanks for the link. I am playing arround with it but could not get it to work yet. But I think that is the way to go. – Alex Jul 28 '16 at 15:27

5 Answers5

6

another option,

library(egg) 
ggarrange(top_plot, empty, scatter, right_plot, 
          ncol=2, nrow=2, widths=c(4, 1), heights=c(1, 4))

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
2

Here is a solution in base R. It uses the line2user function found in this question.

par(mar = c(5, 4, 6, 6))
with(df, plot(y ~ x, bty = "n", type = "n"))
with(df[df$group == 0, ], points(y ~ x, col = "dodgerblue2"))
with(df[df$group == 1, ], points(y ~ x, col = "darkorange"))

x0_den <- with(df[df$group == 0, ], 
               density(x, from = par()$usr[1], to = par()$usr[2]))
x1_den <- with(df[df$group == 1, ], 
               density(x, from = par()$usr[1], to = par()$usr[2]))
y0_den <- with(df[df$group == 0, ], 
               density(y, from = par()$usr[3], to = par()$usr[4]))
y1_den <- with(df[df$group == 1, ], 
               density(y, from = par()$usr[3], to = par()$usr[4]))

x_scale <- max(c(x0_den$y, x1_den$y))
y_scale <- max(c(y0_den$y, y1_den$y))

lines(x = x0_den$x, y = x0_den$y/x_scale*2 + line2user(1, 3), 
      col = "dodgerblue2", xpd = TRUE)
lines(x = x1_den$x, y = x1_den$y/x_scale*2 + line2user(1, 3), 
      col = "darkorange", xpd = TRUE)

lines(y = y0_den$x, x = y0_den$y/x_scale*2 + line2user(1, 4), 
      col = "dodgerblue2", xpd = TRUE)
lines(y = y1_den$x, x = y1_den$y/x_scale*2 + line2user(1, 4), 
      col = "darkorange", xpd = TRUE)

enter image description here

Community
  • 1
  • 1
dayne
  • 7,504
  • 6
  • 38
  • 56
2

Here's an option using a combination of plot_grid from the cowplot package and grid.arrange from the gridExtra package:

library(ggplot2)
library(gridExtra)
library(grid)
library(cowplot)

df <- data.frame(y     = c(rnorm(50, 1, 1), rnorm(50, -1, 1)), 
                 x     = c(rnorm(50, 1, 1), rnorm(50, -1, 1)), 
                 group = factor(c(rep(0, 50), rep(1,50))))

First, some set up: A function to extract the plot legend as a separate grob, plus a couple of reusable plot components:

# Function to extract legend
# https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs
g_legend<-function(a.gplot) {
  tmp <- ggplot_gtable(ggplot_build(a.gplot))
  leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
  legend <- tmp$grobs[[leg]]
  return(legend)
  }


# Set up reusable plot components
my_thm = list(theme_bw(),
              theme(legend.position = "none",
                    axis.title.y = element_blank(),
                    axis.title.x = element_blank(),
                    axis.text.y=element_blank(),
                    axis.text.x=element_blank(),
                    axis.ticks=element_blank()))

marg = theme(plot.margin=unit(rep(0,4),"lines"))

Create the plots:

## Empty plot
empty <- ggplot() + geom_blank() + marg

## Scatterplot
scatter <-  ggplot(df, aes(x = x, y = y, color = group)) + 
  geom_point() +
  theme_bw() + marg +
  guides(colour=guide_legend(ncol=2))

# Copy legend from scatterplot as a separate grob
leg = g_legend(scatter)

# Remove legend from scatterplot
scatter = scatter + theme(legend.position = "none")

## Top density plot
top_plot <- ggplot(df, aes(x = y)) + 
  geom_density(alpha=.5, mapping = aes(fill = group)) + 
  my_thm + marg

## Right density plot
right_plot <- ggplot(df, aes(x = x)) + 
  geom_density(alpha=.5, mapping = aes(fill = group)) + 
  coord_flip() + my_thm + marg

Now lay out the three plots plus the legend:

# Lay out the three plots
p1 = plot_grid(top_plot, empty, scatter, right_plot, align="hv",
               rel_widths=c(3,1), rel_heights=c(1,3))

# Combine plot layout and legend
grid.arrange(p1, leg, heights=c(10,1))

enter image description here

eipi10
  • 91,525
  • 24
  • 209
  • 285
1

Using the answer from Align ggplot2 plots vertically to align the plot by adding to the gtable (most likely over complicating this!!)

library(ggplot2)
library(gtable)
library(grid)

Your data and plots

set.seed(1)
df <- data.frame(y     = c(rnorm(50, 1, 1), rnorm(50, -1, 1)), 
                 x     = c(rnorm(50, 1, 1), rnorm(50, -1, 1)), 
                 group = factor(c(rep(0, 50), rep(1,50))))

scatter <-  ggplot(df, aes(x = x, y = y, color = group)) + 
                geom_point() +  theme(legend.position = "bottom")

top_plot <- ggplot(df, aes(x = y)) + 
                geom_density(alpha=.5, mapping = aes(fill = group))+
               theme(legend.position = "none") 

right_plot <- ggplot(df, aes(x = x)) + 
                geom_density(alpha=.5, mapping = aes(fill = group)) + 
                coord_flip() + theme(legend.position = "none") 

Use the idea from Bapistes answer

g <- ggplotGrob(scatter)

g <- gtable_add_cols(g, unit(0.2,"npc"))    
g <- gtable_add_grob(g, ggplotGrob(right_plot)$grobs[[4]], t = 2, l=ncol(g), b=3, r=ncol(g))

g <- gtable_add_rows(g, unit(0.2,"npc"), 0)
g <- gtable_add_grob(g, ggplotGrob(top_plot)$grobs[[4]], t = 1, l=4, b=1, r=4)

grid.newpage()
grid.draw(g)

Which produces

enter image description here

I used ggplotGrob(right_plot)$grobs[[4]] to select the panel grob manually, but of course you could automate this

There are also other alternatives: Scatterplot with marginal histograms in ggplot2

Community
  • 1
  • 1
user20650
  • 24,654
  • 5
  • 56
  • 91
  • I really like what is possible with `gtable`. Unfortunatly I do not understand the concept of it. Is there somewhere a good explaination of the way it works? I could't find anything helpful. – Alex Jul 28 '16 at 16:07
  • its poorly documented. I can use it a little (very little) just by playing about with the questions and answers on this site. Baptiste has written a bit on his wiki https://github.com/baptiste/gridextra/wiki/gtable – user20650 Jul 28 '16 at 16:13
  • In that case it could be a good candidate for the new Documentation feature. – Alex Jul 28 '16 at 16:19
  • @Alex; probably worth adding the check mark to Baptiste's answer, as it is more automated. – user20650 Jul 28 '16 at 21:00
  • 1
    I agree. The other solution looks good. However, I am right now quite happy with the `gtable` solution because I understood what's gooing on. – Alex Jul 28 '16 at 23:01
0

When you set your axis to element_blank(), it removes the axis and allows the graph to fill the rest of the space. Instead, set to color = "white" (or whatever your background is):

# All other code remains the same:

scatter <-  ggplot(df, aes(x = x, y = y, color = group)) + 
  geom_point() +
  theme(legend.position = "bottom")

top_plot <- ggplot(df, aes(x = y)) + 
  geom_density(alpha=.5, mapping = aes(fill = group)) + 
  theme(legend.position = "none")+
  theme(axis.title = element_text(color = "white"),
        axis.text=element_text(color = "white"),
        axis.ticks=element_line(color = "white") )

right_plot <- ggplot(df, aes(x = x)) + 
  geom_density(alpha=.5, mapping = aes(fill = group)) + 
  coord_flip() +
  theme(legend.position = "bottom") +
  theme(axis.title = element_text(color = "white"),
        axis.text  = element_text(color = "white"),
        axis.ticks=element_line(color = "white"))

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

enter image description here

I also had to add a legend to the right plot. If you do not want this, I would also suggest also moving the legend in the scatter to be inside the plot:

scatter <-  ggplot(df, aes(x = x, y = y, color = group)) + 
  geom_point() +
  theme(legend.position = c(0.05,0.1))

top_plot <- ggplot(df, aes(x = y)) + 
  geom_density(alpha=.5, mapping = aes(fill = group)) + 
  theme(legend.position = "none")+
  theme(axis.title = element_text(color = "white"),
        axis.text=element_text(color = "white"),
        axis.ticks=element_line(color = "white") )

right_plot <- ggplot(df, aes(x = x)) + 
  geom_density(alpha=.5, mapping = aes(fill = group)) + 
  coord_flip() +
  theme(legend.position = "none") +
  theme(axis.title = element_text(color = "white"),
        axis.text  = element_text(color = "white"),
        axis.ticks=element_line(color = "white"))

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

enter image description here

Chris
  • 6,302
  • 1
  • 27
  • 54
  • That is closer to what I want but I would like the legend to be where it is and if the scatter plot has a label at the y axis it still not aligns properly on the left side. – Alex Jul 28 '16 at 14:28