【问题标题】:igraph in R: Add edges between vertices with shared attributesR中的igraph:在具有共享属性的顶点之间添加边
【发布时间】:2023-11-05 03:24:01
【问题描述】:

我正在尝试使用基于规则的 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, .)

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

【问题讨论】:

    标签: r igraph


    【解决方案1】:

    所以,我认为 igraph 没有像 gremlin 示例那样简洁,其中connect any vertex (A) with any vertex (B) if they share an attribute 的一般性声明但是,R 提供了许多使用矩阵(如@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 传递给它自己。我假设方向在这里无关紧要,我们想摆脱重复。如果是这种情况,那么只需使用&lt; 运算符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)
    

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

    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)
    

    【讨论】:

      【解决方案2】:

      给定一个图表,

      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) 等等。

      【讨论】: