使用data.table作为参数,在函数中使用deparse(substitute())的含义是什么?

13

如果我想解析函数的参数以便于处理错误或警告,但是当函数内部参数转换为data.table时,会出现一些奇怪的问题:

e <- data.frame(x = 1:10)
### something strange is happening
foo <- function(u) {
  u <- data.table(u)
  warning(deparse(substitute(u)), " is not a data.table")
  u
}
foo(e)

##  foo(e)
##      x
##  1:  1
##  2:  2
##  3:  3
##  4:  4
##  5:  5
##  6:  6
##  7:  7
##  8:  8
##  9:  9
## 10: 10
## Warning message:
## In foo(e) :
##   structure(list(x = 1:10), .Names = "x", row.names = c(NA, -10L), class = c("data.table", "data.frame"), .internal.selfref = <pointer: 0x10026568>) is not a data.table

如果在使用data.table之前对其进行解析,一切都正常:

### ok
foo1 <- function(u) {
  nu <- deparse(substitute(u))
  u <- data.table(u)
  warning(nu, " is not a data.table")
  u
}
## foo1(e)
##      x
##  1:  1
##  2:  2
##  3:  3
##  4:  4
##  5:  5
##  6:  6
##  7:  7
##  8:  8
##  9:  9
## 10: 10
## Warning message:
## In foo1(e) : e is not a data.table
顺便说一下,如果e已经是一个data.table或者不是,都没有区别。 我故意这样做的,当我正在分析一些代码时,deparse非常耗费时间,因为e相当大。 这里发生了什么,我如何处理data.framedata.table输入的这种函数? nachti
2个回答

16
这是因为在处理普通变量而不是promise对象时,substitute 的行为会有所不同。promise对象是形式参数,并且具有包含生成它的表达式的特殊槽位。换句话说,promise对象是函数中作为参数列表一部分的变量。当您在函数中使用 substitute 处理promise对象时,它将检索分配给该形式参数的函数调用中的表达式。从 ?substitute 中查看更多信息:
"通过检查解析树的每个组件来进行替换:如果它不是环境中的绑定符号,则不变。如果它是 promise 对象(即函数的形式参数)或者显式地使用 delayedAssign() 创建的,则用 promise 的表达式槽替换该符号。如果它是普通变量,则其值被替换,除非 env 是 .GlobalEnv,在这种情况下该符号保持不变。"
在您的情况下,您实际上使用以下代码用新值覆盖了原始的 promise 变量:
u <- data.table(u)

在这个时候,u 成为一个包含数据表的普通变量。当你在此之后对 u 进行 substitute 操作时,substitute 只会返回数据表,然后 deparse 将其处理回生成它的 R 语言,这就是为什么它很慢的原因。

这也解释了为什么你的第二个示例可以工作。在变量成为 promise(即在覆盖 u 之前)时执行 substitute。这也是第二个问题的答案。要么在覆盖 promise 之前进行替换,要么不要覆盖 promise。

有关详细信息,请参见 R 语言定义(promise)的2.1.8 节,这里是我摘录的:

 

Promise 对象是 R 的惰性求值机制的一部分。它们包含三个插槽:值、表达式和环境。当调用函数时,将匹配参数,然后将每个形式参数都绑定到一个 promise 上。给定该形式参数的表达式和指向调用函数的环境的指针存储在 promise 中。


@nachti,这难道不回答了你的问题吗? - BrodieG
@BrodieG:感谢您的回答。如上所述:我该如何处理data.framedata.table输入的这些函数?我应该复制它(需要很多空间)吗?还是先解析一切,然后再覆盖它? - nachti
1
@nachti,后者先解析。此外,如果您想避免副本,应考虑使用setDT而不是data.table。前者通过引用创建数据表。 - BrodieG
也许在第一个示例中可以使用delayedAssign("u", data.table(u))代替u<-data.table(u) - Aaron McDaid
@AaronMcDaid 我觉得那样做并没有帮助;你试过了吗?期望的输出很可能是 警告:data.table(e) 不是一个 data.table - BrodieG
你说得对。我没有测试过它。感谢您的检查!在我的当前测试中,它解析为data.table(u),而我猜问者想要的是data.table(e)或者只是e - Aaron McDaid

0

你可以使用 sprintfis.data.table 来完成这个任务。

> e <- data.frame(x = 1:10)
> foo <- function(u){
      nu <- deparse(substitute(u))
      if(!is.data.table(u)){
          warning(sprintf('%s is not a data table', nu))
          u
      } else {
          u
      }
  }
> foo(e)
    x
1   1
2   2
3   3
4   4
5   5
6   6
7   7
8   8
9   9
10 10
Warning message:
In foo(e) : e is not a data table

感谢您的回答!@Richard:好主意,但这仍然不是一个data.table,例如我不能使用:=。它应该在一个函数内使用,该函数接受data.framedata.table作为输入,在data.table语法中执行某些操作,如果输入是data.frame则将其转换回data.frame。顺便说一下,u很大(约250 MB)。 - nachti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接