为什么在数据框列上操作比矩阵列更快?

3
我有以下数据:
height = 1:10000000
length = -(1:10000000)
body_dim = data.frame(height,length)
body_dim_mat = as.matrix(body_dim)

which()在数据框中为什么比矩阵快?

> microbenchmark(body_dim[which(body_dim$height==50000),"length"])
Unit: milliseconds
                                                expr      min       lq   median       uq      max neval
 body_dim[which(body_dim$height == 50000), "length"] 124.4586 125.1625 125.9281 127.9496 284.9824   100

> microbenchmark(body_dim_mat[which(body_dim_mat[,1] == 50000),2])
Unit: milliseconds
                                               expr      min       lq   median      uq     max neval
 body_dim_mat[which(body_dim_mat[, 1] == 50000), 2] 251.1282 252.4457 389.7251 400.313 1004.25   100
2个回答

5

一个data.frame是一个列表,而一个column则是一个简单的向量(vector),非常容易从列表中提取。一个matrix则是带有维度属性的向量。哪些值属于一个column需要从维度中计算出来。这会影响到子集(subset)操作,这也是你在基准测试中包含的内容:

library(microbenchmark)

set.seed(42)
m <- matrix(rnorm(1e5), ncol=10)
DF <- as.data.frame(m)

microbenchmark(m[,1], DF[,1], DF$V1)
#Unit: microseconds
#   expr    min     lq median      uq      max neval
# m[, 1] 80.997 82.536 84.230 87.1560 1147.795   100
#DF[, 1] 15.399 16.939 20.789 22.6365  100.090   100
#  DF$V1  1.849  2.772  3.389  4.3130   90.235   100

然而,重点不是你应该总是使用 data.frame。因为如果你进行子集操作,结果不是向量:
microbenchmark(m[1:10, 1:10], DF[1:10, 1:10])
# Unit: microseconds
#           expr     min       lq   median      uq      max neval
#  m[1:10, 1:10]   1.233   1.8490   3.2345   3.697   11.087   100
# DF[1:10, 1:10] 211.267 219.7355 228.2050 252.226 1265.131   100

有趣的额外测试:取 m[1:1e4],因为 R 内部将矩阵视为具有 dim 属性的向量。结果发现这比 m[,1] 稍微快一点。 - Carl Witthoft

1

看起来问题出现在which()之前,与对整个矩阵进行子集化相比,对data.frame列进行子集化似乎更快:

microbenchmark(body_dim$height==50000)
# Unit: milliseconds
#                      expr      min       lq   median       uq      max neval
#  body_dim$height == 50000 138.2619 148.5132 170.1895 170.8909 249.4592   100

microbenchmark(body_dim_mat[,1]==50000)
# Unit: milliseconds
#                       expr     min       lq   median       uq      max neval
# body_dim_mat[, 1] == 50000 299.599 308.6066 310.9036 354.4641 432.7833   100

顺便说一下,这种情况正是 data.table 可以发挥作用的地方:
require(data.table)
dt <- data.table(body_dim, key="height")
microbenchmark(dt[J(50000)]$length, unit="ms")
# Unit: milliseconds
#                 expr     min      lq   median       uq      max neval
#  dt[J(50000)]$length 0.96637 0.97908 0.989772 1.025257 2.588402   100

你的 data.table 查询的类比,对于一个数据框 df,可能是 df$length[50000](data.table 慢 20 倍);子集比较可能是 dt[height==50000] vs df[df$height==50000,](data.table 快 2 倍)。 - Martin Morgan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接