何时应该使用setDT()而不是data.table()来创建data.table? [英] When should I use setDT() instead of data.table() to create a data.table?
问题描述
我很难理解setDT()
函数的本质。当我阅读SO上的代码时,我经常遇到使用setDT()
来创建data.table。当然,data.table()
的用法无处不在。我觉得我确实理解了data.table()
的本质,但setDT()
的相关性却让我摸不着头脑。?setDT
告诉我:
setDT
通过引用将列表(已命名和未命名)和数据帧转换为data.ables。
以及:
在data.table
术语中,所有set*
函数通过引用更改其输入。也就是说,除了与一列一样大的临时工作内存外,根本不会创建任何副本。
这让我认为我应该只使用setDT()
来创建一个data.table,对吗?setDT()
只是列表到数据表的转换器吗?
library(data.table)
a <- letters[c(19,20,1,3,11,15,22,5,18,6,12,15,23)]
b <- seq(1,41,pi)
ab <- data.frame(a,b)
d <- data.table(ab)
e <- setDT(ab)
str(d)
#Classes ‘data.table’ and 'data.frame': 13 obs. of 2 variables:
# $ a: Factor w/ 12 levels "a","c","e","f",..: 9 10 1 2 5 7 11 3 8 4 ...
# $ b: num 1 4.14 7.28 10.42 13.57 ...
# - attr(*, ".internal.selfref")=<externalptr>
str(e)
#Classes ‘data.table’ and 'data.frame': 13 obs. of 2 variables:
# $ a: Factor w/ 12 levels "a","c","e","f",..: 9 10 1 2 5 7 11 3 8 4 ...
# $ b: num 1 4.14 7.28 10.42 13.57 ...
# - attr(*, ".internal.selfref")=<externalptr>
在这种情况下似乎没有区别。在另一个实例中,差异是显而易见的:
ba <- list(a,b)
f <- data.table(ba)
g <- setDT(ba)
str(f)
#Classes ‘data.table’ and 'data.frame': 2 obs. of 1 variable:
# $ ba:List of 2
# ..$ : chr "s" "t" "a" "c" ...
# ..$ : num 1 4.14 7.28 10.42 13.57 ...
# - attr(*, ".internal.selfref")=<externalptr>
str(g)
#Classes ‘data.table’ and 'data.frame': 13 obs. of 2 variables:
# $ V1: chr "s" "t" "a" "c" ...
# $ V2: num 1 4.14 7.28 10.42 13.57 ...
# - attr(*, ".internal.selfref")=<externalptr>
什么时候应该使用setDT()
?什么使setDT()
具有相关性?为什么不使原始data.table()
函数能够执行setDT()
能够执行的操作?
推荐答案
更新:
@Roland在评论区提出了一些很好的观点,而这篇帖子对他们来说更好。虽然我最初专注于内存溢出问题,但他指出,即使不发生这种情况,各种副本的内存管理也会花费大量时间,这是更常见的日常问题。现在还添加了这两个问题的示例。
我喜欢这个关于堆栈溢出的问题,因为我认为它实际上是关于在处理更大的数据集时避免R中的堆栈溢出。😊不熟悉data.table
系列set
操作的人可以从本讨论中受益!
setDT()
,因为该操作将在适当位置修改每个对象,从而节省内存。对于只占RAM很小百分比的数据,可以使用data.table的复制和修改。
setDT
函数的创建实际上是受到以下堆栈溢出线程的启发,该线程是关于处理大型数据集(几GB)的。您将看到Matt Dowle插话,建议使用‘setDT’名称。
Convert a data frame to a data.table without copy
再深入一点:
使用R,数据存储在内存中。这大大加快了速度,因为RAM的访问速度比存储设备快得多。然而,当一个人的数据集是RAM的一大部分时,可能会出现问题。为什么?因为当对每个data.frame
应用某些操作时,基数R有复制它们的倾向。这一点在3.1版之后有所改进,但解决这个问题超出了本文的讨论范围。如果将多个data.frame
或list
拉入一个data.frame
或data.table
中,则内存使用量会迅速增加,因为在操作过程中的某个时刻,RAM中存在数据的多个副本。如果数据集足够大,则在生成所有副本时可能会耗尽内存,堆栈将溢出。请参阅下面的示例。我们收到一个错误,并且对象的原始内存地址和类没有更改。
> N <- 1e8
> P <- 1e2
> data <- as.data.frame(rep(data.frame(rnorm(N)), P))
>
> pryr::object_size(data)
800 MB
>
> tracemem(data)
[1] "<0000000006D2DF18>"
>
> data <- data.table(data)
Error: cannot allocate vector of size 762.9 Mb
>
> tracemem(data)
[1] "<0000000006D2DF18>"
> class(data)
[1] "data.frame"
>
只需就地修改对象而不进行复制的能力是一件大事。这就是setDT
接受list
或data.frame
并返回data.table
时所做的事情。与上面使用setDT
的示例相同,现在可以很好地工作,并且没有错误。类和内存地址都会更改,并且不会进行任何复制。
> tracemem(data)
[1] "<0000000006D2DF18>"
> class(data)
[1] "data.frame"
>
> setDT(data)
>
> tracemem(data)
[1] "<0000000006A8C758>"
> class(data)
[1] "data.table" "data.frame"
@Roland指出,对于大多数人来说,更大的担忧是速度,这是如此密集地使用内存管理的副作用。以下是一个不会使CPU崩溃的较小数据的示例,并说明了setDT
对此作业的速度有多快。请注意data <- data.table(data)
后面的‘tracemem’的结果,即复制data
。与此形成对比的是setDT(data)
,它不打印任何副本。然后,我们必须调用tracemem(data)
以查看新的内存地址。
> N <- 1e5
> P <- 1e2
> data <- as.data.frame(rep(data.frame(rnorm(N)), P))
> pryr::object_size(data)
808 kB
> # data.table method
> tracemem(data)
[1] "<0000000019098438>"
> data <- data.table(data)
tracemem[0x0000000019098438 -> 0x0000000007aad7d8]: data.table
tracemem[0x0000000007aad7d8 -> 0x0000000007c518b8]: copy as.data.table.data.frame as.data.table data.table
tracemem[0x0000000007aad7d8 -> 0x0000000018e454c8]: as.list.data.frame as.list vapply copy as.data.table.data.frame as.data.table data.table
> class(data)
[1] "data.table" "data.frame"
>
> # setDT method
> # back to data.frame
> data <- as.data.frame(data)
> class(data)
[1] "data.frame"
> tracemem(data)
[1] "<00000000125BE1A0>"
> setDT(data)
> tracemem(data)
[1] "<00000000125C2840>"
> class(data)
[1] "data.table" "data.frame"
>
这对时间安排有何影响?如我们所见,setDT
对它来说要快得多。
> # timing example
> data <- as.data.frame(rep(data.frame(rnorm(N)), P))
> microbenchmark(setDT(data), data <- data.table(data))
Unit: microseconds
expr min lq mean median max neval uq
setDT(data) 49.948 55.7635 69.66017 73.553 100.238 100 79.198
data <- data.table(data) 54594.289 61238.8830 81545.64432 64179.131 611632.427 100 68647.917
集函数可用于许多领域,而不仅仅是在将对象转换为data.ables时使用。通过调用主题上的Vignette,您可以找到有关引用语义以及如何在其他地方应用它们的更多信息。
library(data.table)
vignette("datatable-reference-semantics")
这是一个很好的问题,那些考虑将R用于更大的数据集或只想加快数据操作活动的人,可以从熟悉data.table
引用语义的显著性能改进中受益。
这篇关于何时应该使用setDT()而不是data.table()来创建data.table?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!