提问人:microbe 提问时间:3/2/2011 最后编辑:MichaelChiricomicrobe 更新时间:7/4/2023 访问量:97408
我应该使用 data.frame 还是矩阵?
Should I use a data.frame or a matrix?
答:
部分答案已经包含在您的问题中:如果列(变量)可以预期为不同类型的(数字/字符/逻辑等),则使用数据框。矩阵用于相同类型的数据。
因此,只有在具有相同类型的数据时,选择 matrix/data.frame 才会有问题。
答案取决于你要如何处理 data.frame/matrix 中的数据。如果要将其传递给其他函数,则这些函数的参数的预期类型决定了选择。
也:
矩阵的内存效率更高:
m = matrix(1:4, 2, 2)
d = as.data.frame(m)
object.size(m)
# 216 bytes
object.size(d)
# 792 bytes
如果您计划进行任何线性代数类型的运算,矩阵是必不可少的。
如果经常按名称(通过紧凑的 $ 运算符)引用数据框的列,则数据框会更方便。
恕我直言,数据帧也更适合报告(打印)表格信息,因为您可以分别将格式应用于每列。
评论
@Michal没有提到的是,矩阵不仅比等效数据帧小,而且使用矩阵可以使代码比使用数据帧效率高得多,通常相当高效。这就是为什么在内部,许多 R 函数将强制到数据帧中的数据矩阵的原因之一。
数据框通常更方便;人们并不总是只有原子数据块。
请注意,您可以有一个字符矩阵;您不仅需要拥有数值数据才能在 R 中构建矩阵。
在将数据框转换为矩阵时,请注意有一个函数,该函数通过将因子转换为基于内部水平的数值来适当地处理因子。如果任何因子标签是非数字的,则强制通孔将生成字符矩阵。比较:data.matrix()
as.matrix()
> head(as.matrix(data.frame(a = factor(letters), B = factor(LETTERS))))
a B
[1,] "a" "A"
[2,] "b" "B"
[3,] "c" "C"
[4,] "d" "D"
[5,] "e" "E"
[6,] "f" "F"
> head(data.matrix(data.frame(a = factor(letters), B = factor(LETTERS))))
a B
[1,] 1 1
[2,] 2 2
[3,] 3 3
[4,] 4 4
[5,] 5 5
[6,] 6 6
我几乎总是使用数据框来执行数据分析任务,因为我通常不仅仅是数字变量。当我为包编写函数时,我几乎总是强制矩阵,然后将结果格式化为数据帧。这是因为数据框很方便。
评论
矩阵实际上是一个具有附加方法的向量。而 Data.Frame 是一个列表。 区别在于向量与列表。为了提高计算效率,请坚持使用 Matrix。如有必要,请使用 data.frame。
评论
@Michal:矩阵的内存效率并不高:
m <- matrix(1:400000, 200000, 2)
d <- data.frame(m)
object.size(m)
# 1600200 bytes
object.size(d)
# 1600776 bytes
...除非您有大量列:
m <- matrix(1:400000, 2, 200000)
d <- data.frame(m)
object.size(m)
# 1600200 bytes
object.size(d)
# 22400568 bytes
评论
data.frames
data.frame(a = rnorm(1e6), b = sample(letters, 1e6, TRUE))
matrix
矩阵和数据框是矩形 2D 数组,可以按行和列进行异构。他们分享了一些方法和 属性,但不是全部。
例子:
M <- list(3.14,TRUE,5L,c(2,3,5),"dog",1i) # a list
dim(M) <- c(2,3) # set dimensions
print(M) # print result
# [,1] [,2] [,3]
# [1,] 3.14 5 "dog"
# [2,] TRUE Numeric,3 0+1i
DF <- data.frame(M) # a data frame
print(DF) # print result
# X1 X2 X3
# 1 3.14 5 dog
# 2 TRUE 2, 3, 5 0+1i
M <- matrix(c(1,1,1,1,2,3,1,3,6),3) # a numeric matrix
DF <- data.frame(M) # a all numeric data frame
solve(M) # obtains inverse matrix
solve(DF) # obtains inverse matrix
det(M) # obtains determinant
det(DF) # error
我不能再强调两者之间的效率差异了!虽然 DF 在某些尤其是数据分析的情况下确实更方便,但它们也允许异构数据,并且一些库只接受它们,除非您为特定任务编写一次性代码,否则这些都是次要的。
让我举个例子。有一个函数可以计算 MCMC 方法的 2D 路径。基本上,这意味着我们取一个初始点 (x,y),并迭代某个算法以在每一步找到一个新点 (x,y),以这种方式构建整个路径。该算法涉及计算一个相当复杂的函数并在每次迭代时生成一些随机变量,因此当它运行 12 秒时,考虑到它每一步做了多少事情,我认为它很好。话虽如此,该函数收集了构造路径中的所有点以及 3 列 data.frame 中目标函数的值。所以,3列不是那么大,步数也超过了合理的10,000(在这种问题中,长度为1,000,000的路径是典型的,所以10,000不算什么)。所以,我认为 DF 10,000x3 绝对不是问题。使用 DF 的原因很简单。调用该函数后,调用 ggplot() 来绘制生成的 (x,y) 路径。ggplot() 不接受矩阵。
然后,出于好奇,我决定更改函数以收集矩阵中的路径。值得庆幸的是,DF 和矩阵的语法是相似的,我所做的只是将 df 指定为 data.frame 的行更改为将其初始化为矩阵的行。在这里,我还需要提到,在初始代码中,DF 被初始化为具有最终大小,因此在函数的代码中,只有新值被记录到已经分配的空间中,并且没有向 DF 添加新行的开销。这使得比较更加公平,也使我的工作更简单,因为我不需要在函数中进一步重写任何内容。只需将所需大小的 data.frame 的初始分配更改为相同大小的矩阵,只需更改一行。为了使新版本的函数适应 ggplot(),我将现在返回的矩阵转换为 data.frame 以在 ggplot() 中使用。
重新运行代码后,我简直不敢相信结果。代码在几分之一秒内运行!而不是大约 12 秒。同样,该函数在 10,000 次迭代期间仅读取和写入 DF 中已分配的空间(现在在矩阵中)的值。这种差异也适用于合理(或相当小)的尺寸 10000x3。
因此,如果你使用 DF 的唯一原因是让它与库函数(如 ggplot())兼容,你总是可以在最后一刻将其转换为 DF -- 只要你觉得方便,就可以使用矩阵。另一方面,如果有更实质性的理由使用测向表,例如您使用一些数据分析包,否则需要不断从矩阵转换到测向表并返回,或者您自己不进行任何密集的计算,只使用标准包(其中许多实际上在内部将测向转换为矩阵, 做他们的工作,然后把结果转换回来——所以他们为你做所有的效率工作),或者做一次性的工作,这样你就不在乎DF了,感觉更舒服了,那么你就不应该担心效率了。
或者一个更实用的规则:如果你有一个问题,比如在OP中,使用矩阵,所以只有在你没有这样的问题时,你才会使用DF(因为你已经知道你必须使用DF,或者因为你并不真正关心,因为代码是一次性的,等等)。
但总的来说,始终牢记这个效率点作为优先事项。
这是一个有趣的结果。使用矩阵与小块处理速度更快,但随着矩阵变大,差异会缩小。请注意,将 tibble 转换为矩阵的开销大于将 tibble 转换为矩阵的开销。从广义上讲,在矩阵空间中专门工作(无类型强制)比在 dplyr 空间中工作快约 20%。
library(dplyr)
# big (?) matrix
m <- matrix(runif(50000), 10000, 5, dimnames =list(NULL,c(letters[1:5])))
d <- as_tibble(m)
# return a matrix, convert tibble to matrix after operation
bench::mark(
zm <- apply(m, 2, FUN = \(x) cumprod(1+x)*100),
zd <- d |>
mutate(across(everything(),.fns = \(x) cumprod(1+x)*100)) |>
as.matrix()
)
#> # A tibble: 2 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:> <bch:> <dbl> <bch:byt> <dbl>
#> 1 zm <- apply(m, 2, FUN = function(x… 5.49ms 5.66ms 176. 2.52MB 8.67
#> 2 zd <- as.matrix(mutate(d, across(e… 6.9ms 7.05ms 140. 3.54MB 4.17
# return a tibble. Convert matrix to tibble after operation
bench::mark(
zm <- as_tibble(apply(m, 2, FUN = \(x) cumprod(1+x)*100)),
zd <- d |>
mutate(across(everything(),.fns = \(x) cumprod(1+x)*100))
)
#> # A tibble: 2 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:> <bch:> <dbl> <bch:byt> <dbl>
#> 1 zm <- as_tibble(apply(m, 2, FUN = … 5.71ms 5.92ms 167. 2.87MB 11.7
#> 2 zd <- mutate(d, across(everything(… 6.72ms 6.85ms 144. 788.62KB 4.18
# small matrix
m <- matrix(runif(500), 100, 5, dimnames =list(NULL,c(letters[1:5])))
d <- as_tibble(m,.name_repair = "unique")
# return a matrix
bench::mark(
zm <- apply(m, 2, FUN = \(x) cumprod(1+x)*100),
zd <- d |>
mutate(across(everything(),.fns = \(x) cumprod(1+x)*100)) |>
as.matrix()
)
#> # A tibble: 2 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:t> <bch:t> <dbl> <bch:byt> <dbl>
#> 1 zm <- apply(m, 2, FUN = function… 24.1µs 27.2µs 29303. 35.8KB 17.6
#> 2 zd <- as.matrix(mutate(d, across… 1.69ms 1.79ms 548. 19.2KB 17.6
# return a tibble
bench::mark(
zm <- as_tibble(apply(m, 2, FUN = \(x) cumprod(1+x)*100)),
zd <- d |>
mutate(across(everything(),.fns = \(x) cumprod(1+x)*100))
)
#> # A tibble: 2 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
#> 1 zm <- as_tibble(apply(m, 2, FU… 111.3µs 126.1µs 7586. 39.9KB 19.2
#> 2 zd <- mutate(d, across(everyth… 1.63ms 1.72ms 565. 15.2KB 17.1
创建于 2023-07-03 with reprex v2.0.2
上一个:使用行、列索引的矩阵中的索引值
评论