我很难理解setDT()函数的本质.当我阅读SO上的代码时,我经常遇到使用setDT()来创建数据.桌子当然,data.table()的使用无处不在.我觉得我已经牢牢地理解了data.table()的本质,但setDT()的相关性却让我不知所措.?setDT告诉我:

setDT转换列表(已命名和未命名)和数据.帧到数据.表by reference.

以及:

data.table种说法,所有set*个函数都通过引用来更改输入.也就是说,除了一列大小的临时工作内存之外,根本不复制任何内容.

这让我觉得我应该只用setDT()来制作一个数据.桌子,对吗?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()的功能呢?

推荐答案

Update:

@Roland在 comments 部分给出了一些好的观点,这篇帖子对他们来说更好.虽然我最初关注的是内存溢出问题,但他指出,即使不发生这种情况,各种拷贝的内存管理也需要大量时间,这是一个更常见的日常问题.这两个问题的例子现在也被添加.

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

在处理占用大量RAM的较 Big Data 集时,应该使用setDT(),因为该操作将修改每个对象,从而节省内存.对于占RAM很小百分比的数据,使用数据.表的复制和修改是可以的.

setDT函数的创建实际上受到了以下堆栈溢出线程的启发,这是关于处理大型数据集(几GB)的.你会看到Matt Dowle chime在一篇文章中建议使用"setDT"名称.

Convert a data frame to a data.table without copy

A bit more depth:

使用R,数据存储在内存中.这大大加快了速度,因为RAM的访问速度比存储设备快得多.然而,当一个人的数据集是RAM的很大一部分时,就会出现问题.为什么?因为当一些操作应用于base R时,base R倾向于复制每个data.frame.这在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"

@罗兰指出,对大多数人来说,更大的担忧是速度,这是大量使用内存管理的副作用.下面是一个小数据的例子,它不会使cpu崩溃,并且说明了setDT对于这个工作来说要快得多.注意data <- data.table(data)之后的"TraceMe"结果,制作data的副本.与setDT(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"
> 

这对时间安排有何影响?正如我们所见,setDTsetDT快得多.

> # 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

Set函数可用于许多领域,而不仅仅是在将对象转换为数据时.桌子.您可以通过调用主题的vignette,找到有关引用语义的更多信息,以及如何在其他地方应用它们.

library(data.table)    
vignette("datatable-reference-semantics")

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

R相关问答推荐

如何使用R以NASAGIBS.ViirsEarthAtNight2012风格绘制自定义 map

如何按行和列组合多个格式?

使用lares::corr_var函数在for循环中分配变量的问题

指定要保留在wrap_plots中的传奇

保存shiny 的代码嗅探器:避免$ Symbol问题

工作流程_set带有Dplyrr风格的 Select 器,用于 Select 结果和预测因子R

gt()从gt为相同内容的单元格 colored颜色 不同?

如何在R中合并和合并多个rabrame?

在df中保留原始变量和新变量

将小数分隔符放在R中的前两位数字之后

合并DFS列表并将索引提取为新列

如何在R中描绘#符号?

在使用tidyModels和XGBoost的二进制分类机器学习任务中,所有模型都失败

在列表中排列R数据框中的列顺序

有没有可能用shiny 的书签恢复手风琴面板?

是否可以将线性模型的p值添加到tbl_summary中

R基于变量组合创建新的指标列

我需要使用ggplot2制作堆叠条形图

R预测包如何处理ARIMA(Auto.arima函数)中的缺失值

使用列名和r中的前缀 Select 列的CREATE函数