我应该何时使用setDT()而不是data.table()来创建data.table?

Dod*_*dge 36 r data.table

我很难掌握setDT()功能的本质.当我在SO上阅读代码时,我经常遇到setDT()创建data.table的用法.当然,data.table()无处不在的使用.我觉得我领悟扎实的性质data.table()还没有相关的setDT()逃避我.?setDT告诉我这个:

setDT通过引用将列表(包括命名和未命名)和data.frames转换为data.tables .

以及:

data.table说法,所有set*函数都通过引用来改变它们的输入.也就是说,除了临时工作存储器之外,根本不会复制任何副本,而临时工作存储器与一列一样大.

所以这让我觉得我应该只setDT()用来制作一个data.table,对吗?是setDT()一个简单的列表data.table转换器?

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>
Run Code Online (Sandbox Code Playgroud)

在这种情况下看似没有区别.在另一个例子中,差异很明显:

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>
Run Code Online (Sandbox Code Playgroud)

我应该什么时候使用setDT()?什么使setDT()相关?为什么不让原始data.table()功能能够setDT()做到能做什么?

Jus*_*tin 29

更新:

@Roland在评论部分提出了一些好处,而帖子对他们来说更好.虽然我最初专注于内存溢出问题,但他指出,即使没有发生这种情况,各种副本的内存管理也需要相当长的时间,这是日常关注的问题.现在也添加了这两个问题的例子.

我喜欢stackoverflow上的这个问题,因为我认为在处理更大的数据集时,它确实是为了避免R中的堆栈溢出.那些不熟悉操作data.table系列的人set可以从这个讨论中受益!

setDT()在处理占用大量RAM的较大数据集时应该使用,因为操作将修改每个对象,从而节省内存.对于RAM占很小比例的数据,使用data.table的复制和修改很好.

这个setDT函数的创建实际上是受到堆栈溢出的后续线程的启发,它涉及使用大型数据集(几个GB).你会看到Matt Dowle在建议'setDT'的名字.

将数据帧转换为data.table而不复制

更深入一点:

使用R,数据存储在内存中.这大大加快了速度,因为RAM比存储设备快得多.但是,当一个人的数据集占RAM的很大一部分时,就会出现问题.为什么?因为data.frame当对它们应用某些操作时,基础R倾向于制作每个副本.这在3.1版之后有所改进,但是解决这个问题超出了本文的范围.如果将多个data.frames或lists 拉入一个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"
>
Run Code Online (Sandbox Code Playgroud)

只需在不复制的情况下修改对象的能力是一件大事.这就是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"
Run Code Online (Sandbox Code Playgroud)

@Roland指出,对于大多数人来说,更大的担忧是速度,它会受到如此密集使用内存管理的副作用.这是一个较小的数据示例,它不会使cpu崩溃,并说明了setDT这项工作的速度有多快.注意"tracemem"的结果data <- data.table(data),制作副本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"
> 
Run Code Online (Sandbox Code Playgroud)

这对时间有何影响?我们可以看到,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
Run Code Online (Sandbox Code Playgroud)

设置函数可以在许多方面使用,而不仅仅是在将对象转换为data.tables时.您可以通过调用主题上的插图来找到有关引用语义的更多信息以及如何将它们应用于其他地方.

library(data.table)    
vignette("datatable-reference-semantics")
Run Code Online (Sandbox Code Playgroud)

这是一个很好的问题,那些考虑将R用于更大的数据集或只想加速数据操作活动的人,可以从熟悉data.table参考语义的显着性能改进中受益.

  • 你过分关注内存.通过引用和副本进行的更改也具有巨大的速度影响,这对大多数人来说更为重要.一个好的答案应该包含一些基准.此外,您不应过多依赖古代帖子中有关这些问题的信息.Base R的内存管理自2009年以来不断发展. (3认同)
  • 我认为你没有抓住我的意思。副本不仅需要额外的内存,而且内存管理需要大量时间。这就是为什么避免复制不仅节省内存而且节省时间。 (2认同)

Nat*_*rth 16

setDT()不是替代品data.table().它是一种更有效的替代品as.data.table(),可用于某些类型的物体.

  • mydata <- as.data.table(mydata)将复制后面的对象mydata,将副本转换为a data.table,然后将mydata符号更改为指向副本.
  • setDT(mydata)将后面的对象mydata改为a data.table.没有复制.

那么使用什么是现实的情况setDT()呢?当你无法控制原始数据的类时.例如,大多数用于处理数据库的包都会提供data.frame输出.在这种情况下,您的代码将是类似的

mydata <- dbGetQuery(conn, "SELECT * FROM mytable")  # Returns a data.frame
setDT(mydata)                                        # Make it a data.table
Run Code Online (Sandbox Code Playgroud)

你应该什么时候使用as.data.table(x)?每当x不是listdata.frame.最常见的用途是矩阵.

  • 小挑剔:在你的第一个项目符号中,当你指的是mydata [符号]时,你引用一个变量(https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Symbol-对象). (3认同)