R中的igraph:在具有共享属性的顶点之间添加边

时间:2019-01-27 14:56:59

标签: r igraph

我正在尝试根据规则使用igraph在R中创建一个图形。我有一个带有节点的图,每个节点都有几个属性。我想基于这些属性添加边缘。玩具示例:

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)

如果我在Python中使用Gremlin,这似乎是一个解决方案,但是我对它/ igraph的理解不足以翻译成igraph:Gremlin: adding edges between nodes having the same property

如果tidygraph可以简化这一过程,那将是一个可以接受的依赖项。

任何帮助将不胜感激。

编辑:可以,但是感觉超级混乱。

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, .)

类似但更干净的东西会很棒。

2 个答案:

答案 0 :(得分:1)

给出一个图形

g <- make_empty_graph() %>% 
  add_vertices(nv = 5, attr = list(this_attr = sample(c("a", "b"), 5, replace = TRUE)))

我们首先可以根据属性定义此邻接矩阵

(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

并使用它来添加边缘,如

g <- add_edges(g, c(t(which(auxAdj == 1, arr.ind = TRUE))))

其中

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

表示我们想要边(2,1),(3,1),(4,1)等。

答案 1 :(得分:1)

因此,我认为 igraph 并不像 gremlin 例子那样简洁,在该例子中,connect any vertex (A) with any vertex (B) if they share an attribute的一般说法是提供了多种方法来处理矩阵(如@Julius所示)和数据帧。下面是我如何使用igraph和R解决这个问题。

给出以下图形:

set.seed(4321)
g <- make_empty_graph() %>% 
       add_vertices(nv = 5, attr = list(sample_attr = sample(c("a", "b"), 5, replace = TRUE)))

我们可以创建一个数据帧,其中包含从顶点获取的信息,然后使用属性列将其left_join自身返回。我假设这里的方向无关紧要,我们想摆脱重复。如果是这种情况,则只需使用filter运算符<节点列即可。

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

一旦我们掌握了数据帧中的边缘列表信息,就需要将一对节点列转换为成对向量。这可以通过以下方式完成:将列转换为矩阵,对矩阵进行转置以使行现在为列,然后将矩阵转换为单个(成对的)向量。

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

现在,我们要做的就是将成对向量和关联的属性添加到图中。

g <- add_edges(g,
               edge_vector, 
               attr = list(this_attr = edge_list$attr))  #order of pairwise vector matches order of edgelist

让我们来绘制它是否有效。

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)

enter image description here

另一种可能的解决方案是从数据框开始,而不是从空图开始。数据框将代表一个节点列表,我们可以将其加入自身并创建边缘列表。

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)