Here is a possible solution where I create a helper data.frame to build borders with geom_rect() . I'm not sure that it is as simple as we would like! I hope that the code that calculates the coordinates of the rectangle will be reused / generalized with a little extra effort.
library(ggplot2) # Load example data. df = data.frame(Block=rep(1:2, each=18), Row=rep(1:9, 4), Col=rep(1:4, each=9), Treat=sample(c(1:6),replace=F)) df$Mainplot = ceiling(df$Row/3) + 3*(ceiling(df$Col/2) - 1) # Create an auxiliary data.frame for plotting borders. group_dat = data.frame(Mainplot=sort(unique(df$Mainplot)), xmin=0, xmax=0, ymin=0, ymax=0) # Fill data.frame with appropriate values. for(i in 1:nrow(group_dat)) { item = group_dat$Mainplot[i] tmp = df[df$Mainplot == item, ] group_dat[i, "xmin"] = min(tmp$Row) - 0.5 group_dat[i, "xmax"] = max(tmp$Row) + 0.5 group_dat[i, "ymin"] = min(tmp$Col) - 0.5 group_dat[i, "ymax"] = max(tmp$Col) + 0.5 } p2 = ggplot() + geom_tile(data=df, aes(x=Row, y=Col, fill=factor(Treat)), colour="grey30", size=0.35) + geom_rect(data=group_dat, aes(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax), size=1.4, colour="grey30", fill=NA) ggsave(filename="plot_2.png", plot=p2, height=3, width=6.5)

source share