【问题标题】:get(x) does not work in R data.table when x is also a column in the data table当 x 也是数据表中的一列时,get(x) 在 R data.table 中不起作用
【发布时间】:2018-01-12 21:12:50
【问题描述】:

我注意到当 x 也是同一个数据表中的一列时,get(x) 在 R 数据表中不起作用。请参阅下面的代码 sn-p。在编写将数据表作为输入的 R 函数时,这很难完全避免。这是 R data.table 包中的错误吗?谢谢!

library(data.table)

dt = data.table(x=1:3, y=2:4)

var = 'y'
x = 'y'

dt[, 3*get(var)]      # [1] 6 9 12
dt[, 3*get(x)]        # Error in get(x): invalid first argument

【问题讨论】:

  • 有没有其他人对此评估感到困惑:dt[, 3*get(var)]get(var) -> "y"; 3 * "y"?例如,dt[,3*"y"] 给出错误...
  • 在我看来 data.table 实现中的一个错误。很明显,x 被称为对象,它应该优先于列名。
  • @RichScriven 我不确定这是否是一个真正的重复,在那个问题中,参考是在i 中评估的,而这个问题是在j 中评估的。我不确定,但通过深入阅读data.table 文档,我得到的印象是行为可能完全不同。

标签: r data.table


【解决方案1】:

一般来说,当列和变量之间存在命名冲突时,列会优先。从 data.table 的 v1.10.2(2017 年 1 月 31 日)开始,澄清名称是不是列名的首选方法是使用 .. 前缀 [1]:

当 j 是一个以 .. 为前缀的符号时,它将在调用范围内查找,其值被视为列名或数字。 当您看到.. 前缀时,请考虑上一级,就像所有操作系统中的目录.. 都表示父目录。 将来,.. 前缀可以用于在DT[...] 内任何地方出现的所有符号。 ...

我们认为.. 实现的主要重点是解决var 在调用范围内且var 也是列名时更常见的歧义。此外,我们没有忘记过去我们建议您自己在调用作用域中为变量添加前缀..。如果你这样做了并且..var 存在于调用范围中,那么它仍然有效,前提是调用范围中既不存在var,也不存在..var 作为列名。现在请在调用范围中删除 ..var 上的 .. 前缀以整理此内容。将来 data.table 将开始对此类使用发出警告/错误。

在您的情况下,您可以 get(..x) 强制名称 x 在调用范围内而不是在 data.table 环境中解析:

library(data.table)

dt = data.table(x=1:3, y=2:4)

var = 'y'
x = 'y'

dt[, 3*get(var)]      # [1] 6 9 12
dt[, 3*get(x)]        # Error in get(x): invalid first argument
dt[, 3*get(..x)]      # [1]  6  9 12

.. 前缀仍处于试验阶段,因此文档有限,但在data.table 的帮助页面上简要提及:

默认情况下with=TRUEjx的框架内计算;列名可以用作变量。如果数据集中和父范围内的变量名称重叠,您可以使用双点前缀 ..cols 明确引用 'cols 变量父范围,而不是来自您的数据集。

这不是一个错误,而是with = T 允许在数据环境中使用列作为变量的一个不幸但自然的结果。实际上,您可以通过使用get()posenvir 参数以更基本的R 方式避免此问题。

【讨论】:

    【解决方案2】:

    新答案

    根据@Frank 和this section of the vignette I can't believe I hadn't read before 的建议,这里有一个不允许执行任意代码的解决方案。

    library(data.table)
    dt = data.table(x=1:3, y=2:4)
    
    x = "y"
    ExecuteMeLater = substitute(3*x, list(x=as.symbol(x)))
    dt[, eval(ExecuteMeLater)]
    
    # [1]  6  9 12
    

    这种行为尤其是我更喜欢这种解决方案的原因:

    x = "(system(paste0('kill ',Sys.getpid())))"
    ExecuteMeLater = substitute(3*x, list(x=as.symbol(x)))
    dt[, eval(ExecuteMeLater)]
    
    #Error in eval(jsub, SDenv, parent.frame()) : 
    #  object '(system(paste0('kill ',Sys.getpid())))' not found
    

    原答案

    注意:遇到类似useful resource 这样性质的问题...也许可以在某个时候用不那么老套的解决方案进行更新。

    get() 的行为无疑为意想不到的结果敞开了大门,而且过去似乎不止有几个some github issues 提到了这一点。老实说,我已经进行了大量的调查,但我仍然没有完全完全遵循正确的用法。

    解决此问题的一种方法是将表达式粘贴在一起并在data.table 环境之外评估您的函数输入列名称并将其存储为字符。

    然后,通过在data.table 环境中解析和评估预先构造的表达式,我们避免了表中名为x 的列优先于变量x 的内容的任何机会。

    library(data.table)
    
    dt = data.table(x=1:3, y=2:4)
    
    x = 'y'
    ExecuteMeLater <- paste0("3*",x)  ## "3*y"
    dt[, eval(parse(text = ExecuteMeLater))]
    

    输出:

    [1]  6  9 12
    

    不是最漂亮的解决方案,但过去它对我有用过无数次。

    关于eval(parse(...))可能的假设世界末日场景的快速免责声明

    关于危险的eval(parse(...)) 有更多的in depth discussions,但我会避免全部重复。

    理论上你可能有问题,如果你的专栏被命名为"(system(paste0('kill ',Sys.getpid())))"这样不幸的东西(不要执行,它会当场终止你的R会话) 。这可能足以让你在外面睡不着觉,除非你打算把它放在 CRAN 上的一个包裹里。

    【讨论】:

    • 从链接的问题跟进,我会做expr = substitute(3*x, list(x=as.symbol(x))); dt[, eval(expr)]。这通常是我在j 中处理表达式的方式——首先组合它们,然后使用eval。当j 以编程方式组合stackoverflow.com/a/41619112 时,这也是我所知道的利用GForce 的唯一可靠方法
    • 非常感谢!我感觉有一种“安全”的方法可以做到这一点,但我发现自己在阅读evalparseget 等的文档时没有取得任何真正的进展。将用这个更新我对另一个问题的回答!
    • 赞成,因为即使这不再是惯用的解决方案,您至少努力防止代码注入
    【解决方案3】:

    这是来自 get 函数中第一个参数的 R 文档:“对象名称(以字符串形式给出)。”

    所以dt[, 3*get("x")] 应该可以工作。

    【讨论】:

    • 我认为关键是 OP 定义了x = 'y',就像他定义了var = 'y' 一样。他希望得到与dt[, 3*get(var)] 相同的结果,但这个建议给出了不同的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-14
    • 1970-01-01
    • 2013-11-03
    • 2011-06-13
    • 2021-09-12
    • 1970-01-01
    相关资源
    最近更新 更多