将data.frame列名传递给一个函数

我试图写一个函数来接受data.frame( x )和它的一column 。 该函数在x上执行一些计算,然后返回另一个data.frame。 我坚持最佳实践方法将列名传递给函数。

下面的两个最小例子fun1fun2产生所需的结果,能够在x$column上执行操作,以max()为例。 但是,两人都依靠看似(至less对我来说)不雅

  1. 调用substitute()和可能eval()
  2. 需要将列名作为字符向量传递。

 fun1 <- function(x, column){ do.call("max", list(substitute(x[a], list(a = column)))) } fun2 <- function(x, column){ max(eval((substitute(x[a], list(a = column))))) } df <- data.frame(B = rnorm(10)) fun1(df, "B") fun2(df, "B") 

例如,我希望能够将该函数调用为fun(df, B) 。 其他选项,我已经考虑,但没有尝试过:

  • 传递column作为列号的整数。 我认为这将避免substitute() 。 理想情况下,该function也可以接受。
  • with(x, get(column)) ,但是,即使它起作用,我认为这仍然需要substitute
  • 利用formula()match.call() ,我也没有太多的经验。

子问题do.call()是否优于eval()

您可以直接使用列名称:

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

没有必要使用替代品,评估等。

您甚至可以将所需的function作为parameter passing:

 fun1 <- function(x, column, fn) { fn(x[,column]) } fun1(df, "B", max) 

或者,使用[[也适用于一次select一个列:

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

这个答案将涵盖许多与现有答案相同的元素,但是这个问题(将列名传递给函数)经常出现,所以我想在那里得到一个更全面的答案。

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

 dat <- data.frame(x = 1:4, y = 5:8) 

我们想写一个函数来创build一个新的列z ,它是列xy的总和。

这里一个非常常见的绊脚石是,一个自然的(但不正确的)尝试通常是这样的:

 foo <- function(df,col_name,col1,col2){ df$col_name <- df$col1 + df$col2 df } #Call foo() like this: foo(dat,z,x,y) 

这里的问题是, df$col1不会评估expression式col1 。 它只是在df查找一个名为col1的列。 此行为在“recursion(类列表)对象”一节下的“ ?Extract进行了描述。

最简单,也是最经常推荐的解决scheme是简单地从$切换到[[并将函数参数作为string传递:

 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") xyz 1 1 5 6 2 2 6 8 3 3 7 10 4 4 8 12 

这通常被认为是“最佳做法”,因为这是最难解决的方法。 将字段名称作为string传递是毫无疑义的。

以下两个选项更为先进。 许多stream行的软件包使用这些技术,但是使用它们需要更多的关心和技巧,因为它们可以引入微妙的复杂性和意想不到的失败点。 这部分哈德利的高级R书是这些问题的一个很好的参考。

如果您确实想要保存用户input所有这些引号,则可以使用deparse(substitute())将裸露的未引用的列名转换为string:

 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) xyz 1 1 5 6 2 2 6 8 3 3 7 10 4 4 8 12 

坦率地说,这可能有点愚蠢,因为我们真的和new_column1做的一样,只是把一些额外的工作转换成string。

最后,如果我们想要变得花哨,我们可以决定不是传递两列的名字来添加,而是希望变得更加灵活,并允许两个variables的其他组合。 在这种情况下,我们可能会在包含两列的expression式上使用eval()

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

为了好玩,我仍然使用deparse(substitute())作为新列的名称。 在这里,以下所有内容都将起作用:

 > new_column3(dat,z,x+y) xyz 1 1 5 6 2 2 6 8 3 3 7 10 4 4 8 12 > new_column3(dat,z,xy) xyz 1 1 5 -4 2 2 6 -4 3 3 7 -4 4 4 8 -4 > new_column3(dat,z,x*y) xyz 1 1 5 5 2 2 6 12 3 3 7 21 4 4 8 32 

所以简单的答案基本上是:将data.frame列名作为string传递,并使用[[select单个列。 只有开始钻研evalsubstitute等,如果你真的知道你在做什么。

我个人认为,将字段作为string传递是非常难看的。 我喜欢做一些事情:

 get.max <- function(column,data=NULL){ column<-eval(substitute(column),data, parent.frame()) max(column) } 

这将产生:

 > get.max(mpg,mtcars) [1] 33.9 > get.max(c(1,2,3,4,5)) [1] 5 

注意data.frame的规范是可选的。 你甚至可以使用你的列的function:

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