为什么在数据帧上运行"独特"比在R中的矩阵更快?

Ite*_*tor 26 performance r matrix dataframe data.table

我开始相信数据框没有矩阵优势,除了符号方便.但是,当unique在矩阵和数据帧上运行时,我注意到这种奇怪:它似乎在数据帧上运行得更快.

a   = matrix(sample(2,10^6,replace = TRUE), ncol = 10)
b   = as.data.frame(a)

system.time({
    u1 = unique(a)
})
 user  system elapsed
1.840   0.000   1.846


system.time({
    u2 = unique(b)
})
 user  system elapsed
0.380   0.000   0.379
Run Code Online (Sandbox Code Playgroud)

随着行数的增加,定时结果更加明显.所以,这个问题有两个部分.

  1. 为什么矩阵的速度会变慢?转换为数据帧,运行unique,然后转换回来似乎更快.

  2. 有什么理由不只是包装uniquemyUnique,它执行转换部分#1?


注意1.鉴于矩阵是原子的,unique对于矩阵来说似乎应该更快,而不是更慢.能够迭代固定大小的连续内存块通常比运行在链接列表的单独块上更快(我假设数据帧是如何实现的......).

注2:如表现所示data.table,unique在数据框或矩阵上运行是一个相对糟糕的想法 - 请参阅Matthew Dowle的答案和相关时间的评论.我已经将很多对象迁移到数据表中,这种性能是另一个原因.因此,尽管用户应该很好地采用数据表,但出于教学/社区的原因,我现在要问的问题是,为什么这需要花费更长的时间来处理矩阵对象.下面地址的答案,其中没有时间去了,怎么回事,我们可以得到更好的性能(即数据表).这个问题的答案,为什么就在眼前-该代码可以通过发现unique.data.frameunique.matrix.:)一个英语解释它正在做什么以及为什么缺乏这一切.

All*_*rdt 13

  1. 在这个实现中,unique.matrix是一样的unique.array

    > identical(unique.array, unique.matrix)

    [1] TRUE

  2. unique.array必须处理多维数组,这需要额外的处理来"折叠"在paste()二维情况下不需要的额外维度(那些额外的调用).代码的关键部分是:

    collapse <- (ndim > 1L) && (prod(dx[-MARGIN]) > 1L)

    temp <- if (collapse) apply(x, MARGIN, function(x) paste(x, collapse = "\r"))

  3. unique.data.frame针对2D情况进行了优化,unique.matrix不是.正如你所说的那样,它可能不在当前的实现中.

请注意,在所有情况下(unique.{array,matrix,data.table}),如果存在多个维度,则为唯一性进行比较的字符串表示形式.对于浮点数,这意味着15个十进制数字

NROW(unique(a <- matrix(rep(c(1, 1+4e-15), 2), nrow = 2)))

1

NROW(unique(a <- matrix(rep(c(1, 1+5e-15), 2), nrow = 2)))

NROW(unique(a <- matrix(rep(c(1, 1+4e-15), 1), nrow = 2)))

都是2.你确定 unique你想要的吗?


Mat*_*wle 12

  1. 不确定,但我想因为matrix是一个连续的向量,R首先将它复制到列向量中(如a data.frame),因为paste需要一个向量列表.请注意,两者都很慢,因为两者都使用paste.

  2. 也许因为unique.data.table已经快很多倍了.请通过从R-Forge存储库下载来升级到v1.6.7,因为这样可以解决unique您在此问题中提出的问题.data.table不习惯pasteunique.

a = matrix(sample(2,10^6,replace = TRUE), ncol = 10)
b = as.data.frame(a)
system.time(u1<-unique(a))
   user  system elapsed 
   2.98    0.00    2.99 
system.time(u2<-unique(b))
   user  system elapsed 
   0.99    0.00    0.99 
c = as.data.table(b)
system.time(u3<-unique(c))
   user  system elapsed 
   0.03    0.02    0.05  # 60 times faster than u1, 20 times faster than u2
identical(as.data.table(u2),u3)
[1] TRUE
Run Code Online (Sandbox Code Playgroud)


Ite*_*tor 9

在试图回答我自己的问题时,尤其是第1部分,我们可以通过查看结果看到时间花在哪里Rprof.我用5M元素再次运行它.

以下是第一个独特操作(对于矩阵)的结果:

> summaryRprof("u1.txt")
$by.self
                     self.time self.pct total.time total.pct
"paste"                   5.70    52.58       5.96     54.98
"apply"                   2.70    24.91      10.68     98.52
"FUN"                     0.86     7.93       6.82     62.92
"lapply"                  0.82     7.56       1.00      9.23
"list"                    0.30     2.77       0.30      2.77
"!"                       0.14     1.29       0.14      1.29
"c"                       0.10     0.92       0.10      0.92
"unlist"                  0.08     0.74       1.08      9.96
"aperm.default"           0.06     0.55       0.06      0.55
"is.null"                 0.06     0.55       0.06      0.55
"duplicated.default"      0.02     0.18       0.02      0.18

$by.total
                     total.time total.pct self.time self.pct
"unique"                  10.84    100.00      0.00     0.00
"unique.matrix"           10.84    100.00      0.00     0.00
"apply"                   10.68     98.52      2.70    24.91
"FUN"                      6.82     62.92      0.86     7.93
"paste"                    5.96     54.98      5.70    52.58
"unlist"                   1.08      9.96      0.08     0.74
"lapply"                   1.00      9.23      0.82     7.56
"list"                     0.30      2.77      0.30     2.77
"!"                        0.14      1.29      0.14     1.29
"do.call"                  0.14      1.29      0.00     0.00
"c"                        0.10      0.92      0.10     0.92
"aperm.default"            0.06      0.55      0.06     0.55
"is.null"                  0.06      0.55      0.06     0.55
"aperm"                    0.06      0.55      0.00     0.00
"duplicated.default"       0.02      0.18      0.02     0.18

$sample.interval
[1] 0.02

$sampling.time
[1] 10.84
Run Code Online (Sandbox Code Playgroud)

对于数据框:

> summaryRprof("u2.txt")
$by.self
                     self.time self.pct total.time total.pct
"paste"                   1.72    94.51       1.72     94.51
"[.data.frame"            0.06     3.30       1.82    100.00
"duplicated.default"      0.04     2.20       0.04      2.20

$by.total
                        total.time total.pct self.time self.pct
"[.data.frame"                1.82    100.00      0.06     3.30
"["                           1.82    100.00      0.00     0.00
"unique"                      1.82    100.00      0.00     0.00
"unique.data.frame"           1.82    100.00      0.00     0.00
"duplicated"                  1.76     96.70      0.00     0.00
"duplicated.data.frame"       1.76     96.70      0.00     0.00
"paste"                       1.72     94.51      1.72    94.51
"do.call"                     1.72     94.51      0.00     0.00
"duplicated.default"          0.04      2.20      0.04     2.20

$sample.interval
[1] 0.02

$sampling.time
[1] 1.82
Run Code Online (Sandbox Code Playgroud)

我们注意的是,矩阵版本花费了大量的时间apply,pastelapply.相比之下,数据框版本简单运行duplicated.data.frame并且大部分时间都用于paste,可能是聚合结果.

虽然这说明那里的时候是怎么回事,它没有解释为什么这些有不同的实现,也不是简单地从一个对象类型转换到另一种效果.