简体   繁体   中英

igraph in R: Add edges between vertices with shared attributes

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM