dplyr无需硬编码变量名

18

是否可以在不硬编码变量名称的情况下使用dplyr的mutate函数?例如,以下代码有效,因为我硬编码了变量名Var1:

            > d=expand.grid(1:3, 20:22)
            > d
            Var1 Var2
            1    1   20
            2    2   20
            3    3   20
            4    1   21
            5    2   21
            6    3   21
            7    1   22
            8    2   22
            9    3   22
            > d=mutate(d, x=percent_rank(Var1))
            > d
            Var1 Var2     x
            1    1   20 0.000
            2    2   20 0.375
            3    3   20 0.750
            4    1   21 0.000
            5    2   21 0.375
            6    3   21 0.750
            7    1   22 0.000
            8    2   22 0.375
            9    3   22 0.750
然而,当我把变量的名称作为变量时,它就不再起作用了:
            > my.variable='Var1'
            > d=mutate(d, x=percent_rank(my.variable))
            > d
                Var1 Var2   x
            1    1   20 NaN
            2    2   20 NaN
            3    3   20 NaN
            4    1   21 NaN
            5    2   21 NaN
            6    3   21 NaN
            7    1   22 NaN
            8    2   22 NaN
            9    3   22 NaN

eval() 和 as.symbol() 函数似乎也无法帮助解决这个问题。


为什么你想要这样做?你正在将x设置为字符字符串的percent_rank,你还期望发生什么? - rawr
3个回答

10

伟大的 Hadley Wickham 本人(他的名字万福!)在 mutatr Google 群组上建议了这个

d <- expand.grid(1:3, 20:22)
my.variable <- 'Var1'
percent_rank <- function(x) rank(x)/max(rank(x))
call <- substitute(mutate(d, percent_rank(var)), 
                   list(var = as.name(my.variable)))
eval(call)
#   Var1 Var2 percent_rank(Var1)
# 1    1   20              0.250
# 2    2   20              0.625
# 3    3   20              1.000
# 4    1   21              0.250
# 5    2   21              0.625
# 6    3   21              1.000
# 7    1   22              0.250
# 8    2   22              0.625
# 9    3   22              1.000

1
+1,虽然我更喜欢不包含“eval”的解决方案。但是,嘿,如果这对于Hadley来说足够好的话 :)。 - Paul Hiemstra
为什么你不喜欢使用 eval - fabians
就像 eval(parse(text="<string>")) 这样的东西,我同意你的观点,但是当你以编程方式构建一个 call 时,我认为避免使用 eval 是不可能的。 - fabians
同意。如果dplyr可以处理这种类型的事情而不强制我们诉诸这种“计算语言”的方式,那会更好。我相信这种功能还会出现,它仍处于测试阶段。 - fabians
是的,这在待办事项清单上。与使用自定义环境的get相比,我更喜欢这种解决方案。问题在于解析而不是评估。 - hadley
显示剩余2条评论

5
您可以使用get并确定对象“Var1”所处的环境。
> my.variable = 'Var1'
> mutate(d, x = percent_rank(get(my.variable, envir = as.environment(d))))
  Var1 Var2     x
1    1   20 0.000
2    2   20 0.375
3    3   20 0.750
4    1   21 0.000
5    2   21 0.375
6    3   21 0.750
7    1   22 0.000
8    2   22 0.375
9    3   22 0.750

我建议您阅读 Hadley Wickham 的 "Advanced R programming" 维基百科,了解有关“非标准评估”的更多信息:http://adv-r.had.co.nz/Computing-on-the-language.html

编辑

最近这个答案被投票,所以我意识到我一年半前给出的解决方案并不是很好,我借此机会升级我的答案。

自 dplyr 0.3 版本起,您可以使用 dplyr 函数的标准评估版本,使用它们的“fun_”版本。

如果您正在对变量进行某些计算,则还需要使用 lazyeval 包中的 interp

my.variable = "Var1"
expr <- lazyeval::interp(~percent_rank(x), x = as.name(my.variable))
mutate_(d, .dots = setNames(list(expr), "x"))
Var1 Var2     x
1    1   20 0.000
2    2   20 0.375
3    3   20 0.750
4    1   21 0.000
5    2   21 0.375
6    3   21 0.750
7    1   22 0.000
8    2   22 0.375
9    3   22 0.750

3
dplyr的开发版本(等待新版本发布0.6.0)中,通过引入quosures和非引用函数(!!UQ)来评估group_by / summarise / mutate中的引用,这变得更加容易了。
 my.variable <- quo(Var1)
 percent_rank <- function(x) rank(x)/max(rank(x))
 d %>% 
   mutate(x = percent_rank(!!my.variable))
#  Var1 Var2     x
#1    1   20 0.250
#2    2   20 0.625
#3    3   20 1.000
#4    1   21 0.250
#5    2   21 0.625
#6    3   21 1.000
#7    1   22 0.250
#8    2   22 0.625
#9    3   22 1.000

它还具有通过列名传递的其他功能。
mynewvar <- 'x'
d %>% 
   mutate(!!mynewvar := percent_rank(!!my.variable))
#  Var1 Var2     x
#1    1   20 0.250
#2    2   20 0.625
#3    3   20 1.000
#4    1   21 0.250
#5    2   21 0.625
#6    3   21 1.000
#7    1   22 0.250
#8    2   22 0.625
#9    3   22 1.000

我们也可以创建一个函数并传递参数

f1 <- function(dat, myvar, colN){
  myvar <- enquo(myvar)
  colN <- quo_name(enquo(colN))
 
  dat %>%
      mutate(!!colN := percent_rank(!!myvar))
 }

f1(d, Var1, x)
#  Var1 Var2     x
#1    1   20 0.250
#2    2   20 0.625
#3    3   20 1.000
#4    1   21 0.250
#5    2   21 0.625
#6    3   21 1.000
#7    1   22 0.250
#8    2   22 0.625
#9    3   22 1.000

在上述函数中,enquo 执行的功能类似于 base R 中的 substitute,将用户输入的参数转换为 quosure。由于我们需要字符串形式的列名,因此可以使用 quo_name 将其转换为字符串,在 mutate 调用内部执行的计算通过取消引用(!!UQ)完成。

数据

d <- expand.grid(1:3, 20:22)

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