在R中,特定的for循环速度太慢

3

我需要使用两个数据框,每个数据框有200万行记录。我使用了一个for循环来从一个数据框中获取数据,但是速度太慢了。我创建了一个示例来演示我需要做什么。

ratings = data.frame(id = c(1,2,2,3,3),
                     rating = c(1,2,3,4,5),
                     timestamp = c("2006-11-07 15:33:57","2007-04-22 09:09:16","2010-07-16 19:47:45","2010-07-16 19:47:45","2006-10-29 04:49:05"))
stats = data.frame(primeid = c(1,1,1,2),
                   period = c(1,2,3,4),
                   user = c(1,1,2,3), 
                   id = c(1,2,3,2), 
                   timestamp = c("2011-07-01 00:00:00","2011-07-01 00:00:00","2011-07-01 00:00:00","2011-07-01 00:00:00"))

ratings$timestamp = strptime(ratings$timestamp, "%Y-%m-%d %H:%M:%S")
stats$timestamp = strptime(stats$timestamp, "%Y-%m-%d %H:%M:%S")

for (i in(1:nrow(stats)))
{
   cat("Processing ",i," ...\r\n")
   temp = ratings[ratings$id == stats$id[i],]
   stats$idrating[i] = max(temp$rating[temp$timestamp < stats$timestamp[i]])
}

有没有其他的替代方法?我知道apply可能有效,但我不知道如何翻译for函数。

更新:感谢帮助。我提供更多信息。

表stats具有primeid、period、user、id的唯一组合。 表ratings有多个id记录,具有不同的评分和时间戳。

我想要做的是:对于在stats中找到的每个id,在ratings表中查找所有记录(id列),然后根据从stats获得的特定时间戳获取最大评分。


1
我相信这不需要循环。你应该能够将temp的定义“嵌入”到你想要使用的ratings子集中,并用单个公式完成整个过程。顺便问一下,stats$id的每个元素都能在ratings$id中找到吗?但是一些ddply专家会提出更好的方法。编辑:在向量公式中使用pmax - Carl Witthoft
1
请添加您想要做的描述。从您的代码中很难理解。 - Roland
1
而且,对于这种时间序列数据,查看xtszoo也可能是一个好主意。 - Paul Hiemstra
而且你的数据框具有相同的维度,为什么要给出一个具有不同大小(评级/统计)的示例? - agstudy
4个回答

6
我喜欢 plyr 以及 Hadley Wickham 创造的大部分工具,但我发现如果要在 ID 字段上进行拆分,它可能会非常慢。这时候,我就会转向使用 sqldf。通常可以获得20倍的加速。
首先,我需要使用 lubridate,因为 sqldfPOSIXlt 类型数据处理有问题。
library(lubridate)
ratings$timestamp = ymd_hms(ratings$timestamp)
stats$timestamp = ymd_hms(stats$timestamp)

合并数据框,按照Vincent所做的方式,并移除那些违反日期限制的数据:
tmp <- merge(stats, ratings, by="id")
tmp <- subset(tmp, timestamp.y < timestamp.x )

最后,获取每个ID的最高评分:
library(sqldf)
sqldf("SELECT *, MAX(rating) AS rating FROM tmp GROUP BY id")

4

根据ID与数据点的比例,这种方法可能更有效:

r = split(ratings, ratings$id)
stats$idrating = sapply(seq.int(nrow(stats)), function(i) {
  rd = r[[stats$id[i]]]
  if (length(rd))
    max(rd$rating[rd$timestamp < stats$timestamp[i]])
  else NA
})

如果您的ID不是连续的整数(您可以使用all(names(r) == seq_along(r))进行检查),则在引用r[[时必须添加as.character(),或者使用match一次创建映射表,但这会降低速度。

显然,您也可以不使用split来完成相同的操作,但通常会更慢,但会使用更少的内存:

stats$idrating = sapply(seq.int(nrow(stats)), function(i) {
  rd = ratings[ratings$id == stats$id[i],]
  if (nrow(rd))
    max(rd$rating[rd$timestamp < stats$timestamp[i]])
  else NA
})

如果您确定不会出现不匹配的情况,也可以省略if


3

尽管我使用了另一种方法获得相同的结果,但我投票支持提供的答案。

在合并数据集中,我首先删除早于条件日期的日期,然后运行以下操作:

aggregate (rating ~ id+primeid+period+user, data=new_stats, FUN = max)

我不知道这个函数的存在。这种方法更加优雅,谢谢分享! - cantdutchthis

1

从数据结构的角度来看,您似乎想要合并两个表,然后执行分组应用方法。

与其通过循环检查哪一行属于哪一个表,不如简单地合并这两个表(类似于SQL中的JOIN语句),然后执行“aaply”类型的方法。我建议您下载“plyr”库。

new_stats = merge(stats, ratings, by='id')

library(plyr) 
ddply(new_stats, 
      c('primeid', 'period', 'user'),  
      function(new_stats) 
      c( max(new_stats[as.Date(new_stats$timestamp.x) > as.Date(new_stats$timestamp.y)]$rating )))

如果您对plyr的使用感到困惑,请访问此教程:http://www.creatapreneur.com/2013/01/split-group-apply/

for 循环背后的代码是什么?你认为它是用 R 编写的吗?为什么在 R 中循环速度较慢? - agstudy
我相信R是建立在C之上的,对吧?原生的C代码中会有一个比R更快的for循环。 - cantdutchthis
1
R主要是用C和FORTRAN(以及R本身)构建的。我反对的是认为在R中使用for循环本质上比预期的慢的观念。实际上,它们非常快。问题在于,正如我所说,人们在编写for循环的过程中会犯其他错误,导致大量不必要的复制。正是这种对象复制减慢了速度,而不是for循环代码本身。 - joran
2
这段代码使用 ddply() 在我的机器上运行10000次的示例时,实际上比OP使用普通的 for 循环的代码更慢。我非常想看看在OP的大数据集上这个表现如何。但我很怀疑如果不使用Rcpp,这个代码能否变得更快... - Theodore Lytras
3
不要使用ddply处理较大的数据,改用data.table。在SO上有很多示例。 - mnel
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接