Search code examples
rdplyrigraphbipartite

Horizontal layout for tripartite graph


I'm using igraph function to create a tripartite graph linking an instructor (A) to a list of students (B to R) who participate in the 3 clubs mentored by this instructor. Student have crossed memberships in 2 classes and may have the same gender. Finally, the width of the edges represents the amount of time the instructor and each student dedicate, on average, to each club in a given week.

Creating the graph is straightforward (my code is provided at below), but given that my list contains a total of 17 students (B to R), it would be better to present the graph in a horizontal way by placing the instructor (A) on top, the 3 club in the middle, with the 17 students (B to R) on the bottom. I suspect this is because I used layout_with_sugiyama() for my graph, but could anyone suggest an alternative to achieve my desired horizontal layout?

Below is my current R code for this graph:

rm(list=ls())
library(foreign)
library(igraph)
library(dplyr)

### create tripartite node list and pairwise attributes
time <- data.frame(student = c("A", "A", "A", "B", "B", "B", "C", "C", "C", "D", "D", "D", "E", "E", "E", "F", "F", "F", "G", "G", "G", "H", "H", "H", "I", "I", "I", "J", "J", "J", "K", "K", "K", "L", "L", "L", "M", "M", "M", "N", "N", "N", "O", "O", "O", "P", "P", "P", "Q", "Q", "Q", "R", "R", "R"),
club = c("club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3"), 
hours = c(10, 3, 6, 5, 2, 1, 3,	3, 2, 7, 5, 11,	1, 0, 3, 8, 2, 2, 2, 2,	0, 5, 7, 11, 1, 0, 1, 0, 1, 3, 8, 9, 2,	0, 0, 3, 4, 3, 6, 3, 1,	0, 3, 1, 7, 0, 0, 1, 0,	1, 5, 1, 3, 3))

### convert time dataframe into a graph object
df <- time[!time$hours == 0, ]
g <- graph_from_data_frame(df, directed = FALSE)
E(g)$width <- log(E(g)$hours)

### parse the data into three disjoint sets, use different node shapes to distinguish them

A <- "A"
club <- c("club 1", "club 2", "club 3")

V(g)$type <- 1
V(g)[name %in% club]$type <- 2
V(g)[name %in% "A"]$type <- 3
shape <- c("circle", "square", "circle")
size <- c(12, 15, 12)

### label class affiliation (except node A; G, K, L, Q do not belong to any classes)
Class1 <- c("B", "C", "E", "H", "J", "O")
Class2 <- c("D", "F", "M", "P", "I", "N", "R")

V(g)$color[V(g)$name] = "white"
V(g)$color[V(g)$name %in% Class1] = "red"
V(g)$color[V(g)$name %in% Class2] = "orange"
V(g)$color[V(g)$name == "A"] = "olivedrab1"

### highlight same sex nodes
s <- c("B", "D", "F", "G", "H", "K", "M", "P", "Q")
s_col = ifelse(V(g)$name %in% s,'black','grey80')

layout = layout_with_sugiyama(g, layers=V(g)$type)
V(g)$vertex_degree <-  igraph::degree(g)



plot(g,
     layout=cbind(V(g)$type, layout$layout[,1]), edge.curved=0,
     vertex.color = V(g)$color,
     vertex.label.color = "black",
     vertex.label.cex = 0.45,
     vertex.size = size[V(g)$type],
     vertex.shape = shape[V(g)$type],
     vertex.frame.color = s_col,
     edge.color= "grey30",
     asp = 1.3,
     edge.width = E(g)$width
)

The above code generates this graph.

enter image description here

Yet my desired output should look something like this

enter image description here


Solution

  • Thanks for the clarification. This is more meant as a comment than an answer. If I run the code as proposed by yourself and commenters here:

    plot(g,
         layout=cbind(V(g)$type, layout$layout[,1])[,2:1], edge.curved=0,
         vertex.color = V(g)$color,
         vertex.label.color = "black",
         vertex.label.cex = 0.45,
         vertex.size = size[V(g)$type],
         vertex.shape = shape[V(g)$type],
         vertex.frame.color = s_col,
         edge.color= "grey30",
         asp = 1.3,
         edge.width = E(g)$width
    )
    

    I get the following output:enter image description here

    How is that different from what you trying to accomplish?

    EDIT: One way to find a nicer looking distribution of the vertices on the x-axes is to use the cut function:

    idx <- which(layout$layout[,2] == 2)  # find "club"-vertices
    cuts <- layout$layout[idx, 1]         # find x-coords of vertices
    cut(cuts, length(idx))                # cut into 3 intervals
    layout$layout[idx,1] <- c(6,7.5,9)    # manually calculated even spans between x-coords
    

    However, I am sure that there are better ways to do this.