Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing nested venn diagrams

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.

like image 879
user1701545 Avatar asked Jul 31 '16 00:07

user1701545


People also ask

What is a nested Venn diagram?

Nested Venn diagrams uses single-level Classic Venn diagrams to construct multi-level ones, which are easier to interpret than other forms of Venn diagrams when the datasets reaches more than six.

Can Venn diagrams show nesting relationships?

Although the novel mathematical content of this paper is limited, we believe that Nested Venn Diagrams have significant practical value for displaying the relationships between any number of sets between five and eight.


2 Answers

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

like image 85
Weihuang Wong Avatar answered Oct 17 '22 04:10

Weihuang Wong


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()
like image 3
cuttlefish44 Avatar answered Oct 17 '22 06:10

cuttlefish44