是否有 R 函数可以在无向(非定向)网络中获取唯一边?

Is there an R function to get the unique edges in an undirected (not directed) network?

我想计算无向网络中唯一边的数量,例如 net

   x  y
1  A  B
2  B  A
3  A  B

这个矩阵应该只有一条唯一的边,因为无向网络的边 A-B 和 B-A 是相同的。

对于定向网络,我可以通过以下方式获得唯一边的数量:

nrow(unique(net[,c("x","y"]))

但这对无向网络不起作用。

试试这个,

df <- data.frame(x=c("A", "B", "A"), y = c("B", "A", "B"))
unique(apply(df, 1, function(x) paste(sort(unlist(strsplit(x, " "))),collapse = " ")))
[1] "A B"

那么这是如何工作的?

  1. 我们正在对数据框的每一行应用一个函数,因此我们可以一次获取每一行。 取df的第二行,

    df[2,]
      x y
    1 B A
    
  2. 然后我们将 (strsplit) 和 unlist 拆分为每个字母的向量,(我们使用 as.matrix 来隔离元素)

    unlist(strsplit(as.matrix(df[2,]), " "))
    [1] "B" "A"
    
  3. 使用排序功能按字母顺序排列,然后粘贴回去,

    paste(sort(unlist(strsplit(as.matrix(df[2,]), " "))), collapse = " ")
    [1] "A B"
    

然后 apply 函数对所有行执行此操作,因为我们将索引设置为 1,然后使用 unique 函数来识别唯一边。

分机

这可以扩展到n个变量,例如n=3,

df <- data.frame(x=c("A", "B", "A"), y = c("B", "A", "B"),  z = c("C", "D", "D"))
unique(apply(df, 1, function(x) paste(sort(unlist(strsplit(x, " "))),collapse = " ")))
[1] "A B C" "A B D"

如果需要更多字母,只需像下面这样组合两个字母,

df <- data.frame(x=c("A", "BC", "A"), y = c("B", "A", "BC"))
df
   x  y
1  A  B
2 BC  A
3  A BC
unique(apply(df, 1, function(x) paste(sort(unlist(strsplit(x, " "))),collapse = " ")))
[1] "A B"  "A BC"

旧版本

使用 tidyverse 包,创建一个名为 rev 的函数,它可以对我们的边进行排序,然后使用 mutate 创建一个新的列来组合 x 和 y 列,这样它与 rev 函数一起工作的一种方式,然后 运行 通过该函数的新列并找到唯一的对。

library(tidyverse)
rev <- function(x){
  unname(sapply(x, function(x) {
    paste(sort(trimws(strsplit(x[1], ',')[[1]])), collapse=',')} ))
}
df <- data.frame(x=c("A", "B", "A"), y = c("B", "A", "B"))
rows <- df %>% 
  mutate(both = c(paste(x, y, sep = ", ")))

unique(rev(rows$both))

鉴于您正在使用网络,igraph 解决方案:

library(igraph)

as_data_frame(simplify(graph_from_data_frame(dat, directed=FALSE)))

然后使用nrow


说明

dat %>% 
  graph_from_data_frame(., directed=FALSE) %>% # convert to undirected graph
  simplify %>%                                 # remove loops / multiple edges
  as_data_frame                                # return remaining edges

这是一个没有 igraph 干预的解决方案,全部在一个管道内:

df = tibble(x=c("A", "B", "A"), y = c("B", "A", "B"))

可以通过 mutate() 在新列中使用 group_by() 然后 sort() 值组合和 paste() 它们。 unique() 如果你有 "true" 重复(A-B,A-B 将进入一组)。

df %>%
  group_by(x, y) %>%
  mutate(edge_id = paste(sort(unique(c(x,y))), collapse=" ")) 

当您在新列中对边名称进行正确排序后,计算唯一值或从数据框中过滤掉重复值就非常简单了。
如果你有额外的边缘变量,只需将它们添加到分组中即可。

如果您不使用{igraph}或者只是想知道如何在没有任何依赖的情况下干净利落地使用...

这是您的数据...

your_edge_list <- data.frame(x = c("A", "B", "A"),
                             y = c("B", "A", "B"),
                             stringsAsFactors = FALSE)
your_edge_list
#>   x y
#> 1 A B
#> 2 B A
#> 3 A B

这是一个逐步的细分...

`%>%` <- magrittr::`%>%`

your_edge_list %>% 
  apply(1L, sort) %>%              # sort dyads
  t() %>%                          # transpose resulting matrix to get the original shape back
  unique() %>%                     # get the unique rows
  as.data.frame() %>%              # back to data frame
  setNames(names(your_edge_list))  # reset column names
#>   x y
#> 1 A B

如果我们放下管道,它的核心看起来像这样...

unique(t(apply(your_edge_list, 1, sort)))
#>      [,1] [,2]
#> [1,] "A"  "B"

我们可以将其包装在一个函数中,该函数 1) 处理有向和无向,2) 处理数据帧和(更常见的)矩阵,以及 3) 可以丢弃循环...

simplify_edgelist <- function(el, directed = TRUE, drop_loops = TRUE) {
  stopifnot(ncol(el) == 2)

  if (drop_loops) {
    el <- el[el[, 1] != el[, 2], ]
  }

  if (directed) {
    out <- unique(el)
  } else {
    out <- unique(t(apply(el, 1, sort)))
  }

  colnames(out) <- colnames(el)

  if (is.data.frame(el)) {
    as.data.frame(out, stringsAsFactors = FALSE)
  } else {
    out
  }
}

el2 <- rbind(your_edge_list, 
             data.frame(x = c("C", "C"), y = c("C", "A"), stringsAsFactors = FALSE))
el2
#>   x y
#> 1 A B
#> 2 B A
#> 3 A B
#> 4 C C
#> 5 C A

simplify_edgelist(el2, directed = FALSE)
#>   x y
#> 1 A B
#> 5 A C