将data.frame列名传递给一个函数
我试图写一个函数来接受data.frame( x
)和它的一column
。 该函数在x上执行一些计算,然后返回另一个data.frame。 我坚持最佳实践方法将列名传递给函数。
下面的两个最小例子fun1
和fun2
产生所需的结果,能够在x$column
上执行操作,以max()
为例。 但是,两人都依靠看似(至less对我来说)不雅
- 调用
substitute()
和可能eval()
- 需要将列名作为字符向量传递。
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
,它是列x
和y
的总和。
这里一个非常常见的绊脚石是,一个自然的(但不正确的)尝试通常是这样的:
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单个列。 只有开始钻研eval
, substitute
等,如果你真的知道你在做什么。
我个人认为,将字段作为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