R 在函数中通过引用向data.table添加新列并不总是有效

R 在函数中通过引用向data.table添加新列并不总是有效,r,data.table,R,Data.table,在编写一个依赖于data.table的包时,我发现了一些奇怪的行为。我有一个函数,可以通过引用删除和重新排序某些列,它工作得很好,这意味着数据。我传入的表在没有分配函数输出的情况下被修改了。但是,我还有一个函数可以添加新的列,但是这些更改并不总是保留在传入的data.table中 下面是一个小例子: library(data.table) # I'm using 1.9.4 test <- data.table(id = letters[1:2], val=1:2) foobar <

在编写一个依赖于
data.table
的包时,我发现了一些奇怪的行为。我有一个函数,可以通过引用删除和重新排序某些列,它工作得很好,这意味着
数据。我传入的表
在没有分配函数输出的情况下被修改了。但是,我还有一个函数可以添加新的列,但是这些更改并不总是保留在传入的
data.table

下面是一个小例子:

library(data.table)  # I'm using 1.9.4
test <- data.table(id = letters[1:2], val=1:2)
foobar <- function(dt, col) {
    dt[, (col) := 1]
    invisible(dt)
}

test
#  id val
#1: a   1
#2: b   2
saveRDS(test, "test.rds")
test2 <- readRDS("test.rds")
all.equal(test, test2)
#[1] TRUE
foobar(test, "new")
test
#  id val new
#1: a   1   1
#2: b   2   1
foobar(test2, "new")
test2
#  id val
#1: a   1
#2: b   2
但是添加到
test2
仍然不起作用:

foobar(test2, "someothercol")
.Last.value
#  id val someothercol
#1: a   1            1
#2: b   1            1
test2
#  id val
#1: a   1
#2: b   1
我无法确定我看到这种行为的所有情况,但保存到RDS并从RDS读取是我可以可靠复制的第一个情况。向CSV写入和从CSV读取似乎没有相同的问题

这是指针问题吗,就像序列化data.table会破坏过度分配的指针一样?有没有简单的方法来恢复它们?如何在函数中检查它们,以便在操作不起作用时恢复指针或错误

我知道我可以将函数输出指定为一种解决方法,但这不是很好的
数据。这不也会在内存中创建一个临时副本吗

对阿伦解决方案的回应 Arun指出,这确实是一个指针问题,可以通过
truelength
诊断,并通过
setDT
alloc.col
修复。我在将他的解决方案封装到函数中时遇到了一个问题(从上面的代码继续):

func
这是指针问题还是这个问题,就像序列化data.table会破坏过度分配的指针一样

是从磁盘加载将外部指针设置为NULL。我们将不得不再次超额分配

有没有简单的方法来恢复它们

对。您可以测试data.table的
truelength()
,如果
0
,则在其上使用
setDT()
alloc.col()

truelength(test2) # [1] 0
if (!truelength(test2))
    setDT(test2)
truelength(test2) # [1] 100

foobar(test2, "new")
test2[]
#    id val new
# 1:  a   1   1
# 2:  b   2   1
这可能会成为常见问题解答(不记得在那里看到过)。
已在警告消息部分中

这是指针问题还是这个问题,就像序列化data.table会破坏过度分配的指针一样

是从磁盘加载将外部指针设置为NULL。我们将不得不再次超额分配

有没有简单的方法来恢复它们

对。您可以测试data.table的
truelength()
,如果
0
,则在其上使用
setDT()
alloc.col()

truelength(test2) # [1] 0
if (!truelength(test2))
    setDT(test2)
truelength(test2) # [1] 100

foobar(test2, "new")
test2[]
#    id val new
# 1:  a   1   1
# 2:  b   2   1
这可能会成为常见问题解答(不记得在那里看到过)。

已经在警告信息部分。

精彩的问题/解释/重复的例子/动机。荣誉我甚至可以在开发版本上复制这一点。顺便说一句,在你的函数IMO中,你不需要
不可见(dt)
部分。@DavidArenburg谢谢!我知道我本可以省略它,但出于某种原因,我想禁止打印。再想一想,显示函数输出和原始data.table之间的差异可能会更清楚。哦,好吧。难以置信的问题/解释/可复制的例子/动机。荣誉我甚至可以在开发版本上复制这一点。顺便说一句,在你的函数IMO中,你不需要
不可见(dt)
部分。@DavidArenburg谢谢!我知道我本可以省略它,但出于某种原因,我想禁止打印。再想一想,显示函数输出和原始data.table之间的差异可能会更清楚。哦,好吧。有没有理由选择
setDT
而不是
alloc.col
?没有理由。我只是选择了
setDT()
,因为
alloc.col
不会以不可见的方式返回结果,所以我必须用
invisible()
来包装它<代码>setDT()
因此看起来更短。哦,好吧,这是有道理的。因为它将在函数中,所以这一步的不可见性并不重要,所以我认为
alloc.col
更好地传达意图。感谢您快速准确的回复@Arun!实际上,这在函数内部似乎不起作用,无论是
setDT
还是
alloc.col
。我可以使用
browser
查看块触发器和bump up
truelength
,但是我传入的表仍然没有修改,调用后它的
truelength
仍然是0,例如
func!这让我得出了一个我希望避免的结论(必须重新分配到某个地方),但至少副本是肤浅的。是否有理由选择
setDT
而不是
alloc.col
?没有理由。我只是选择了
setDT()
,因为
alloc.col
不会以不可见的方式返回结果,所以我必须用
invisible()
来包装它<代码>setDT()
因此看起来更短。哦,好吧,这是有道理的。因为它将在函数中,所以这一步的不可见性并不重要,所以我认为
alloc.col
更好地传达意图。感谢您快速准确的回复@Arun!实际上,这在函数内部似乎不起作用,无论是
setDT
还是
alloc.col
。我可以使用
browser
查看块触发器和bump up
truelength
,但是我传入的表仍然没有修改,调用后它的
truelength
仍然是0,例如
func!这让我得出了一个我希望避免的结论(必须重新分配到某个地方),但至少副本是肤浅的。
truelength(test2) # [1] 0
if (!truelength(test2))
    setDT(test2)
truelength(test2) # [1] 100

foobar(test2, "new")
test2[]
#    id val new
# 1:  a   1   1
# 2:  b   2   1