何时应该使用setDT()而不是data.table()来创建data.table? [英] When should I use setDT() instead of data.table() to create a data.table?

查看:4
本文介绍了何时应该使用setDT()而不是data.table()来创建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操作的人可以从本讨论中受益!

在处理占用大量RAM的较大数据集时,应使用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.framelist拉入一个data.framedata.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接受listdata.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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆