在大型data.table中查找(快速)唯一值的数量

时间:2015-11-19 18:05:23

标签: r data.table

我需要处理1.5Mx7 data.table。我写的代码运行速度非常慢(每行18秒,估计完成75小时),我希望我可以优化它。

我会把伪示例代码放在最后,因为它很长。

str(review)

Classes ‘data.table’ and 'data.frame':  1500000 obs. of  7 variables:
 $ user_id     : Factor w/ 375000 levels "aA1aJ9lJ1lB5yH5uR6jR7",..: 275929 313114 99332 277686 57473 31780 236964 44371 210127 217770 ...
 $ stars       : int  2 1 3 3 1 1 2 1 2 2 ...
 $ business_id : Factor w/ 60000 levels "aA1kR2bK6nH8yQ9gU2uI9",..: 40806 29885 43018 58297 58444 31626 26018 2493 37883 34204 ...
 $ votes.funny : int  3 0 0 7 2 9 6 8 2 7 ...
 $ votes.useful: int  4 1 0 5 9 2 4 7 4 9 ...
 $ votes.cool  : int  5 3 6 8 3 2 0 8 10 9 ...
 $ IDate       : IDate, format: "2012-01-01" "2012-01-01" "2012-01-01" ...
 - attr(*, ".internal.selfref")=<externalptr> 
 - attr(*, "sorted")= chr "IDate"

我需要按日期对数据集进行子集化,然后按business_id计算多个列。

setkey(review, IDate)

system.time(
  review[
    #(IDate >= window.start) & (IDate <= window.end),
    1:10,
    .SD, 
    keyby = business_id
  ][
    ,
    list(
      review.num = .N,
      review.users = length(unique(user_id)),
      review.stars = mean(stars),
      review.votes.funny = sum(votes.funny),
      review.votes.useful = sum(votes.useful),
      review.votes.cool = sum(votes.cool)
    ),
    by = business_id
  ]
)

   user  system elapsed 
  1.534   0.000   1.534 

示例数据集的较小版本的时间是

# 1% of original size - 15000 rows
   user  system elapsed 
   0.02    0.00    0.02 

# 10% of original size - 150000 rows
   user  system elapsed 
   0.25    0.00    0.25 

所以,即使我只处理了10行,时间也随着原始数据集的大小而增加。

我尝试评论上面的review.users变量,并且原始数据集的计算时间大幅下降:

   user  system elapsed 
      0       0       0 

所以,我的挑战是让unique()更快地完成工作。

我需要为user_id的每个分组计算business_id中的唯一值。

不确定还有什么要说明,但我很乐意回答问题。

以下是一些用于创建伪示例数据集的代码。我不确定减速的确切原因是什么,所以我试图尽可能具体地重新创建数据,但由于随机变量的处理时间太长,我将尺寸减小了~90%

z <- c()
x <- c()

for (i in 1:6000) {
  z <<- c(z, paste0(
    letters[floor(runif(7, min = 1, max = 26))],
    LETTERS[floor(runif(7, min = 1, max = 26))],
    floor(runif(7, min = 1, max = 10)),
    collapse = ""
  ))
}

z <- rep(z, 25)

for (i in 1:37500) {
  x <<- c(x, paste0(
    letters[floor(runif(7, min = 1, max = 26))],
    LETTERS[floor(runif(7, min = 1, max = 26))],
    floor(runif(7, min = 1, max = 10)),
    collapse = ""
  ))
}

x <- rep(x, 4)

review2 <- data.table(
  user_id = factor(x),
  stars = as.integer(round(runif(150000) * 5, digits = 0)),
  business_id = factor(z),
  votes.funny = as.integer(round(runif(150000) * 10, digits = 0)),
  votes.useful = as.integer(round(runif(150000) * 10, digits = 0)),
  votes.cool = as.integer(round(runif(150000) * 10, digits = 0)),
  IDate = rep(as.IDate("2012-01-01"), 150000)
)

setkey(review2, IDate)

2 个答案:

答案 0 :(得分:1)

似乎Fragment在计算因子变量的长度时效率很低,因为水平变得非常大。

改为使用length(unique())(感谢@Frank):

uniqueN()

使用 user system elapsed 0.12 0.00 0.12 set(review, NULL, "user_id", as.character(review$user_id))

length(unique))

答案 1 :(得分:1)

这个怎么样 - 在匿名函数中使用额外data.table的唯一替代方法:

review2[,{

  uid <- data.table(user_id)
  rev_user <- uid[, .N, by = user_id][, .N]

  #browser()
  list(
    review.num = .N,
    review.users = rev_user,
    review.stars = mean(stars),
    review.votes.funny = sum(votes.funny),
    review.votes.useful = sum(votes.useful),
    review.votes.cool = sum(votes.cool)
)}, by = business_id]