我应该使用 data.frame 还是矩阵?

Should I use a data.frame or a matrix?

提问人:microbe 提问时间:3/2/2011 最后编辑:MichaelChiricomicrobe 更新时间:7/4/2023 访问量:97408

问:

什么时候应该使用 ,什么时候使用 ?data.framematrix

两者都以矩形格式保存数据,因此有时不清楚。

对于何时使用哪种数据类型,是否有任何一般的经验法则?

矩阵 数据帧 R-FAQ

评论

0赞 xApple 9/4/2013
通常,矩阵可能更适合特定类型的数据,但是如果要用于分析所述矩阵的包需要数据帧,则始终必须对其进行不必要的转换。我认为没有办法避免重复哪个包使用哪个包。

答:

182赞 Michał 3/2/2011 #1

部分答案已经包含在您的问题中:如果列(变量)可以预期为不同类型的(数字/字符/逻辑等),则使用数据框。矩阵用于相同类型的数据。

因此,只有在具有相同类型的数据时,选择 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

如果您计划进行任何线性代数类型的运算,矩阵是必不可少的。

如果经常按名称(通过紧凑的 $ 运算符)引用数据框的列,则数据框会更方便。

恕我直言,数据帧也更适合报告(打印)表格信息,因为您可以分别将格式应用于每列。

评论

5赞 Bajcz 3/28/2017
我要补充的一件事是,如果您打算使用 ggplot2 包来制作图表,ggplot2 仅适用于 data.frames,而不适用于矩阵。只是需要注意的事情!
79赞 Gavin Simpson 3/2/2011 #2

@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

我几乎总是使用数据框来执行数据分析任务,因为我通常不仅仅是数字变量。当我为包编写函数时,我几乎总是强制矩阵,然后将结果格式化为数据帧。这是因为数据框很方便。

评论

0赞 microbe 3/2/2011
我也一直想知道data.matrix()和as.matrix()之间的区别。感谢您澄清它们和您在编程中的技巧。
0赞 YJZ 7/20/2015
感谢您分享@Gavin辛普森!您能介绍一下如何从 1-6 回到 a-f 吗?
1赞 Gavin Simpson 7/20/2015
@YZhang 您需要存储每个因子的标签和一个逻辑向量,以指示矩阵的哪些列是因子。然后,将那些作为因子的列转换回具有正确标签的因子将相对简单。注释不是代码的好地方,所以看看之前是否问过这个问题,如果没有问一个新问题。
11赞 user8341 3/2/2011 #3

矩阵实际上是一个具有附加方法的向量。而 Data.Frame 是一个列表。 区别在于向量与列表。为了提高计算效率,请坚持使用 Matrix。如有必要,请使用 data.frame。

评论

3赞 Gavin Simpson 3/2/2011
嗯,矩阵是一个有维度的向量,我不明白方法在哪里?
50赞 petrelharp 2/3/2012 #4

@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

评论

0赞 MichaelChirico 12/13/2017
内存效率的论点实际上是关于提供比列类型更大的灵活性。 由于类型强制,内存中的内存将比版本小得多(根据我的快速计算为 6 倍)。data.framesdata.frame(a = rnorm(1e6), b = sample(letters, 1e6, TRUE))matrix
0赞 Trisquel 12/10/2017 #5

矩阵和数据框是矩形 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
1赞 Vadim 10/25/2018 #6

我不能再强调两者之间的效率差异了!虽然 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,或者因为你并不真正关心,因为代码是一次性的,等等)。

但总的来说,始终牢记这个效率点作为优先事项。

0赞 Art 7/4/2023 #7

这是一个有趣的结果。使用矩阵与小块处理速度更快,但随着矩阵变大,差异会缩小。请注意,将 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