【问题标题】:Use variable names in functions of dplyr在 dplyr 的函数中使用变量名
【发布时间】:2014-08-25 11:44:53
【问题描述】:

我想在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 语法。

【问题讨论】:

  • 对于 select 和其他方法,您可以使用 select_ 传递变量,但我不确定如何使用 filter()...

标签: r dplyr r-faq rlang


【解决方案1】:

在较新的版本中,我们可以使用我们可以创建引用的变量然后取消引用(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

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

【讨论】:

  • 输入(!!"term") 时,我得到Error in !"term" : invalid argument type。我正在使用 dplyr 0.7.4。
  • @MichaelBarton 我使用的是 0.7.3,它可以使用Update 中的两种方法。我不确定term 是什么意思,如果您传递的是带引号的字符串,那么filter(df, (!!rlang::sym("color")) == "blue")
  • 是的,你是对的,这是我的错误。我在输入这个时一定很困惑,我不应该在这个周围加上引号。
  • 现在!! 具有更高的优先级,不再需要括号
【解决方案2】:

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

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

截至dplyr 0.3,每个使用非标准评估(NSE,参见release postvignette)的dplyr 函数都有一个以下划线结尾的标准评估(SE) 双胞胎。这些可用于传递变量。对于filter,它将是filter_。使用filter_,您可以将逻辑条件作为字符串传递。

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

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

用逻辑条件来构造字符串当然是直截了当

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

【讨论】:

  • 使用 filter_ 您可以将逻辑条件作为字符串传递。 感谢您指出这一点。在阅读您的那部分帖子之前,我真的不明白 NSE 是如何在 dplyr 中工作的。
  • 小插图的链接已损坏,您能更新一下吗?
【解决方案3】:

从 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 programming vignette 中解释了更一般的情况。

【讨论】:

  • 谢谢。这就是我一直在寻找的答案。不过,这对我来说就像 dplyr 的一个令人困惑的方向。我花了很长时间来解析和理解 quoenquo 在这里做什么。我可以想象我也不会是唯一一个,这感觉就像是一段非常高级的代码,几乎就像在 LISP 中编写宏一样。我喜欢宏和 LISP,但我不知道它们是否符合每个人的口味,尤其是对于编写相对简单的函数来操作小标题。
  • @MichaelBarton 这些命令指定要捕获的环境。我试图添加 cmets 来澄清。编程小插曲中有更好的解释。
  • 是的,这不是对您的答案的评论,这正是我想要的。而是这是对 dplyr 的个人评论。我认为很难要求用户必须理解 quoenquo 才能编写我认为使用 dplyr 甚至相对简单的功能。几乎就像在教某人编写基于 dplyr 的函数时,您还必须捆绑解释如何使用 quo 捕获环境。
【解决方案4】:

rlang 新版本 >= 0.4.0

.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

【讨论】:

  • 这里的.data[[var]] 方法对我来说立即起作用,以否定管道中的过滤器(例如:df %&gt;% filter(!.data[[var]] %in% df2[[var]]))。我无法立即在此应用程序中使用其他一些解决方案。
【解决方案5】:

经常被问到,但仍然不容易支持 afaik。但是,关于this posting

eval(substitute(filter(df, var == "blue"), 
                list(var = as.name(var))))
#   color value
# 1  blue     1
# 2  blue     3
# 3  blue     4

【讨论】:

    【解决方案6】:

    这是使用rlang 包中的sym() 函数的一种方法:

    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")
    

    【讨论】:

    • 我遇到了双字符串用例。我不明白为什么普通的filter(UQ(col_name) == UQ(value)) 方法不起作用,必须先使用rlang::sym(column_string)。这种在filter() 中使用== 运算符进行双重取消引用的情况在我找到的任何教程中都没有介绍。
    【解决方案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)
    

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

    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
    

    【讨论】:

    • 这里的所有答案中,dplyr 1.0.1 对我有用,谢谢!
    【解决方案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
    

    【讨论】:

      【解决方案9】:

      这个问题是 6 年前发布的。 dplyr 现在升级到 1.0.2 版。然而,这仍然是一个很好的讨论,并极大地帮助了我解决我的问题。我希望能够从所有由内存中的变量指定的列、运算符和值构造过滤器。哦,还有不定数量的过滤器!

      考虑以下列表,我在其中指定列、运算符和两个过滤器的值:

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

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

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

      我们可以在上面的list 上使用lapply 来创建call 对象的list,使用!!! 运算符强制评估调用,并将其传递给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)
      

      解决方案使用calls,它们是未计算的表达式。 (参见 Hadley 的 Advanced R book)基本上,从变量中列出 call 对象,然后在调用 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)
      

      【讨论】:

        猜你喜欢
        • 2018-12-23
        • 2022-01-13
        • 2018-10-17
        • 1970-01-01
        • 2015-08-03
        • 2015-03-13
        • 1970-01-01
        • 2019-01-27
        • 2021-10-02
        相关资源
        最近更新 更多