I'm trying to create a graph in R using igraph based on rules. I have a graph with nodes, each of which has several attributes. I'd like to add edges based on those attributes. Toy example:
library(igraph)
make_empty_graph() %>%
add_vertices(
nv = 5,
attr = list(
this_attr = sample(c("a", "b"), 5, replace = TRUE)
)
) %>%
{something here to add edges where this_attr is the same)
This appears to be a solution if I were using Gremlin in Python, but I don't grok it/igraph enough to translate to igraph: Gremlin: adding edges between nodes having the same property
If tidygraph would make this easier, that'd be an acceptable dependency.
Any help would be appreciated.
Edit: This works but feels super messy.
g <- igraph::make_empty_graph() %>%
igraph::add_vertices(
nv = 5,
attr = list(
sample_attr = sample(c("a", "b"), 5, replace = TRUE)
)
)
g %>%
igraph::vertex_attr() %>%
unname() %>%
purrr::map(
function(this_attribute) {
unique(this_attribute) %>%
purrr::map(
function(this_value) {
utils::combn(
which(this_attribute == this_value), 2
) %>%
as.integer()
}
) %>% unlist()
}
) %>%
unlist() %>%
igraph::add_edges(g, .)
Something similar but cleaner would be fantastic.
Given a graph,
g <- make_empty_graph() %>%
add_vertices(nv = 5, attr = list(this_attr = sample(c("a", "b"), 5, replace = TRUE)))
we can first define this adjacency matrix in terms of the attribute
(auxAdj <- tcrossprod(table(1:gorder(g), V(g)$this_attr)) - diag(gorder(g)))
# 1 2 3 4 5
# 1 0 1 1 1 0
# 2 1 0 1 1 0
# 3 1 1 0 1 0
# 4 1 1 1 0 0
# 5 0 0 0 0 0
and use it to add edges as in
g <- add_edges(g, c(t(which(auxAdj == 1, arr.ind = TRUE))))
where
c(t(which(auxAdj == 1, arr.ind = TRUE)))
# [1] 2 1 3 1 4 1 1 2 3 2 4 2 1 3 2 3 4 3 1 4 2 4 3 4
meaning the we want edges (2,1), (3,1), (4,1) and so on.
So, I don't think igraph has anything as succinct as the gremlin example in which a general statement of connect any vertex (A) with any vertex (B) if they share an attribute
However, R provides a bunch of ways to do this with matrices (as @Julius showed) and data frames. Below is how I'd go about this problem with igraph and R.
Given the following graph:
set.seed(4321)
g <- make_empty_graph() %>%
add_vertices(nv = 5, attr = list(sample_attr = sample(c("a", "b"), 5, replace = TRUE)))
We can make a data frame with information taken from the vertices and then left_join
it to itself using the attribute column. I'm assuming direction doesn't matter here and that we want to get rid of duplicates. If that is the case, then simply filter
the node columns using a <
operator.
edge_list <- data.frame(
#id = V(g)$name #if it has a name.....
id = 1:vcount(g), #if no name exists, then then the order of a vertex represents an id
attr = V(g)$sample_attr #the first item in this vector corresponds to the first vertex/node
) %>%
dplyr::left_join(., ., by = 'attr') %>% #join the data frame with itself
dplyr::filter(id.x < id.y) #remove self pointing edges and duplicates
# 1 %--% 2 equals 2 %--% 1 connection and are duplicates
Once we have information the edge list in a data frame, we need to convert the pair of node columns into a pairwise vector. This can be done by converting the columns into a matrix, transposing the matrix so that the rows are now columns, then converting the matrix into a single (pair-wise) vector.
edge_vector <- edge_list %>%
dplyr::select(id.x, id.y) %>% #select only the node/vertex columns
as.matrix %>% #convert into a matrix so we can make a pairwise vector
t %>% #transpose matrix because matrices convert to vectors by columns
c #now we have a pairwise vector
Now, all we need to do is add the pairwise vector and the associated attributes to the graph.
g <- add_edges(g,
edge_vector,
attr = list(this_attr = edge_list$attr)) #order of pairwise vector matches order of edgelist
Let's plot this to see if it worked.
set.seed(4321)
plot(g,
vertex.label = V(g)$sample_attr,
vertex.color = ifelse(V(g)$sample_attr == 'a', 'pink', 'skyblue'),
edge.arrow.size = 0)
Another potential solution is to start with a data frame instead of an empty graph. The data frame would represent a node list that we can join to itself and create an edge list.
set.seed(4321)
node_list <- data.frame(id = 1:5,
attr= sample(c('a', 'b'), 5, replace = T))
edge_list <- merge(node_list, node_list, by = 'attr') %>% #base R merge
.[.$id.x < .$id.y, c('id.x', 'id.y', 'attr')] #rearrange columns in base so first two are node ids
g <- graph_from_data_frame(d = edge_list, directed = F, vertices = node_list)
set.seed(4321)
plot(g,
vertex.label = V(g)$attr,
vertex.color = ifelse(V(g)$attr == 'a', 'pink', 'skyblue'),
edge.arrow.size = 0)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.