ggplot2: How to force the number of facets with too few plots?

孤人 提交于 2019-12-01 04:26:27

One approach is to create a plot for each non-empty factor level and a blank placeholder for each empty factor level:

First, using the built-in mtcars data frame, we set up the faceting variable as a factor with 9 levels, but only 5 levels with any data:

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

d = mtcars
set.seed(4193)
d$cyl = sample(1:9, nrow(d), replace=TRUE)
d$cyl <- factor(d$cyl, levels=sort(unique(d$cyl)))
d <- subset(d, cyl %in% c(1,5,7:9))

# Identify factor levels without any data
blanks = which(table(d$cyl)==0)

# Initialize a list
pl = list()

The for loop below runs through each level of the faceting variable and creates a plot of the level has data or a nullGrob (that is, an empty placeholder where the plot would be if there were data for that factor level) and adds it to the list pl.

for (i in 1:length(levels(d$cyl))) {

  if(i %in% blanks) {

    pl[[i]] = nullGrob()

  } else {

    pl[[i]] = ggplot(d[d$cyl %in% levels(d$cyl)[i], ], aes(x=am, y=wt) ) +
      geom_point() +
      facet_grid(.~ cyl)

  }
}

Now, lay out the plots and add a border around them:

do.call(grid.arrange, c(pl, ncol=3))
grid.rect(.5, .5, gp=gpar(lwd=2, fill=NA, col="black"))

UPDATE: A feature I'd like to add to my answer is removing axis labels for plots that are not on the left-most column or the bottom row (to be more like format in the OP). Below is my not-quite-successful attempt.

The problem that comes up when you remove axis ticks and/or labels from some of the plots is that the plot areas end up being different sizes in different plots. The reason for this is that all the plots take up the same physical area, but the plots with axis labels use some of that area for the axis labels, making their plot areas smaller relative to plots without axis labels.

I had hoped that I could resolve this using plot_grid from the cowplot package (authored by @ClausWilke), but plot_grid doesn't work with nullGrobs. Then @baptiste added another answer to this question, which he's since deleted, but which is still visible to SO users with at least 10,000 in reputation. That answer made me aware of his egg package and the set_panel_size function, for setting a common panel size across different ggplots.

Below is my attempt to use set_panel_size to solve the plot-area problem. It wasn't quite successful, which I'll discuss in more detail after showing the code and the plot.

# devtools::install_github("baptiste/egg")
library(egg)

# Fake data for making a barplot. Once again we have 9 facet levels, 
# but with data for only 5 of the levels.
set.seed(4193)
d = data.frame(facet=rep(LETTERS[1:9],each=100), 
               group=sample(paste("Group",1:5),900,replace=TRUE))
d <- subset(d, facet %in% LETTERS[c(1,5,7:9)])

# Identify factor levels without any data
blanks = which(table(d$facet)==0)

# Initialize a list
pl = list()

for (i in 1:length(levels(d$facet))) {

  if(i %in% blanks) {

    pl[[i]] = nullGrob()

  } else {

    # Create the plot, including a common y-range across all plots
    # (though this becomes the x-range due to coord_flip)
    pl[[i]] = ggplot(d[d$facet %in% levels(d$facet)[i], ], aes(x=group) ) +
      geom_bar() +
      facet_grid(. ~ facet) +
      coord_flip() +
      labs(x="", y="") +
      scale_y_continuous(limits=c(0, max(table(d$group, d$facet)))) 

    # If the panel isn't on the left edge, remove y-axis labels
    if(!(i %in% seq(1,9,3))) {
      pl[[i]] = pl[[i]] + theme(axis.text.y=element_blank(),
                                axis.ticks.y=element_blank())
    }

    # If the panel isn't on the bottom, remove x-axis labels
    if(i %in% 1:6) {
      pl[[i]] = pl[[i]] + theme(axis.text.x=element_blank(),
                                axis.ticks.x=element_blank())
    }
  }

  # If the panel is a plot (rather than a nullGrob), 
  # remove margins and set to common panel size
  if(any(class(pl[[i]]) %in% c("ggplot","gtable"))) {
    pl[[i]] = pl[[i]] + theme(plot.margin=unit(rep(-1,4), "lines"))
    pl[[i]] = set_panel_size(pl[[i]], width=unit(4,"cm"), height=unit(3,"cm"))
  }

}

Now lay out the plots:

do.call(grid.arrange, c(pl, ncol=3))
grid.rect(.5, .5, gp=gpar(lwd=2, fill=NA, col="black"))

As you can see in the plot below, even though the plots all have the same panel sizes, the margins between them are not constant, presumably due to the way grid.arrange handles spacing for null grobs, depending on which positions have actual plots. Also, because set_panel_size sets absolute sizes, I had to size the final plot by hand to get the panels as close to together as possible while still avoiding overlaps. I'm hoping one of SO's resident grid experts will drop by and suggest a more effective approach.

(Also note that with this approach, you can end up without a labeled plot in a given row or column. In the example below, plot "E" has no y-axis labels and plot "D" is missing, so you have to look in a different row to see what the labels are. If only plots "B", "C","E" and "F" were present, there would not be any labeled plots in the layout. I don't know how the OP wants to deal with this situation (one option would be to add logic to keep labels on "interior" plots if the "outer" plot is absent for a given row or column), but I thought it was worth pointing out.)

try this

d <- subset(mtcars, cyl==4)
d$cyl <- factor(d$cyl, levels=unique(mtcars$cyl))
ggplot(d, aes(x=am, y=wt) ) +
  geom_point() +
  facet_grid(.~cyl, drop = FALSE)

a possible solution is to produce a list of ggplots, and replace some of them with dummy placeholders.

d <- data.frame(x=rnorm(90), y=rnorm(90), 
                f1 = gl(3, 30) , f2 = rep(gl(3, 10), 3) )

p <- ggplot(d, aes(x, y)) + 
  geom_point()

# if you want to keep the facet labels
# p <- p + facet_grid(f1~f2)

library(plyr)
pl <- dlply(d, .(f1, f2), "%+%", e1=p, .drop = FALSE)

.dummy_plot <- ggplot() + theme_void()
pl[c(3,4,7)] <- rep(list(.dummy_plot), 3, simplify=FALSE)


# devtools::install_github("baptiste/egg)
library(grid)
grid.newpage()
grid.draw(egg::ggarrange(plots=pl))

# alternatively
# library(cowplot)
# plot_grid(plotlist = pl)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!