【问题标题】:How to evaluate a glue expression in a nested function?如何评估嵌套函数中的胶水表达式?
【发布时间】:2021-12-14 21:50:57
【问题描述】:

我正在尝试嵌套一个将两个字符串粘合在一起的函数,该函数使用组合字符串来命名数据框的列。但是,问题似乎是胶水表达式没有足够早地评估为字符串。我可以(并且应该)在将表达式作为参数传递给另一个函数之前强制对其进行评估吗?

library(tidyverse)

# define inner function
add_prefix <- function(string) {
  x <- glue::glue("prefix_{string}")
  return(as.character(x))
}

# define outer function
mod_mtcars <- function(df, name) {
  df %>% 
    mutate({{ name }} := mpg ^ 2)
}

mod_mtcars(mtcars, add_prefix("foo"))
#> Error: The LHS of `:=` must be a string or a symbol

# alternative outer function with explicit enquoting
mod_mtcars2 <- function(df, name) {
  name <- ensym(name)
  
  df %>% 
    mutate(!!name := mpg ^ 2)
}

mod_mtcars2(mtcars, add_prefix("foo"))
#> Error: Only strings can be converted to symbols

reprex package (v2.0.1) 于 2021-10-30 创建

【问题讨论】:

    标签: r metaprogramming tidyeval


    【解决方案1】:

    name 不是符号。试试:

    mod_mtcars <- function(df, name) {
      name <- sym(name)
      df %>%
        mutate({{ name }} := mpg ^ 2)
    }
    
    mod_mtcars(mtcars, add_prefix("foo"))
    

    更棒的是,tidy eval 现在支持glue strings,因此您可以通过以下方式进行简化:

    # don't need add_prefix()
    mod_mtcars <- function(df, name){
      df %>% 
        mutate("prefix_{{name}}" := mpg ^ 2)
    }
    
    mod_mtcars(mtcars, foo)
    

    最后,curly-curly 操作符隧道化你的表达式并且你传递给它一个字符串。如果您保留当前功能,请改用 bang-bang 运算符:

    mod_mtcars <- function(df, name){
      df %>% 
        mutate(!! name := mpg ^ 2)
    }
    
    mod_mtcars(mtcars, add_prefix("foo"))
    

    【讨论】:

    • 嗨 LMc,您也可以使用胶水注入 "{" 而不是 "{{",这是现在的首选语法。我们正试图从 !! 转移到数据分析任务。请参阅下面的答案。
    • @LionelHenry 感谢您的评论,并期待下周发布整洁的评估文档。我使用{{ 允许用户传递引用的表达式foo 而不是"foo"。我在那个代码块中有一个使用示例。
    【解决方案2】:

    我们可能需要对输入进行转义(保持 OP 的原始功能不变)

    mod_mtcars(mtcars, !!add_prefix("foo"))
    

    -输出

                       mpg cyl  disp  hp drat    wt  qsec vs am gear carb prefix_foo
    Mazda RX4           21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4     441.00
    Mazda RX4 Wag       21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4     441.00
    Datsun 710          22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1     519.84
    Hornet 4 Drive      21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1     457.96
    Hornet Sportabout   18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2     349.69
    Valiant             18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1     327.61
    Duster 360          14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4     204.49
    ...
    

    【讨论】:

    • 这似乎可能会增加更多的混乱,我会修复这个函数。意图似乎是让 name 成为一个常规参数,而不是一个化解/引用的参数。
    • @LionelHenry 这是有道理的。谢谢
    【解决方案3】:

    首先,胶合字符串语法现在比直接在 LHS 中更受欢迎。所以更喜欢"{{ var }}" := expr 而不是{{ var }} := expr。在 rlang 的未来版本(明年)中,我们将可以使用带有 = 的胶水字符串。届时,:= 将几乎被取代。在添加胶水支持之前,我们使用 := 允许在 LHS 上注入 !!

    其次,您的问题是您使用的是{{ 而不是简单的注入。 {{ 用于注入作为参数提供的表达式,而不是表达式的。使用带有"{" 的普通胶水插值代替注入值:

    mod_mtcars <- function(df, name) {
      df %>% 
        mutate("{name}" := mpg ^ 2)
    }
    

    PS:你的!! 版本也有类似的问题。因为您在参数上使用了ensym(),所以您正在解析作为参数提供的表达式而不是使用值。但是ensym() 要求表达式是一个简单的名称,并且您提供了完整的计算,从而导致错误。你可以这样修复它:

    mod_mtcars2 <- function(df, name) {
      df %>%
        mutate(!!name := mpg ^ 2)
    }
    

    但现在首选胶合语法。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多