【问题标题】:Alternative to for loop R替代 for 循环 R
【发布时间】:2014-06-30 21:01:12
【问题描述】:

我写了一个函数来比较 IP 地址的相似性,并让用户选择八位字节中的详细程度。例如,在地址255.255.255.0255.255.255.1 中,用户可以指定他们只想比较第一个、第一个和第二个、第一个、第二个、第三个等八位字节。

函数如下:

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)
}

一旦数据开始变大,它真的很慢。对于 500,000 行的数据集,system.time() 结果为:

user  system elapsed 
 208.36    0.59  209.84

是否有任何 R 高级用户对如何更有效地编写此代码有所了解?我知道lapply() 是循环遍历向量/数据帧的首选方法,但我不知道如何为此目的访问向量中的前一个元素。我试图快速勾勒出一些东西,但它返回一个语法错误:

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

【问题讨论】:

    标签: r for-loop vectorization


    【解决方案1】:

    这是另一种仅使用基础 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)
    }
    

    这里我们使用scan() 将IP 地址分解为数字。然后我们使用diff 查看每个八位字节的差异。看起来这比原来的提议要快,但比@josilber 的 stringr 解决方案稍慢(使用具有 3,000 个 ip 地址的微基准测试)

    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
    

    【讨论】:

    • +1 用于基础 R,因为它有助于让我以更“类似 R”的方式思考。
    【解决方案2】:

    我能想到的最简单的方法是构建一个仅包含您想要的 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)]))
    }
    

    这对于 500,000 行来说似乎相当有效:

    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,这需要重新分配向量500,000 次。您可以在second circle of the R inferno 中阅读更多相关信息。

    【讨论】:

    • 1) 你可以看出我来自 python 背景,因为这个工作流程没有什么意义(而且不知何故效果很好)。 2)谢谢你的回答,也谢谢你的PDF。这绝对是我写的。
    【解决方案3】:

    不确定你想要的是否只是计数,但这可能是一个解决方案:

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-12
      相关资源
      最近更新 更多