循环R的替代方案
我已经编写了一个函数,它将比较IP地址的相似性,并允许用户选择八位字节中的详细级别。例如,在地址255.255.255.0和255.255.255.1中,用户可以指定他们只想比较第一个、第一个和第二个、第一个第二个和第三个等八位字节 功能如下:循环R的替代方案,r,for-loop,vectorization,R,For Loop,Vectorization,我已经编写了一个函数,它将比较IP地址的相似性,并允许用户选择八位字节中的详细级别。例如,在地址255.255.255.0和255.255.255.1中,用户可以指定他们只想比较第一个、第一个和第二个、第一个第二个和第三个等八位字节 功能如下: did.change.ip=function(vec, detail){ counter=2 result.vec=FALSE r.list=strsplit(vec, '.', fixed=TRUE) for(i in vec){
did.change.ip=function(vec, detail){
counter=2
result.vec=FALSE
r.list=strsplit(vec, '.', fixed=TRUE)
for(i in vec){
if(counter>length(vec)){
break
}
first=as.numeric(r.list[[counter-1]][1:detail])
second=as.numeric(r.list[[counter]][1:detail])
if(sum(first==second)==detail){
result.vec=append(result.vec,FALSE)
}
else{
result.vec=append(result.vec,TRUE)
}
counter=counter+1
}
return(result.vec)
}
一旦数据开始变大,速度就会变慢。对于500000行的数据集,system.time结果为:
user system elapsed
208.36 0.59 209.84
有没有R超级用户对如何更有效地编写这篇文章有见解?我知道lappy是在向量/数据帧上循环的首选方法,但我不知道如何为此访问向量中的前一个元素。我试图快速地画出一些东西,但它返回了一个语法错误:
test=function(vec, detail){
rlist=strsplit(vec, '.', fixed=TRUE)
r.value=vapply(rlist, function(x,detail) ifelse(x[1:detail]==x[1:detail] TRUE, FALSE))
}
我在下面创建了一些用于测试的示例数据:
stack.data=structure(list(V1 = c("247.116.209.66", "195.121.47.105", "182.136.49.12",
"237.123.100.50", "120.30.174.18", "29.85.72.70", "18.186.76.177",
"33.248.142.26", "109.97.92.50", "217.138.155.145", "20.203.156.2",
"71.1.51.190", "31.225.208.60", "55.25.129.73", "211.204.249.244",
"198.137.15.53", "234.106.102.196", "244.3.87.9", "205.242.10.22",
"243.61.212.19", "32.165.79.86", "190.207.159.147", "157.153.136.100",
"36.151.152.15", "2.254.210.246", "3.42.1.208", "30.11.229.18",
"72.187.36.103", "98.114.189.34", "67.93.180.224")), .Names = "V1", class = "data.frame", row.names = c(NA,
-30L))
不确定您想要的是计数,但这可能是一个解决方案:
library(dplyr)
library(tidyr)
# split ip addresses into "octets"
octets <- stack.data %>%
separate(V1,c("first","second","third","fourth"))
# how many shared both their first and second octets?
octets %>%
group_by(first,second) %>%
summarize(n = n())
first second n
1 109 97 1
2 120 30 1
3 157 153 1
4 18 186 1
5 182 136 1
6 190 207 1
7 195 121 1
8 198 137 1
9 2 254 1
10 20 203 1
11 205 242 1
12 211 204 1
13 217 138 1
14 234 106 1
15 237 123 1
16 243 61 1
17 244 3 1
18 247 116 1
19 29 85 1
20 3 42 1
21 30 11 1
22 31 225 1
23 32 165 1
24 33 248 1
25 36 151 1
26 55 25 1
27 67 93 1
28 71 1 1
29 72 187 1
30 98 114 1
我能想到的最简单的方法是构建一个只包含所需IP部分的变换向量。然后,它是一个一行程序,用于检查每个元素是否等于它前面的元素:
library(stringr)
did.change.josilber <- function(vec, detail) {
s <- str_extract(vec, paste0("^(\\d+\\.){", detail, "}"))
return(s != c(s[1], s[1:(length(s)-1)]))
}
这对于500000行似乎相当有效:
set.seed(144)
big.vec <- sample(stack.data[,1], 500000, replace=T)
system.time(did.change.josilber(big.vec, 3))
# user system elapsed
# 0.527 0.030 0.554
代码中最大的问题是每次迭代都要调用append,这需要重新分配向量500000次。您可以在中了解更多信息。这里是另一个仅使用base R的解决方案
did.change.ip <- function(vec, detail=4){
ipv <- scan(text=paste(vec, collapse="\n"),
what=c(replicate(detail, integer()), replicate(4-detail,NULL)),
sep=".", quiet=TRUE)
c(FALSE, rowSums(vapply(ipv[!sapply(ipv, is.null)],
diff, integer(length(vec)-1))!=0)>0)
}
1你可以看出我来自python的背景,因为这个工作流没有什么意义,而且工作起来很好。2谢谢你的回答,谢谢你的PDF。这绝对是为了我而写的。+1代表基本R,因为它有助于让我以更像R的方式思考。
Unit: milliseconds
expr min lq median uq max neval
orig 35.251886 35.716921 36.019354 36.700550 90.159992 100
scan 2.062189 2.116391 2.170110 2.236658 3.563771 100
strngr 2.027232 2.075018 2.136114 2.200096 3.535227 100