将data.frame列名传递给函数

将data.frame列名传递给函数,r,dataframe,r-faq,R,Dataframe,R Faq,我正在尝试编写一个函数来接受data.frame(x)和其中的列。该函数对x执行一些计算,然后返回另一个data.frame。我一直坚持使用最佳实践方法将列名传递给函数 下面的两个最小示例fun1和fun2产生了所需的结果,能够以max()为例对x$column执行操作。然而,两者都依赖于表面上(至少对我来说)不雅的东西 调用substitute()并可能调用eval() 需要将列名作为字符向量传递 fun1您可以直接使用列名: df <- data.frame(A=1:10, B=2:

我正在尝试编写一个函数来接受data.frame(
x
)和其中的
列。该函数对x执行一些计算,然后返回另一个data.frame。我一直坚持使用最佳实践方法将列名传递给函数

下面的两个最小示例
fun1
fun2
产生了所需的结果,能够以
max()
为例对
x$column
执行操作。然而,两者都依赖于表面上(至少对我来说)不雅的东西

  • 调用
    substitute()
    并可能调用
    eval()
  • 需要将列名作为字符向量传递


    fun1您可以直接使用列名:

    df <- data.frame(A=1:10, B=2:11, C=3:12)
    fun1 <- function(x, column){
      max(x[,column])
    }
    fun1(df, "B")
    fun1(df, c("B","A"))
    

    我个人认为将列作为字符串传递是非常难看的。我喜欢这样做:

    get.max <- function(column,data=NULL){
        column<-eval(substitute(column),data, parent.frame())
        max(column)
    }
    
    请注意data.frame的规范是如何可选的。您甚至可以使用列的函数:

    > get.max(1/mpg,mtcars)
    [1] 0.09615385
    

    这个答案将涵盖许多与现有答案相同的元素,但这个问题(将列名传递给函数)经常出现,因此我希望有一个更全面的答案

    假设我们有一个非常简单的数据框:

    dat <- data.frame(x = 1:4,
                      y = 5:8)
    
    这里的问题是
    df$col1
    不计算表达式
    col1
    。它只是在
    df
    中查找一列,字面上称为
    col1
    。此行为在“递归(类似列表的)对象”一节下的
    ?Extract
    中描述

    最简单也是最常推荐的解决方案是简单地从
    $
    切换到
    [[
    并将函数参数作为字符串传递:

    new_column1 <- function(df,col_name,col1,col2){
        #Create new column col_name as sum of col1 and col2
        df[[col_name]] <- df[[col1]] + df[[col2]]
        df
    }
    
    > new_column1(dat,"z","x","y")
      x y  z
    1 1 5  6
    2 2 6  8
    3 3 7 10
    4 4 8 12
    
    坦率地说,这可能有点愚蠢,因为我们实际上在做与
    new\u column1
    中相同的事情,只是需要做大量额外的工作来将裸名称转换为字符串

    最后,如果我们真的想增加想象力,我们可能会决定,与其传递要添加的两列的名称,不如更灵活一些,并允许两个变量的其他组合。在这种情况下,我们可能会在涉及两列的表达式上使用
    eval()

    new_column3 <- function(df,col_name,expr){
        col_name <- deparse(substitute(col_name))
        df[[col_name]] <- eval(substitute(expr),df,parent.frame())
        df
    }
    

    因此,简单的答案基本上是:将data.frame列名作为字符串传递,并使用
    [[
    选择单个列。如果您真的知道自己在做什么,只需开始钻研
    eval
    substitute
    ,等等。

    作为额外考虑,如果需要将列名无引号地传递给自定义函数,那么在这种情况下,
    match.call()
    也可能很有用,作为
    deparse的替代方法(替换())

    df返回-Inf
    #>[1]-Inf
    #如果输入错误,请停止
    乐趣[1]10
    
    由(v0.2.1)于2019-01-11创建

    我不认为我会使用这种方法,因为除了像上面的答案那样只传递引用的列名外,还有额外的类型和复杂性。但是,这是一种方法。

    另一种方法是使用这种方法。将数据帧的列作为字符串或裸列名传递非常简单。请参阅有关
    t的更多信息田园诗

    库(rlang)
    图书馆(tidyverse)
    种子集(123)
    df B D
    #> 1 1.715065 1.786913
    
    使用裸列名

    fun4 1.715065
    fun4(df、B、D)
    #>B-D
    #> 1 1.715065 1.786913
    #>
    

    由(v0.2.1.9000)创建于2019-03-01如果您试图在R包中构建此功能或只是想降低复杂性,可以执行以下操作:

    test_func <- function(df, column) {
      if (column %in% colnames(df)) {
        return(max(df[, column, with=FALSE])) 
      } else {
        stop(cat(column, "not in data.frame columns."))
      }
    }
    

    test_func使用
    dplyr
    现在还可以访问数据帧的特定列,只需在函数体中所需的列名周围使用双大括号
    {…}
    ,例如对于
    列名

    library(tidyverse)
    
    fun <- function(df, col_name){
       df %>% 
         filter({{col_name}} == "test_string")
    } 
    
    库(tidyverse)
    乐趣%
    过滤器({{col_name}}==“测试字符串”)
    } 
    
    是否有任何方法可以传递列名而不是字符串?您需要传递列名作为字符引用或列的整数索引。仅传递
    B
    将假定B本身是一个对象。我明白了。我不确定如何得到复杂的替换、eval等。谢谢!我找到了
    [[
    解决方案是唯一对我有效的解决方案。嗨@路易斯,请注意,你需要摆脱使用引号的习惯。不使用引号是丑陋的!为什么?因为你创建了一个只能交互使用的函数-很难用它编程。我很高兴看到更好的方式,但我看不到其中的区别e在这和qplot之间(x=每加仑,数据=每节车厢).ggplot2从不将列作为字符串传递,我认为这样做更好。为什么你说这只能以交互方式使用?在什么情况下会导致不希望的结果?如何使用它进行编程更困难?在本文的正文中,我展示了它的灵活性。5年后-…为什么我们需要:parent.frame()?7年后:是否使用引号仍然丑陋?相关:不确定为什么这不是最佳答案。我也是!很好的解释!
    foo <- function(df,col_name,col1,col2){
          df$col_name <- df$col1 + df$col2
          df
    }
    
    #Call foo() like this:    
    foo(dat,z,x,y)
    
    new_column1 <- function(df,col_name,col1,col2){
        #Create new column col_name as sum of col1 and col2
        df[[col_name]] <- df[[col1]] + df[[col2]]
        df
    }
    
    > new_column1(dat,"z","x","y")
      x y  z
    1 1 5  6
    2 2 6  8
    3 3 7 10
    4 4 8 12
    
    new_column2 <- function(df,col_name,col1,col2){
        col_name <- deparse(substitute(col_name))
        col1 <- deparse(substitute(col1))
        col2 <- deparse(substitute(col2))
    
        df[[col_name]] <- df[[col1]] + df[[col2]]
        df
    }
    
    > new_column2(dat,z,x,y)
      x y  z
    1 1 5  6
    2 2 6  8
    3 3 7 10
    4 4 8 12
    
    new_column3 <- function(df,col_name,expr){
        col_name <- deparse(substitute(col_name))
        df[[col_name]] <- eval(substitute(expr),df,parent.frame())
        df
    }
    
    > new_column3(dat,z,x+y)
      x y  z
    1 1 5  6
    2 2 6  8
    3 3 7 10
    4 4 8 12
    > new_column3(dat,z,x-y)
      x y  z
    1 1 5 -4
    2 2 6 -4
    3 3 7 -4
    4 4 8 -4
    > new_column3(dat,z,x*y)
      x y  z
    1 1 5  5
    2 2 6 12
    3 3 7 21
    4 4 8 32
    
    test_func <- function(df, column) {
      if (column %in% colnames(df)) {
        return(max(df[, column, with=FALSE])) 
      } else {
        stop(cat(column, "not in data.frame columns."))
      }
    }
    
    library(tidyverse)
    
    fun <- function(df, col_name){
       df %>% 
         filter({{col_name}} == "test_string")
    }