26

我已经开始相信数据框与矩阵相比没有优势,除了符号方便。但是,在矩阵和数据帧上运行时,我注意到了这种奇怪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

随着行数的增加,时序结果的差异甚至更大。所以,这个问题有两个部分。

  1. 为什么这对于矩阵来说更慢?unique转换为数据框,运行,然后转换回来 似乎更快。

  2. 有什么理由不只是换uniquemyUnique,第 1 部分中的转换是什么?


注意 1. 鉴于矩阵是原子的,似乎unique矩阵应该更快,而不是更慢。能够迭代固定大小的连续内存块通常应该比运行单独的链表块更快(我假设这就是数据帧的实现方式......)。

注意 2. 正如 的性能所证明的那样,在数据帧或矩阵上data.table运行unique是一个相对糟糕的主意 - 请参阅 Matthew Dowle 的答案和相对时间的评论。我已经将很多对象迁移到数据表中,而这种性能是这样做的另一个原因。因此,尽管应该很好地为用户提供采用数据表的服务,但出于教学/社区的原因,我将暂时保留这个问题,即为什么矩阵对象需要更长的时间。下面的答案解决了时间都去哪儿了,以及我们如何才能获得更好的性能(即数据表)。答案近在咫尺- 代码可以通过unique.data.frameunique.matrix. :) 对它在做什么的英文解释以及为什么缺少这一切。

4

3 回答 3

13
  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
于 2011-10-18T17:05:49.373 回答
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是你想要的吗?

于 2011-10-19T06:51:47.203 回答
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

对于数据框:

> 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

我们注意到矩阵版本在 、 和 上花费了apply大量paste时间lapply。相比之下,数据框版本简单运行duplicated.data.frame,大部分时间都花在paste,大概是聚合结果。

尽管这解释时间的去向,但并没有解释为什么它们有不同的实现,也没有解释从一种对象类型简单地更改为另一种对象类型的影响。

于 2011-10-18T15:36:05.467 回答