【问题标题】:What is the difference among eval(parse()), eval(str2lang()), eval(str2expression()), and eval(call()[[1]])?eval(parse())、eval(str2lang())、eval(str2expression()) 和 eval(call()[[1]]) 有什么区别?
【发布时间】:2020-04-23 06:41:04
【问题描述】:

我正在编写一个函数来从给定的 data.frame df 或环境 env 中提取作为字符串提供的变量。最初,我一直使用eval(parse(text=s), df, env) 构造来执行此操作,但我了解到还有更有效的替代方案。其他选项包括:

  1. eval(str2lang(s), df, env)
  2. eval(str2expression(s), df, env)
  3. eval(call(s)[[1]], df, env)

可能也有get的解决方案,但我不知道它是否可以先检查变量是否在df中,然后再转向env,如果不是。

使用microbenchmark,看来call是最快的:

library(microbenchmark)

x1 = 1
df = data.frame(x2 = 2)

microbenchmark(call = eval(call('x1')[[1]], df), 
               parse = eval(parse(text='x1'), df), 
               str2lang = eval(str2lang('x1'), df), 
               str2exp = eval(str2expression('x1'), df), 
               check = "identical")
#> Unit: microseconds
#>      expr    min      lq     mean  median      uq     max neval cld
#>      call  1.128  1.2115  1.60815  1.4585  1.6360   4.659   100  a 
#>     parse 39.183 39.8705 46.60755 40.2405 42.0415 135.462   100   b
#>  str2lang  2.235  2.3570  3.26144  2.5995  2.8925  24.641   100  a 
#>   str2exp  2.230  2.3200  2.81387  2.4780  2.6970  10.312   100  a

microbenchmark(call = eval(call('x2')[[1]], df), 
               parse = eval(parse(text='x2'), df), 
               str2lang = eval(str2lang('x2'), df), 
               str2exp = eval(str2expression('x2'), df), 
               check = "identical")
#> Unit: microseconds
#>      expr    min     lq     mean  median      uq     max neval cld
#>      call  1.124  1.194  1.47770  1.3675  1.5795   9.031   100  a 
#>     parse 38.254 38.762 40.21497 38.9630 39.3120 116.510   100   b
#>  str2lang  2.214  2.304  2.55036  2.3960  2.6530  10.639   100  a 
#>   str2exp  2.238  2.331  2.50011  2.4210  2.6515   3.619   100  a

reprex package (v0.3.0) 于 2020 年 4 月 23 日创建

因此,我倾向于使用call,但我想确保这样做不会产生任何意外后果,而不是使用其他解决方案。换句话说,在什么情况下(在我使用它们的上下文中)这四种方法会给出不同的答案,从而导致其中一种方法优于其他方法?

【问题讨论】:

    标签: r eval


    【解决方案1】:

    我认为这取决于您要评估的字符串。如果它总是一个名字,那么as.name('x1') 可能也是一个竞争者;我的测试表明它比你使用call 的解决方案慢一点,但我想说它更可取,因为它不那么晦涩难懂,而且不太可能引起未来的头痛。

    因此可能会出现令人头疼的问题:当前call("x1") 生成语言对象x1(),然后您提取被调用函数的名称。但是,如果在 R 的某个未来版本中它记得那个表达式中的 x1 应该是一个函数呢? eval(x1()) 已经知道要忽略名为 x1 的非函数对象,所以它不会破坏太多代码来进行更改,从效率的角度来看它可能很有用。

    as.name 也允许非字符参数,例如as.name(123);这对您来说可能是积极的或消极的。 (两种解决方案都允许"123"。)

    【讨论】:

      【解决方案2】:

      callas.symbol 都很好,我认为as.symbol 更可取。 as.symbol 是明确的 R 术语(as.name 是 S 术语)并且与 call 一样快。调用可以包含任何 R 对象,包括符号和其他调用,而且文档无处不在。因为字符串可能包含标点符号,所以我们无法确定 str2langstr2expression 在哪个点失败或可能会弹出什么错误消息。

      检查环境中是否存在变量比检查 data.frames 更安全。

      区别

      深入了解它们的主要区别在于它们的类:

      l = list(w = as.symbol("x"), x = str2lang("x"), y = str2expression("x"), z = call("x"))
      rbind(sapply(l, class), sapply(l, typeof), sapply(l, is.language))
           w        x        y            z         
      [1,] "name"   "name"   "expression" "call"    
      [2,] "symbol" "symbol" "expression" "language"
      [3,] "TRUE"   "TRUE"   "TRUE"       "TRUE"  
      

      一个很好的起点是R Language Definition,例如 Hadley Wickham 的 Advanced R (source) 中的表达式章节,然后阅读 ?call?parse、@987654337 @。

      它们是什么?

      评估 R 语言的设计 (source) 中给出了以下定义:

      调用的参数是表达式,可以用符号name命名。

      • 表达式 - 动作动作
      • 调用 - 表示调用函数的动作
      • 符号(名称) - 引用 R 对象 (2.1.3)

      什么时候使用?

      来自?call 文档:

      考虑使用str2lang(*) 而不是as.call(<string>),它是parse(text=*) 的有效版本。 call()as.call(),比基于 parse() 的方法更可取。

      使用pryr::show_c_source(.Internal(str2lang(s))) 我们可以看到str2langstr2expression 都调用 C function do_str2lang 但他们的论点不同。这个区别可以在?parse找到:

      • str2expression,等于 parse(text = "x", keep.source = F) 始终是一个表达式。
      • str2lang("x"),等于 parse(text = "x", keep.source = F)[[1]] 可以评估为调用OR符号、NULL、数字、整数或逻辑,即调用或更简单。李>

      R 语言定义中提到,表达式只有在传递给eval 时才会被评估,其他语言对象可能会在一些意外情况下被评估。这些情况是什么,我找不到。

      我们可以在所有方法中抛出一些边缘情况,看看什么时候可能会失败:

      • "\"" 读入数据时引用错误解析?
      • " " 空变量?变量名中的空格?
      • "_" 非法令牌(遗留赋值运算符)问题?
      • 反引号
      • 不适用
      # if it breaks, show how it breaks
      do <- function(x, ...) tryCatch(eval(x, ..1, ..2), error = function(t) t$message)
      check <- function(x, ...){
        list(
          do(call(x)[[1]], ...),
          do(as.symbol(x), ...),
          do(str2lang(x), ...),
          do(str2expression(x), ...)
        )
      }
      
      # test 1: some variables do not exist
      e <- new.env() ; e$x1 <- 5
      df = data.frame(x2 = 3)
      no_var <- lapply(list("x1", "x2", "\"", " ", "_", "`", NA_character_), check, df, e)
      
      # test 2: some variables exist
      e <- new.env() ; e$x1 = 5; e$`_` = 2 ; e$"\"" = 5; e$`NA` <- 5
      df = data.frame(x2 = 3, " " = 7, "_" = 2)
      var <- lapply(list("x1", "x2", "\"", " ", "_", "`", NA_character_), check, df, e)
      
      # difference in outcomes in
      var[3:7]
      no_var[3:7]
      

      当变量不存在时: as.symbolcall 总是到达 evalstr2langstr2expression 在所有情况下都会提前失败。当给定一个空字符串时,str2expressionstr2lang 不同。

      当变量存在时: as.symbolcall 在案例 3、4、7 中成功,而 str2langstr2expression 抛出错误。

      顺便说一句,在 data.frames 中存储变量时要小心,它们很容易损坏。

      names(data.frame(" " = 1, "_" = 2, "\"" = 3, "`" = 4))
      [1] "X."   "X_"   "X..1" "X..2"
      

      【讨论】:

        猜你喜欢
        • 2018-12-15
        • 2015-11-10
        • 1970-01-01
        • 2011-01-14
        • 1970-01-01
        相关资源
        最近更新 更多