我开始相信除了符号方便之外,数据框没有矩阵优势。但是,在矩阵和数据帧上运行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
随着行数的增加,时序结果更加明显。因此,这个问题有两个部分。
为什么矩阵的速度会变慢?转换为数据框似乎更快,运行unique
,然后转换回来。
有没有理由不将unique
包裹在myUnique
中,这会在第1部分进行转换?
注意1.鉴于矩阵是原子的,似乎unique
对于矩阵应该更快,而不是更慢。能够迭代固定大小的连续内存块通常比运行链接列表的单独块更快(我假设数据帧是如何实现的......)。
注意2.正如data.table
的表现所证明的那样,在数据框或矩阵上运行unique
是一个相对糟糕的想法 - 请参阅Matthew Dowle的答案和相关时间的评论。我已经将很多对象迁移到数据表中,这种性能是另一个原因。因此,虽然用户应该很好地采用数据表,但出于教学/社区的原因,我现在将问题留待为什么这对矩阵对象需要更长的时间。下面的答案解决了 时间的问题,以及如何我们可以获得更好的性能(即数据表)。 为什么的答案就在眼前 - 代码可以通过unique.data.frame
和unique.matrix
找到。 :)英语解释它正在做什么&为什么缺少这一切。
答案 0 :(得分:13)
在此实施中,unique.matrix
与unique.array
> identical(unique.array, unique.matrix)
[1] TRUE
unique.array
必须处理多维数组,这需要额外处理以“折叠”额外维度(对paste()
的额外调用),这在二维案例中是不需要的。代码的关键部分是:
collapse <- (ndim > 1L) && (prod(dx[-MARGIN]) > 1L)
temp <- if (collapse)
apply(x, MARGIN, function(x) paste(x, collapse = "\r"))
unique.data.frame
针对2D情况进行了优化,unique.matrix
未针对2D情况进行优化。正如你所说,它可能不在当前的实施中。
请注意,在所有情况下(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
是您想要的吗?
答案 1 :(得分:12)
不确定,但我想因为matrix
是一个连续的向量,R首先将其复制到列向量中(如data.frame
),因为paste
需要一个向量列表。请注意,两者都很慢,因为它们都使用paste
。
也许是因为unique.data.table
已经快了很多倍。请从R-Forge存储库下载升级到v1.6.7,因为它具有您在this question中引发的unique
修复程序。 data.table
不使用paste
来unique
。
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
答案 2 :(得分: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
上,可能会汇总结果。
虽然这解释了 的时间,但它没有解释为什么这些具有不同的实现,也没有解释从一种对象类型转换到另一种对象类型的效果。