Finagling the space and width arguments to barplot to align 2x1 plot window

You can actually pass widths in ggplot as vectors as well. You'll need the dev version of ggplot2 to get the correct dodging though:

library(dplyr)
library(ggplot2)

df1 <- data.frame(n = 1:5, y = -2:2)
df1$x <- cumsum(df1$n)
df2 <- data.frame(n = rep(1:5, each = 4), y2 = -10:9)
df2$id <- 1:4                                                    # just for the colors

df3 <- full_join(df1, df2)

p1 <- ggplot(df1, aes(x, y)) + geom_col(width = df1$n, col = 1)
p2 <- ggplot(df3, aes(x, y2, group = y2, fill = factor(id))) + 
  geom_col(width = df3$n, position = 'dodge2', col = 1) +
  scale_fill_grey(guide = 'none')

cowplot::plot_grid(p1, p2, ncol = 1, align = 'v')

enter image description here


You can do this in ggplot2 by setting the x-axis locations of the bars explicitly and using geom_rect for plotting. Here's an example that's probably more complicated than it needs to be, but hopefully it will demonstrate the basic idea:

library(tidyverse)

sp = 0.4

d1 = data.frame(value=-2:2) %>% 
  mutate(key=paste0("V", 1:n()),
         width=1:n(),
         spacer = cumsum(rep(sp, n())) - sp,
         xpos = cumsum(width) - 0.5*width + spacer)

d2 = matrix(-10:9, nrow = 4L, ncol = 5L) %>% 
  as.tibble %>% 
  gather(key, value) %>%
  mutate(width = as.numeric(gsub("V","",key))) %>% 
  group_by(key) %>% 
  mutate(width = width/n()) %>% 
  ungroup %>% 
  mutate(spacer = rep(cumsum(rep(sp, length(unique(key)))) - sp, each=4),
         xpos = cumsum(width) - 0.5*width + spacer)

d = bind_rows(list(d1=d1, d2=d2), .id='source') %>% 
  group_by(source, key) %>% 
  mutate(group = LETTERS[1:n()])

ggplot(d, aes(fill=group, colour=group)) +
  geom_rect(aes(xmin=xpos-0.5*width, xmax=xpos+0.5*width, ymin=0, ymax=value)) +
  facet_grid(source ~ ., scales="free_y") +
  theme_bw() +
  guides(fill=FALSE, colour=FALSE) +
  scale_x_continuous(breaks = d1$xpos, labels=d1$key)

enter image description here


It is possible to do this with base plot as well, but it helps to pass the matrix as a vector for the second plot. Subsequently, you need to realize the space argument is a fraction of the average bar width. I did it as follows:

par(mfrow = c(2, 1))
widthsbarplot1 <- 1:5
spacesbarplot1 <- c(0, rep(.2, 4))

barplot(-2:2, width = widthsbarplot1, space = spacesbarplot1)

widthsbarplot2 <- rep(widthsbarplot1/4, each = 4)
spacesbetweengroupsbarplot2 <- mean(widthsbarplot2)

allspacesbarplot2 <- c(rep(0,4), rep(c(spacesbetweengroupsbarplot2, rep(0,3)), 4))

matrix2 <- matrix(-10:9, nrow = 4L, ncol = 5L)

barplot(c(matrix2),
    width = widthsbarplot2,
    space = allspacesbarplot2,
    col = c("red", "yellow", "green", "blue"))

Base plot


Another way, using only base R and still using barplot (not going "down" to rect) is to do it in several barplot calls, with add=TRUE, playing with space to put the groups of bars at the right place.

As already highlighted, the problem is that space is proportional to the mean of width. So you need to correct for that.

Here is my way:

# draw first barplot, getting back the value
bp <- barplot(-2:2, width = n, space = .2)

# get the xlim
x_rg <- par("usr")[1:2]

# plot the "frame"
plot(0, 0, type="n", axes=FALSE, xlab="", ylab="", xlim=x_rg, xaxs="i", ylim=range(as.vector(pr_bp2)))

# plot the groups of bars, one at a time, specifying space, with a correction according to width, so that each group start where it should
sapply(1:5, function(i) barplot(pr_bp2[, i, drop=FALSE], beside = TRUE, width = n[i]/4, space = c((bp[i, 1]-n[i]/2)/(n[i]/4), rep(0, 3)), add=TRUE))

enter image description here

Tags:

Plot

R

Bar Chart