R 创建边缘列表
R create edge list
Stack Overflow 的新手,尽管我一直在检查你们 post 并回答了很长一段时间(只是在攻读硕士学位时没有时间加入)。
TL;DR:我使用此处提供的脚本来处理结构如下所示的数据集,以获取网络的边缘。它有效但处理时间太长(2k 行需要 24 小时)。对初学者 R 用户有什么让它更快的提示吗?
在我上一个研究项目中,我得到了一个 data.frame 很像:
ID | Trait 1 | Trait 2 | Trait 3 | Trait 4 | Trait 5
01 | TRUE | TRUE | Photo | City | Portrait
02 | FALSE | TRUE | Draw | Child | Portrait
03 | TRUE | FALSE | Photo | Misc | Landscape
.
.
.
这持续了大约 2k 行。目的是建立一个网络,其中每个 ID 都是一个节点,2 个 ID 之间的共同特征的总和将构成一个加权边,即 ID 01 对 ID 2 和 3 的边权重为 2,而 ID 2 将没有 ID 3 的优势。
为了解决这个问题,我使用了以下脚本,它遍历每一行,比较每一列的值以增加权重(每个匹配 = +1),忽略已经比较的行(作为一个无向网络,没有必要匹配两者方式):
键:源=要比较的ID; Target = 被比较的ID;权重 = 匹配总和 cells/traits.
findEdges <- function(){
input <- read.csv("nodes.csv",header=TRUE,stringsAsFactors=FALSE,sep=";")
edges <- read.csv("edges.csv",header=TRUE,stringsAsFactor=FALSE,skip=1,colClasses=c("integer","integer","integer"),col.names=c("Source","Target","Weight"))
for(i in 1:nrow(input)){ #row to be compared: Source
for(j in 1:nrow(entrada)){ #rows that will compare to: Target
weight <- 0
if( i >= j ){
} else {
for(k in 1:ncol(input)){ #column by column comparison
col <- k
if(input[i,k] == input[j,k]){ #edge weight modifier
weight <- weight+1
}
}
print(c("source= ",i,"target= ",j,"weight= ",weight)) #visual feedback of running script
newRow <- data.frame(Source=i,Target=j,Weight=weight) #create row for compared pair
edges <- rbind(edges,newRow) # add edge row to data frame
}
}
}
write.csv(edges,"edges.csv") #write data frame to csv file
}
findEdges()
效果很好,给了我需要的加权边缘列表。边缘列表的每一行将显示为:
Source | Target | Weight
01 | 02 | 2
01 | 03 | 2
等等...
然而,这个脚本花了将近 24 小时来处理整个数据集(2k 行,5 列,除了 ID),虽然这在以前不是问题,但我认为检查一些关于 better/faster 实现相同结果的方法。
一种方法是分别处理每一列,在每一行之间生成成对相似矩阵。例如,假设我们正在对单个列进行操作:
col <- c(1, 1, 2, 3, 2, 4)
outer(col, col, "==") * 1
# [,1] [,2] [,3] [,4] [,5] [,6]
# [1,] 1 1 0 0 0 0
# [2,] 1 1 0 0 0 0
# [3,] 0 0 1 0 1 0
# [4,] 0 0 0 1 0 0
# [5,] 0 0 1 0 1 0
# [6,] 0 0 0 0 0 1
outer
函数在每对元素之间执行我们的运算符 (==
),返回矩阵(*1
只是将 TRUE
/FALSE
到 0/1)。一个好的方面是,这是一个矢量化运算符,因此与涉及 for 循环的方法相比,它的工作速度非常快。
现在很明显,我们需要做的就是为每一列获取一个相似度矩阵并将它们全部相加。
(dat <- data.frame(ID=c(1, 2, 3), T1=c(F, F, T), T2=c(T, T, F), T3=c("Photo", "Draw", "Photo"), T4=c("City", "Child", "Misc"), T5=c("Portrait", "Portrait", "Landscape")))
# ID T1 T2 T3 T4 T5
# 1 1 FALSE TRUE Photo City Portrait
# 2 2 FALSE TRUE Draw Child Portrait
# 3 3 TRUE FALSE Photo Misc Landscape
(res <- Reduce("+", lapply(2:ncol(dat), function(x) outer(dat[,x], dat[,x], "=="))))
# [,1] [,2] [,3]
# [1,] 5 3 1
# [2,] 3 5 0
# [3,] 1 0 5
此函数已识别出每一行都具有与自身相同的所有 5 列。此外,第 1 行和第 2 行有 3 个共同元素,第 1 行和第 3 行有 1 个共同元素,第 2 行和第 3 行没有共同元素。
您可以在最后轻松地将图表的宽表示形式转换为长表示形式(此处我过滤掉了自链接和源 ID > 目标 ID 的边):
subset(cbind(expand.grid(Source=dat$ID, Target=dat$ID), Weight=as.vector(res)),
Source < Target)
# Source Target Weight
# 4 1 2 3
# 7 1 3 1
# 8 2 3 0
基准测试表明向量化 outer
函数比 for 循环有很大优势:
set.seed(144)
big.dat <- data.frame(ID=1:100, A=sample(letters, 100, replace=T), B=sample(letters, 100, replace=T), C=sample(1:10, 100, replace=T))
OP <- function(dat) {
edges <- data.frame(Source=c(), Target=c(), Weight=c())
for (i in 1:nrow(dat)) {
for (j in 1:nrow(dat)) {
if (i < j) {
weight <- 0
for (k in 2:ncol(dat)) {
if (dat[i,k] == dat[j,k]) {
weight <- weight + 1
}
}
edges <- rbind(edges, data.frame(Source=i, Target=j, Weight=weight))
}
}
}
edges
}
josilber <- function(dat) {
res <- Reduce("+", lapply(2:ncol(dat), function(x) outer(dat[,x], dat[,x], "==")))
ret <- subset(cbind(expand.grid(Source=dat$ID, Target=dat$ID), Weight=as.vector(res)), Source < Target)
# Changes to exactly match OP's output
ret <- ret[order(ret$Source, ret$Target),]
row.names(ret) <- NULL
ret
}
all.equal(OP(big.dat), josilber(big.dat))
# [1] TRUE
library(microbenchmark)
microbenchmark(OP(big.dat), josilber(big.dat), times=10)
# Unit: milliseconds
# expr min lq mean median uq max neval
# OP(big.dat) 5931.354448 6062.872595 6137.497152 6076.736039 6175.002149 6519.977217 10
# josilber(big.dat) 5.882283 5.914646 6.316981 5.978082 6.368297 8.801991 10
我们使用矢量化方法为包含 100 行的示例实现了大约 1000 倍的加速。
Stack Overflow 的新手,尽管我一直在检查你们 post 并回答了很长一段时间(只是在攻读硕士学位时没有时间加入)。
TL;DR:我使用此处提供的脚本来处理结构如下所示的数据集,以获取网络的边缘。它有效但处理时间太长(2k 行需要 24 小时)。对初学者 R 用户有什么让它更快的提示吗?
在我上一个研究项目中,我得到了一个 data.frame 很像:
ID | Trait 1 | Trait 2 | Trait 3 | Trait 4 | Trait 5
01 | TRUE | TRUE | Photo | City | Portrait
02 | FALSE | TRUE | Draw | Child | Portrait
03 | TRUE | FALSE | Photo | Misc | Landscape
.
.
.
这持续了大约 2k 行。目的是建立一个网络,其中每个 ID 都是一个节点,2 个 ID 之间的共同特征的总和将构成一个加权边,即 ID 01 对 ID 2 和 3 的边权重为 2,而 ID 2 将没有 ID 3 的优势。
为了解决这个问题,我使用了以下脚本,它遍历每一行,比较每一列的值以增加权重(每个匹配 = +1),忽略已经比较的行(作为一个无向网络,没有必要匹配两者方式):
键:源=要比较的ID; Target = 被比较的ID;权重 = 匹配总和 cells/traits.
findEdges <- function(){
input <- read.csv("nodes.csv",header=TRUE,stringsAsFactors=FALSE,sep=";")
edges <- read.csv("edges.csv",header=TRUE,stringsAsFactor=FALSE,skip=1,colClasses=c("integer","integer","integer"),col.names=c("Source","Target","Weight"))
for(i in 1:nrow(input)){ #row to be compared: Source
for(j in 1:nrow(entrada)){ #rows that will compare to: Target
weight <- 0
if( i >= j ){
} else {
for(k in 1:ncol(input)){ #column by column comparison
col <- k
if(input[i,k] == input[j,k]){ #edge weight modifier
weight <- weight+1
}
}
print(c("source= ",i,"target= ",j,"weight= ",weight)) #visual feedback of running script
newRow <- data.frame(Source=i,Target=j,Weight=weight) #create row for compared pair
edges <- rbind(edges,newRow) # add edge row to data frame
}
}
}
write.csv(edges,"edges.csv") #write data frame to csv file
}
findEdges()
效果很好,给了我需要的加权边缘列表。边缘列表的每一行将显示为:
Source | Target | Weight
01 | 02 | 2
01 | 03 | 2
等等...
然而,这个脚本花了将近 24 小时来处理整个数据集(2k 行,5 列,除了 ID),虽然这在以前不是问题,但我认为检查一些关于 better/faster 实现相同结果的方法。
一种方法是分别处理每一列,在每一行之间生成成对相似矩阵。例如,假设我们正在对单个列进行操作:
col <- c(1, 1, 2, 3, 2, 4)
outer(col, col, "==") * 1
# [,1] [,2] [,3] [,4] [,5] [,6]
# [1,] 1 1 0 0 0 0
# [2,] 1 1 0 0 0 0
# [3,] 0 0 1 0 1 0
# [4,] 0 0 0 1 0 0
# [5,] 0 0 1 0 1 0
# [6,] 0 0 0 0 0 1
outer
函数在每对元素之间执行我们的运算符 (==
),返回矩阵(*1
只是将 TRUE
/FALSE
到 0/1)。一个好的方面是,这是一个矢量化运算符,因此与涉及 for 循环的方法相比,它的工作速度非常快。
现在很明显,我们需要做的就是为每一列获取一个相似度矩阵并将它们全部相加。
(dat <- data.frame(ID=c(1, 2, 3), T1=c(F, F, T), T2=c(T, T, F), T3=c("Photo", "Draw", "Photo"), T4=c("City", "Child", "Misc"), T5=c("Portrait", "Portrait", "Landscape")))
# ID T1 T2 T3 T4 T5
# 1 1 FALSE TRUE Photo City Portrait
# 2 2 FALSE TRUE Draw Child Portrait
# 3 3 TRUE FALSE Photo Misc Landscape
(res <- Reduce("+", lapply(2:ncol(dat), function(x) outer(dat[,x], dat[,x], "=="))))
# [,1] [,2] [,3]
# [1,] 5 3 1
# [2,] 3 5 0
# [3,] 1 0 5
此函数已识别出每一行都具有与自身相同的所有 5 列。此外,第 1 行和第 2 行有 3 个共同元素,第 1 行和第 3 行有 1 个共同元素,第 2 行和第 3 行没有共同元素。
您可以在最后轻松地将图表的宽表示形式转换为长表示形式(此处我过滤掉了自链接和源 ID > 目标 ID 的边):
subset(cbind(expand.grid(Source=dat$ID, Target=dat$ID), Weight=as.vector(res)),
Source < Target)
# Source Target Weight
# 4 1 2 3
# 7 1 3 1
# 8 2 3 0
基准测试表明向量化 outer
函数比 for 循环有很大优势:
set.seed(144)
big.dat <- data.frame(ID=1:100, A=sample(letters, 100, replace=T), B=sample(letters, 100, replace=T), C=sample(1:10, 100, replace=T))
OP <- function(dat) {
edges <- data.frame(Source=c(), Target=c(), Weight=c())
for (i in 1:nrow(dat)) {
for (j in 1:nrow(dat)) {
if (i < j) {
weight <- 0
for (k in 2:ncol(dat)) {
if (dat[i,k] == dat[j,k]) {
weight <- weight + 1
}
}
edges <- rbind(edges, data.frame(Source=i, Target=j, Weight=weight))
}
}
}
edges
}
josilber <- function(dat) {
res <- Reduce("+", lapply(2:ncol(dat), function(x) outer(dat[,x], dat[,x], "==")))
ret <- subset(cbind(expand.grid(Source=dat$ID, Target=dat$ID), Weight=as.vector(res)), Source < Target)
# Changes to exactly match OP's output
ret <- ret[order(ret$Source, ret$Target),]
row.names(ret) <- NULL
ret
}
all.equal(OP(big.dat), josilber(big.dat))
# [1] TRUE
library(microbenchmark)
microbenchmark(OP(big.dat), josilber(big.dat), times=10)
# Unit: milliseconds
# expr min lq mean median uq max neval
# OP(big.dat) 5931.354448 6062.872595 6137.497152 6076.736039 6175.002149 6519.977217 10
# josilber(big.dat) 5.882283 5.914646 6.316981 5.978082 6.368297 8.801991 10
我们使用矢量化方法为包含 100 行的示例实现了大约 1000 倍的加速。