6

I have data which contain binary indicators for two groups, and to more groups that are nested within one of the first two groups.

For example:

set.seed(1)
df <- data.frame(a=rep(0,10),b=rep(0,10),b.1=rep(0,10),b.2=rep(0,10))
df$a[sample(10,5,replace=F)] <- 1
df$b[sample(10,5,replace=F)] <- 1
df$b.1[sample(which(df$b==1),3,replace=F)] <- 1
df$b.2[sample(which(df$b==1),3,replace=F)] <- 1
df <- df[which(rowSums(df)==0),]

a and b are the two groups and b.1 and b.2 are nested within group b.

What I'd like to do is draw one venn diagram of all groups. This means that b.1 and b.2 will be circumscribed within b, which will intersect a.

Is there any way to achieve this? Using a ggplot solution would be great.

Trying R's VennDiagram' only for groups b, b.1, and b.2 doesn't even work for me:

library(VennDiagram)
draw.triple.venn(area1=sum(df$b),area2=sum(df$b.1),area3=sum(df$b.2),
                   n12=sum(df$b*df$b.1),n23=sum(df$b.1*df$b.2),n13=sum(df$b*df$b.2),n123=sum(df$b*df$b.1*df$b.2),
                   category=c("b","b1","b2"))

enter image description here

With the Vennerable package I get close only drawing the "b" groups:

library(Vennerable)
plot(Venn(Sets=list(b=which(df$b==1),b.1=which(df$b.1==1),b.2=which(df$b.2==1))),doEuler=T,doWeight=T)

enter image description here

But when I add the a group it gets messed up: enter image description here

Because what I really need is one circle for group a with an intersecting area with group b, and within the circle of group b are the circles of groups b.1 and b.2.

zx8754
  • 52,746
  • 12
  • 114
  • 209
user1701545
  • 5,706
  • 14
  • 49
  • 80
  • I found a slight mistake of code. I think `df <- df[-which(rowSums(df)==0),]` is what you intend (a last line of a first code block). – cuttlefish44 Aug 08 '16 at 02:02

2 Answers2

5

The main idea is to draw a triple Venn with a, b1, and b2, and then manually overlay an ellipse for b.

library(VennDiagram)
library(gridExtra)
polygons <- draw.triple.venn(
    area1=sum(df$a),
    area2=sum(df$b.1),
    area3=sum(df$b.2),
    n12=sum(df$a*df$b.1),
    n23=sum(df$b.1*df$b.2),
    n13=sum(df$a*df$b.2),
    n123=sum(df$a*df$b.1*df$b.2),
    category=c("a","b1","b2"),
    margin=.1)

Now we draw the ellipse and add the label. This requires a fair bit of trial and error to get the location, angle, and size right. As it is, it's not perfect, but it's almost there.

b <- ellipseGrob(
    x=unit(0.562,"npc"),
    y=unit(0.515,"npc"),
    angle=(1.996*pi)/3,
    size=65.5, ar=2, gp=gpar(lwd=2.2))
grid.draw(b)
grid.text("b", x=unit(.9,"npc"), y=unit(.9,"npc"), gp=gpar(fontfamily="serif"))

enter image description here

Weihuang Wong
  • 12,868
  • 2
  • 27
  • 48
3

In your assumption, there are few patterns of circle locations. I think it would be better to make your function().

Here is my example (edited; change default vp):

nest_venn <- function(data_list, fill = c(2, 4, 5, 6), alpha = 0.15, 
                      vp = viewport(height=unit(1 ,"snpc"), width=unit(1,"snpc"))) {
  counts <- get.venn.partitions(data_list)$..count..      # calculation of each area's value
  if(any(counts[c(3, 4, 7, 8, 11, 12)]==!0)) warning("data_list[[3]] and/or data_list[[4]] isn't nested")
  grobs <- grobTree(
    circleGrob(x = 0.33, y = 0.5, r = 0.3, gp = gpar(fill = alpha(fill[1], alpha), col=8, lwd = 2)),  # a circle
    circleGrob(x = 0.67, y = 0.5, r = 0.3, gp = gpar(fill = alpha(fill[2], alpha), col=8, lwd = 2)),  # b circle
    circleGrob(x = 0.67, y = 0.6, r = 0.16, gp = gpar(fill = alpha(fill[3], alpha), col=8, lwd = 2)), # b.1 circle
    circleGrob(x = 0.67, y = 0.4, r = 0.16, gp = gpar(fill = alpha(fill[4], alpha), col=8, lwd = 2)), # b.2 circle
    textGrob(names(data_list)[1], x = 0.33, y = 0.82, gp = gpar(cex = 1, fontface = 4)), # a label
    textGrob(names(data_list)[2], x = 0.67, y = 0.82, gp = gpar(cex = 1, fontface = 4)), # b label
    textGrob(names(data_list)[3], x = 0.83, y = 0.7, gp = gpar(cex = 1, fontface = 4)),  # b.1 label
    textGrob(names(data_list)[4], x = 0.83, y = 0.3, gp = gpar(cex = 1, fontface = 4)),  # b.2 label
    textGrob(counts[15], x = 0.28, y = 0.5, gp = gpar(cex = 1.2)),  # a
    textGrob(counts[14], x = 0.9, y = 0.5, gp = gpar(cex = 1.2)),   #     b
    textGrob(counts[13], x = 0.47, y = 0.5, gp = gpar(cex = 1.2)),  # a & b
    textGrob(counts[10], x = 0.68, y = 0.65, gp = gpar(cex = 1.2)), #     b & b.1
    textGrob(counts[6], x = 0.68, y = 0.35, gp = gpar(cex = 1.2)),  #     b       & b.2
    textGrob(counts[9], x = 0.57, y = 0.6, gp = gpar(cex = 1.2)),   # a & b & b.1
    textGrob(counts[5], x = 0.57, y = 0.4, gp = gpar(cex = 1.2)),   # a & b       & b.2
    textGrob(counts[2], x = 0.69, y = 0.5, gp = gpar(cex = 1.2)),   #     b & b.1 & b.2
    textGrob(counts[1], x = 0.6, y = 0.5, gp = gpar(cex = 1.2)),    # a & b & b.1 & b.2
    vp = vp)
  return(grobs)
}

preparation of data list:

set.seed(1)
df <- data.frame(a=rep(0,10),b=rep(0,10),b.1=rep(0,10),b.2=rep(0,10))
df$a[sample(10,5,replace=F)] <- 1
df$b[sample(10,5,replace=F)] <- 1
df$b.1[sample(which(df$b==1),3,replace=F)] <- 1
df$b.2[sample(which(df$b==1),3,replace=F)] <- 1
df <- df[-which(rowSums(df)==0),]            # the same as OP's example data

data_list <- list()
for(i in colnames(df)) data_list[[i]] <- which(df[,i]==1)
  # > data_list[1]
  # $a
  # [1] 2 3 4 5 7

use above function and draw the output:

library(VennDiagram); library(grid); library(ggplot2)

nestvenn.obj <- nest_venn(data_list)
grid.newpage()
grid.draw(nestvenn.obj)

# [ edited ]
# If you want a fixed size etc, please give an argument, vp.
vp1 <- viewport(height=unit(150 ,"mm"), width=unit(150, "mm")) # example
nestvenn.obj <- nest_venn(data_list, vp = vp1)
grid.newpage()

enter image description here

# an example with ggplot
library(gtable); library(dplyr)

grid.newpage()
ggplot(data.frame(x=1, y=1), aes(x, y)) %>% ggplotGrob() %>% 
  gtable_filter("panel") %>% gList(nestvenn.obj) %>% grid.draw()
cuttlefish44
  • 6,586
  • 2
  • 17
  • 34