alexwhan
alexwhan

Reputation: 16026

Add rectangles around common values in ggplot

When I make an experimental design, I use ggplot to show the layout. Here's a simple example:

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))

Which I'll plot like:

df.p <- ggplot(df, aes(Row, Col)) + geom_tile(aes(fill=as.factor(Treat)))

to give:

design

Sometimes I have a structure within the design I would like to highlight by putting a box around it, for example a mainplot. In this case:

df$Mainplot <- ceiling(df$Row/3) + 3*(ceiling(df$Col/2) - 1)

I then use geom_rect and some messy code that needs adjusting for each design to generate something like:

designwithmainplot

Question: How do I add the rectangles around the mainplots in a simple way? It seems like a simple enough problem, but I haven't found an obvious way. I can map colour or some other aesthetic to mainplot, but I can't seem to surround them with a box. Any pointers greatly appreciated.

Upvotes: 7

Views: 1967

Answers (3)

alexwhan
alexwhan

Reputation: 16026

I thought it would be worth posting my own (non-ideal) solution, since it seems there's nothing obvious I'm missing. I'm going to leave the question unanswered in the hope someone will come up with something.

At present, I use geom_rect in a fashion that would probably be able to be made general (perhaps into a geom_border addition to ggplot??). For the example in my question, the essential information is that each mainplot is 3 x 2.

Adding onto df.p from the original question, this is what I do currently:

df.p1 <- df.p + geom_rect(aes(xmin=((Mainplot- 3*(ceiling(Col/2)-1) )-1)*3 + 0.5,
                         xmax=((Mainplot - 3*(ceiling(Col/2)-1))-1)*3 + 3.5,
                         ymin=ceiling(ceiling(Col/2)/2 + 2*(ceiling(Col/2)-1))-0.5,
                         ymax=2*ceiling(Col/2)+0.5),
                     colour="black", fill="transparent",size=1)

Ugly, I know - hence the question. That code generates the second plot from the question. Maybe the best option is building this all into a function.

Upvotes: 0

bdemarest
bdemarest

Reputation: 14667

Here is a possible solution where I create an auxiliary data.frame for plotting borders with geom_rect(). I'm not sure if this is as simple as you would like! I hope the code that computes the rectangle coordinates will be reusable/generalizable with just a bit of additional 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) 

enter image description here

Upvotes: 6

emhart
emhart

Reputation: 804

Here's a solution that might be a easier. Just use geom_tile with alpha set to 0. I didn't take the time to give you an exact solution, but here's an example. To achieve what you want I'm guessing you'll need to actually create a new data frame, which should be easy enough.

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$blocking <- rep(sort(rep(1:3,3)),4)
df.p <- ggplot(df, aes(Row, Col)) + geom_tile(aes(fill=as.factor(Treat)))
df.p+ geom_tile(data=df,aes(x=Row,y=blocking),colour="black",fill="white",alpha=0,lwd=1.4)

the alpha=0 will create a blank tile, and then you can set the line width using lwd. That's probably easier than specifying all the rectangles. Hope it helps.

Upvotes: 2

Related Questions