这个答案将涵盖许多与现有答案相同的元素,但这个问题(将列名传递给函数)经常出现,以至于我希望有一个更全面的答案。
假设我们有一个非常简单的数据框:
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 中进行了描述。
最简单且最常被推荐的解决方案是从 $ 切换到 [[ 并将函数参数作为字符串传递:
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 书籍的This 部分是解决其中一些问题的绝佳参考。
如果您真的想避免用户键入所有这些引号,一种选择可能是使用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 等。