kmm*_*kmm 105 r dataframe r-faq
我正在尝试编写一个函数来接受data.frame(x
)和a column
.该函数对x执行一些计算,然后返回另一个data.frame.我坚持使用最佳实践方法将列名传递给函数.
两个最小的例子fun1
和fun2
下面产生所需的结果,能够执行操作x$column
,使用max()
,例如,然而,两者都依赖于看似(至少对我而言)不优雅
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")
Run Code Online (Sandbox Code Playgroud)
我希望能够将该功能称为fun(df, B)
例如.我考虑但尚未尝试的其他选项:
column
为列号的整数.我认为这会避免substitute()
.理想情况下,该功能可以接受.with(x, get(column))
但是,即使它有效,我认为这仍然需要 substitute
formula()
和match.call()
,我都没有多少经验.子问题:do.call()
首选eval()
?
Sha*_*ane 92
您可以直接使用列名称:
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"))
Run Code Online (Sandbox Code Playgroud)
没有必要使用替代品,评估等.
您甚至可以将所需的函数作为参数传递:
fun1 <- function(x, column, fn) {
fn(x[,column])
}
fun1(df, "B", max)
Run Code Online (Sandbox Code Playgroud)
或者,使用[[
也可以一次选择一列:
df <- data.frame(A=1:10, B=2:11, C=3:12)
fun1 <- function(x, column){
max(x[[column]])
}
fun1(df, "B")
Run Code Online (Sandbox Code Playgroud)
jor*_*ran 65
这个答案将涵盖许多与现有答案相同的元素,但是这个问题(将列名传递给函数)经常出现,我希望有一个更全面地涵盖事情的答案.
假设我们有一个非常简单的数据框:
dat <- data.frame(x = 1:4,
y = 5:8)
Run Code Online (Sandbox Code Playgroud)
我们想编写一个函数来创建一个新列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)
Run Code Online (Sandbox Code Playgroud)
这里的问题是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
Run Code Online (Sandbox Code Playgroud)
这通常被认为是"最佳实践",因为它是最难搞砸的方法.将列名称作为字符串传递与您可以得到的一样明确.
以下两个选项更先进.许多流行软件的使用这类技术,但使用起来也需要更多的谨慎态度和技能,因为他们可以引入微妙的复杂性和失败的意料之外点.Hadley's 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
Run Code Online (Sandbox Code Playgroud)
坦率地说,这可能有点愚蠢,因为我们真的在做同样的事情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
}
Run Code Online (Sandbox Code Playgroud)
只是为了好玩,我仍然使用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
Run Code Online (Sandbox Code Playgroud)
所以简短的答案基本上是:将data.frame列名作为字符串传递,并用于[[
选择单列.只有开始钻研eval
,substitute
等等.如果你真的知道自己在做什么.
Ian*_*ows 22
就个人而言,我认为将列作为字符串传递是非常难看的.我喜欢做类似的事情:
get.max <- function(column,data=NULL){
column<-eval(substitute(column),data, parent.frame())
max(column)
}
Run Code Online (Sandbox Code Playgroud)
这将产生:
> get.max(mpg,mtcars)
[1] 33.9
> get.max(c(1,2,3,4,5))
[1] 5
Run Code Online (Sandbox Code Playgroud)
请注意data.frame的规范是如何可选的.您甚至可以使用列的功能:
> get.max(1/mpg,mtcars)
[1] 0.09615385
Run Code Online (Sandbox Code Playgroud)
另一种方法是使用tidy evaluation
方法。以字符串或裸列名称的形式传递数据帧的列非常简单。tidyeval
在这里查看更多信息。
library(rlang)
library(tidyverse)
set.seed(123)
df <- data.frame(B = rnorm(10), D = rnorm(10))
Run Code Online (Sandbox Code Playgroud)
使用列名作为字符串
fun3 <- function(x, ...) {
# capture strings and create variables
dots <- ensyms(...)
# unquote to evaluate inside dplyr verbs
summarise_at(x, vars(!!!dots), list(~ max(., na.rm = TRUE)))
}
fun3(df, "B")
#> B
#> 1 1.715065
fun3(df, "B", "D")
#> B D
#> 1 1.715065 1.786913
Run Code Online (Sandbox Code Playgroud)
使用裸列名称
fun4 <- function(x, ...) {
# capture expressions and create quosures
dots <- enquos(...)
# unquote to evaluate inside dplyr verbs
summarise_at(x, vars(!!!dots), list(~ max(., na.rm = TRUE)))
}
fun4(df, B)
#> B
#> 1 1.715065
fun4(df, B, D)
#> B D
#> 1 1.715065 1.786913
#>
Run Code Online (Sandbox Code Playgroud)
由reprex软件包(v0.2.1.9000)创建于2019-03-01
有了dplyr
它现在也可以通过简单地使用双大括号来访问一个数据帧的特定列{{...}}
函数体,例如用于围绕内所需的列名col_name
:
library(tidyverse)
fun <- function(df, col_name){
df %>%
filter({{col_name}} == "test_string")
}
Run Code Online (Sandbox Code Playgroud)