通过引用传递函数时处理 R data.table 中的无效 selfref

Posted

技术标签:

【中文标题】通过引用传递函数时处理 R data.table 中的无效 selfref【英文标题】:Handling invalid selfref in R data.table when passing by reference to a function 【发布时间】:2014-09-07 16:10:04 【问题描述】:

我的小组使用 data.table 编写了很多代码,我们偶尔会被“检测到无效 .internal.selfref 并通过获取整个表的副本来修复...”警告所困扰。当通过引用函数传递数据表时,这种行为可能会破坏我们的代码,而我正试图弄清楚如何解决它。

假设我有一个函数可以将一列添加到 data.table 作为副作用——注意原始 data.table 没有返回。

foo <- function(mydt)
   mydt[, c := c("a", "b")]
   return(123)
)

> x<- data.table(a=c(1,2), b=c(3,4))
> foo(x) 
[1] 123
> x
   a b c
1: 1 3 a
2: 2 4 b

x 已更新为新列。这是期望的行为。

现在假设发生了一些事情,破坏了 x 中的内部自引用:

> x<- data.table(a=c(1,2), b=c(3,4))
> x[["a"]] <- c(7,8)
> foo(x)
[1] 123
Warning message:
In `[.data.table`(mydt, , `:=`(c, c("a", "b"))) :
Invalid .internal.selfref detected and fixed by taking a copy ...

 > x
    a b
 1: 7 3
 2: 8 4

我了解(大部分)发生了什么。 [["a"]] 构造对 data.table 不友好; x 被转换为数据框,然后返回到数据表,这不知何故弄乱了内部工作。然后在foo()里面,在添加列的引用操作过程中,检测到了这个问题,复制了一份mydt;新列“c”已添加到 mydt。但是,该复制操作切断了 x 和 mydt 之间的按引用传递关系,因此附加列不是 x 的一部分。

函数 foo() 将被不同的人使用,并且很难防止无效的内部 selfref 情况。那里的人可能很容易做类似 x[["a"]] 的事情,这会导致输入无效。我试图弄清楚如何从 foo 内部处理这个问题。

到目前为止我有这个想法,在foo()的开头:

if(!data.table:::selfrefok(mydt)) stop("mydt is corrupt.")

这至少让我们有机会发现问题,但它对 foo() 的用户不是很友好,因为这些输入被破坏的方式可能非常不透明。理想情况下,我希望能够纠正损坏的输入并保持 foo() 的所需功能。但是我看不到如何,除非我重组我的代码以便 foo 返回 mydt 并将其分配给调用范围内的 x ,这是可能的,但并不理想。有什么想法吗?

【问题讨论】:

【参考方案1】:

你应该阅读整个警告......

然后你会注意到

在更早的时候,这个 data.table 已经被 R 复制了(或者是使用 structure() 或类似方法手动创建的)。避免 key

[[&lt;-names&lt;-attr&lt;- 相似,因为它会创建一个副本。

可以确保按引用的行为是用substitute构造调用,然后在父框架中求值

foo <- function(x) 
   l <- substitute(x[,c := 'a'], as.list(match.call())['x']); 
   eval.parent(l)
   return(123)

xx<- data.table(a=c(1,2), b=c(3,4))
xx[["a"]] <- c(7,8)
foo(xx)
# [1] 123
# Warning message: .....

# but it now works!
xx
#    a b c
# 1: 7 3 a
# 2: 8 4 a

警告仍然存在,但功能按预期工作。

【讨论】:

是的,我知道它会创建一个副本。挑战在于我不能保证我的代码的用户不会使用 [[ 【参考方案2】:

@pteehan,好问题!在我看来,更简洁的解决方法是在分配步骤本身期间恢复过度分配,并带有 警告 基本上说 “不要这样做! ”。

这样做的方法是通过[[&lt;-.data.table 方法,目前不存在。除非我遗漏了什么,否则它会是一个很好的补充,其目的不是鼓励使用它,而是为了抓住这样的案例并引导人们正确使用(带有警告),同时恢复超额分配。

大致:

`[[<-.data.table` <- function(x, i, j, value) 
    warning("Don't do this. Use := instead.")
    call = sys.call()
    call[[1L]] = `[[<-.data.frame`
    ans = copy(eval(call, envir=parent.frame()))


foo <- function(mydt) 
   mydt[, c := c("a", "b")]
   return(123)

x <- data.table(a = c(1,2), b = c(3,4))

x[["a"]] <- c(7,8)
# Warning message:
# In `[[<-.data.table`(`*tmp*`, "a", value = c(7, 8)) :
#   Don't do this. Use := instead.

data.table:::selfrefok(x)
# [1] 1

foo(x)
# [1] 123

x
#    a b c
# 1: 7 3 a
# 2: 8 4 b

我相信这些方面的东西应该提供一个更清洁的解决方案。也许这应该得到实施。

PS:This post 详细解释了您问题中出现警告的原因。

【讨论】:

以上是关于通过引用传递函数时处理 R data.table 中的无效 selfref的主要内容,如果未能解决你的问题,请参考以下文章

使用带有R内核的jupyter笔记本,如何通过引用来抑制打印结果更新data.table?

R之data.table 介绍

data.table 包中的 := (按引用传递)运算符同时修改另一个数据表对象

R:尝试转换 data.table 时出错

R语言基因组数据分析可能会用到的data.table函数整理

当data.table:=是最后一个操作时,R函数返回而不返回data.table对象[duplicate]