我很难理解setDT()
函数的本质。当我在SO上阅读代码时,我经常遇到使用data.table()
来创建data.table。当然data.table()
的使用无处不在。我觉得我坚定地理解setDT()
的本质,但?setDT
的相关性使我无法理解。 setDT
告诉我:
data.table
通过引用将列表(包括命名和未命名)和data.frames转换为data.tables 。
以及:
在
set*
用语中,所有setDT()
函数都通过引用更改其输入。也就是说,除了临时工作存储器之外,根本不会复制任何副本,而临时工作存储器就像一列一样大。
所以这让我觉得我应该只使用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>
只是data.table转换器的列表吗?
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()
函数能够执行class MyClass {
constructor(o) {
if (o == null) o = false
if (o.run) Promise.resolve()
.then(() => this.method())
.then(o.exit).catch(o.reject)
}
async method() {}
}
class Process {
static launch(construct) {
return new Promise(r => r(
new construct({run: true, exit: Process.exit, reject: Process.fatal})
)).catch(Process.fatal)
}
static exit() {
process.exit()
}
static fatal(e) {
console.error(e.message)
process.exit(1)
}
}
Process.launch(MyClass)
能够执行的操作?
答案 0 :(得分:26)
更新:
@Roland 在评论部分提出了一些好处,而帖子对他们来说更好。虽然我最初专注于内存溢出问题,但他指出,即使没有发生这种情况,各种副本的内存管理也需要大量时间,这是日常关注的问题。现在也增加了这两个问题的例子。
我喜欢stackoverflow上的这个问题,因为我认为它真的是在处理更大的数据集时避免R中的堆栈溢出。那些不熟悉data.table
set
家族setDT()
行动的人可能会从此次讨论中受益!
在处理占用大量RAM的较大数据集时应该使用setDT
因为操作将修改每个对象,从而节省内存。对于RAM占很小比例的数据,使用data.table的复制和修改很好。
data.frame
函数的创建实际上是受到堆栈溢出的后续线程的启发,它涉及使用大型数据集(几个GB)。你会看到Matt Dowle在建议'setDT'的名字。
Convert a data frame to a data.table without copy
更深入一点:
使用R,数据存储在内存中。这大大加快了速度,因为RAM比存储设备快得多。但是,当一个人的数据集占RAM的很大一部分时,就会出现问题。为什么?因为当对它们应用某些操作时,基础R倾向于复制每个data.frame
。这在3.1版之后有所改进,但是解决这个问题超出了本文的范围。如果将多个list
或data.frame
拉入一个data.table
或> 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"
>
,您的内存使用量将会相当快地扩展,因为在操作过程中的某个时刻,会有多个副本您的数据存在于RAM中。如果数据集足够大,则在生成所有副本时可能会耗尽内存,并且堆栈将溢出。请参阅下面的示例。我们得到一个错误,原始内存地址和对象类不会改变。
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"
的上述示例现在可以正常工作且没有错误。类和内存地址都会发生变化,不会发生任何副本。
setDT
@Roland指出,对于大多数人来说,更大的担忧是速度,这会受到如此密集使用内存管理的副作用。下面是一个示例,其中包含较小的数据,不会导致cpu崩溃,并说明了此作业的快data <- data.table(data)
。请注意data
之后'tracemem'的结果,制作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
对它来说要快得多。
library(data.table)
vignette("datatable-reference-semantics")
设置函数可以在许多方面使用,而不仅仅是在将对象转换为data.tables时。您可以通过调用主题上的插图来找到有关参考语义以及如何将其应用于其他地方的更多信息。
data.table
这是一个很好的问题,那些考虑将R用于更大的数据集或只想加速数据操作活动的人,可以从熟悉Dim sht As Worksheet
Set sht = ActiveSheet
With sht.AutoFilter
With .Filters
ReDim filtarr(1 To .Count, 1 To 4) ' change array
For f = 1 To .Count
With .Item(f)
If .On Then
filtarr(f, 1) = .Criteria1
filtarr(f, 4) = Cells(1, f) 'field
Debug.Print .Criteria1, Cells(1, f)
If .Operator Then
filtarr(f, 2) = .Operator
filtarr(f, 3) = .Criteria2
Debug.Print .Operator & ", " & .Criteria2
End If
End If
End With
Next f
End With
End With
引用语义的显着性能改进中受益。
答案 1 :(得分:13)
setDT()
不是data.table()
的替代品。它是as.data.table()
的一种更有效的替代品,可用于某些类型的对象。
mydata <- as.data.table(mydata)
会复制mydata
后面的对象,将副本转换为data.table
,然后将mydata
符号更改为指向副本。setDT(mydata)
会将mydata
后面的对象更改为data.table
。没有复制。那么使用setDT()
的现实情况是什么?当你无法控制原始数据的类时。例如,大多数用于处理数据库的包都会提供data.frame
输出。在这种情况下,您的代码将类似于
mydata <- dbGetQuery(conn, "SELECT * FROM mytable") # Returns a data.frame
setDT(mydata) # Make it a data.table
什么时候应该使用as.data.table(x)
?每当x
不是list
或data.frame
时。最常见的用途是矩阵。