我试图编写一个函数来接受一个data.frame(x
)和来自它的一个column
。该函数对x执行一些计算,然后返回另一个data.frame。我被最佳实践方法卡住了,无法将列名传递给函数。
下面的两个最小的例子fun1
和fun2
产生了想要的结果,能够在x$column
上执行操作,以max()
为例。
1.调用substitute()
,也可能调用eval()
1.需要将列名作为字符向量传递。
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()
。理想情况下,函数可以接受任何一个。 with(x, get(column))
,但是,即使它工作,我认为这仍然需要substitute
- 利用
formula()
和match.call()
,这两个我都没有太多的经验。 - 子问题 *:
do.call()
是否优于eval()
?
8条答案
按热度按时间dxpyg8gm1#
您可以直接使用列名:
不需要使用替代、评估等。
您甚至可以将所需的函数作为参数传递:
或者,使用
[[
也可以一次选择一列:mdfafbf12#
这个答案将涵盖许多与现有答案相同的元素,但这个问题(将列名传递给函数)经常出现,我希望有一个答案,涵盖的东西更全面一点。
假设我们有一个非常简单的 Dataframe :
我们想写一个函数来创建一个新的列
z
,它是列x
和y
的和。这里一个非常常见的障碍是一个自然的(但不正确的)尝试通常看起来像这样:
这里的问题是
df$col1
并不计算表达式col1
。它只是在df
中查找一个字面上称为col1
的列。这个行为在?Extract
中的“递归(类似列表的)对象”一节中描述。最简单也是最常推荐的解决方案是从
$
切换到[[
,并将函数参数作为字符串传递:这通常被认为是“最佳实践”,因为这是最难搞砸的方法。将列名作为字符串传递是最明确的。
下面两个选项更高级。许多流行的包都使用了这类技术,但是要 * 好 * 使用它们需要更多的小心和技巧,因为它们可能会引入微妙的复杂性和意外的故障点。Hadley的Advanced R书中的This部分是解决这些问题的很好的参考。
如果您 * 真的 * 想让用户不必键入所有这些引号,一个选项可能是使用
deparse(substitute())
将空的、不带引号的列名转换为字符串:坦率地说,这可能有点傻,因为我们实际上做的事情和
new_column1
中的一样,只是有一堆额外的工作要把裸名称转换成字符串。最后,如果我们真的想要更有趣,我们可能会决定不传递两列的名称,而是更灵活地允许两个变量的其他组合。在这种情况下,我们可能会在包含两列的表达式中使用
eval()
:为了好玩,我仍然使用
deparse(substitute())
作为新列的名称。所以简单的回答就是:将data.frame列名作为字符串传递,并使用
[[
来选择单个列。只有在真正了解自己在做什么的情况下,才开始深入研究eval
、substitute
等。68bkxrlz3#
我个人认为将列作为字符串传递是相当丑陋的。我喜欢这样做:
这将产生:
请注意,数据框架的规格说明是可选的。您甚至可以使用列的函数:
agyaoht74#
另一种方法是使用
tidy evaluation
方法。将 Dataframe 的列作为字符串或空列名传递是非常简单的。请参阅有关tidyeval
here的更多信息。将列名用作字符串
使用空列名
由reprex package(v0.2.1.9000)于2019年3月1日创建
3ks5zfa05#
使用
dplyr
,现在还可以访问 Dataframe 的特定列,只需在函数体中使用双花括号{{...}}
将所需的列名括起来,例如col_name
:jmp7cifd6#
作为一个额外的想法,如果需要将不带引号的列名传递给自定义函数,那么
match.call()
在这种情况下可能也很有用,可以作为deparse(substitute())
的替代方法:如果列名中有拼写错误,则更安全的做法是停止并显示错误:
由reprex package(v0.2.1)于2019年1月11日创建
我不认为我会使用这种方法,因为除了传递上面答案中指出的带引号的列名之外,还有额外的类型输入和复杂性,但这是一种方法。
wfveoks07#
Tung's answer和mgrund's answer提供了tidy evaluation。在此答案中,我将说明如何使用这些概念来执行类似于joran's answer的操作(特别是他的函数
new_column3
)。这样做的目的是更容易看出基本求值和整洁求值之间的区别,并查看可用于整洁计算的不同语法。为此,您将需要rlang
和dplyr
。使用基地评估工具(约兰的回答):
在第一行中,
substitute
使我们将col_name
作为表达式来计算,更具体地说,是作为符号(有时也称为名称),而不是对象。rlang的替代项可以是:ensym
-将其转换为符号;enexpr
-将其转换为表达式;enquo
-将其转换为引号,这是一个表达式,它也指向R应该在其中查找变量以对其求值的环境。大多数情况下,你希望有一个指向环境的指针。当你不特别需要它的时候,有它很少会导致问题。因此,大多数情况下你可以使用
enquo
。在这种情况下,你可以使用ensym
使代码更容易阅读,因为它使col_name
更清楚。同样在第一行中,
deparse
将表达式/符号转换为字符串。您也可以使用as.character
或rlang::as_string
。在第二行中,
substitute
将expr
转换为“完整”表达式(而不是符号),因此ensym
不再是一个选项。同样在第二行中,我们现在可以将
eval
改为rlang::eval_tidy
。Eval仍然可以使用enexpr
,但不能使用引号。当你有一个引号时,你不需要将环境传递给求值函数(就像joran对parent.frame()
所做的那样)。以上建议的取代的一种组合可以是:
我们还可以使用
dplyr
运算符,它允许数据屏蔽(将数据框中的列作为变量计算,通过其名称调用它)。我们可以使用[[
和mutate
来更改将符号转换为字符+子集df
的方法:为了避免将新列命名为“col_name”,我们使用bang-bang
!!
运算符对它进行焦虑求值(与lazy-evaluate相反,默认值为R)。因为我们对左手进行了运算,所以不能使用'normal'=
,而必须使用新的sintax:=
。将列名转换为符号,然后使用bang-bang对其进行焦虑评估的常见操作有一个快捷方式: curl - curl
{{
运算符:我不是R评估方面的Maven,可能做了过度简化,或者使用了错误的术语,所以请在评论中纠正我。我希望在比较回答这个问题时使用的不同工具时有所帮助。
pgccezyw8#
如果您尝试在R包中构建此函数,或者只是想降低复杂性,可以执行以下操作:
参数
with=FALSE
“禁用将列作为变量引用的功能,从而恢复“data.frame mode”(根据CRAN documentation)。如果提供的列名在data.frame中,则if语句是一种快速捕获的方法。此处还可以使用tryCatch错误处理。