【问题标题】:Finding number of unique values (quickly) across a large data.table在大型 data.table 中(快速)查找唯一值的数量
【发布时间】:2015-11-19 18:05:22
【问题描述】:

我有一个 1.5Mx7 data.table 需要处理。我编写的代码运行非常缓慢(每行 0.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() 工作得更快。

我需要为business_id 的每个分组计算user_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)

【问题讨论】:

  • 我没有读完所有这些,但从你的标题来看,也许你想要uniqueN
  • 就是这个。谢谢@Frank。

标签: r data.table


【解决方案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]

【讨论】:

    【解决方案2】:

    似乎length(unique()) 在计算因子变量的长度时效率很低,因为级别变得非常大。

    改用uniqueN()(感谢@Frank):

       user  system elapsed 
       0.12    0.00    0.12 
    

    使用set(review, NULL, "user_id", as.character(review$user_id))length(unique))

       user  system elapsed 
       0.11    0.00    0.11 
    

    【讨论】:

    • uniqueN 很可能会在下一个版本 (v1.9.8) 中进行优化以更快。
    猜你喜欢
    • 1970-01-01
    • 2020-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-13
    • 2020-02-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多