iGraph 和 disparityfilter 包的字符和大数字问题

iGraph and disparityfilter package issues with characters and large numbers

在没有得到创建我将在下面提到的包的两位作者的回复之后,我认为这里的某个人可以阐明这个问题。

我正在处理一个大型数据集,其中包括出发地和目的地对,以及从 A 到 B 的相应乘客。出发地和目的地变量都使用 IATA 机场名称(3 个字母)进行编码。 可以在此处找到原始 csv 文件 https://github.com/FilipeamTeixeira/network。 请注意,所有 3 个 csv 文件都是相同的,除了一个具有 ORIGIN/DEST 变量作为字符,另一个是数字,第三个是更大的数字。但出于网络目的,它们完全相同,因为它们提供相同数量的连接。

    ORIGIN  DEST    weight
     ABE    ATL     1530
     ABE    AVP     6
     ABE    BDL     2
     ABE    BOS     1
     ABE    BWI     3
     ABE    CLT     1053

导入文件后,我用 a <- graph_from_data_frame(netchr, directed = TRUE) 创建了一个新图表。

然后,因为我通常处理大型数据集,所以我使用差异过滤器 https://github.com/alessandrobessi/disparityfilter/blob/master/R/disparity_filter.R,找到我的网络的 backbone 结构,并减少 edges/nodes 的数量.

为此我 运行 backbone(a).

现在的问题是,每当原始数据框将 Origin 和 Destination 变量作为字符或数字超过 4 位时,它 returns 0。但是当原始数据框具有这 2 个变量时用 3 位数字代替,它工作得很好并且它 returns 一些结果。

运行 下面的代码清楚地概述了问题。

# Import network
# Imports csv

netchr <- read.csv("netchr.csv", header = TRUE,sep = “,”, stringsAsFactors = FALSE)

netnumber <- read.csv("netnum.csv", header = TRUE, sep = “,”, stringsAsFactors = FALSE)

netnumber2 <- read.csv("netnum2.csv", header = TRUE, sep = “,”, stringsAsFactors = FALSE)

# Load igraph and dispfilter

library(igraph)
library(disparityfilter)

a <- graph_from_data_frame(netchr, directed = TRUE)

b <- graph_from_data_frame(netnumber, directed = TRUE)

c <- graph_from_data_frame(netnumber2, directed = TRUE)

# Create backbone network

backbone(a) # finds 0

backbone(b) # has results

backbone(c) # finds 0

我真的很难理解可能会发生什么,因为即使 iGraph 创建图形时,它也会将节点转换为字符,所以从逻辑上讲,最终一切都应该相同。

问题源于 disparityfilter 包中的错误。由 backbone() 调用的 disparity_filter 函数的内部结构涉及将节点名称与节点索引进行比较(一个错误),因此该函数仅在节点名称恰好等于节点时才有效指数。需要明确的是,这意味着在一般情况下(您的示例中的情况 b),结果 可能都是错误的 无论如何 - 尽管有些东西正在 returned.

将索引与名称进行比较是函数不 return 任何字符的原因,也是它不 return 任何大数字的原因:如果数字的大小超过网络中节点数的匹配永远不会发生。

我会演示,然后指出代码中的问题所在。然后我将快速展示结果与当前存在的代码版本有何不同,以及导致 'correct' 输出的快速 'fix'(丑陋的 hack)(我是恐吓引用正确因为我真的不知道输出应该是什么或如何测试它)。

复制您的发现

好的,您的网络文件的 link 已损坏,所以我将使用 igraphdata 包中的一些数据:

# Load the requisite libraries
library(igraph)
library(disparityfilter)
library(igraphdata)

# We'll use the enron email network (b/c cool)
data(enron)

# convert it to a df
df <- igraph::as_data_frame(enron, what = 'edges')
summary(df) # we see nodes numbered from 1:184
#>       from             to          Time            Reciptype        
#>  Min.   :  1.0   Min.   :  1   Length:125409      Length:125409     
#>  1st Qu.: 64.0   1st Qu.: 64   Class :character   Class :character  
#>  Median :108.0   Median :113   Mode  :character   Mode  :character  
#>  Mean   :105.4   Mean   :108                                        
#>  3rd Qu.:156.0   3rd Qu.:156                                        
#>  Max.   :184.0   Max.   :184                                        
#>      Topic         LDC_topic     
#>  Min.   :0.000   Min.   :-1.000  
#>  1st Qu.:1.000   1st Qu.: 0.000  
#>  Median :1.000   Median : 0.000  
#>  Mean   :1.711   Mean   : 2.572  
#>  3rd Qu.:3.000   3rd Qu.: 1.000  
#>  Max.   :3.000   Max.   :32.000

# create a weights variable
df$weight <- df$Topic

现在让我们创建角色和顶点名称的大量版本

# Create a char version of the nodes by appending 'char' to the number
dfchar <- df
dfchar$from <- paste0("char", dfchar$from)
dfchar$to <- paste0("char", dfchar$to)

# create a big num version
dfbnum <- df
dfbnum$from <- 1000 * dfbnum$from
dfbnum$to <- 1000 * dfbnum$to

现在我们将 data.frames 转换回图形

# Now convert the DFs back to graphs
smallnum <- graph_from_data_frame(df, directed = TRUE)

chars <- graph_from_data_frame(dfchar, directed = TRUE)

bignum <- graph_from_data_frame(dfbnum, directed = TRUE)

然后我们可以 运行 backbone() 跨越这三个图表来复制您发现的内容:

## Now we document what you found: namely the anomolous behavior of backbone
newbbs <- backbone(smallnum)
dim(newbbs)
#> [1] 231   4

newbbc <- backbone(chars) 
dim(newbbc)
#> [1] 0 4

newbbb <- backbone(bignum)
dim(newbbb)
#> [1] 0 4

因此,正如您所指出的,即使在其他数据上,backbone() 函数也找不到匹配项,除非节点被一般地标记为 1:N

找到问题

好的,到目前为止我正在复制您记录的内容。我们怎么知道它是 backbone() 中的索引问题?首先让我告诉你如果我们让节点的名称比它们的索引大一点会发生什么:

# now to demonstrate the indexing issue quickly, lets increment
# the node names just a bit, and see what gets returned.
# create a medium num version
dfmnum <- df
dfmnum$from <- dfmnum$from + 90 #add about half the number of nodes to the name
dfmnum$to <- dfmnum$to + 90

# convert back to graph
midnum <- graph_from_data_frame(dfmnum)
bbmid <- backbone(midnum)
dim(bbmid)
#> [1] 28  4

如您所见,这极大地改变了函数的性能——我们找到了 28 个,而不是找到 231 个结果!原因是一半的节点名称现在与索引不匹配,大约一半匹配——所以我们得到随机(并且完全不正确)的结果。

问题出在哪里?

disparityfilter 包在 github 上,由文件 disparity_filter.R 组成,可以看到 here。在第 58 行,disparity_filter 函数将 backbone() 中提供的图形转换回数据帧。让我们用我们的 'character' 版本的图表来做到这一点:

e <- igraph::as_data_frame(chars)[,1:2]
head(e)
    from      to
1 char25 char154
2 char25 char154
3 char30  char30
4 char30  char30
5 char30  char30
6 char30  char30

我们可以看到这里的 from 和 to 列具有我们分配的名称。然后从第 63 行开始,disparity_filter() 函数在第 for (u in which(d > 1)) 行循环遍历次数 (d) 大于 1 的情况。然后在第 65 行的 switch 语句中,将节点名称与 index u:

进行一系列比较
w = switch(substr(mode, 1, 1),
      a = which(e[, 1] == u | e[, 2] == u),
      i = which(e[, 2] == u),
      o = which(e[, 1] == u)
)

这显然是不正确的,并且只有在节点名称恰好与其索引匹配时才有效。明确地说,使用我们的 chars 版本的图,u 的第一个值将是对应于节点 char25 的 1。值 char25 存在于向量 e[,1] 中,但当然永远不会与索引匹配——尽管这可能是作者的意图。同样的问题在下面第 76 行开始的 switch() 语句中重复出现。由于没有匹配项,因此当节点具有非数字名称或数字名称超过节点数时,不会 returned。

问题有多严重?

好的,那么节点名称从一开始计数的情况呢?让我们检查一下 ue[,1] 的值是什么。我们将使用顶点名称为 'work':

的图形版本
d <- degree(smallnum)
which(d>1)
 25  30  39  52  61  64  66  67  93 100 115 125 138 141 146 156 164 168 170 
  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19 

正如我们所见,顶点的数字名称无论如何都不对应于索引!所以即使在某些东西被 returned 的情况下,我们也只是得到了回声。快速编辑以查看差异;我们将重命名顶点,使它们对应于它们的索引:

renamed <- set.vertex.attribute(smallnum, "name", value=1:length(V(smallnum)))
bbs_problem_revealed <- backbone(renamed)
dim(bbs_problem_revealed)
[1] 9 4

好的,现在索引与顶点匹配,我们只得到 9 个观测值!显然这个功能有问题。这个新答案正确吗?老实说我不确定,因为我不确定输出应该是什么或者我如何验证它。此外,如果我要依赖代码,我真的想重做比较以将名称与名称匹配。

无论如何,我的建议是在软件包作者有机会修复它之前不要使用此功能。我将在 github.

上打开错误报告