这个答案将涵盖许多与现有答案相同的元素,但是这个问题(将列名传递给函数)经常出现,以至于我希望有一个更全面地涵盖所有内容的答案。
假设我们有一个非常简单的数据框:
dat <- data.frame(x = 1:4,
y = 5:8)
我们想编写创建一个新列的函数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
不评估表达式col1
。它只是在df
字面上查找名为的列col1
。在?Extract
“递归(类似列表)的对象”一节中介绍了此行为。
最简单,也是最常推荐的解决方案是简单地从$
to 切换[[
并以字符串形式传递函数参数:
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
这通常被认为是“最佳实践”,因为这是最难解决的方法。尽可能将列名称作为字符串传递。
以下两个选项更高级。许多流行的软件包都使用了这类技术,但是很好地使用它们需要更多的关怀和技巧,因为它们会引入微妙的复杂性和意外的故障点。哈德利(Hadley)的Advanced R本书的这一部分是其中一些问题的绝佳参考。
如果您确实想避免用户输入所有这些引号,则一种选择可能是使用以下命令将裸露的,未引号的列名转换为字符串deparse(substitute())
:
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_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
}
只是为了好玩,我仍在使用deparse(substitute())
新列的名称。在这里,以下所有功能均适用:
> 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
因此,简短的答案基本上是:将data.frame列名作为字符串传递,并用于[[
选择单个列。只有开始钻研eval
,substitute
等等。如果你真的知道自己在做什么。