在 dplyr 函数中使用变量名

Use variable names in functions of dplyr

提问人:kuba 提问时间:7/4/2014 最后编辑:Jaapkuba 更新时间:10/22/2022 访问量:40014

问:

我想在函数中使用变量名作为字符串。请参阅以下示例:dplyr

df <- data.frame( 
      color = c("blue", "black", "blue", "blue", "black"), 
      value = 1:5)
filter(df, color == "blue")

它工作得很好,但我想用字符串来引用,如下所示:color

var <- "color"
filter(df, this_probably_should_be_a_function(var) == "blue").

我很乐意以任何方式做到这一点,并且非常乐意使用易于阅读的语法。dplyr

dplyr r-faq rlang

评论

1赞 Kevin 1/29/2015
对于 select 和其他方法,您可以使用 select_ 传递变量,但我不确定如何使用 filter() 执行此操作......

答:

7赞 lukeA 7/4/2014 #1

经常被问到,但仍然没有轻松的支持afaik。但是,关于此帖子

eval(substitute(filter(df, var == "blue"), 
                list(var = as.name(var))))
#   color value
# 1  blue     1
# 2  blue     3
# 3  blue     4
38赞 akrun 7/4/2014 #2

在较新的版本中,我们可以创建带引号的变量,然后取消引号(或)进行评估UQ!!

var <- quo(color)
filter(df, UQ(var) == "blue")
#   color value
#1  blue     1
#2  blue     3
#3  blue     4

由于运算符的优先级,我们可能需要绕行()!!

filter(df, (!!var) == "blue")
#   color value
#1  blue     1
#2  blue     3
#3  blue     4

对于新版本,具有更高的优先级,因此||

filter(df, !! var == "blue")

应该工作(正如@Moody_Mudskipper评论的那样)

较旧的选项

我们还可能使用:

 filter(df, get(var, envir=as.environment(df))=="blue")
 #color value
 #1  blue     1
 #2  blue     3
 #3  blue     4

编辑:重新排列了解决方案的顺序

评论

0赞 Michael Barton 11/5/2017
打字时,我得到.我正在使用 dplyr 0.7.4。(!!"term")Error in !"term" : invalid argument type
1赞 akrun 11/5/2017
@MichaelBarton我正在使用 0.7.3,并且它适用于 .我不确定你的意思,如果你传递的是带引号的字符串,那么Updatetermfilter(df, (!!rlang::sym("color")) == "blue")
1赞 Michael Barton 11/6/2017
是的,你是对的,这是我的错误。我在输入这个时一定很困惑,我不应该在它周围加上引号。
2赞 moodymudskipper 7/1/2019
现在具有更高的优先级,不再需要括号!!
27赞 Mark Heckmann 10/31/2015 #3

对于版本 [0.3 - 0.7) (? - 2017 年 6 月)dplyr

(有关更新的 dplyr 版本,请参阅此问题的其他答案)

由于每个使用非标准评估(NSE,请参阅发布帖子小插图)的函数都有一个标准评估 (SE) 孪生体,下面划线结尾。这些可用于传递变量。因为它将是.使用 u 可以将逻辑条件作为字符串传递。dplyr 0.3dplyrfilterfilter_filter_

filter_(df, "color=='blue'")

#   color value
# 1  blue     1
# 2  blue     3
# 3  blue     4

当然,用逻辑条件来解释字符串是很纯粹的

l <- paste(var, "==",  "'blue'")
filter_(df, l)

评论

0赞 Berk U. 5/4/2016
使用 filter_您可以将逻辑条件作为字符串传递。谢谢你指出这一点。在我读到你帖子的那部分之前,我真的不明白 NSE 是如何在 dplyr 中工作的。
0赞 slhck 5/9/2018
小插曲的链接断开了,你能更新一下吗?
17赞 takje 7/7/2017 #4

从 dplyr 0.7 开始,有些事情又发生了变化。

library(dplyr)
df <- data.frame( 
  color = c("blue", "black", "blue", "blue", "black"), 
  value = 1:5)
filter(df, color == "blue")

# it was already possible to use a variable for the value
val <- 'blue'
filter(df, color == val)

# As of dplyr 0.7, new functions were introduced to simplify the situation
col_name <- quo(color) # captures the current environment
df %>% filter((!!col_name) == val)

# Remember to use enquo within a function
filter_col <- function(df, col_name, val){
  col_name <- enquo(col_name) # captures the environment in which the function was called
  df %>% filter((!!col_name) == val)
}
filter_col(df, color, 'blue')

在 dplyr 编程小插曲中解释了更一般的情况。

评论

4赞 Michael Barton 11/5/2017
谢谢。这就是我一直在寻找的答案。不过,在我看来,这对 dplyr 来说是一个令人困惑的方向。我花了很长时间来解析和理解这里正在做什么。我可以想象我不会是唯一一个,这感觉就像一段非常高级的代码,几乎就像在LISP中编写宏一样。我喜欢宏和 LISP,但我不知道它们是否符合每个人的口味,尤其是对于编写相对简单的函数来操作 tibbles。quoenquo
0赞 takje 11/6/2017
@MichaelBarton 这些命令指定要捕获的环境。我试图添加评论来澄清。更好的解释是在编程小插曲中。
1赞 Michael Barton 11/7/2017
是的,这不是对你的答案的评论,这就是我一直在寻找的。相反,这是对 dplyr 的个人评论。我认为很难要求用户必须理解和编写我认为使用 dplyr 的相对简单的函数。几乎就像在教某人基于 dplyr 编写函数时,您还必须捆绑有关如何捕获环境的解释。quoenquoquo
5赞 Tom Roth 9/27/2017 #5

以下是使用包中的函数执行此操作的一种方法:sym()rlang

library(dplyr)

df <- data.frame( 
  main_color = c("blue", "black", "blue", "blue", "black"), 
  secondary_color = c("red", "green", "black", "black", "red"),
  value = 1:5, 
  stringsAsFactors=FALSE
)

filter_with_quoted_text <- function(column_string, value) {
    col_name <- rlang::sym(column_string)
    df1 <- df %>% 
      filter(UQ(col_name) == UQ(value))
    df1
}

filter_with_quoted_text("main_color", "blue")
filter_with_quoted_text("secondary_color", "red")

评论

1赞 CoderGuy123 6/19/2018
我遇到了双字符串用例。我不明白为什么正常的方法不起作用,必须先使用。这种使用运算符重复引用的情况在我找到的任何教程中都没有涉及。filter(UQ(col_name) == UQ(value))rlang::sym(column_string)==filter()
14赞 Ben Bolker 7/1/2019 #6

版本 >= 0.4.0 的新功能rlang

.data现在被识别为引用父数据框的一种方式,因此按字符串引用的工作方式如下:

var <- "color"
filter(df, .data[[var]] == "blue")

如果变量已经是一个符号,则将正确取消引用它{{}}

示例 1:

var <- quo(color)
filter(df, {{var}} == "blue")

或更现实地

f <- function(v) {
    filter(df, {{v}} == "blue")
}
f(color) # Curly-curly provides automatic NSE support

更多阅读和示例,请参阅使用 dplyr 编程文章/小插曲。

评论

0赞 steve_b 7/7/2021
这里的方法立即对我起作用,可以否定管道中的过滤器(例如:)。我无法立即让其他一些解决方案在此应用程序中工作。.data[[var]] df %>% filter(!.data[[var]] %in% df2[[var]])
0赞 Gregor Thomas 9/15/2022
@steve_b这些方法是针对不同问题/在不同情况下使用的解决方案。 当你有一个字符串时工作。 当您有一个符号时有效,例如,一个不带引号的列名。.data{{
6赞 llewmills 2/4/2020 #7

上面的几个解决方案对我不起作用。现在有函数,我们把它包装在里面。似乎有点简单。as.symbol!!

set.seed(123)

df <- data.frame( 
  color = c("blue", "black", "blue", "blue", "black"), 
  shape = c("round", "round", "square", "round", "square"),
  value = 1:5)

现在将变量作为字符串输入到 dplyr 函数中,方法是通过 和as.symbol()!!

var <- "color"
filter(df, !!as.symbol(var) == "blue")

#   color  shape value
# 1  blue  round     1
# 2  blue square     3
# 3  blue  round     4

var <- "shape"
df %>% group_by(!!as.symbol(var)) %>% summarise(m = mean(value))

#   shape      m
#   <fct>  <dbl>
# 1 round   2.33
# 2 square  4

评论

0赞 Alex L 9/6/2020
在这里的所有答案中,这对我有用,谢谢!dplyr 1.0.1
5赞 llewmills 9/6/2020 #8

更新。新功能具有一些出色的新功能,可以更轻松地解决此类问题。您可以在新软件包随附的“编程”小插曲中了解它。dplyr1.0.0

基本上,该函数允许您更轻松地将字符串传递到函数中。.data[[foo]]

所以你可以这样做

filtFunct <- function(d, var, crit) {
filter(d, .data[[var]] %in% crit)
}

filtFunct(df, "value", c(2,4))

#   color value
# 1 black     2
# 2  blue     4

filtFunct(df, "color", "blue")

#   color value
# 1  blue     1
# 2  blue     3
# 3  blue     4
3赞 Geoffrey Poole 12/20/2020 #9

这个问题发布于 6 年前。 现在最高版本为 1.0.2。然而,这仍然是一个很棒的讨论,对我的问题有很大帮助。我希望能够从列、运算符和值中构造过滤器,这些变量都是由内存中的变量指定的。哦,还有不确定数量的过滤器!dplyr

请考虑以下列表,其中我指定了两个筛选器的列、运算符和值:

myFilters = 
  list(
    list(var = "color", op = "%in%", val = "blue"),
    list(var = "value", op = "<=", val = 3)
  )

从这个列表中,我想运行:

dplyr::filter(color %in% "blue", value <= 3)

我们可以在上面创建一个对象,使用运算符强制评估调用,并将其传递给:lapplylistlistcall!!!filter

library(dplyr)

df <- data.frame( 
  color = c("blue", "black", "blue", "blue", "black"), 
  value = 1:5)

result = 
  lapply(myFilters, function(x) call(x$op, as.name(x$var), x$val)) %>%
  {filter(df, !!!.)}

...还有沙赞!

> result
  color value
1  blue     1
2  blue     3

这需要吸收很多东西,所以如果不能立即看出发生了什么,让我稍微解开一下。考虑:

var = "color"
op = "%in%"
val = "blue"

我希望能够运行:

filter(df, color %in% "blue")

如果我也有:

var2 = "value"
op2 = "<="
val2 = 3

我可能希望能够获得:

filter(df, color %in% "blue", value <= 3)

该解决方案使用 s,它们是未计算的表达式。(参见 Hadley 的 Advanced R 书籍)基本上,从变量中列出对象,然后在调用时使用运算符强制评估调用。callcall!!!dplyr::filter

call1 = call(op, as.name(var), val)

以下是 的值:call1

> call1
color %in% "blue"

让我们创建另一个:call

call2 = call(op2, as.name(var2), val2)

把它们放在列表中:

calls = list(call1, call2)

并用于在将呼叫发送到 :!!!filter

result = filter(df, !!!calls)