避免R中的lappy(),并为向量a的每个元素查找满足条件的向量B的所有元素

避免R中的lappy(),并为向量a的每个元素查找满足条件的向量B的所有元素,r,list,loops,lapply,R,List,Loops,Lapply,我有两个向量。对于向量A的每个元素,我想知道向量B中满足特定条件的所有元素。例如,包含向量的两个数据帧: person <- data.frame(name = c("Albert", "Becca", "Celine", "Dagwood"), tickets = c(20, 24, 16, 17)) prize <- data.frame(type = c("potato", "lollipop", "yo-yo", "stickyhand",

我有两个向量。对于向量A的每个元素,我想知道向量B中满足特定条件的所有元素。例如,包含向量的两个数据帧:

person <- data.frame(name = c("Albert", "Becca", "Celine", "Dagwood"),
                 tickets = c(20, 24, 16, 17))
prize <- data.frame(type = c("potato", "lollipop", "yo-yo", "stickyhand", 
                         "moodring", "figurine", "whistle", "saxophone"),
                cost = c(6, 11, 13, 17, 21, 23, 25, 30))
等等。现在,我用lappy做这个,但这实际上并不比R中的for循环快

library(dplyr)
matching_Function <- function(person, prize, tolerance = 5){
  matchlist <- lapply(split(person, list(person$name)),
                      function(x) filter(prize, abs(x$tickets-cost)<=tolerance)$type)
  longlist <- data.frame("person" = rep(names(matchlist), 
                                    times = unlist(lapply(matchlist, length))),
                         "prize" = unname(unlist(matchlist))
  )
  return(longlist)
}
matching_Function(person, prize)
我的实际数据集要大得多,有几十万个,我的匹配条件更复杂,检查B的坐标,看它们是否在a的坐标半径范围内,所以这永远需要几个小时


有没有比for和lapply更聪明的方法来解决这个问题?

这对于测试数据和完整的外部连接来说非常简单:

library(data.table)

setDT(person)
setDT(prize)

person[, JA := 1]
prize[, JA := 1]

merge(person,prize, by = "JA", allow.cartesian = TRUE)[abs(tickets - cost) < 6, .(name, type)]

#       name       type
# 1:  Albert stickyhand
# 2:  Albert   moodring
# 3:  Albert   figurine
# 4:  Albert    whistle
# 5:   Becca   moodring
# 6:   Becca   figurine
# 7:   Becca    whistle
# 8:  Celine   lollipop
# 9:  Celine      yo-yo
# 10:  Celine stickyhand
# 11:  Celine   moodring
# 12: Dagwood      yo-yo
# 13: Dagwood stickyhand
# 14: Dagwood   moodring
我们所做的是一个完全的外部联接,然后排除任何不符合条件的行

但是,如果这是100000对100000的完全外部联接,则使用这种方法可能会耗尽内存。在这种情况下,我将并行化:

library(data.table)
library(foreach)
library(doParallel)    

setDT(person)
setDT(prize)
person[, JA := 1]
prize[, JA := 1]

seq_s <- seq(1,nrow(person), by = 500) #change the 500 here based on memory/speed tradeoff
ln_s <- length(seq_s)
str_seq <- paste0(seq_s,":",c(seq_s[2:ln_s],nrow(person) + 1) - 1)

cl<-makeCluster(4)
registerDoParallel(cl)

ls<-foreach(i = 1:ln_s) %dopar% {

  library(data.table)
  person_batch <- person[eval(parse(text = str_seq[i]))]
  Output <- merge(person_batch,prize, by = "JA", allow.cartesian = TRUE)
  Output <- Output[abs(tickets - cost) < 6, .(name, type)]
}

stopCluster(cl)

Output <- unique(do.call(rbind,ls))

这本质上是完全相同的过程,只需分成更小的批,这些批不会运行到内存限制,因为我们正在动态筛选这对于测试数据和完整的外部联接来说非常简单:

library(data.table)

setDT(person)
setDT(prize)

person[, JA := 1]
prize[, JA := 1]

merge(person,prize, by = "JA", allow.cartesian = TRUE)[abs(tickets - cost) < 6, .(name, type)]

#       name       type
# 1:  Albert stickyhand
# 2:  Albert   moodring
# 3:  Albert   figurine
# 4:  Albert    whistle
# 5:   Becca   moodring
# 6:   Becca   figurine
# 7:   Becca    whistle
# 8:  Celine   lollipop
# 9:  Celine      yo-yo
# 10:  Celine stickyhand
# 11:  Celine   moodring
# 12: Dagwood      yo-yo
# 13: Dagwood stickyhand
# 14: Dagwood   moodring
我们所做的是一个完全的外部联接,然后排除任何不符合条件的行

但是,如果这是100000对100000的完全外部联接,则使用这种方法可能会耗尽内存。在这种情况下,我将并行化:

library(data.table)
library(foreach)
library(doParallel)    

setDT(person)
setDT(prize)
person[, JA := 1]
prize[, JA := 1]

seq_s <- seq(1,nrow(person), by = 500) #change the 500 here based on memory/speed tradeoff
ln_s <- length(seq_s)
str_seq <- paste0(seq_s,":",c(seq_s[2:ln_s],nrow(person) + 1) - 1)

cl<-makeCluster(4)
registerDoParallel(cl)

ls<-foreach(i = 1:ln_s) %dopar% {

  library(data.table)
  person_batch <- person[eval(parse(text = str_seq[i]))]
  Output <- merge(person_batch,prize, by = "JA", allow.cartesian = TRUE)
  Output <- Output[abs(tickets - cost) < 6, .(name, type)]
}

stopCluster(cl)

Output <- unique(do.call(rbind,ls))
这本质上是完全相同的过程,只需分成更小的批,不会遇到内存限制,因为我们正在动态筛选数据中的foverlaps。表执行您希望的操作:

require(data.table)

# Turn the datasets into data.table
setDT(person)
setDT(prize)
# Add the min and max from tolerance
person[,`:=`(start=tickets-tolerance,end=tickets+tolerance)]
# add a dummy column for use as range
prize[,dummy:=cost]
# Key the person table on start and end
setkey(person,start,end)
# As foverlaps to get the corresponding rows from prize into person, filter the NA results and return only the name and type of prize
r<-foverlaps(prize,person,type="within",by.x=c("cost","dummy"))[!is.na(name),list(name=name,prize=type)]
# Re order the result by name instead of prize cost
setorder(r,name)
我希望我对代码进行了足够多的注释,使其能够不言自明

对于问题的第二部分,使用坐标和半径内的测试

person <- structure(list(name = c("Albert", "Becca", "Celine", "Dagwood"), 
                         x = c(26, 16, 32, 51), 
                         y = c(92, 51, 25, 4)), 
                    .Names = c("name", "x", "y"), row.names = c(NA, -4L), class = "data.frame")
antenas <- structure(list(name = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"), 
                          x = c(40, 25, 38, 17, 58, 19, 34, 38, 67, 26, 46, 17), 
                          y = c(36, 72, 48, 6, 78, 41, 18, 28, 54, 8, 28, 47)), 
                     .Names = c("name", "x", "y"), row.names = c(NA, -12L), class = "data.frame")

setDT(person)
setDT(antenas)
r<-10

results <- person[,{dx=x-antenas$x;dy=y-antenas$y; list(antena=antenas$name[dx^2+dy^2<=r^2])},by=name]
使用data.table中的foverlaps执行您希望的操作的替代方案:

require(data.table)

# Turn the datasets into data.table
setDT(person)
setDT(prize)
# Add the min and max from tolerance
person[,`:=`(start=tickets-tolerance,end=tickets+tolerance)]
# add a dummy column for use as range
prize[,dummy:=cost]
# Key the person table on start and end
setkey(person,start,end)
# As foverlaps to get the corresponding rows from prize into person, filter the NA results and return only the name and type of prize
r<-foverlaps(prize,person,type="within",by.x=c("cost","dummy"))[!is.na(name),list(name=name,prize=type)]
# Re order the result by name instead of prize cost
setorder(r,name)
我希望我对代码进行了足够多的注释,使其能够不言自明

对于问题的第二部分,使用坐标和半径内的测试

person <- structure(list(name = c("Albert", "Becca", "Celine", "Dagwood"), 
                         x = c(26, 16, 32, 51), 
                         y = c(92, 51, 25, 4)), 
                    .Names = c("name", "x", "y"), row.names = c(NA, -4L), class = "data.frame")
antenas <- structure(list(name = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"), 
                          x = c(40, 25, 38, 17, 58, 19, 34, 38, 67, 26, 46, 17), 
                          y = c(36, 72, 48, 6, 78, 41, 18, 28, 54, 8, 28, 47)), 
                     .Names = c("name", "x", "y"), row.names = c(NA, -12L), class = "data.frame")

setDT(person)
setDT(antenas)
r<-10

results <- person[,{dx=x-antenas$x;dy=y-antenas$y; list(antena=antenas$name[dx^2+dy^2<=r^2])},by=name]


看来这对于Rcpp来说是一个相当容易的胜利,尤其是因为一个天真的循环方法将很容易编写和翻译成C++。[whichprize$cost>=门票-5&prize$cost%sep\u rowsprize,sep=',%%>%选择门票,尽管我不确定其价格performance@I.mik更新了坐标选择的我的答案,以满足您的第二部分:顺便说一句,恭喜您的Q,很好地介绍了,我们将精心制作。遗憾的是,它对您的实际用例过于简化,这使得答案没有达到目标。@Tensiba我很感激!我已经咨询StackOverflow很多年了,从来没有作为一个实际用户咨询过,很高兴终于有了一个尚未以某种形式得到回答的问题。看来这对Rcpp来说是一个相当容易的胜利,特别是因为简单的for-loop方法很容易编写和测试向C++提供.dPLYR,用TyDIR给人%> %行%%%MutuType=PasTestBuy $类型[whichprize$cost>=门票-5&prize$cost%sep\u rowsprize,sep=',%%>%选择门票,尽管我不确定其价格performance@I.mik更新了坐标选择的我的答案,以满足您的第二部分:顺便说一句,恭喜您的Q,很好地介绍了,我们将精心制作。遗憾的是,它对您的实际用例过于简化,这使得答案没有达到目的。@Tensiba我很感激!我已经咨询StackOverflow很多年了,从来没有作为一个真正的用户,我很兴奋终于有了一个问题,这个问题还没有以某种形式得到回答。这确实很巧妙地解决了我的示例问题。但是,我不认为这是因为你自己的过错,也不能推广到其他人身上我的实际数据。我将示例中的公差简化为一维尺度上的距离。但在我的实际数据集中,它是一个由方程求解的二维距离。因此,最小/最大距离不能由一个“开始”和“结束”数字表示。无论如何,它都是向上投票的,因为它解决了示例,其他人可能会发现它很有用!@I.mik你可以发布一个后续问题。但我非常确定,最坏的情况下,只需2个foverlaps调用和结尾处的合并即可。@I.Mik根据,这可能更简单。我将更新到明天谢谢编辑!我的数学方程实际上不同,它是一个在球体表面绘制的圆,但使用了你的解决方案和su的基本结构通过我自己的数学计算,我最终找到了我需要的地方。对于我实际数据的一小部分,microbenchmark发现这比我的旧Lappy方法要快一些。非常感谢!@I.mik我明白了如何计算纵横坐标的基本差可能是个问题。但是IIRC有一些包在做地图投影,至少在这将简化en的数学
D这是又一个步骤,但正如我曾经做过的那样,我非常确定这会加快整个过程。这确实很巧妙地解决了我的示例问题。然而,并非由于你自己的过错,我认为它不能推广到我的实际数据中。我将示例中的公差简化为一维尺度上的距离。但在我的实际数据集中,这是一个二维距离,由一个方程求解。因此,最小/最大距离不能用一个“开始”和“结束”数字表示。无论如何都要投票,因为它确实解决了这个例子,其他人可能会发现它很有用@mik你可以发布一个后续问题。但我敢肯定,在最坏的情况下,通过2个foverlaps呼叫和末尾的合并,@I.Mik,这可能会更简单。我会在明天更新谢谢你的编辑!我的数学方程实际上是不同的,它是一个画在球体表面上的圆,但是使用你的解的基本结构,再把我自己的数学代入,最终我得到了我需要的地方。对于我实际数据的一小部分,microbenchmark发现这比我的旧lapply方法稍微快一些。非常感谢@I.mik我知道计算纵横坐标的基本差可能是个问题。但是IIRC有一些软件包在做地图投影,至少是mapproject,这最终会简化数学。这是又一个步骤,但一旦完成,我很确定它会加快整个过程。嗯,我以前没有做过并行化。我必须考虑把它纳入我的R工具箱。即使在接近内存限制之前,这真的会比Lappy快吗?在大约是最终数据大小0.1%的数据上测试它,至少在没有并行化的情况下,似乎不会更快。不管怎样,谢谢你介绍并行化@i、 Mik这应该比你的初始函数快得多-你有很多初始化和子集,而merge+过滤器应该更快。尝试使用1000行长的数据并使用microbenchmarkSo,一些测试表明,对于示例数据,您的方法显然比我的方法好得多。出于某种原因,为了解决我的实际数据,microbenchmark显示它运行得相当慢。我不确定这是为什么——也许是我的距离公式的一些怪癖。但这更多的是我的错,因为我的例子与我的真实数据并不是一个完美的类比。我得深入研究,找出原因。谢谢你的回答!嗯,我以前没有做过并行化。我必须考虑把它纳入我的R工具箱。即使在接近内存限制之前,这真的会比Lappy快吗?在大约是最终数据大小0.1%的数据上测试它,至少在没有并行化的情况下,似乎不会更快。不管怎样,谢谢你介绍并行化@i、 Mik这应该比你的初始函数快得多-你有很多初始化和子集,而merge+过滤器应该更快。尝试使用1000行长的数据并使用microbenchmarkSo,一些测试表明,对于示例数据,您的方法显然比我的方法好得多。出于某种原因,为了解决我的实际数据,microbenchmark显示它运行得相当慢。我不确定这是为什么——也许是我的距离公式的一些怪癖。但这更多的是我的错,因为我的例子与我的真实数据并不是一个完美的类比。我得深入研究,找出原因。谢谢你的回答!